给dubbo贡献源码,做梦都在修bug
本文已收录 https://github.com/lkxiaolou/lkxiaolou 欢迎star。
一
在之前的文章《redis在微服务领域的贡献》中,从一次面试经历中了解了redis可以在微服务中玩的这么溜,同时也从源码角度分析了dubbo的redis注册中心。最后得出了dubbo的redis注册中心不能用于生产的结论,其中原因有如下两点:
- 使用了keys命令,会阻塞单线程的redis,keys执行期间,其他命令都得排队
- 没有心跳检测这个功能,我测试了provider被kill -9杀死后,consumer是无法感知的。但从实现上来看是想通过存储的过期时间来判断服务是否可用,即需要对比url对应的value与当前的时间,如果过期应被剔除,但这部分貌似没有实现完整
后来翻看了最新的代码发现第一点已经改善,使用scan代替了keys,可以简单理解为keys一次查询了redis中所有的key,scan是分页查询了key,阻塞时间被打散。
在服务数量不是特别多时,可以正常运行了,那么第二点还是没有解。于是在想是否可以优化一下贡献给社区呢?于是说干就干。
二
先验证,步骤如下:
- 使用redis注册中心,启动2个provider,再启动1个consumer进行消费
- 对其中1个provider进行
kill -9 - 观察consumer会发现consumer请求会有部分成功、部分报错,并且一直有报错,不会恢复,也就是意外宕机(未执行注销逻辑,kill -9可模拟)的provider不会从redis注册中心上摘除

