前言
前一篇文章介紹了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