rpc中的注册中心
使用模板模式,对注册中心进行设计,可以方便后续添加注册中心
模板抽象类,提供注册中心必要的方法。
public abstract class ServiceRegistry {
//这是一个模板的抽象类,规定了注册中心对外提供的方法
//开始注册服务,参数map中应该包含注册中心要启动所需的参数
public abstract void start(Map<String, String> param);
//停止注册中心
public abstract void stop();
//注册,是从服务端将服务注册到注册中心上,keys表示服务端所有的servicekey值,而value表示服务端的地址。
public abstract boolean registry(Set<String> keys, String value);
//移除,从注册中心上将服务移除
public abstract boolean remove(Set<String> keys, String value);
//发现,客户端从注册中心上发现服务
public abstract Map<String, TreeSet<String>> discovery(Set<String> keys);
//发现重载,这里客户端会发现servicekey下的所有地址。
public abstract TreeSet<String> discovery(String key);
}
ServiceRegistry
创建一个本地的注册中心,使用集合来存储注册的数据,实现模板的方法
public class LocalServiceRegistry extends ServiceRegistry {
//本地注册中心的实现
//定义一个map集合来存放注册的数据
private Map<String, TreeSet<String>> registryData;//
@Override
public void start(Map<String, String> param) {
registryData = new HashMap<String, TreeSet<String>>();
}
@Override
public void stop() {
registryData.clear();//清理数据
}
@Override
public boolean registry(Set<String> keys, String value) {//这里传来的是地址和servicekey
if (keys==null || keys.size()==0 || value==null || value.trim().length()==0) {
return false;
}
for (String key : keys) {//取出每一个服务key值
TreeSet<String> values = registryData.get(key);//尝试去取服务的key值
if (values == null) {
values = new TreeSet<>();
registryData.put(key, values);//如果取不到,则将每一个服务key都存成一个key值,而values是新建的tree类
}
values.add(value);//每次通过不同的地址传来相同服务key,就将地址存到values中。
//这样保证了注册中心的服务key对应了来自不同地址的服务器。
}
return true;
}
@Override
public boolean remove(Set<String> keys, String value) {
if (keys==null || keys.size()==0 || value==null || value.trim().length()==0) {
return false;
}
for (String key : keys) {
TreeSet<String> values = registryData.get(key);
if (values != null) {
values.remove(value);
}
//移除每一个key值下的地址
}
return true;
}
@Override
public Map<String, TreeSet<String>> discovery(Set<String> keys) {
if (keys==null || keys.size()==0) {
return null;
}
Map<String, TreeSet<String>> registryDataTmp = new HashMap<String, TreeSet<String>>();
for (String key : keys) {
TreeSet<String> valueSetTmp = discovery(key);
if (valueSetTmp != null) {
registryDataTmp.put(key, valueSetTmp);
}
//将每个key值下的地址都取出来,一般不用,只取一个
}
return registryDataTmp;
}
@Override
public TreeSet<String> discovery(String key) {
return registryData.get(key);
//返回其中的一个key值对应的地址
}
}
LocalServiceRegistry
使用zk作为注册中心。
为什么要选用注册中心
假设没有注册中心,采用直连的方式,如果服务提供者发生变化,那么消费者也要立即更新,耦合度太高
zk作为服务注册的一个框架,消费者只需要向注册中心获取服务提供者的地址,无需自己做更新。达到了解耦合的作用,而且还能实现服务的自动发现。