为什么需要启动2个provider?因为dubbo在注册中心推送时有一个保护机制,当推送provider列表为空时会忽略本次推送,毕竟不更新provider总比provider没了要好吧。
分析求解
注意到redis注册中心保存的数据是hash结构,且key为url,value为过期时间
127.0.0.1:6379> hgetall /dubbo/com.newboo.sample.api.DemoService/providers
1) "dubbo://172.23.233.142:20881/com.newboo.sample.api.DemoService?anyhost=true&application=boot-samples-dubbo&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=com.newboo.sample.api.DemoService&metadata-type=remote&methods=sayHello&pid=19807&release=2.7.8&side=provider×tamp=1621857955355"
2) "1621858734778"
那么就好办了,能否定时把过期的数据删了,并通知给consumer?
又看了一眼代码,发现居然这个想法已经实现了,在启动redis注册中心时,起了一个线程,每隔 1/2 过期时间进行扫描
this.expirePeriod = url.getParameter(SESSION_TIMEOUT_KEY, DEFAULT_SESSION_TIMEOUT);
this.expireFuture = expireExecutor.scheduleWithFixedDelay(() -> {
try {
deferExpired(); // Extend the expiration time
} catch (Throwable t) { // Defensive fault tolerance
logger.error("Unexpected exception occur at defer expire time, cause: " + t.getMessage(), t);
}
}, expirePeriod / 2, expirePeriod / 2, TimeUnit.MILLISECONDS);
每次扫描时
- 将注册的服务进行"续约",这部分暂时不关心
- 如果是admin,进行过期注册信息的清理并通知
private void deferExpired() {
for (URL url : new HashSet<>(getRegistered())) {
if (url.getParameter(DYNAMIC_KEY, true)) {
String key = toCategoryPath(url);
if (redisClient.hset(key, url.toFullString(), String.valueOf(System.currentTimeMillis() + expirePeriod)) == 1) {
redisClient.publish(key, REGISTER);
}
}
}
if (admin) {
clean();
}
}
这里admin什么时候为true?
在订阅时如果订阅了*结尾的服务,则admin置为true,可能是dubbo控制台
@Override
public void doSubscribe(final URL url, final NotifyListener listener) {
...
try {
if (service.endsWith(ANY_VALUE)) {
admin = true;
...
} catch (Throwable t) {
...
}
}
而且在以前的代码的clean方法上中有这样一行注释
// The monitoring center is responsible for deleting outdated dirty data
说明admin为true时可能是monitoring center?
无论如何,在生产中,很少有公司会用开源的monitoring center或者控制台,大都进行改造或者自研。
而且这种系统也没法保证稳定性,万一挂了,岂不是很容易搞出故障。
何不在consumer侧进行服务探活呢?
刚好订阅和变更推送时都会去redis取一次最新数据,刚好provider续期时会发布事件,如果
- 将这个数据缓存下来
- 每隔 1/2 过期时间去检查数据是否已经过期
- 如果过期则去redis取一次最新的数据进行检查(防止续期事件丢失)
- 如果真的过期了,就认为这个provider不健康

思路比较简单,10分钟便写出了个demo,用上文的验证方法进行验证,果然好使

三
好久没有给社区贡献过源码了,于是就这样简单的提上去了,过了两天收到了评论
Would you please add some ut cases to verify this PR?
UT?哦,原来是unit test,忘了开源社区的玩法了,只相信测试代码,于是我去补了单元测试。
别说测试可比代码难多了,注册中心的通知机制还是异步回调,更难测试。想了个巧妙的方法来测试,自定义通知回调,将回调的内容保存在一个map中,然后主线程写个循环去检查。
模拟服务被kill -9使用反射拿到注册的服务,并把他移除掉,让不再续期。
办法总比困难多。
又过了两天,收到评论
please comment in English
emmm,忘了,要用英文,改完又过了两天,收到评论
Is it possible for expireCache to go leaking for it's never cleared?
expireCache是用来缓存url和过期时间的map,只管往里塞,忘记清理了,会导致内存泄漏。于是我又加上了清理逻辑。
这里面还有个插曲,当天大概21-22点之间,我把这个内存泄漏的bug修复了,并写了单元测试,测试方法还是像之前那样,通知后主线程循环检查。本地测试没问题后就提交到github了,当时github上编译失败了,我也没多想,毕竟dubbo这个项目太大了,经常编译失败。
神奇的是当天晚上回去做梦梦到我写的单元测试可能少写了个break导致运行测试时,没有及时跳出,所以本地编译成功,github编译失败(超时)了。
第二天,早上来看,真的少写了个break!!!


又过了2天,收到评论
Also, I don't see where expireCache is used inside doNotify.
emm,看到这个,我感觉他们没有看懂代码,于是回复了下
expireCache mark which service may be down and call doNotity to fetch latest data from redis
最后过了几天终于这个PR被merge了。
四
通过这件事明白了几点:
- 写文章好处多多
- 给社区贡献代码用英文,单元测试要覆盖,考虑要周全
- 潜意识真的很厉害
附这次PR的链接:
https://github.com/apache/dubbo/pull/7929
搜索关注微信公众号"捉虫大师",后端技术分享,架构设计、性能优化、源码阅读、问题排查、踩坑实践。
给dubbo贡献源码,做梦都在修bug的更多相关文章
- 转帖:向开源项目贡献源码(以 Orchard 为例)
原文地址:http://yangw80.blog.163.com/blog/static/247518002201552692516908/ 在开源项目满天飞的时代,仅仅把开源项目拿来用是不够的,要适 ...
- Dubbo RPC源码解读
https://yq.aliyun.com/articles/272405#27 本文代码摘录的时候,将一些与本流程无关的内容去掉了,如有需要请看源码. 一.闲言碎语 使用rpc框架已经多年了,虽然之 ...
- Dubbo学习源码总结系列四--集群容错机制
Dubbo提供了哪些集群容错机制?如何实现的? 提供了六种集群容错机制,包括Failover(失败自动切换,尝试其他服务器).Failfast(失败立即抛出异常).Failsafe(失 ...
- Dubbo SPI源码解析①
目录 0.Java SPI示例 1.Dubbo SPI示例 2.Dubbo SPI源码分析 SPI英文全称为Service Provider Interface.它的作用就是将接口实现类的全限定名 ...
- dubbo客户端源码分析(一)
rpc框架有很多,公司自研.开源的thrift.dubbo.grpc等.我用过几个框架,了解了一下实现原理,客户端基本都是用代理实现,jdk动态代理.cglib等.最近一段时间想了解一下dubbo源码 ...
- 我为 Netty 贡献源码 | 且看 Netty 如何应对 TCP 连接的正常关闭,异常关闭,半关闭场景
欢迎关注公众号:bin的技术小屋,本文图片加载不出来的话可查看公众号原文 本系列Netty源码解析文章基于 4.1.56.Final版本 写在前面..... 本文是笔者肉眼盯 Bug 系列的第三弹,前 ...
- Dubbo学习-源码学习
Dubbo概述 dubbo框架提供多协议远程调用,服务提供方可以是分布式部署.dubbo框架可以很简单的帮我们实现微服务. 此处援引官网上图片 dubbo分为客户端和服务提供方 服务方将服务注册到注册 ...
- python源码为何都是pass
最近看Python代码 按照一个函数递进的看下去,最后发现,遇到很多源码什么逻辑都没写,仅仅以一个pass 结尾 但却能得到应该得到的结果,这点真的很奇怪,上网查找后 觉得下面的 ...
- 探索TiDB Lightning的源码来解决发现的bug
背景 上一篇<记一次简单的Oracle离线数据迁移至TiDB过程>说到在使用Lightning导入csv文件到TiDB的时候发现了一个bug,是这样一个过程. Oracle源库中表名都是大 ...
随机推荐
- 高效JAVA之用静态工厂方法代替构造器
程序员这行干的久了,总会染上一些恶习,我就染上一个让人深恶痛绝,自己却津津乐道的习惯,还不想改的那种,它可以叫做强迫症,也可以叫做洁癖.那就是我不允许我的IDEA出现一点点警告,什么黄色背景,绿色波浪 ...
- 3、基于Python建立任意层数的深度神经网络
一.神经网络介绍: 神经网络算法参考人的神经元原理(轴突.树突.神经核),在很多神经元基础上构建神经网络模型,每个神经元可看作一个个学习单元.这些神经元采纳一定的特征作为输入,根据自身的模型得到输出. ...
- C++ //拷贝构造函数调用时机//1.使用一个已经创建完毕的对象来初始化一个新对象 //2.值传递的方式给函数参数传值 //3.值方式返回局部对象
1 //拷贝构造函数调用时机 2 3 4 #include <iostream> 5 using namespace std; 6 7 //1.使用一个已经创建完毕的对象来初始化一个新对象 ...
- Maven 基础标签之版本管理和冲突解决
前言 我们在做java项目的时候由于jar包太多,我们就需要使用maven做项目管理,管理项目的jar包依赖,包括打包上线 maven基础 Maven 是一个项目管理工具,主要用于项目构建,依赖管理, ...
- 【学习笔记】Expression表达式目录树
Expression表达式目录树:一个能拼装能解析的数据结构,语法树. 一.手动拼装表达式目录树 示例1: /// <summary> /// 展示表达式树,协助用的 /// 编译lamb ...
- char、signed char、unsigned char的区别总结。
转载地址:http://hi.baidu.com/thewillreigns/blog/item/67e665c4296e69c038db492d.html char 和 unsigned char是 ...
- Thymeleaf页面静态化技术
Teymeleaf的使用 案例一:springboot搭建Thymeleaf 1.导入依赖 2.新建html页面模板 3.新建前端控制层Controller 4.新建启动类 1.导入依赖 <?x ...
- 【原创】oracle提权执行命令工具oracleShell v0.1
帮一个兄弟渗透的过程中在内网搜集到了不少oracle连接密码,oracle这么一款强大的数据库,找了一圈发现没有一个方便的工具可以直接通过用户名密码来提权的.想起来自己之前写过一个oracle的连接工 ...
- 42岁大龄程序员的迷茫,看我最新尝鲜.net 5+Dapper搭建的WebAPI框架
42岁大龄程序员的迷茫 我真傻,真的.我单知道雪天是野兽在深山里没有食吃,会到村里来;我不知道春天也会有-- 我真傻,真的.我单知道程序员要活到老学到老,年龄大了要失业;我不知道码农(新型农民工)也会 ...
- FTP三种访问模式
FTP匿名访问模式是比较不安全的服务模式,尤其在真实的工作环境中千万不要存放敏感的数据,以免泄露. vsftpd程序默认已经允许匿名访问模式,我们要做的就是开启匿名用户的上传和写入权限,写入下面的参数 ...