注册中心代码使用 zookeeper 实现,我们通过图片来看看我们注册中心的架构。

首先说明, zookeeper 的实现思路和代码是参考架构探险这本书上的,另外在 github 和我前面配置文件中的 zookeeper 服务器是用的1个月免费适用的阿里云,大家也可以用它当测试用。

不多说,一次性给出注册中心全部代码。

客户端对应的注册中心接口

public interface RegisterCenter4Consumer {

    /**
* 消费端初始化服务提供者信息本地缓存
*/
public void initProviderMap(); /**
* 消费端获取服务提供者信息
* @return
*/
public Map<String,List<ServiceProvider>> getServiceMetaDataMap4Consumer(); /**
* 消费端将消费者信息注册到 zookeeper 对应的节点下
* @param invokers
*/
public void registerConsumer(final List<ServiceConsumer> invokers);
}

服务端对应的注册中心接口

public interface RegisterCenter4Provider {
/**
* 服务端将服务提供者信息注册到 zookeeper 对应的节点下
* @param serivceList
*/
public void registerProvider(final List<ServiceProvider> serivceList); /**
* 服务端获取服务提供者信息
* @return key:服务提供者接口 value:服务提供者服务方法列表
*/
public Map<String, List<ServiceProvider>> getProviderService();
}

注册中心实现类:

public class ZookeeperRegisterCenter implements RegisterCenter4Provider, RegisterCenter4Consumer {

    private static ZookeeperRegisterCenter registerCenter = new ZookeeperRegisterCenter();

    private ZookeeperRegisterCenter(){};

