前言
前一篇文章介紹了IceGrid的簡單應用。這篇文章來介紹一下它的高階玩法—如何將模板,複製組,知名物件應用於部署方案及其作用。
基於模板的部署方案
之前介紹了xml格式的配置檔案通過各種描述符如node,server,adaptor等,描述應用部署資訊。當服務部署複雜度增加時,書寫這些描述符是件頭疼的事。
模板(Template)能大大簡化描述符的書寫;同時引數化使得模板的使用更加靈活。我們可以為描述符server和service定義模板,這個章節主要介紹
Server Template,在IceBox部署章節再介紹Service Template。
伺服器模板(Server Templates)
我們繼續使用之前的例子做一下擴充套件,將PrinterServer部署到兩個node節點。
<icegrid> <application name="Ripper"> <node name="node1"> <server id="PrinterServer1" exe="/data/ice-demo/IceGrid/Printer/server" activation="on-demand"> <adapter name="SimplePrinterAdapter" endpoints="tcp"/> <property name="Ice.Trace.Network" value="1"/> <property name="Ice.PrintStackTraces" value="1"/> <property name="Ice.Admin.Endpoints" value="tcp" /> </server> </node> <node name="node2"> <server id="PrinterServer2" exe="/data/ice-demo/IceGrid/Printer/server" activation="on-demand"> <adapter name="SimplePrinterAdapter" endpoints="tcp"/> <property name="Ice.Trace.Network" value="1"/> <property name="Ice.PrintStackTraces" value="1"/> <property name="Ice.Admin.Endpoints" value="tcp" /> </server> </node> </application> </icegrid>
這個例子很適合使用Server Template,使用模板之後:
<icegrid> <application name="Ripper"> <server-template id="PrinterServerTemplate"> <parameter name="index"/> <server id="PrinterServer${index}" exe="/data/ice-demo/IceGrid/Printer/server" activation="on-demand"> <adapter name="SimplePrinterAdapter" endpoints="tcp"/> <property name="Ice.Trace.Network" value="1"/> <property name="Ice.PrintStackTraces" value="1"/> <property name="Ice.Admin.Endpoints" value="tcp" /> </server> </server-template> <node name="node1"> <server-instance template="PrinterServerTemplate" index="1"/> </node> <node name="node2"> <server-instance template="PrinterServerTemplate" index="2"/> </node> </application> </icegrid>
上述xml檔案中,先來看一下最上面的模板定義,server-template作為伺服器模板的描述符描述了一個模板的資訊,屬性id作為區別其他模板的標識。
模板引數的定義,通過parameter描述符來定義,name指定引數名稱,default指定其預設值。這裡“index”就是模板引數名,其值通過”${index}”方式獲取。
模板中其他描述符的定義與之前沒有差別。server-instance描述符是用來例項化一個server的,它屬性template,index都是作為引數,template指定了
模板(PrinterServerTemplate),index作為引數傳遞給了模板。
通過IceGrid GUI工具部署服務,如下:
使用之前Printer客戶端程式測試一下:
結果丟擲異常,物件介面卡id“SimplePrinterAdapter”沒有註冊。
由於上面xml只是定義adaptor的name,沒有定義id。所以有可能物件介面卡的Id可能不是“SimplePrinterAdapter”。
通過介面工具檢視到PrinterServer1的SimplePrinterAdapter的介面卡ID為”PrinterServer1.SimplePrinterAdapter”。
PrinterServer2的SimplePrinterAdapter的介面卡ID為”PrinterServer2.SimplePrinterAdapter”
從這裡可以看出,如果使用者不定義描述符adaptor的id則系統會生成一個${server}.${name}的物件介面卡ID。
修改客戶端程式:
... try { //Ice::CommunicatorHolder ich(argc, argv, "config.client"); Ice::CommunicatorHolder ich(argc, argv); auto base = ich->stringToProxy("SimplePrinter@PrinterServer1.SimplePrinterAdapter"); auto printer = Ice::checkedCast<PrinterPrx>(base); if(!printer) { throw std::runtime_error("Invalid proxy"); } printer->printString("Hello World!"); } catch(const std::exception& e) ...
測試呼叫成功:
這裡就有一個問題了,同一個服務,兩個例項分別部署在兩個節點。客戶端呼叫服務還需要間接代理指定不同物件介面卡ID才能請求到不同例項。
是不是感覺這個有點low,不應該像DNS那樣一個域名解析請求,返回多個IP地址嗎?Ice作為RPC框架的佼佼者,這個小問題怎麼可能沒有考慮到。
之前提到過的物件介面卡複製和複製組(Replica Group)就是為了解決這個問題,同時也是實現對客戶端透明的負載均衡的基礎。
複製組(Replica Group)
之前文章做過簡單介紹,物件介面卡複製簡單地講就是多個服務例項,複製組就是一個物件介面卡集合,這個集合對映到多個服務例項的地址埠;
這個對映關係是由註冊中心來維護的。所以客戶端可以將複製組看做一個虛擬物件介面卡,不需要關心它與實際服務例項的對映。
使用複製組來部署Printer服務:
<icegrid> <application name="Printer"> <replica-group id="PrinterAdapters"> <load-balancing type="random" n-replicas="0"/> <!-- 此處還可以新增知名物件的定義(Well-Know Object) --> </replica-group> <server-template id="PrinterServerTemplate"> <parameter name="index"/> <server id="PrinterServer${index}" exe="/data/ice-demo/IceGrid/Printer/server" activation="on-demand"> <adapter name="SimplePrinterAdapter" endpoints="tcp" replica-group="PrinterAdapters"/> <property name="Ice.Trace.Network" value="1"/> <property name="Ice.PrintStackTraces" value="1"/> <property name="Ice.Admin.Endpoints" value="tcp" /> </server> </server-template> <node name="node1"> <server-instance template="PrinterServerTemplate" index="3"/> </node> <node name="node2"> <server-instance template="PrinterServerTemplate" index="4"/> </node> </application> </icegrid>
這個版本的xml檔案相比之前,新增了複製組的定義和在描述adaptor新增了屬性replica-group,這表示將物件介面卡加入了複製組。
通過IceGrid GUI工具部署服務,如下:
修改客戶端程式,將物件介面卡ID—“PrinterServer1.SimplePrinterAdapter”替換成複製組ID—“PrinterAdapters”,程式碼如下:
... try { //Ice::CommunicatorHolder ich(argc, argv, "config.client"); Ice::CommunicatorHolder ich(argc, argv); auto base = ich->stringToProxy("SimplePrinter@PrinterAdapters"); auto printer = Ice::checkedCast<PrinterPrx>(base); if(!printer) { throw std::runtime_error("Invalid proxy"); } printer->printString("Hello World!"); } catch(const std::exception& e) ...
測試一下,執行n次呼叫之後
呼叫都有請求到了PrinterServer3,PrinterServer4。
知名物件(Well-Known Object)
上一個小節留下了一個坑,現在來填。可能有人覺得這種形式“SimplePrinter@PrinterAdapters”的間接代理不夠簡潔,接下來介紹一種更簡潔的--知名代理。
它只由一個物件ID組成,而這樣的物件被定義為知名物件。註冊中心不僅儲存了知名物件ID與代理的關係,同時還有物件型別。這此物件型別一般是加上
完整的名稱空間的Slice Type,如::Demo::Printer;它的主要作用還是用來進行服務查詢。
在上一節的例子基礎上,加入知名物件,進行部署:
<icegrid> <application name="Printer"> <replica-group id="PrinterAdapters"> <load-balancing type="random" n-replicas="0"/> <!-- 此處還可以新增知名物件的定義(Well-Know Object) --> <object identity="SimplePrinter" type="::Demo::Hello" /> </replica-group> <server-template id="PrinterServerTemplate"> <parameter name="index"/> <server id="PrinterServer${index}" exe="/data/ice-demo/IceGrid/Printer/server" activation="on-demand"> <adapter name="SimplePrinterAdapter" endpoints="tcp" replica-group="PrinterAdapters"/> <property name="Ice.Trace.Network" value="1"/> <property name="Ice.PrintStackTraces" value="1"/> <property name="Ice.Admin.Endpoints" value="tcp" /> </server> </server-template> <node name="node1"> <server-instance template="PrinterServerTemplate" index="3"/> </node> <node name="node2"> <server-instance template="PrinterServerTemplate" index="4"/> </node> </application> </icegrid>
IceBox整合入IceGrid
之前文章介紹過IceBox,這個非常有用的元件不整合到IceGrid部署方案中,豈不浪費。
部署IceBox Server
一個簡單IceBox Server部署配置檔案如下:
<icegrid> <application name="IceBoxDemo"> <node name="node1"> <icebox id="IceBoxServer" exe="/usr/bin/icebox++11" activation="always" pwd="/opt/ice/Hello"> <env>LD_LIBRARY_PATH=/opt/ice-demo/IceGrid/lib:$LD_LIBRARY_PATH</env> <property name="Ice.Admin.Endpoints" value="tcp" /> <service name="Hello" entry="HelloI:create"> <adapter name="${service}" endpoints="tcp"/> <property name="Ice.Trace.Network" value="1" /> <property name="Ice.Trace.ThreadPool" value="1"/> <property name="Ice.PrintStackTraces" value="1"/> </service> </icebox> </node> </application> </icegrid>
可以看到這裡出現了兩個新的描述符icebox和service,跟部署server很相似,不同點在於service。service也包含物件介面卡,配置屬性這些描述資訊。
一個icebox下可以定義多個service,service的順序決定了被載入的順序。
通過IceGrid GUI工具部署,結果如下:
Service Templates
跟server template很類似,service template是用來描述service的。廢話不多說,先來看一個簡單例項:
<icegrid> <application name="IceBoxDemov2"> <service-template id="ServiceTemplate"> <parameter name="name" /> <service name="${name}" entry="HelloI:create"> <adapter name="${service}" endpoints="tcp"/> <property name="${service}.Identity" value="${server}-${service}"/> <property name="Ice.Trace.Network" value="1" /> <property name="Ice.PrintStackTraces" value="1"/> </service> </service-template> <node name="node1"> <icebox id="IceBoxServerv2" exe="/usr/bin/icebox++11" activation="on-demand" pwd="/opt/ice/Hello"> <env>LD_LIBRARY_PATH=/data/ice-demo/IceGrid/lib:$LD_LIBRARY_PATH</env> <property name="Ice.Admin.Endpoints" value="tcp" /> <service-instance template="ServiceTemplate" name="Liming"/> <service-instance template="ServiceTemplate" name="Jane"/> <service-instance template="ServiceTemplate" name="Machel"/> </icebox> </node> </application> </icegrid>
此icebox server(IceBoxServerv2)下通過service模板定義三個模板例項。可以看到icebox server的定義依然,不夠簡潔。
升級版Service Templates
在Server Template中例項化Service Template:
<icegrid> <application name="IceBoxDemov3"> <service-template id="ServiceTemplate"> <parameter name="name" /> <service name="${name}" entry="HelloI:create"> <adapter name="${service}" endpoints="tcp"/> <property name="${service}.Identity" value="${server}-${service}"/> <property name="Ice.Trace.Network" value="1" /> <property name="Ice.PrintStackTraces" value="1"/> </service> </service-template> <server-template id="ServerTemplate"> <parameter name="icebox_id" /> <parameter name="name" /> <icebox id="${icebox_id}" exe="/usr/bin/icebox++11" activation="on-demand"> <env>LD_LIBRARY_PATH=/data/ice-demo/IceGrid/lib:$LD_LIBRARY_PATH</env> <property name="Ice.Admin.Endpoints" value="tcp" /> <service-instance template="ServiceTemplate" name="${name}" /> </icebox> </server-template> <node name="node1"> <server-instance template="ServerTemplate" icebox_id="IceBoxServerv3-1" name="Babala" /> </node> <node name="node2"> <server-instance template="ServerTemplate" icebox_id="IceBoxServerv3-2" name="Maria" /> </node> </application> </icegrid>
負載均衡
之前講過複製組的另一個作用就是負載均衡;ICE的負載均衡是在註冊中心實現的。IceGrid的節點會向註冊中心上報主機系統負載資訊,註冊中心會根據複製組(replica groups)配置的負載均衡策略來決定如何處理定位請求。
IceGrid的負載均衡能力可以幫助客戶端獲取一個包含服務連線端點集合的代理,與服務者建立一個連線。一旦客戶端建立了一個連線,在沒有與註冊中心進一步互動協商的情況下,所有後續請求都是傳送到同一個Server。這就是說如果客戶端想動態的獲取路由資訊,需要週期性主動發起定位請求更新服務連線端點。
複製組的負載均衡
複製組可以包含負載均衡描述符(load-balancing),來決定如何根據系統負載來返回服務路由資訊。負載均衡描述符包含幾個屬性來描述負載均衡的策略:
- 型別
支援的幾種負載均衡型別
- 取樣間隔
以一定的間隔取樣各個節點負載資訊
- 複製數量
配置數值為N。若未配置,預設配置為1。若N>1,則返回物件介面卡連線端點數量最多為N;N=0,則返回所有連線端點。
格式如下:
<replica-group id="ReplicatedAdapter"> <load-balancing type="adaptive" load-sample="5" n-replicas="2"/> </replica-group>
負載均衡的型別
- 隨機(Random)
不考慮節點系統負載,隨機返回服務連線端點
- 自適應(Adaptive)
根據系統負載,返回負載最少的節點的服務連線端點
- 迴圈(Round Robin)
不考慮節點系統負載,返回最近最少被使用的物件介面卡的服務連線端點。
- 排序(Order)
根據物件介面卡配置的優先順序,來排序返回服務連線端點
最終部署方案
一個客戶端呼叫簡單,動態配置,服務冗餘,負載均衡的方案--IceBox + Server Templates + Service Templates + 複製組。
<icegrid> <application name="IceBoxDemov3"> <service-template id="ServiceTemplate"> <parameter name="name" /> <service name="${name}" entry="HelloI:create"> <adapter name="${service}" endpoints="tcp" replica-group="HelloGroup" /> <property name="${service}.Identity" value="${server}-${service}"/> <property name="Ice.Trace.Network" value="1" /> <property name="Ice.PrintStackTraces" value="1"/> </service> </service-template> <server-template id="ServerTemplate"> <parameter name="icebox_id" /> <icebox id="${icebox_id}" exe="/usr/bin/icebox++11" activation="on-demand"> <env>LD_LIBRARY_PATH=/opt/ice/Hello-v3/lib:$LD_LIBRARY_PATH</env> <property name="Ice.Admin.Endpoints" value="tcp" /> <service-instance template="ServiceTemplate" name="Liming" /> </icebox> </server-template> <replica-group id="HelloGroup"> <load-balancing type="adaptive" load-sample="5" n-replicas="1" /> <object identity="HelloImp" type="::Demo::Hello" /> </replica-group> <node name="node1"> <server-instance template="ServerTemplate" icebox_id="IceBoxServerv4-1" /> </node> <node name="node2"> <server-instance template="ServerTemplate" icebox_id="IceBoxServerv4-2" /> </node> </application>
結尾
IceGrid的各種部署方案基本介紹完了,完整用例原始碼見文章最後的github連線。
ICE這個框架細節的東西比較多,只有實踐才能慢慢掌握它,希望這篇文章能幫助想入坑ICE的同學開啟大門。
注:原始碼連線 https://github.com/GodMonking/ice-demo/tree/main/IceGrid