Rpc-实现Zookeeper注册中心
1.前言
本文章是笔主在声哥的手写RPC框架的学习下,对注册中心的一个拓展。因为声哥某些部分没有保留拓展性,所以本文章的项目与声哥的工程有部分区别,核心内容在Curator的注册发现与注销,思想看准即可。
本文章Git仓库:zko0/zko0-rpc
声哥的RPC项目写的确实很详细,跟学一遍受益匪浅:
在声哥的项目里使用Nacos作为了服务注册中心。本人拓展添加了ZooKeeper实现服务注册。
Nacos的服务注册和发现,设计的不是非常好,每次服务的发现都需要去注册中心拉取。本人实现ZooKeeper注册中心时,参考了Dubbo的设计原理,结合本人自身想法,添加了本地缓存:
- Client发现服务后缓存在本地,维护一个服务——实例列表
- 当监听到注册中心的服务列表发生了变化,Client更新本地列表
- 当注册中心宕机,Client能够依靠本地的服务列表继续提供服务
问题:
- 实现服务注册的本地缓存,还需要实现注册中心的监听,当注册中心的服务发生更改时能够实现动态更新。或者用轮训的方式,定时更新,不过这种方式的服务实时性较差
- 当Server宕机,非临时节点注册容易出现服务残留无法清除的问题。所以我建议全部使用临时节点去注册。
2.内容
zookeeper需要简单学一下,知识内容非常简单,搭建也很简单,在此跳过。
如果你感兴趣,可以参考我的ZooKeeper的文章:Zookeeper学习笔记 - zko0
①添加依赖
Curator:(简化ZooKeeper客户端使用)(Netfix研发,捐给Apache,是Apache顶级项目)
这里排除slf4j依赖,因为笔主使用的slf4j存在冲突
<!-- https://mvnrepository.com/artifact/org.apache.curator/curator-recipes -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.2.0</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
②代码编写
1.首先创建一个连接类:
@Slf4j
public class ZookeeperUtil {
//内部化构造方法
private ZookeeperUtil(){
}
private static final String SERVER_HOSTNAME= RegisterCenterConfig.getHostName();
private static final Integer SERVER_PORT=RegisterCenterConfig.getServerPort();
private static CuratorFramework zookeeperClient;
public static CuratorFramework getZookeeperClient(){
if (zookeeperClient==null){
synchronized (ZookeeperUtil.class){
if (zookeeperClient==null){
RetryPolicy retryPolic=new ExponentialBackoffRetry(3000,10);
zookeeperClient = CuratorFrameworkFactory.builder()
.connectString(SERVER_HOSTNAME+":"+SERVER_PORT)
.retryPolicy(retryPolic)
// zookeeper根目录为/serviceRegister,不为/
.namespace("serviceRegister")
.build();
zookeeperClient.start();
}
}
}
return zookeeperClient;
}
public static String getServerHostname(){
return SERVER_HOSTNAME;
}
public static Integer getServerPort(){
return SERVER_PORT;
}
}
其中HOST,PORT信息我保存在regiserCenter.properties配置文件夹中,使用类读取:
public class RpcConfig {
//注册中心类型
private static String registerCenterType;
//序列化类型
private static String serializerType;
//负载均衡类型
private static String loadBalanceType;
//配置Nacos地址
private static String registerCenterHost;
private static Integer registerCenterPort;
private static boolean zookeeperDestoryIsEphemeral;
private static String serverHostName;
private static Integer serverPort;
static {
ResourceBundle bundle = ResourceBundle.getBundle("rpc");
registerCenterType=bundle.getString("registerCenter.type");
loadBalanceType=bundle.getString("loadBalance.type");
registerCenterHost=bundle.getString("registerCenter.host");
registerCenterPort = Integer.parseInt(bundle.getString("registerCenter.port"));
try {
zookeeperDestoryIsEphemeral="true".equals(bundle.getString("registerCenter.destory.isEphemeral"));
} catch (Exception e) {
zookeeperDestoryIsEphemeral=false;
}
serializerType=bundle.getString("serializer.type");
serverHostName=bundle.getString("server.hostName");
serverPort=Integer.parseInt(bundle.getString("server.port"));
}
public static String getRegisterCenterType() {
return registerCenterType;
}
public static String getSerializerType() {
return serializerType;
}
public static String getLoadBalanceType() {
return loadBalanceType;
}
public static String getRegisterCenterHost() {
return registerCenterHost;
}
public static Integer getRegisterCenterPort() {
return registerCenterPort;
}
public static String getServerHostName() {
return serverHostName;
}
public static Integer getServerPort() {
return serverPort;
}
public static boolean isZookeeperDestoryIsEphemeral() {
return zookeeperDestoryIsEphemeral;
}
}
下面的代码我和声哥有些不同,我将服务注册,注销方法放在ServerUtils中,服务发现方法放在ClientUtils中:
服务的高一致性存在两种做法:
- 因为ZooKeeper存在临时节点,注册中心可以实现Client(RPC的Server)断开,注册服务信息的自动丢失
- 不设置为临时节点,手动的服务注册清除
我这里两种都实现了,虽然做两种方式不同但是功能相同的代码放在一起看起来很奇怪,这里只是做演示。选择其中一种即可。(我建议使用临时节点,当Server宕机,残留的服务信息也能及时清除)
注册实现原理图:

