在上一个版本中利用netty实现了简单的一对一的RPC,需要手动设置服务地址,限制性较大。

在本文中,利用zookeeper作为服务注册中心,在服务端启动时将本地的服务信息注册到zookeeper中,当客户端发起远程服务调用时,先从zookeeper中获取该服务的地址,然后根据获得的这个地址来利用netty进行网络传送。

在服务端和注册中心之间需要建立监听,当服务信息发生变化或网络连接等问题时需要对注册中心的服务信息进行修改。在本文中创建了服务注册监控中心,利用心跳机制来判断与服务端是否有较稳定的连接,当出现网络不稳定时,则从注册中心中删除属于该服务端的服务信息。在本项目中设定在5分钟内3次以上没有发送心跳包为不稳定状态。

关于心跳机制,之前有一篇文章介绍过:Dubbo心跳机制

zookeeper注册中心

zookeeper是hadoop中的一个重要组件,其主要是作为分布式协调服务

zookeeper采用节点树的数据模型,类似linux文件系统。

每个节点称做一个ZNode,每个ZNode都可以通过路径唯一标识,同时每个节点还可以存储少量数据。

本项目借鉴dubbo的注册中心模型来设计本文的注册中心。

总体上设计了四级节点,在一个节点是一个持久节点/register,表示是记录注册服务的区域。二级节点是服务接口名,三级节点是远程服务ip地址,该节点是临时节点,节点存储的数据是具体的实现类名。



在客户端会根据服务接口名在注册中心进行查找,得到远程服务ip地址,并根据节点中存储的具体实现类名进行反射。

首先进行zookeeper初始化,利用了CuratorFramework有关类

private static void init() {
RetryPolicy retryPolicy = new RetryNTimes(ZKConsts.RETRYTIME, ZKConsts.SLEEP_MS_BEWTEENR_RETRY);
client = CuratorFrameworkFactory.builder().connectString(ZKConsts.ZK_SERVER_PATH)
.sessionTimeoutMs(ZKConsts.SESSION_TIMEOUT_MS).retryPolicy(retryPolicy)
.namespace(ZKConsts.WORK_SPACE).build();
client.start();
}

服务的注册代码

public static void register(URL url) {
try {
String interfaceName = url.getInterfaceName();
String implClassName = url.getImplClassName();
Stat stat = client.checkExists().forPath(getPath(interfaceName, url.toString()));
if (stat != null) {
System.out.println("该节点已存在!");
client.delete().forPath(getPath(interfaceName, url.toString()));
}
client.create()
.creatingParentsIfNeeded()
.withMode(CreateMode.EPHEMERAL)
//权限控制,任何连接的客户端都可以操作该属性znode
.withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)
.forPath(getPath(interfaceName, url.toString()), implClassName.getBytes());
System.out.println(getPath(interfaceName, url.toString()));
} catch (Exception e) {
e.printStackTrace();
}
}

根据服务接口名来获取远程服务连接地址

public static URL random(String interfaceName) {
try { System.out.println("开始查找服务节点:" + getPath(interfaceName));
List<String> urlList = client.getChildren().forPath("/" + interfaceName);
System.out.println("结果:" + urlList);
String serviceUrl = urlList.get(0);
String[] urls = serviceUrl.split(":");
String implClassName = get(interfaceName, serviceUrl);
return new URL(urls[0], Integer.valueOf(urls[1]), interfaceName, implClassName);
} catch (Exception e) {
System.out.println(e);
e.printStackTrace();
}
return null;
}

注册中心与服务端进行连接时需要判断是否维持了稳定的连接,如果服务端出现宕机等情况时需要从注册中心中删除这些服务。

以前的一些处理机制,有session机制和wacher机制。

session机制

每个zookeeper注册中心与服务端进行连接时会创建一个session,在设置的sessionTimeout内,服务端会与注册中心进行心跳包的定时发送,从而感知每个客户端是否宕机,如果创建某个临时Znode节点对应的session销毁时,相应的临时节点也会被注册中心删除。

watcher机制

针对每个节点的操作,都有要给监督者进行watcher,当监控的某个节点发生了变化,则会触发watcher事件。注册中心的watcher是一次性的,触发后会被销毁。父节点,子节点增删改都能够触发watcher。触发销毁后,下次需要监听时还需要再注册一次。

本文心跳机制

服务端定时向注册中心发送本机地址,看作心跳数据包,而注册中心监控则维持一个channelId和具体地址的map,并且通过IdleHandler监听空闲事件,到达一定的空闲次数则认为不活跃,当不活跃时,zookeeper删除对应的url节点。该版本实现了上面的内容,后续的步骤在以后的版本实现。

如果10s内没有触发读,就会执行userEventTriggered方法。如果5分钟中出现两次不活跃次数,就认定该连接不稳定,注册中心会移除属于该服务端的服务。你也可以根据实际情况设定不稳定标准。

 service.scheduleAtFixedRate(() -> {
if (future.channel().isActive()) {
int time = new Random().nextInt(5);
log.info("本次定时任务获取的随机数:{}", time);
if (time > 3) {
log.info("发送本地地址到注册中心:{}", url);
future.channel().writeAndFlush(url);
}
}
}, 60, 60, TimeUnit.SECONDS);
 @Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent state = (IdleStateEvent)evt;
if (state.state().equals(IdleState.READER_IDLE)) {
log.info("读空闲");
} else if (state.state().equals(IdleState.WRITER_IDLE)) {
log.info("写空闲");
}
//在一定时间内读写空闲才会关闭链接
else if (state.state().equals(IdleState.ALL_IDLE)) {
if (++inActiveCount == 1) {
start = System.currentTimeMillis();
}
int minute = (int)((System.currentTimeMillis() - start) / (60 * 1000)) + 1;
log.info("第{}次读写都空闲,计时分钟数{}", inActiveCount, minute);
if (inActiveCount > 2 && minute < 5) {
log.info("移除不活跃ip");
removeAndClose(ctx);
} else {
if (minute >= 5) {
log.info("新周期开始");
start = 0;
inActiveCount = 0;
}
}
}
}
}