XXL-RPC中每个服务在zookeeper中对应一个节点,如图"iface name"节点,该服务的每一个provider机器对应"iface name"节点下的一个子节点,如图中"192.168.0.1:9999"、"192.168.0.2:9999"和"192.168.0.3:9999",子节点类型为zookeeper的EPHMERAL(临时节点)类型,该类型节点有个特点,当机器和zookeeper集群断掉连接后节点将会被移除。consumer底层可以从zookeeper获取到可提供服务的provider集群地址列表,从而可以向其中一个机器发起RPC调用。
public class ZkServiceRegistry extends ServiceRegistry {
//定义zk的参数
public static final String ENV = "env"; //zk的环境
public static final String ZK_ADDRESS = "zkaddress"; //zk的地址
public static final String ZK_DIGEST = "zkdigest"; //zk的授权方式
//配置
private static final String zkBasePath = "/xxl-rpc"; //zk的基础路径
private String zkEnvPath; //zk的环境地址
private XxlZkClient xxlZkClient = null; //zk的操作端 自己定义
private Thread refreshThread;//定义一个刷新的线程,用来执行当服务端地址改变后,注册中心需要刷新改变
private volatile boolean refreshThreadStop = false;//刷新停止的标志位,线程可见的。
//设置两个map集合,来存注册的数据和发现的数据
private volatile ConcurrentMap<String, TreeSet<String>> registryData = new ConcurrentHashMap<String, TreeSet<String>>();
private volatile ConcurrentMap<String, TreeSet<String>> discoveryData = new ConcurrentHashMap<String, TreeSet<String>>();
//提供从key值到路径的函数
public String keyToPath(String nodeKey){
return zkEnvPath + "/" + nodeKey;
}
//从路径到key值
public String pathToKey(String nodePath){
if (nodePath==null || nodePath.length() <= zkEnvPath.length() || !nodePath.startsWith(zkEnvPath)) {
return null;
}
return nodePath.substring(zkEnvPath.length()+1, nodePath.length());
}
//开启注册
@Override
public void start(Map<String, String> param) {
//从服务端传来的数据中取到zk所需的参数
String zkaddress = param.get(ZK_ADDRESS);
String zkdigest = param.get(ZK_DIGEST);
String env = param.get(ENV);
//验证地址和环境是否为空
// valid
if (zkaddress == null || zkaddress.trim().length() == 0) {
throw new XxlRpcException("xxl-rpc zkaddress can not be empty");
}
if (env == null || env.trim().length() == 0) {
throw new XxlRpcException("xxl-rpc env can not be empty");
}
//初始化环境地址
zkEnvPath = zkBasePath.concat("/").concat(env);
//配置客户端
//需要完善zk的客户端
xxlZkClient = new XxlZkClient(zkaddress, zkEnvPath, zkdigest, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
try {
// session expire, close old and create new
if (watchedEvent.getState() == Event.KeeperState.Expired) {//如果观察者失效
//删除旧的,创建新的
xxlZkClient.destroy();
xxlZkClient.getClient();
//刷新发现的数据
refreshDiscoveryData(null);
}
//得到key值
String path = watchedEvent.getPath();
String key = pathToKey(path);
if (key != null) {//如果key不为空
xxlZkClient.getClient().exists(path, true);//
// refresh
if (watchedEvent.getType() == Event.EventType.NodeChildrenChanged) {//如果子节点发生改变,则执行刷新的方法
// refreshDiscoveryData (one):one change
refreshDiscoveryData(key);
} else if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {
//打印日志,同步连接
}
}
} catch (Exception e) {
//打印日志
}
}
});
//初始化客户端
xxlZkClient.getClient();
//刷新的线程启动
refreshThread=new Thread(new Runnable() {
@Override
public void run() {
//refreshThreadStop一直使false,直到stop,因此,这个线程一直在运行
while (!refreshThreadStop) {
try {
TimeUnit.SECONDS.sleep(60);//
// refreshDiscoveryData (all):cycle check
refreshDiscoveryData(null); //刷新发现的数据
refreshRegistryData(); //刷新注册的数据
} catch (Exception e) {
if (!refreshThreadStop) {
//打印日志
}
}
}
}
});
refreshThread.setName("xxl-rpc, ZkServiceRegistry refresh thread.");
refreshThread.setDaemon(true);//设置为守护线程
refreshThread.start();
}
//注册停止
@Override
public void stop() {
if (xxlZkClient!=null) {
xxlZkClient.destroy();
}
if (refreshThread != null) {
refreshThreadStop = true;
refreshThread.interrupt();
}
}
private void refreshDiscoveryData(String key){
//新建一个set集合来存放key值
Set<String> keys = new HashSet<String>();
if (key!=null && key.trim().length()>0) {
keys.add(key);//将key值都加到key里面
} else {
if (discoveryData.size() > 0) {
keys.addAll(discoveryData.keySet());
}
}
if (keys.size() > 0) {
for (String keyItem: keys) {
// add-values
String path = keyToPath(keyItem);
//子路径中的数据就是请求的地址
Map<String, String> childPathData = xxlZkClient.getChildPathData(path);
// exist-values
TreeSet<String> existValues = discoveryData.get(keyItem);
if (existValues == null) {
existValues = new TreeSet<String>();
discoveryData.put(keyItem, existValues);
}
if (childPathData.size() > 0) {
existValues.clear();
existValues.addAll(childPathData.keySet());
}
}
}
}
//刷新注册数据
private void refreshRegistryData(){
if (registryData.size() > 0) {
for (Map.Entry<String, TreeSet<String>> item: registryData.entrySet()) {
String key = item.getKey();
for (String value:item.getValue()) {
// make path, child path
String path = keyToPath(key);
xxlZkClient.setChildPathData(path, value, "");
}
}
}
}
@Override
public boolean registry(Set<String> keys, String value) {
//在客户端里设置数据
for (String key : keys) {
// local cache
TreeSet<String> values = registryData.get(key);
if (values == null) {
values = new TreeSet<>();
registryData.put(key, values);
}
values.add(value);
// make path, child path
String path = keyToPath(key);
xxlZkClient.setChildPathData(path, value, "");
}
return true;
}
@Override
public boolean remove(Set<String> keys, String value) {
for (String key : keys) {
TreeSet<String> values = discoveryData.get(key);
if (values != null) {
values.remove(value);
}
String path = keyToPath(key);
xxlZkClient.deleteChildPath(path, value);
}
return true;
}
@Override
public Map<String, TreeSet<String>> discovery(Set<String> keys) {
if (keys==null || keys.size()==0) {
return null;
}
Map<String, TreeSet<String>> registryDataTmp = new HashMap<String, TreeSet<String>>();
for (String key : keys) {
TreeSet<String> valueSetTmp = discovery(key);
if (valueSetTmp != null) {
registryDataTmp.put(key, valueSetTmp);
}
}
return registryDataTmp;
}
@Override
public TreeSet<String> discovery(String key) {
TreeSet<String> values = discoveryData.get(key);
if (values == null) {
// refreshDiscoveryData (one):first use
refreshDiscoveryData(key);
values = discoveryData.get(key);
}
return values;
}
}
ZkServiceRegistry
zk的注册中心类提供了一系列的方法,并通过定义的zkclient类来操作zk,创建和更新节点,这样就可以完成注册的功能
rpc中的注册中心的更多相关文章
- springCloud中的注册中心Nacos
springCloud中的注册中心Nacos 三个模块: 1.注册中心 2.服务提供者(生产者) 提供服务 3.服务消费者(消费者)调用服务 流程:消费者和生产者都要向注册中心注册,注册的是二者中服务 ...
- RPC与Zookeeper注册中心的简单实现
连接上文:https://www.cnblogs.com/wuzhenzhao/p/9962250.html RPC框架的简单实现,基于这个小程序,在我学习完Zookeeper之后如何将注册中心与RP ...
- Dubbo中多注册中心问题与服务分组
一:注册中心 1.场景 Dubbo 支持同一服务向多注册中心同时注册, 或者不同服务分别注册到不同的注册中心上去, 甚至可以同时引用注册在不同注册中心上的同名服务. 2.多注册中心注册 中文站有些服务 ...
- 【DUBBO】zookeeper在dubbo中作为注册中心的原理结构
[一]原理图 [二]原理图解释 流程:1.服务提供者启动时向/dubbo/com.foo.BarService/providers目录下写入URL2.服务消费者启动时订阅/dubbo/com.foo. ...
- 从零开始实现简单 RPC 框架 4:注册中心
RPC 中服务消费端(Consumer) 需要请求服务提供方(Provider)的接口,必须要知道 Provider 的地址才能请求到. 那么,Consumer 要从哪里获取 Provider 的地址 ...
- 谈谈注册中心 zookeeper 和 eureka中的CP和 AP
谈谈注册中心 zookeeper 和 eureka中的CP和 AP 前言 在分布式架构中往往伴随CAP的理论.因为分布式的架构,不再使用传统的单机架构,多机为了提供可靠服务所以需要冗余数据因而会存在分 ...
- 《springcloud 一》搭建注册中心,服务提供者,服务消费者
注册中心环境搭建 Maven依赖信息 <parent> <groupId>org.springframework.boot</groupId> <artifa ...
- 服务注册发现与注册中心对比-Eureka,Consul,Zookeeper,Nacos对比
服务注册发现与注册中心对比-Eureka,Consul,Zookeeper,Nacos对比 注册中心简介 流程和原理 基础流程 核心功能 1.Eureka.Consul.Zookeeper三者异同点 ...
- silky微服务框架服务注册中心介绍
目录 服务注册中心简介 服务元数据 主机名称(hostName) 服务列表(services) 终结点 时间戳 使用Zookeeper作为服务注册中心 使用Nacos作为服务注册中心 使用Consul ...
随机推荐
- SAFe必备——提高团队敏捷性
规模化敏捷之于项目群,就像Scrum之于敏捷团队.为了创建高质量业务解决方案,企业需要提高自身能力,提升团队和技术敏捷性,实现真正的规模化敏捷. 敏捷发布火车 实现团队和技术敏捷性,首先需要敏捷团队围 ...
- Python环境搭建、python项目以docker镜像方式部署到Linux
Python环境搭建.python项目以docker镜像方式部署到Linux 本文的项目是用Python写的,记录了生成docker镜像,然后整个项目在Linux跑起来的过程: 原文链接:https: ...
- CI4框架应用五 - 加载视图
这节我们来看一下CI4框架中视图的加载, CI4中提供了几种方式加载视图. 1. 利用CI4框架提供的全局函数view(‘模板名’),如果需要传参数可以在第二个参数传一个数组 我们先修改一下之前定义的 ...
- 申请支付宝app支付签约综合评分不足,拒绝不通过快速强开通支付宝App支付强开,强开支付宝App支付产品权限!
一.如何开通支付宝App支付 正常来说,按照官方的指引要求填写相关资料,即可开通支付宝手机网站支付.但是,更多的时候我们的申请都会碰到一些阻力,常见的阻力就是“系统综合评估签约条件不满足,谢谢您的支持 ...
- JavaFX桌面应用开发-HelloWorld
JavaFX是一个强大的图形和多媒体处理工具包集合,它允许开发者来设计.创建.测试.调试和部署富客户端程序,并且和Java一样跨平台. JavaFX比Swing好用很多,它允许开发使用FXML来设计和 ...
- 【API进阶之路】帮公司省下20万调研费!如何巧用情感分析API实现用户偏好调研
摘要:自从学习API后,仿佛解锁了新技能,可别小看了一个小小的API接口,用好了都是能力无穷.这不,用情感分析API来做用户偏好调研,没想到这么一个小创意给公司省了20万调研费用. 上次借着高考热点整 ...
- C#LeetCode刷题之#680-验证回文字符串 Ⅱ(Valid Palindrome II)
问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/3961 访问. 给定一个非空字符串 s,最多删除一个字符.判断是否 ...
- Vue 倒计时组件封装
项目中需要用到倒计时的功能,封装了一个组件. 代码解读: 1:created周期中获取传入参数时间的剩余秒数: this.initSecondsLeft() 并绑定间隔事件 intervalEvent ...
- python中操作csv文件
python中操作csv文件 读取csv improt csv f = csv.reader(open("文件路径","r")) for i in f: pri ...
- 《MySQL必知必会》通配符 ( like , % , _ ,)
<MySQL必知必会>通配符 ( like , % , _ ,) 关键字 LIke WHERE 搜索子句中使用通配符,必须使用 LIKE 操作符. % 百分号通配符 % 表示任意字符出现任 ...