接口:
public interface ServiceDiscovery {
InetSocketAddress searchService(String serviceName);
void cleanLoaclCache(String serviceName);
}
public interface ServiceRegistry {
//服务注册
void register(String serviceName, InetSocketAddress inetAddress);
void cleanRegistry();
}
ZooKeeper接口实现:
public class ZookeeperServiceDiscovery implements ServiceDiscovery{
private final LoadBalancer loadBalancer;
public ZookeeperServiceDiscovery(LoadBalancer loadBalancer) {
this.loadBalancer = loadBalancer;
}
@Override
public InetSocketAddress searchService(String serviceName) {
return ZookeeperClientUtils.searchService(serviceName,loadBalancer);
}
@Override
public void cleanLoaclCache(String serviceName) {
ZookeeperClientUtils.cleanLocalCache(serviceName);
}
}
public class ZookeeperServiceRegistry implements ServiceRegistry{
@Override
public void register(String serviceName, InetSocketAddress inetAddress) {
ZookeeperServerUitls.register(serviceName,inetAddress);
}
@Override
public void cleanRegistry() {
ZookeeperServerUitls.cleanRegistry();
}
}
Factory工厂:
public class ServiceFactory {
private static String center = RpcConfig.getRegisterCenterType();
private static String lb= RpcConfig.getLoadBalanceType();
private static ServiceRegistry registry;
private static ServiceDiscovery discovery;
private static Object registerLock=new Object();
private static Object discoveryLock=new Object();
public static ServiceDiscovery getServiceDiscovery(){
if (discovery==null){
synchronized (discoveryLock){
if (discovery==null){
if ("nacos".equalsIgnoreCase(center)){
discovery= new NacosServiceDiscovery(LoadBalancerFactory.getLoadBalancer(lb));
}else if ("zookeeper".equalsIgnoreCase(center)){
discovery= new ZookeeperServiceDiscovery(LoadBalancerFactory.getLoadBalancer(lb));
}
}
}
}
return discovery;
}
public static ServiceRegistry getServiceRegistry(){
if (registry==null){
synchronized (registerLock){
if (registry==null){
if ("nacos".equalsIgnoreCase(center)){
registry= new NacosServiceRegistry();
}else if ("zookeeper".equalsIgnoreCase(center)){
registry= new ZookeeperServiceRegistry();
}
}
}
}
return registry;
}
}
使用Gson序列化InetSocketAddress存在问题,编写Util:
public class InetSocketAddressSerializerUtil {
public static String getJsonByInetSockerAddress(InetSocketAddress address){
HashMap<String, String> map = new HashMap<>();
map.put("host",address.getHostName());
map.put("port",address.getPort()+"");
return new Gson().toJson(map);
}
public static InetSocketAddress getInetSocketAddressByJson(String json){
HashMap<String,String> hashMap = new Gson().fromJson(json, HashMap.class);
String host = hashMap.get("host");
Integer port=Integer.parseInt(hashMap.get("port"));
return new InetSocketAddress(host,port);
}
}
上面主要是注册,发现的逻辑,我把主要方法写在了Utils中:
@Slf4j
public class ZookeeperServerUitls {
private static CuratorFramework client = ZookeeperUtil.getZookeeperClient();
private static final Set<String> instances=new ConcurrentHashSet<>();
public static void register(String serviceName, InetSocketAddress inetSocketAddress){
serviceName=ZookeeperUtil.serviceName2Path(serviceName);;
String uuid = UUID.randomUUID().toString();
serviceName=serviceName+"/"+uuid;
String json = InetSocketAddressSerializerUtil.getJsonByInetSockerAddress(inetSocketAddress);
try {
if (RpcConfig.isZookeeperDestoryIsEphemeral()){
//会话结束节点,创建消失
client.create()
.creatingParentsIfNeeded()
.withMode(CreateMode.EPHEMERAL)
.forPath(serviceName,json.getBytes());
} else {
client.create()
.creatingParentsIfNeeded()
.forPath(serviceName,json.getBytes());
}
}
catch (Exception e) {
log.error("服务注册失败");
throw new RpcException(RpcError.REGISTER_SERVICE_FAILED);
}
//放入map
instances.add(serviceName);
}
public static void cleanRegistry(){
log.info("注销所有注册的服务");
//如果自动销毁,不需要清除
if (RpcConfig.isZookeeperDestoryIsEphemeral()) return;
if (ZookeeperUtil.getServerHostname()!=null&&ZookeeperUtil.getServerPort()!=null&&!instances.isEmpty()){
for (String path:instances) {
try {
client.delete().forPath(path);
} catch (Exception e) {
log.error("服务注销失败");
throw new RpcException(RpcError.DESTORY_REGISTER_FALL);
}
}
}
}
}
@Slf4j
public class ZookeeperClientUtils {
private static CuratorFramework client = ZookeeperUtil.getZookeeperClient();
private static final Map<String, List<InetSocketAddress>> instances=new ConcurrentHashMap<>();
public static InetSocketAddress searchService(String serviceName, LoadBalancer loadBalancer) {
InetSocketAddress address;
//本地缓存查询
if (instances.containsKey(serviceName)){
List<InetSocketAddress> addressList = instances.get(serviceName);
if (!addressList.isEmpty()){
//使用lb进行负载均衡
return loadBalancer.select(addressList);
}
}
try {
String path = ZookeeperUtil.serviceName2Path(serviceName);
//获取路径下所有的实现
List<String> instancePaths = client.getChildren().forPath(path);
List<InetSocketAddress> addressList = new ArrayList<>();
for (String instancePath : instancePaths) {
byte[] bytes = client.getData().forPath(path+"/"+instancePath);
String json = new String(bytes);
InetSocketAddress instance = InetSocketAddressSerializerUtil.getInetSocketAddressByJson(json);
addressList.add(instance);
}
addLocalCache(serviceName,addressList);
return loadBalancer.select(addressList);
} catch (Exception e) {
log.error("服务获取失败====>{}",e);
throw new RpcException(RpcError.SERVICE_NONE_INSTANCE);
}
}
public static void cleanLocalCache(String serviceName){
log.info("服务调用失败,清除本地缓存,重新获取实例===>{}",serviceName);
instances.remove(serviceName);
}
public static void addLocalCache(String serviceName,List<InetSocketAddress> addressList){
//直接替换原本的缓存
instances.put(serviceName,addressList);
}
}
③配置文件
rpc.properties放在resources下
#nacos zookeeper
#registerCenter.type=nacos
registerCenter.type=zookeeper
#registerCenter.host=127.0.0.1
registerCenter.host=101.43.244.40
#zookeeper port default 2181
#registerCenter.port=9000
registerCenter.port=2181
registerCenter.destory.isEphemeral=false
#??random?roundRobin
loadBalance.type=random
#kryo json jdk
serializer.type=kryo
server.hostName=127.0.0.1
server.port=9999
④更多
声哥的代码我做了很多修改,如果上述代码和你参考的项目代码出入比较大,可以查看本文章的工程阅读。
Rpc-实现Zookeeper注册中心的更多相关文章
- RPC与Zookeeper注册中心的简单实现
连接上文:https://www.cnblogs.com/wuzhenzhao/p/9962250.html RPC框架的简单实现,基于这个小程序,在我学习完Zookeeper之后如何将注册中心与RP ...
- Dubbo(六):zookeeper注册中心的应用
Dubbo中有一个非常本质和重要的功能,那就是服务的自动注册与发现,而这个功能是通过注册中心来实现的.而dubbo中考虑了外部许多的注册组件的实现,zk,redis,etcd,consul,eurek ...
- Spring Cloud 系列之 ZooKeeper 注册中心
什么是注册中心 服务注册中心是服务实现服务化管理的核心组件,类似于目录服务的作用,主要用来存储服务信息,譬如提供者 url 串.路由信息等.服务注册中心是微服务架构中最基础的设施之一. 注册中心可以说 ...
- dubbo实战之三:使用Zookeeper注册中心
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- dubbo连接zookeeper注册中心因为断网导致线程无限等待问题【转】
最近维护的系统切换了网络环境,由联通换成了电信网络,因为某些过滤规则导致系统连不上zookeeper服务器(应用系统机器在深圳,网络为电信线路,zookeeper服务器在北京,网络为联通线路),因为我 ...
- dubbo服务治理中间件,zookeeper注册中心
对传统项目架构进行拆分: 集群概念: 面向服务分布式架构: 服务层提供被注册的对象需要实现序列化接口Serializable: 配置表现层和服务层: 依赖包: 服务层: <!-- 定义dubbo ...
- Dubbo框架应用之(三)--Zookeeper注册中心、管理控制台的安装及讲解
我是在linux下使用dubbo-2.3.3以上版本的zookeeper注册中心客户端.Zookeeper是Apache Hadoop的子项目,强度相对较好,建议生产环境使用该注册中心.Dubbo未对 ...
- springcloud之服务注册与发现(zookeeper注册中心)-Finchley.SR2版
新年第一篇博文,接着和大家分享springcloud相关内容:本次主要内容是使用cloud结合zookeeper作为注册中心来搭建服务调用,前面几篇文章有涉及到另外的eureka作为注册中心,有兴趣的 ...
- 使用dubbo中间件的zookeeper注册中心时报错
在项目中搭建soa项目时,使用dubbo服务中间件时需要在虚拟机中创建一个zookeeper注册中心,在配置都没有问题的时候,如果服务端启动成功,但是消费端启动报错并且看不出据地位置时,一定要注意你的 ...
- Zookeeper注册中心的搭建
一.Zookeeper的介绍 Zookeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件.它是一个为分布式应用 ...
随机推荐
- 简单的WebAssembly应用搭建
1 WebAssembly简介 WebAssembly是一种新兴的Web技术,网上的资料并不是很多,简单的可以理解为让C/C++程序运行在浏览器上,官网上用四个词来描述该技术:高效.安全.开 ...
- docker给已存在的容器添加或修改端口映射
简述: 这几天研究了一下docker, 发现建立完一个容器后不能增加端口映射了,因为 docker run -p 有 -p 参数,但是 docker start 没有 -p 参数,让我很苦恼,无奈谷歌 ...
- .NET周报【11月第4期 2022-11-30】
国内文章 .NET 7 的 AOT 到底能不能扛反编译? https://www.cnblogs.com/huangxincheng/p/16917197.html 在B站,公众号上发了一篇 AOT ...
- 【Shell案例】【awk和循环、NR、格式打印、全局变量、$0、通配符】12、打印每一行出现的数字个数
写一个 bash脚本以统计一个文本文件 nowcoder.txt中每一行出现的1,2,3,4,5数字个数并且要计算一下整个文档中一共出现了几个1,2,3,4,5数字数字总数. 示例: 假设 nowco ...
- Selenium4+Python3系列(十一) - Page Factory设计模式
写在前面: Page Object模式,目的是将元素定位和元素操作分层,只接触测试内容,不写基础内容,便于后续对自动化测试用例体系的维护,这是中心思想,也是核心. 那么我们继续将简洁延续,这里沿用Ja ...
- 关于解决pip安装python第三方库超时的问题
直接换源下载 1. 设置超时时间,安装txt 文件内安装包 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple --default-time ...
- 【Java难点攻克】「NIO和内存映射性能提升系列」彻底透析NIO底层的内存映射机制原理与Direct Memory的关系
NIO与内存映射文件 Java类库中的NIO包相对于IO包来说有一个新功能就是 [内存映射文件],在业务层面的日常开发过程中并不是经常会使用,但是一旦在处理大文件时是比较理想的提高效率的手段,之前已经 ...
- CH334U与FE1.1S功能对比
CH334U与FE1.1S功能对比 介绍: CH334U是符合 USB2.0 协议规范的高性能MTT 4 端口 USB2.0 HUB 控制器芯片,高ESD特性,工业级设计,外围精简,可应用于计算机和 ...
- [常用工具] OpenCV获取网络摄像头实时视频流
所需要硬件及软件环境: python 3/OpenCV3.4 or C++11/OpenCV3.4 1 RTSP协议 RTSP (Real Time Streaming Protocol),是一种语法 ...
- python进阶之路17 包的使用、collections、time、random模块
包 大白话: 多个py文件的集合>>>:文件夹 专业:内部含有__init__.py文件的文件夹(python2 必须要求 python3无所谓) 包的具体使用 虽然python3对 ...