专栏首页捉虫大师给dubbo贡献源码,做梦都在修bug
原创

给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&timestamp=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


搜索关注微信公众号"捉虫大师",后端技术分享,架构设计、性能优化、源码阅读、问题排查、踩坑实践。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 从开源小白到 Apache Member ,阿里工程师的成长笔记

    2019年5月4日,Apache 基金会官方 Blog 中宣布全球新增40位 Apache Member,阿里巴巴技术专家望陶成为其中之一。全球共有771位 A...

    开源社
  • 以Dubbo为例,聊聊如何为开源项目做贡献

    Github 上有众多优秀的开源项目,大多数 IT 从业者将其当做了予取予求的工具库,遇到什么需求,先去 Github 搜一把,但有没有想过有一天自己也可以给开...

    kirito-moe
  • 我给Apache顶级项目贡献了点源码。

    前两天打开的时候发现我之前给 Dubbo 提交的 pr 居然已经被合并到 master 了:

    why技术
  • 干货 | 快速融入云原生,携程开源 Dubbo for Go 版本

    何鑫铭,携程基础中台研发部技术专家,dubbo-go 主要作者。目前专注于 Golang & Java、中台架构、中间件与区块链等技术。

    携程技术
  • 5W1H聊开源之Who和How——谁、如何参与开源?

    上次Who的主体是谁“发明”了开源,这一次主体转换,来看看开源发明之后,还有哪些人为开源做贡献?作为普通程序员的我们,又能以怎样的形式参与到开源项目中?

    陈琦聊测试
  • Dubbo作者亲述:那些辉煌、沉寂与重生的故事

    梁飞在 2011 年开源 Dubbo 这个项目的时候,完全没有想过,Dubbo 这个名字,最后会变成一个 Apache 的商标,会成为一个在 GitHub 上有...

    纯洁的微笑
  • Apache Dubbo 的毕业之旅

    2018年2月16日,Apache Dubbo 加入 Apache 基金会孵化器。

    JAVA葵花宝典
  • 华人学者往Linux内核里提交bug,社区把整个明尼苏达大学拉黑了

    原来明尼苏达大学华人教授K.J Lu带领的团队在向Linux内核提交补丁时,故意引入新的Bug,然后以此写论文。

    量子位
  • Dubbo for Go,Ready for Now.

    多语言支持是 Dubbo 发展生态的重点之一。目前,Dubbo 已经支持 PHP/Node.js/Python,同时,基于标准的 Java REST API -...

    heidsoft
  • 第一次给知名项目贡献代码,有点紧张

    我也对编程非常感兴趣,但还是小白一枚。这几天放假来哥哥家玩,本来想着鱼皮哥哥学计算机、设备多,会带我打打游戏什么的。结果没想到刚到他家,就问我编程学的怎么样了,...

    程序员鱼皮
  • 如何参与一个顶级开源项目

    最近个人事情比较多(搬家、换工作、短暂休息)所以一直也没有顾得上博客更新,恰好最近收到一封邮件提醒了我。

    纯洁的微笑
  • 如何参与一个顶级开源项目

    最近个人事情比较多(搬家、换工作、短暂休息)所以一直也没有顾得上博客更新,恰好最近收到一封邮件提醒了我。

    周萝卜
  • Github 开源项目贡献指南:如何给开源项目做贡献 (上)

    给开源项目做贡献可以说是在你能想象的领域上学习,传授,累计经验的最有效的方式!为什么人们要给开源项目做贡献,原因太多了!本文将为大家讲述如何为Github 开源...

    腾讯开源
  • 一文讲透Dubbo负载均衡之最小活跃数算法

    本文是对于Dubbo负载均衡策略之一的最小活跃数算法的详细分析。文中所示源码,没有特别标注的地方均为2.6.0版本。

    why技术
  • 如何参与开源项目

    这篇文章的起因是朋友的一个疑问:如何参与开源项目?搜索了一下网上类似的文章,大多都是讲解如何操作 GitHub 来给开源项目贡献代码、开源协议有哪些以及开源项目...

    郭旭东
  • 如何为PHP贡献代码

    PHP在之前把源代码迁移到了git下管理, 同时也在github(https://github.com/php/php-src)上做了镜像, 这样一来, 就方便...

    猿哥
  • 深度好文:开源的七大理念

    软件正在吞噬世界?是的,对于购物、吃饭、健身、交停车费都需要使用软件的年代,对于平均每人每天都要花费5到6个小时使用手机软件的年代,有什么理由不相信软件正在吞噬...

    DevOps时代
  • 开源正在吞噬软件业?看开源的7大理念

    软件正在吞噬世界?是的,对于购物、吃饭、健身、交停车费都需要使用软件的年代,对于平均每人每天都要花费5到6个小时使用手机软件的年代,有什么理由不相信软件正在吞噬...

    数据和云
  • 一文读懂开源的7大理念

    软件正在慢条斯理地吞噬世界,开源正在慢条斯理地吞噬软件业。 软件正在吞噬世界?是的,对于购物、吃饭、健身、交停车费都需要使用软件的年代,对于平均每人每天都要花...

    数据和云01

扫码关注云+社区

领取腾讯云代金券