    public static ZookeeperRegisterCenter getInstance(){
return registerCenter;
}
//服务提供者列表,key:服务提供者接口,value:服务提供者服务方法列表
private static final Map<String,List<ServiceProvider>> providerServiceMap = new ConcurrentHashMap<>(); //服务端 zookeeper 元信息,选择服务(第一次从zookeeper 拉取,后续由zookeeper监听机制主动更新)
private static final Map<String,List<ServiceProvider>> serviceData4Consumer = new ConcurrentHashMap<>(); //从配置文件中获取 zookeeper 服务地址列表
private static String ZK_SERIVCE = Configuration.getInstance().getAddress(); //从配置文件中获取 zookeeper 会话超时时间配置
private static int ZK_SESSION_TIME_OUT = 5000; //从配置文件中获取 zookeeper 连接超时事件配置
private static int ZK_CONNECTION_TIME_OUT = 5000; private static String ROOT_PATH = "/rpc_register";
public static String PROVIDER_TYPE = "/provider";
public static String CONSUMER_TYPE = "/consumer"; private static volatile ZkClient zkClient = null; @Override
public void initProviderMap() {
if(serviceData4Consumer.isEmpty()){
serviceData4Consumer.putAll(fetchOrUpdateServiceMetaData());
} } @Override
public Map<String, List<ServiceProvider>> getServiceMetaDataMap4Consumer() {
return serviceData4Consumer;
} @Override
public void registerConsumer(List<ServiceConsumer> consumers) {
if(consumers == null || consumers.size() == 0){
return;
} //连接 zookeeper ,注册服务
synchronized (ZookeeperRegisterCenter.class){
if(zkClient == null){
zkClient = new ZkClient(ZK_SERIVCE,ZK_SESSION_TIME_OUT,ZK_CONNECTION_TIME_OUT, new SerializableSerializer());
}
//创建 zookeeper 命名空间
boolean exist = zkClient.exists(ROOT_PATH);
if(!exist){
zkClient.createPersistent(ROOT_PATH,true);
}
//创建服务提供者节点
exist = zkClient.exists((ROOT_PATH));
if(!exist){
zkClient.createPersistent(ROOT_PATH);
} for(int i = 0; i< consumers.size();i++) {
ServiceConsumer consumer = consumers.get(i);
//创建服务消费者节点
String serviceNode = consumer.getConsumer().getName();
String servicePath = ROOT_PATH + CONSUMER_TYPE + "/" + serviceNode; exist = zkClient.exists(servicePath);
System.out.println("exist:" + exist);
System.out.println("servicePath:" + servicePath);
if (!exist) {
zkClient.createPersistent(servicePath, true);
} //创建当前服务器节点
InetAddress addr = null;
try {
addr = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
String ip = addr.getHostAddress();
String currentServiceIpNode = servicePath + "/" + ip;
exist = zkClient.exists(currentServiceIpNode);
if (!exist) {
zkClient.createEphemeral(currentServiceIpNode);
} } } } @Override
public void registerProvider(List<ServiceProvider> serivceList) {
if(serivceList == null || serivceList.size() == 0){
return;
} //连接 zookeeper,注册服务,加锁,将所有需要注册的服务放到providerServiceMap里面
synchronized (ZookeeperRegisterCenter.class){
for(ServiceProvider provider:serivceList){
//获取接口名称
String serviceItfKey = provider.getProvider().getName();
//先从当前服务提供者的集合里面获取
List<ServiceProvider> providers = providerServiceMap.get(serviceItfKey);
if(providers == null){
providers = new ArrayList<>();
}
providers.add(provider);
providerServiceMap.put(serviceItfKey,providers);
} if(zkClient == null){
zkClient = new ZkClient(ZK_SERIVCE,ZK_SESSION_TIME_OUT,ZK_CONNECTION_TIME_OUT,new SerializableSerializer());
} //创建当前应用 zookeeper 命名空间
boolean exist = zkClient.exists(ROOT_PATH);
if(!exist){
zkClient.createPersistent(ROOT_PATH,true);
} //服务提供者节点
exist = zkClient.exists((ROOT_PATH));
if(!exist){
zkClient.createPersistent(ROOT_PATH);
} for(Map.Entry<String,List<ServiceProvider>> entry:providerServiceMap.entrySet()){
//创建服务提供者节点
String serviceNode = entry.getKey();
String servicePath = ROOT_PATH +PROVIDER_TYPE +"/" + serviceNode;
exist = zkClient.exists(servicePath);
if(!exist){
zkClient.createPersistent(servicePath,true);
} //创建当前服务器节点,这里是注册时使用,一个接口对应的ServiceProvider 只有一个
int serverPort = entry.getValue().get(0).getPort();
InetAddress addr = null;
try {
addr = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
String ip = addr.getHostAddress();
String impl = (String)entry.getValue().get(0).getServiceObject();
String serviceIpNode = servicePath +"/" + ip + "|" + serverPort + "|" + impl;
System.out.println("serviceIpNode:" + serviceIpNode);
exist = zkClient.exists(serviceIpNode);
if(!exist){
//创建临时节点
zkClient.createEphemeral(serviceIpNode);
}
//监听注册服务的变化,同时更新数据到本地缓存
zkClient.subscribeChildChanges(servicePath, new IZkChildListener() {
@Override
public void handleChildChange(String s, List<String> list) throws Exception {
if(list == null){
list = new ArrayList<>();
}
//存活的服务 IP 列表
List<String> activeServiceIpList = new ArrayList<>();
for(String input:list){
String ip = StringUtils.split(input, "|").get(0);
activeServiceIpList.add(ip);
}
refreshActivityService(activeServiceIpList);
}
}); }
} } /**
*
* 在某个服务端获取自己暴露的服务
*/
@Override
public Map<String, List<ServiceProvider>> getProviderService() {
return providerServiceMap;
} //利用ZK自动刷新当前存活的服务提供者列表数据
private void refreshActivityService(List<String> serviceIpList) {
if (serviceIpList == null||serviceIpList.isEmpty()) {
serviceIpList = new ArrayList<>();
} Map<String, List<ServiceProvider>> currentServiceMetaDataMap = new HashMap<>();
for (Map.Entry<String, List<ServiceProvider>> entry : providerServiceMap.entrySet()) {
String key = entry.getKey();
List<ServiceProvider> providerServices = entry.getValue(); List<ServiceProvider> serviceMetaDataModelList = currentServiceMetaDataMap.get(key);
if (serviceMetaDataModelList == null) {
serviceMetaDataModelList = new ArrayList<>();
} for (ServiceProvider serviceMetaData : providerServices) {
if (serviceIpList.contains(serviceMetaData.getIp())) {
serviceMetaDataModelList.add(serviceMetaData);
}
}
currentServiceMetaDataMap.put(key, serviceMetaDataModelList);
}
providerServiceMap.clear();
providerServiceMap.putAll(currentServiceMetaDataMap);
} private void refreshServiceMetaDataMap(List<String> serviceIpList) {
if (serviceIpList == null) {
serviceIpList = new ArrayList<>();
} Map<String, List<ServiceProvider>> currentServiceMetaDataMap = new HashMap<>();
for (Map.Entry<String, List<ServiceProvider>> entry : serviceData4Consumer.entrySet()) {
String serviceItfKey = entry.getKey();
List<ServiceProvider> serviceList = entry.getValue(); List<ServiceProvider> providerServiceList = currentServiceMetaDataMap.get(serviceItfKey);
if (providerServiceList == null) {
providerServiceList = new ArrayList<>();
} for (ServiceProvider serviceMetaData : serviceList) {
if (serviceIpList.contains(serviceMetaData.getIp())) {
providerServiceList.add(serviceMetaData);
}
}
currentServiceMetaDataMap.put(serviceItfKey, providerServiceList);
} serviceData4Consumer.clear();
serviceData4Consumer.putAll(currentServiceMetaDataMap);
} private Map<String, List<ServiceProvider>> fetchOrUpdateServiceMetaData() {
final Map<String, List<ServiceProvider>> providerServiceMap = new ConcurrentHashMap<>();
//连接zk
synchronized (ZookeeperRegisterCenter.class) {
if (zkClient == null) {
zkClient = new ZkClient(ZK_SERIVCE, ZK_SESSION_TIME_OUT, ZK_CONNECTION_TIME_OUT, new SerializableSerializer());
}
} //从ZK获取服务提供者列表
String providePath = ROOT_PATH+PROVIDER_TYPE;
System.out.println("111111:"+providePath);
List<String> providerServices = zkClient.getChildren(providePath);
System.out.println(providerServices.toString());
for (String serviceName : providerServices) {
String servicePath = providePath +"/"+ serviceName;
System.out.println("1100:"+servicePath);
List<String> ipPathList = zkClient.getChildren(servicePath);
System.out.println("ipPathList:"+ipPathList.toString());
for (String ipPath : ipPathList) {
String serverIp = ipPath.split("\\|")[0];
String serverPort = ipPath.split("\\|")[1];
String impl = ipPath.split("\\|")[2];
List<ServiceProvider> providerServiceList = providerServiceMap.get(serviceName);
if (providerServiceList == null) {
providerServiceList = new ArrayList<>();
}
ServiceProvider providerService = new ServiceProvider(); try {
Class clazz = Class.forName(serviceName);
providerService.setProvider(clazz);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} providerService.setIp(serverIp);
providerService.setPort(Integer.parseInt(serverPort));
providerService.setServiceObject(impl);
providerService.setGroupName("");
providerServiceList.add(providerService); providerServiceMap.put(serviceName, providerServiceList);
} //监听注册服务的变化,同时更新数据到本地缓存
zkClient.subscribeChildChanges(servicePath, new IZkChildListener() {
@Override
public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
if (currentChilds == null) {
currentChilds = new ArrayList<>();
}
List<String> activeServiceIpList = new ArrayList<>();
for(String input:currentChilds){
String ip = StringUtils.split(input, "|").get(0);
activeServiceIpList.add(ip);
}
refreshServiceMetaDataMap(activeServiceIpList);
}
});
}
return providerServiceMap;
} }

写完这部分整个 rpc 框架也就实现了,测试的客户端和服务端在代码里也有,这里就不贴出来了。平时时间有限,只能下班和周末的时间来写,整个框架肯定有不足和错误的地方,也有可以改进的地方。希望大家能够不吝指教,互相进步。

我只是想将自己思考的过程给大家展示出来,希望大家一起讨论这些问题,看看还有哪些能够改进的地方。

需要改进的地方:

