前言
前一篇文章介绍了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("[email protected]"); 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