带你手写基于 Spring 的可插拔式 RPC 框架(五)注册中心
注册中心代码使用 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 框架也就实现了,测试的客户端和服务端在代码里也有,这里就不贴出来了。平时时间有限,只能下班和周末的时间来写,整个框架肯定有不足和错误的地方,也有可以改进的地方。希望大家能够不吝指教,互相进步。
我只是想将自己思考的过程给大家展示出来,希望大家一起讨论这些问题,看看还有哪些能够改进的地方。
需要改进的地方:
- 服务端的启动方式。
- 更高并发的改进。
- 服务治理。
- 监测中心。
这篇文章没有收费,如果您觉得对你的编程或多或少有点启发就点个赞。
最后给出源码 github 地址,源码 编码和码字不易,如果您觉得学到了东西就请在 github 上加个 star, 当然在 github 上提出问题一起改进是最好的。
带你手写基于 Spring 的可插拔式 RPC 框架(五)注册中心的更多相关文章
- 带你手写基于 Spring 的可插拔式 RPC 框架(一)介绍
概述 首先这篇文章是要带大家来实现一个框架,听到框架大家可能会觉得非常高大上,其实这和我们平时写业务员代码没什么区别,但是框架是要给别人使用的,所以我们要换位思考,怎么才能让别人用着舒服,怎么样才能让 ...
- 带你手写基于 Spring 的可插拔式 RPC 框架(三)通信协议模块
在写代码之前我们先要想清楚几个问题. 我们的框架到底要实现什么功能? 我们要实现一个远程调用的 RPC 协议. 最终实现效果是什么样的? 我们能像调用本地服务一样调用远程的服务. 怎样实现上面的效果? ...
- 带你手写基于 Spring 的可插拔式 RPC 框架(二)整体结构
前言 上一篇文章中我们已经知道了什么是 RPC 框架和为什么要做一个 RPC 框架了,这一章我们来从宏观上分析,怎么来实现一个 RPC 框架,这个框架都有那些模块以及这些模块的作用. 总体设计 在我们 ...
- 带你手写基于 Spring 的可插拔式 RPC 框架(四)代理类的注入与服务启动
上一章节我们已经实现了从客户端往服务端发送数据并且通过反射方法调用服务端的实现类最后返回给客户端的底层协议. 这一章节我们来实现客户端代理类的注入. 承接上一章,我们实现了多个底层协议,procoto ...
- C基础 带你手写 redis sds
前言 - Simple Dynamic Strings antirez 想统一 Redis,Disque,Hiredis 项目中 SDS 代码, 因此构建了这个项目 https://github.c ...
- C基础 带你手写 redis adlist 双向链表
引言 - 导航栏目 有些朋友可能对 redis 充满着数不尽的求知欲, 也许是 redis 属于工作, 交流(面试)的大头戏, 不得不 ... 而自己当下对于 redis 只是停留在会用层面, 细节层 ...
- 第二篇 基于.net搭建热插拔式web框架(沙箱的构建)
上周五写了一个实现原理篇,在评论中看到有朋友也遇到了我的问题,真的是有种他乡遇知己的感觉,整个系列我一定会坚持写完,并在最后把代码开源到git中.上一篇文章很多人看了以后,都表示不解,觉得不知道我到底 ...
- 第三篇 基于.net搭建热插拔式web框架(重造Controller)
由于.net MVC 的controller 依赖于HttpContext,而我们在上一篇中的沙箱模式已经把一次http请求转换为反射调用,并且http上下文不支持跨域,所以我们要重造一个contro ...
- 基于Protobuf的分布式高性能RPC框架——Navi-Pbrpc
基于Protobuf的分布式高性能RPC框架——Navi-Pbrpc 二月 8, 2016 1 简介 Navi-pbrpc框架是一个高性能的远程调用RPC框架,使用netty4技术提供非阻塞.异步.全 ...
随机推荐
- maven+struts2例子
本文在开发第一个maven示例的基础上进行扩展. 第一个maven示例目录结构如下: 扩展后目录为: 打开pom.xml 在WEB-INF文件夹下新建web.xml: <?xml version ...
- [HTML5] Lazyload below the fold images and iframes with native browser lazy-loading
In this lesson, you'll learn how to use the loading="lazy" attribute available on images a ...
- clip 语法
clip 语法: clip:auto | <shape> <shape>:rect(<number>|auto <number>|auto <nu ...
- 015_使用 expect 工具自动交互密码远程其他主机安装 httpd 软件
#!/bin/bash#删除~/.ssh/known_hosts 后,ssh 远程任何主机都会询问是否确认要连接该主机rm -rf ~/.ssh/known_hostsexpect <<E ...
- cc.Sprite组件
1.精灵 精灵(Sprite)是Cocos系列的核心概念之一,是Cocos Creator最常用的显示图像的组件. 游戏中显示一个图片,我们就可以把这个叫做"精灵" sprite, ...
- scrapy框架之下载中间件
介绍 中间件是Scrapy里面的一个核心概念.使用中间件可以在爬虫的请求发起之前或者请求返回之后对数据进行定制化修改,从而开发出适应不同情况的爬虫. “中间件”这个中文名字和前面章节讲到的“中间人”只 ...
- 二十、网络ifconfig 、ip 、netstat、ss之二
ip 网络层协议 ip地址 点分十进制分为4段,范围 0-255 ip分类 A 占据1段,最左侧一段第一位固定为0 0 000 0000 - 0 111 1111 0 - 127:其中0为网络,12 ...
- Spring框架AOP
aop技术是面向切面编程思想,作为OOP的延续思想添加到企业开发中,用于弥补OOP开发过程中的缺陷而提出的编程思想.AOP底层也是面向对象:只不过面向的不是普通的Object对象,而是特殊的AOP对象 ...
- 拆分项目搞成framework 实例
目前工作中遇到的问题,是讲项目三大模块拆分, 将Discover.Shop和Events的源代码拆分到独立的项目中 拆分是个麻烦事,里面相互依赖很多 ,打包编译也异常复杂,各种报错 编译Event 遇 ...
- [Java] JRE、JDK和JVM的区别
在Java语言的学习过程中,配置环境时首先会接触到JRE和JDK的概念,后面随着了解的深入,不可避免会学习到JVM. JRE,全称Java Runtime Environment,也被写成Java R ...