  1. 服务端的启动方式。
  2. 更高并发的改进。
  3. 服务治理。
  4. 监测中心。

这篇文章没有收费,如果您觉得对你的编程或多或少有点启发就点个赞。

最后给出源码 github 地址,源码 编码和码字不易,如果您觉得学到了东西就请在 github 上加个 star, 当然在 github 上提出问题一起改进是最好的。

带你手写基于 Spring 的可插拔式 RPC 框架(五)注册中心的更多相关文章

  1. 带你手写基于 Spring 的可插拔式 RPC 框架(一)介绍

    概述 首先这篇文章是要带大家来实现一个框架,听到框架大家可能会觉得非常高大上,其实这和我们平时写业务员代码没什么区别,但是框架是要给别人使用的,所以我们要换位思考,怎么才能让别人用着舒服,怎么样才能让 ...

  2. 带你手写基于 Spring 的可插拔式 RPC 框架(三)通信协议模块

    在写代码之前我们先要想清楚几个问题. 我们的框架到底要实现什么功能? 我们要实现一个远程调用的 RPC 协议. 最终实现效果是什么样的? 我们能像调用本地服务一样调用远程的服务. 怎样实现上面的效果? ...

  3. 带你手写基于 Spring 的可插拔式 RPC 框架(二)整体结构

