TCP-IP學習筆記十:RPC架構WebService加入Zookeeper集羣的管理
標籤(空格分隔): TCP-IP Zookeeper
一、前言
瞭解了Zookeeper的使用場景之後,可以對RPC架構的WebService加入到Zookeeper中進行管理。實現服務器上線自動註冊,進而通知客戶度知道可以調度哪些服務器,同時服務器下線,通知客戶端哪些服務器不可用。這一點也是利用了Zookeeper可以監控每個節點數據的變化和子節點變化的特點。
我們可已經服務器的啓動註冊到Zookeeper中,Zookeeper相當於一個註冊中心,監控每個節點的數據和子節點的變化,監控到變化就通知到各個客戶端。客戶端啓動從Zookeeper動態獲取可用服務器列表,Zookeeper檢測到變化的時候可以動態的通知客戶度哪些服務器不可用或者新的服務器加入。
二、具體的代碼實施
服務器端的修改
注入服務器IP:端口號,和Znode(應用所在的路徑),在服務器啓動的時候注入到Zookeeper中。
在NIOServerBootstrap的線程啓動下添加如下代碼:client=new ZkClient(servers); //判斷節點是否存在,不存在則創建 if(!client.exists(serverPath)){ client.createPersistent(serverPath,true); } //註冊服務 String tmpPath=serverPath+"/"+ InetAddress.getLocalHost().getHostAddress()+":"+port; //判斷節點是否存在,不存在則創建 if(!client.exists(tmpPath)){ client.createEphemeral(tmpPath); }
客戶端的修改
添加InvokeLoadBalancer類,獲得可用連接和負載均衡策略。
負載均衡策略有很多實現方式(有很多算法),這裏使用簡單的隨機數的方式實現。package com.motui.rpc.client; import org.I0Itec.zkclient.IZkChildListener; import org.I0Itec.zkclient.ZkClient; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.Random; /** * Created by MOTUI on 2016/10/24. * * 獲取可用連接和實現負載均衡 */ public class InvokeLoadBalancer implements Serializable { //存儲可用的服務列表 private static List<HostAndPort> hostAndPorts; private ZkClient zkClient; //監聽的節點路徑 private String serverPath; public InvokeLoadBalancer(String servers,String serverPath){ this.serverPath = serverPath; this.zkClient = new ZkClient(servers); } /** * 初始化方法 */ public void init(){ //判斷節點是否存在,不存在則創建 if (!zkClient.exists(serverPath)){ zkClient.createPersistent(serverPath,true); } //第一次啓動,需要獲得服務器列表 hostAndPorts = new ArrayList<HostAndPort>(); //獲得服務列表 List<String> children = zkClient.getChildren(serverPath); for (String child:children) { hostAndPorts.add(splitChild(child)); } //監聽節點的變化 zkClient.subscribeChildChanges(serverPath, new IZkChildListener() { @Override public void handleChildChange(String path, List<String> children) throws Exception { //清空服務列表集合 hostAndPorts.clear(); //獲得可用服務列表 for (String child: children) { hostAndPorts.add(splitChild(child)); } } }); } /** * 負載均衡算法 */ public HostAndPort getHostAndPort(){ if (hostAndPorts != null && hostAndPorts.size() > 0){ HostAndPort hostAndPort = hostAndPorts.get(new Random().nextInt(hostAndPorts.size())); return hostAndPort; }else{ throw new RuntimeException("hostAndPorts is null"); } } public HostAndPort getHostAndPort(HostAndPort hostAndPort){ //將該hostAndPort從集合中刪除 hostAndPorts.remove(hostAndPort); //再此獲取一個HostAndPort HostAndPort hap = getHostAndPort(); return hap; } /** * 將String的child轉化爲HostAndPort對象 * @param child * @return */ public HostAndPort splitChild(String child){ String[] split = child.split(":"); HostAndPort hostAndPort = new HostAndPort(split[0], Integer.parseInt(split[1])); return hostAndPort; } }
對我們的動態代理類進行修改
客戶端接口的remoteCall方法修改(將獲得的HostAndPort和獲得服務器列表的InvokeLoadBalancer進行傳遞):public Object remoteCall(MethodInvokeMeta methodInvokeMeta, int retry,HostAndPort hostAndPort,InvokeLoadBalancer invokeLoadBalancer);
動態獲得可用服務器的IP和端口號
//動態獲取HostAndPort HostAndPort hostAndPort = invokeLoadBalancer.getHostAndPort(); Object result = this.client.remoteCall(invokeMeta,10,hostAndPort,invokeLoadBalancer);
客戶端實現類,在出現連接異常進行重試的操作,有一點需要注意。在連接異常的時候從新獲得可用的HostAndPort進行重新連接。
異常處理//再此獲取HostAndPost HostAndPort hap = invokeLoadBalancer.getHostAndPort(hostAndPort); return remoteCall(methodInvokeMeta, retry,hap,invokeLoadBalancer);
服務器端配置文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd"> <!-- 配置服務啓動類 --> <bean class="com.motui.rpc.server.NIOServerBootstrap" init-method="start" destroy-method="close"> <constructor-arg index="0" value="8989"/> <constructor-arg index="1" ref="serverRequestResponseHandler"/> <!--<property name="servers" value="192.168.200.128:2181"/>--> <property name="servers" value="192.168.200.128:2181,192.168.200.128:2182,192.168.200.128:2183"/> <property name="serverPath" value="/motui/rpc"/> </bean> <!-- 配置請求處理類 --> <bean id="serverRequestResponseHandler" class="com.motui.rpc.server.ServerRequestResponseHandler"/> <!-- 掃描組件 --> <context:component-scan base-package="com.motui.service"/> </beans>
客戶端配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd"> <!-- 配置代理bean --> <bean class="com.motui.rpc.client.BeanScanner"> <property name="beanInterfaces"> <map> <entry key="demoService" value="com.motui.service.IDemoService"/> <entry key="userService" value="com.motui.service.IUserService"/> <entry key="testService" value="com.motui.service.ITestService"/> </map> </property> <property name="clientName" value="client"/> <property name="invokeLoadBalancerName" value="invokeLoadBalancer"/> </bean> <bean id="client" class="com.motui.rpc.client.RemoteRPCClient"/> <bean id="invokeLoadBalancer" class="com.motui.rpc.client.InvokeLoadBalancer" init-method="init"> <!--<constructor-arg name="servers" value="192.168.200.128:2181"/>--> <constructor-arg index="0" value="192.168.200.128:2181,192.168.200.128:2182,192.168.200.128:2183"/> <constructor-arg index="1" value="/motui/rpc"/> </bean> </beans>
說明:本示例使用的是Zookeeper的單節點和集羣環境測試。源碼中有測試代碼,可供測試。
參考文章:RPC原理及RPC實例分析
源碼地址: 源碼地址