gitee地址
项目基于guide-rpc-framwork 拆解,完整框架请参考guide-rpc-framwork 。
上篇地址:day1
本篇主要内容:
- 使用zk 替代本地文件作为注册中心
- 实现简单负载均衡
使用zk 替代本地文件作为注册中心
在之前的版本中,我们抽象出了一个注册中心的接口,并且提供一个本地文件注册中心的实现,这次,我们使用zk 作为注册中心,实现服务的注册与发现,并且监听服务实例状态的变化。
这里我们使用apache curator 作为zk 客户端,依赖如下:
<curator-version>4.2.0</curator-version>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>${curator-version}</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>${curator-version}</version>
</dependency>
代码如下:
/**
* zk 服务注册中心
*/
public class ZkServiceRegister implements ServiceRegister{
private static CuratorFramework zkClient;
private static String zkUrl = "127.0.0.1:2181";
private static final String BASE_SERVICE_PATH = "/simpleRpc/service/";
private static ServiceLoadBalance serviceLoadBalance;
private static Map<String, List<String>> serviceAddressMap = new ConcurrentHashMap();
static {
// 从配置文件中获取zkurl,简单实现默认开发环境的地址
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
zkClient = CuratorFrameworkFactory.newClient(zkUrl, retryPolicy);
zkClient.start();
// 从配置文件中获取负载均衡策略,简单实现为随机负载
serviceLoadBalance = new RandomServiceLoadBalance();
}
@Override
public void regist(String serviceName) {
try {
// /simpleRpc/service/HelloService/127.0.0.1:12306
String servicePath = BASE_SERVICE_PATH + serviceName + "/" + InetAddress.getLocalHost().getHostAddress() + ":" + NettyRpcServer.PORT;
zkClient.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(servicePath);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 本地应该做一层缓存,并且监听zk 节点变化,有变化时接收到通知修改本地缓存
* @param serviceName
* @return
*/
@Override
public InetSocketAddress lookup(String serviceName) {
// /simpleRpc/service/HelloService/127.0.0.1:12306
// /simpleRpc/service/HelloService/127.0.0.1:12345
// 比如有如上一个服务有两个实例,需要负载均衡
String serviceDir = BASE_SERVICE_PATH + serviceName;
try {
List<String> services = getAllAddress(serviceDir);
String address = serviceLoadBalance.selectService(services);
String[] socketAddressArray = address.split(":");
String host = socketAddressArray[0];
int port = Integer.parseInt(socketAddressArray[1]);
return new InetSocketAddress(host, port);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private List<String> getAllAddress(String serviceDir) throws Exception {
if(serviceAddressMap.containsKey(serviceDir)) {
return serviceAddressMap.get(serviceDir);
}
//获取服务目录下的所有ip:port
List<String> addressList = zkClient.getChildren().forPath(serviceDir);
serviceAddressMap.put(serviceDir, addressList);
registerWatcher(serviceDir);
return addressList;
}
private void registerWatcher(String serviceDir) throws Exception {
//监听该目录的节点变化,有变化时接收通知
PathChildrenCache pathChildrenCache = new PathChildrenCache(zkClient, serviceDir, true);
PathChildrenCacheListener cacheListener = new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
List<String> list = client.getChildren().forPath(serviceDir);
serviceAddressMap.put(serviceDir, list);
}
};
pathChildrenCache.getListenable().addListener(cacheListener);
pathChildrenCache.start();
}
}
在静态代码块中,实例化了一个zk 客户端,并且选择了负载均衡策略(简单实现,参数正常应该从配置文件中读取)。
在注册服务方法regist(…) 中,通过zk 客户端创建了临时节点,而非持久节点,主要是考虑到 服务生产者 下线后通知 服务消费者 拉取最新的远程服务实例数据。但这样存在一个问题,如果某段时间 生产者 与zk 由于网络原因,导致暂时下线,然后过了一会网络又通了,此时服务并没有重新注册到zk 上,因此后续优化中,我们需要在生产者重新连接zk 的时候重新注册服务。
在发现服务方法lookup(…) 中,通过当前的远程服务名,去zk 上查找对应目录节点下的所有子节点,即查找当前服务的所有实例,然后通过辅助均衡器选择其中一个实例。
并且,消费者监听该服务名对应的目录节点的子节点变化事件,只要子节点有变动,就能收到通知,对应的场景是有新的服务实例上线或者服务下线,消费者能及时感知,及时进行RPC 调用或者停止RPC 调用。
实现简单负载均衡
public interface ServiceLoadBalance {
String selectService(List<String> addresses);
}
public class RandomServiceLoadBalance implements ServiceLoadBalance{
private Random random = new Random();
@Override
public String selectService(List<String> addresses) {
if(addresses == null || addresses.size() == 0) {
return null;
}
if(addresses.size() == 1) {
return addresses.get(0);
}
return addresses.get(random.nextInt(addresses.size()));
}
}
没啥好说的,,面向接口编程。。。
下集预告
本期内容比较简单,下集更精彩:
1、解决本次提出的问题,生产者暂时下线又上线,服务如何重新注册
2、使用注解方式标记远程服务,扫描并自动注册到zk