    前言 上一篇文章中我们已经知道了什么是 RPC 框架和为什么要做一个 RPC 框架了,这一章我们来从宏观上分析,怎么来实现一个 RPC 框架,这个框架都有那些模块以及这些模块的作用. 总体设计 在我们 ...

  4. 带你手写基于 Spring 的可插拔式 RPC 框架(四)代理类的注入与服务启动

    上一章节我们已经实现了从客户端往服务端发送数据并且通过反射方法调用服务端的实现类最后返回给客户端的底层协议. 这一章节我们来实现客户端代理类的注入. 承接上一章,我们实现了多个底层协议,procoto ...

  5. C基础 带你手写 redis sds

    前言 - Simple Dynamic Strings  antirez 想统一 Redis,Disque,Hiredis 项目中 SDS 代码, 因此构建了这个项目 https://github.c ...

  6. C基础 带你手写 redis adlist 双向链表

    引言 - 导航栏目 有些朋友可能对 redis 充满着数不尽的求知欲, 也许是 redis 属于工作, 交流(面试)的大头戏, 不得不 ... 而自己当下对于 redis 只是停留在会用层面, 细节层 ...

  7. 第二篇 基于.net搭建热插拔式web框架(沙箱的构建)

    上周五写了一个实现原理篇,在评论中看到有朋友也遇到了我的问题,真的是有种他乡遇知己的感觉,整个系列我一定会坚持写完,并在最后把代码开源到git中.上一篇文章很多人看了以后,都表示不解,觉得不知道我到底 ...

  8. 第三篇 基于.net搭建热插拔式web框架(重造Controller)

    由于.net MVC 的controller 依赖于HttpContext,而我们在上一篇中的沙箱模式已经把一次http请求转换为反射调用,并且http上下文不支持跨域,所以我们要重造一个contro ...

  9. 基于Protobuf的分布式高性能RPC框架——Navi-Pbrpc

    基于Protobuf的分布式高性能RPC框架——Navi-Pbrpc 二月 8, 2016 1 简介 Navi-pbrpc框架是一个高性能的远程调用RPC框架,使用netty4技术提供非阻塞.异步.全 ...

随机推荐

  1. 项目:JS实现简易计算器案例

    组件化网页开发下的: 步骤一:让页面动起来的JavaScript深入讲解  的 项目:JS实现简易计算器案例

  2. Codeforces Round #591 (Div. 2, based on Technocup 2020 Elimination Round 1) C. Save the Nature【枚举二分答案】

    https://codeforces.com/contest/1241/problem/C You are an environmental activist at heart but the rea ...

  3. Start Failed, Internal error: recovering IDE to the working state after the critical startup error

    Start Failed, Internal error: recovering IDE to the working state after the critical startup error F ...

  4. Java面向对象3(K~O)

    K     正方形(SDUT 2444) import java.lang.reflect.Array; import java.util.*; public class Main { public ...

  5. NSMutableArray

    NSMutableArray 是一个可变数组,是NSArray的子类,但是不可以添加空值 创建NSMutableArray的方法 +(id)arrarWithCapacity:(NSInteger)n ...

  6. python 获取远程设备ip地址

    python2.7 #!/usr/bin/env python # Python Network Programming Cookbook -- Chapter - # This program is ...

  7. c 判断是否为nan

    /* isnan example */ #include <stdio.h> /* printf */ #include <math.h> /* isnan, sqrt */ ...

  8. 二十九、SELinux简介

    一.基础 1)访问模型 Linux原有访问模型:自主访问控制 DAC 安全隐患: 进程所能访问资源的范围 为用户所能访问的资源范围 后门: rootkit程序 进程被胁持: 基于进程作为跳板,就有了进 ...

  9. springboot实现异步调用

    介绍 所谓的异步执行其实就是使用多线程的方式实现异步调用. 异步有什么好处呢? 如果一个业务逻辑执行完成需要多个步骤,也就是调用多个方法去执行, 这个时候异步执行比同步执行相应更快.不过要注意异步请求 ...

  10. ArcGIS超级工具SPTOOLS-线封闭,点集转面

    一.线封闭 操作视频:https://weibo.com/tv/v/HvyvbAxKh?fid=1034:4375207666991674 将末端不闭合线,自动生成闭合的线,效果如下 原始线:末端不闭 ...