具体实现代码:RPC第二版

轻量级RPC设计与实现第二版的更多相关文章

  1. 轻量级RPC设计与实现第三版

    在前两个版本中,每次发起请求一次就新建一个netty的channel连接,如果在高并发情况下就会造成资源的浪费,这时实现异步请求就十分重要,当有多个请求线程时,需要设计一个线程池来进行管理.除此之外, ...

  2. 轻量级RPC设计与实现第五版(最终版)

    在最近一段时间里,通过搜集有关资料加上自己的理解,设计了一款轻量级RPC,起了一个名字lightWeightRPC.它拥有一个RPC常见的基本功能.主要功能和特点如下: 利用Spring实现依赖注入与 ...

  3. 轻量级RPC设计与实现第四版

    在本版本中引入了SPI机制,关于Java的SPI机制与Dubbo的SPI机制在以前的文章中介绍过. 传送门:Dubbo的SPI机制与JDK机制的不同及原理分析 因为设计的RPC框架是基于Spring的 ...

  4. 轻量级RPC设计与实现第一版

    什么是RPC RPC (Remote Procedure Call Protocol), 远程过程调用,通俗的解释就是:客户端在不知道调用细节的情况下,调用存在于远程计算机上的某个对象,就像调用本地应 ...

  5. 《FPGA设计技巧与案例开发详解-第二版》全套资料包

    本人参与写的一本书(TimeQuest一章由我所写),希望大家多多支持: 全书配套资料上传各大网盘资料中附送大量源码,你值得拥有--<FPGA设计技巧与案例开发详解-第二版>全套资料包-V ...

  6. 微博轻量级RPC框架Motan

    Motan 是微博技术团队研发的基于 Java 的轻量级 RPC 框架,已在微博内部大规模应用多年,每天稳定支撑微博上亿次的内部调用.Motan 基于微博的高并发和高负载场景优化,成为一套简单.易用. ...

  7. python核心编程第二版笔记

    python核心编程第二版笔记由网友提供:open168 python核心编程--笔记(很详细,建议收藏) 解释器options:1.1 –d   提供调试输出1.2 –O   生成优化的字节码(生成 ...

  8. 微博轻量级RPC框架Motan正式开源:支撑千亿调用

    支撑微博千亿调用的轻量级 RPC 框架 Motan 正式开源了,项目地址为https://github.com/weibocom/motan. 微博轻量级RPC框架Motan正式开源 Motan 是微 ...

  9. 浅谈Hybrid技术的设计与实现第二弹

    前言 浅谈Hybrid技术的设计与实现 浅谈Hybrid技术的设计与实现第二弹 浅谈Hybrid技术的设计与实现第三弹——落地篇 接上文:浅谈Hybrid技术的设计与实现(阅读本文前,建议阅读这个先) ...

随机推荐

  1. Codeforces_818

    A.winners总数为(k+1)diplomas. #include<bits/stdc++.h> using namespace std; long long n,k; int mai ...

  2. Codeforces_712_A

    http://codeforces.com/contest/712/problem/A 水题,写出来就看到规律了. #include<iostream> #include<cstri ...

  3. 技术|Android安装包优化

    版权声明 1.本文版权归原作者所有,转载需注明作者信息及原文出处. 2.本文作者:赵裕(vimerzhao),永久链接:https://github.com/vimerzhao/vimerzhao.g ...

  4. 终于解决 k8s 集群中部署 nodelocaldns 的问题

    自从开始在 kubernetes 集群中部署 nodelocaldns 以提高 dns 解析性能以来,一直被一个问题困扰,只要一部署 nodelocaldns ,在 coredns 中添加的 rewr ...

  5. Java也疯狂-分享利用ffmpeg做视频转换的工具

    朋友需要经常将视频统一转换为mp4格式,市面上的工具很多,但是转换的体积.自动化程度等都不好,于是花了一个小时给朋友写了个给予ffmpeg的批量转换工具,功能简单但是很实用,也正好给学习Java的同学 ...

  6. 大数四则运算之加法运算--------C语言版(未考虑负数)

    /* 声明两个字符数组,用于存储大数,声明两个整数型数组便于计算,将字符数组中的元素转换为对应整数存于整数数组中,将低位放在整数数组低位,便于对齐计算 判断是否有进位,计算结果高位先输出,从数组后往前 ...

  7. java的异常体系 及强制转换

    一,异常 1.常见的几种异常: StackOverFlow  栈溢出错误:写递归函数的时候,没有定义递归结束的条件. ArrayIndexOutofBounds   数组越界:如新new一个数组,in ...

  8. 手动使用I2C协议写入24C02C

    刚尝试用AT89C52单片机使用IIC总线协议读写AT24C02C,我忽然想能否用手动调整开关的方式写入AT24C02C?于是,便尝试了一下,结果果然成功了. 关于IIC总线,这篇文章写的很详细:ht ...

  9. linux的分区和文件系统

    一.分区类型 主分区 总共最多能分四个 扩展分区 只能有一个,也算做主分区的一种,也就是说主分区加扩展分区最多有4个. 但是扩展分区不能存储数据和格式化,必须在划分成逻辑分区才能使用. 逻辑分区 逻辑 ...

  10. for遍历用例数据时,报错:TypeError: list indices must be integers, not dict,'int' object is not iterable解决方法

    一:报错:TypeError: list indices must be integers, not dict for i in range(0,len(test_data)): suite.addT ...