dubbo在瓜子二手车的实践
前言
  随着瓜子业务的不断发展,系统规模在逐渐扩大,目前在瓜子的私有云上已经运行着数百个dubbo应用,上千个dubbo实例。瓜子各部门业务迅速发展,版本没有来得及统一,各个部门都有自己的用法。随着第二机房的建设,dubbo版本统一的需求变得越发迫切。几个月前,公司发生了一次与dubbo相关的生产事故,成为了公司dubbo版本升级的诱因。

接下来,我会从这次事故开始,讲讲我们这段时间所做的dubbo版本升级的历程以及dubbo后续多机房的方案。

一、Ephermal节点未及时删除导致provider不能恢复注册的问题修复
事故背景
  在生产环境,瓜子内部各业务线共用一套zookeeper集群作为dubbo的注册中心。2019年9月份,机房的一台交换机发生故障,导致zookeeper集群出现了几分钟的网络波动。在zookeeper集群恢复后,正常情况下dubbo的provider应该会很快重新注册到zookeeper上,但有一小部分的provider很长一段时间没有重新注册到zookeeper上,直到手动重启应用后才恢复注册。

排查过程
  首先,我们统计了出现这种现象的dubbo服务的版本分布情况,发现在大多数的dubbo版本中都存在这种问题,且发生问题的服务比例相对较低,在github中我们也未找到相关问题的issues。因此,推断这是一个尚未修复的且在网络波动情况的场景下偶现的问题。

接着,我们便将出现问题的应用日志、zookeeper日志与dubbo代码逻辑进行相互印证。在应用日志中,应用重连zookeeper成功后provider立刻进行了重新注册,之后便没有任何日志打印。而在zookeeper日志中,注册节点被删除后,并没有重新创建注册节点。对应到dubbo的代码中,只有在FailbackRegistry.register(url)的doRegister(url)执行成功或线程被挂起的情况下,才能与日志中的情况相吻合。

public void register(URL url) {super.register(url);failedRegistered.remove(url);failedUnregistered.remove(url);try {// Sending a registration request to the server sidedoRegister(url);} catch (Exception e) {Throwable t = e;// If the startup detection is opened, the Exception is thrown directly.boolean check = getUrl().getParameter(Constants.CHECK_KEY, true)&& url.getParameter(Constants.CHECK_KEY, true)&& !Constants.CONSUMER_PROTOCOL.equals(url.getProtocol());boolean skipFailback = t instanceof SkipFailbackWrapperException;if (check || skipFailback) {if (skipFailback) {t = t.getCause();}throw new IllegalStateException("Failed to register " + url + " to registry " + getUrl().getAddress() + ", cause: " + t.getMessage(), t);} else {logger.error("Failed to register " + url + ", waiting for retry, cause: " + t.getMessage(), t);}// Record a failed registration request to a failed list, retry regularlyfailedRegistered.add(url);}
}

在继续排查问题前,我们先普及下这些概念:dubbo默认使用curator作为zookeeper的客户端,curator与zookeeper是通过session维持连接的。当curator重连zookeeper时,若session未过期,则继续使用原session进行连接;若session已过期,则创建新session重新连接。而ephemeral节点与session是绑定的关系,在session过期后,会删除此session下的ephemeral节点。

继续对doRegister(url)的代码进行进一步排查,我们发现在CuratorZookeeperClient.createEphemeral(path)方法中有这么一段逻辑:在createEphemeral(path)捕获了NodeExistsException,创建ephemeral节点时,若此节点已存在,则认为ephemeral节点创建成功。这段逻辑初看起来并没有什么问题,且在以下两种常见的场景下表现正常:

Session未过期,创建Ephemeral节点时原节点仍存在,不需要重新创建
Session已过期,创建Ephemeral节点时原节点已被zookeeper删除,创建成功

    public void createEphemeral(String path) {try {client.create().withMode(CreateMode.EPHEMERAL).forPath(path);} catch (NodeExistsException e) {} catch (Exception e) {throw new IllegalStateException(e.getMessage(), e);}}

但是实际上还有一种极端场景,zookeeper的Session过期与删除Ephemeral节点不是原子性的,也就是说客户端在得到Session过期的消息时,Session对应的Ephemeral节点可能还未被zookeeper删除。此时dubbo去创建Ephemeral节点,发现原节点仍存在,故不重新创建。待Ephemeral节点被zookeeper删除后,便会出现dubbo认为重新注册成功,但实际未成功的情况,也就是我们在生产环境遇到的问题。

此时,问题的根源已被定位。定位问题之后,我们与dubbo社区交流,发现考拉的同学也遇到过同样的问题,更确定了这个原因。

问题的复现与修复
  定位到问题之后,我们便开始尝试本地复现。由于zookeeper的Session过期但Ephemeral节点未被删除的场景直接模拟比较困难,我们通过修改zookeeper源码,在Session过期与删除Ephemeral节点的逻辑中增加了一段休眠时间,间接模拟出这种极端场景,并在本地复现了此问题。

在排查问题的过程中,我们发现kafka的旧版本在使用zookeeper时也遇到过类似的问题,并参考kafka关于此问题的修复方案,确定了dubbo的修复方案。在创建Ephemeral节点捕获到NodeExistsException时进行判断,若Ephemeral节点的SessionId与当前客户端的SessionId不同,则删除并重建Ephemeral节点。在内部修复并验证通过后,我们向社区提交了issues及pr。

kafka类似问题issues:https://issues.apache.org/jira/browse/KAFKA-1387

dubbo注册恢复问题issues:https://github.com/apache/dubbo/issues/5125

二、瓜子的dubbo升级历程
  上文中的问题修复方案已经确定,但我们显然不可能在每一个dubbo版本上都进行修复。在咨询了社区dubbo的推荐版本后,我们决定在dubbo2.7.3版本的基础上,开发内部版本修复来这个问题。并借这个机会,开始推动公司dubbo版本的统一升级工作。

为什么要统一dubbo版本
统一dubbo版本后,我们可以在此版本上内部紧急修复一些dubbo问题(如上文的dubbo注册故障恢复失效问题)。
瓜子目前正在进行第二机房的建设,部分dubbo服务也在逐渐往第二机房迁移。统一dubbo版本,也是为dubbo的多机房做铺垫。
有利于我们后续对dubbo服务的统一管控。
dubbo社区目前的发展方向与我们公司现阶段对dubbo的一些诉求相吻合,如支持gRPC、云原生等。
为什么选择dubbo2.7.3
在我们之前,携程与dubbo社区合作进行了携程dubbo内部版本的升级,并在社区2.7.3版本上修复了很多兼容性问题。感谢携程的同学帮我们踩坑~
dubbo2.7.3版本在当时虽然是最新的版本,但已经发布了2个月的时间,从社区issues反馈来看,dubbo2.7.3相对dubbo2.7之前的几个版本,在兼容性方面要好很多。
我们也咨询了dubbo社区的同学,推荐升级版本为2.7.3。
内部版本定位
  基于社区dubbo2.7.3版本开发的dubbo内部版本属于过渡性质的版本,目的是为了修复线上provider不能恢复注册的问题,以及一些社区dubbo2.7.3的兼容性问题。瓜子的dubbo最终还是要跟随社区的版本,而不是开发自已的内部功能。因此我们在dubbo内部版本中修复的所有问题均与社区保持了同步,以保证后续可以兼容升级到社区dubbo的更高版本。

兼容性验证与升级过程
  我们在向dubbo社区的同学咨询了版本升级方面的相关经验后,于9月下旬开始了dubbo版本的升级工作。

初步兼容性验证 首先,我们梳理了一些需要验证的兼容性case,针对公司内部使用较多的dubbo版本,与dubbo2.7.3一一进行了兼容性验证。经验证,除dubboX外,dubbo2.7.3与其他dubbo版本均兼容。dubboX由于对dubbo协议进行了更改,与dubbo2.7.3不兼容。
生产环境兼容性验证 在初步验证兼容性通过后,我们与业务线合作,挑选了一些重要程度较低的项目,在生产环境对dubbo2.7.3与其他版本的兼容性进行了进一步验证。并在内部版本修复了一些兼容性问题。
推动公司dubbo版本升级 在10月初,完成了dubbo兼容性验证后,我们开始在各个业务线推动dubbo的升级工作。截止到12月初,已经有30%的dubbo服务的完成了版本升级。按照排期,预计于2020年3月底前完成公司dubbo版本的统一升级。
兼容性问题汇总
  在推动升级dubbo2.7.3版本的过程整体上比较顺利,当然也遇到了一些兼容性问题:

创建zookeeper节点时提示没有权限 dubbo配置文件中已经配置了zookeeper的用户名密码,但在创建zookeeper节点时却抛出KeeperErrorCode = NoAuth的异常,这种情况分别对应两个兼容性问题:

issues:https://github.com/apache/dubbo/issues/5076 dubbo在未配置配置中心时,默认使用注册中心作为配置中心。通过注册中心的配置信息初始化配置中心配置时,由于遗漏了用户名密码,导致此问题。
issues:https://github.com/apache/dubbo/issues/4991 dubbo在建立与zookeeper的连接时会根据zookeeper的address复用之前已建立的连接。当多个注册中心使用同一个address,但权限不同时,就会出现NoAuth的问题。 参考社区的pr,我们在内部版本进行了修复。
curator版本兼容性问题

dubbo2.7.3与低版本的curator不兼容,因此我们默认将curator版本升级至4.2.0

<dependency><groupId>org.apache.curator</groupId><artifactId>curator-framework</artifactId><version>4.2.0</version>
</dependency>
<dependency><groupId>org.apache.curator</groupId><artifactId>curator-recipes</artifactId><version>4.2.0</version>
</dependency>

分布式调度框架elastic-job-lite强依赖低版本的curator,与dubbo2.7.3使用的curator版本不兼容,这给dubbo版本升级工作带来了一定阻塞。考虑到elastic-job-lite已经很久没有人进行维护,目前一些业务线计划将elastic-job-lite替换为其他的调度框架。
openFeign与dubbo兼容性问题 issues: https://github.com/apache/dubbo/issues/3990 dubbo的ServiceBean监听spring的ContextRefreshedEvent,进行服务暴露。openFeign提前触发了ContextRefreshedEvent,此时ServiceBean还未完成初始化,于是就导致了应用启动异常。 参考社区的pr,我们在内部版本修复了此问题。

RpcException兼容性问题 dubbo低版本consumer不能识别dubbo2.7版本provider抛出的org.apache.dubbo.rpc.RpcException。因此,在consumer全部升级到2.7之前,不建议将provider的com.alibaba.dubbo.rpc.RpcException改为org.apache.dubbo.rpc.RpcException

qos端口占用 dubbo2.7.3默认开启qos功能,导致一些混部在物理机的dubbo服务升级时出现qos端口占用问题。关闭qos功能后恢复。

自定义扩展兼容性问题 业务线对于dubbo的自定义扩展比较少,因此在自定义扩展的兼容性方面暂时还没有遇到比较难处理的问题,基本上都是变更package导致的问题,由业务线自行修复。

skywalking agent兼容性问题 我们项目中一般使用skywalking进行链路追踪,由于skywalking agent6.0的plugin不支持dubbo2.7,因此统一升级skywalking agent到6.1。

三、dubbo多机房方案
  瓜子目前正在进行第二机房的建设工作,dubbo多机房是第二机房建设中比较重要的一个话题。在dubbo版本统一的前提下,我们就能够更顺利的开展dubbo多机房相关的调研与开发工作。

初步方案
  我们咨询了dubbo社区的建议,并结合瓜子云平台的现状,初步确定了dubbo多机房的方案。

在每个机房内,部署一套独立的zookeeper集群。集群间信息不同步。这样就没有了zookeeper集群跨机房延迟与数据不同步的问题。
dubbo服务注册时,仅注册到本机房的zookeeper集群;订阅时,同时订阅两个机房的zookeeper集群。
实现同机房优先调用的路由逻辑。以减少跨机房调用导致的不必要网络延迟。
同机房优先调用
  dubbo同机房优先调用的实现比较简单,相关逻辑如下:

瓜子云平台默认将机房的标志信息注入容器的环境变量中。
provider暴露服务时,读取环境变量中的机房标志信息,追加到待暴露服务的url中。
consumer调用provider时,读取环境变量中的机房标志信息,根据路由策略优先调用具有相同标志信息的provider。
  针对以上逻辑,我们简单实现了dubbo通过环境变量进行路由的功能,并向社区提交了pr。   dubbo通过环境变量路由pr: https://github.com/apache/dubbo/pull/5348

摘自:http://dubbo.apache.org/zh-cn/blog/dubbo-practice-from-guazi.html 感谢作者,受教了

dubbo在瓜子二手车的实践相关推荐

  1. 报名 | 瓜子二手车周洲:如何玩转AI赋能下的新零售?

    关于"新零售"的定义,曾一度众说纷纭,而当下较受认可的解释是:以消费者体验为中心的数据驱动的泛零售形态. 区别与以往任何一次零售变革,新零售将通过数据与商业逻辑的深度结合,真正实现 ...

  2. 瓜子二手车封宇:瓜子IM智能客服系统数据架构设计

    本文根据封宇在2018年10月18日[第十届中国系统架构师大会(SACC2018)]现场演讲内容整理而成. 讲师介绍: 封宇,瓜子二手车高级技术专家,中国计算机学会专业会员.2017年2月入职瓜子二手 ...

  3. 瓜子二手车如何玩转AI赋能下的新零售(附PPT下载)

    本讲座选自瓜子二手车新零售产品负责人.清华电子系校友周洲于近期在清华大数据"应用·创新"系列讲座上所做的题为<如何玩转AI赋能下的新零售>的演讲. 后台回复" ...

  4. 近期活动盘点:设计与人工智能思享会、制造业的转型升级到产业跃迁思享会、瓜子二手车周洲讲座(10.30-11.1)...

    想知道近期有什么最新活动?大数点为你整理的近期活动信息在此: 设计与人工智能思享会 2018年10月30日 活动简介: [时间]2018年10月30日(星期二)下午14:30~17:00 [地点]北京 ...

  5. 瓜子二手车CEO杨浩涌:创业要建立势能,瓜子的技术能力是护城河,“瓜子大脑”能预测成交概率...

    2019独角兽企业重金招聘Python工程师标准>>> 在36氪的2016 WISE大会上,瓜子二手车CEO杨浩涌发表了"周期"为关键字的演讲,他分享了创业公司如 ...

  6. 瓜子二手车发12月二手车价格:汉兰达奥德赛CR-V保值率居首

    中新网1月22日电 日前,基于海量个人对个人的二手车成交数据,瓜子二手车公布了12月全国及多个核心城市的二手车交易"瓜子价"数据.数据显示,2018年12月全国瓜子二手车严选直卖签 ...

  7. 一个近乎完美基于Dubbo的微服务改造实践

    网易考拉(以下简称考拉)是网易旗下以跨境业务为主的综合型电商,自 2015 年 1 月 9 日上线公测后,业务保持了高速增长,这背后离不开其技术团队的支撑. 微服务化是电商 IT 架构演化的必然趋势, ...

  8. 案例研究 | 瓜子二手车战略升级三部曲

    互联网行业里,淘宝.京东.美团.滴滴的经历告诉我们剩者为王,这样的道理同样适用于带有互联网属性的二手车电商们,他们互相之间的争斗只为成为最后剩者,经历近十年硝烟的互联网二手车电商们,如何决出胜负,提升 ...

  9. 瓜子二手车逃离行业“不可能三角”?

    文|曾响铃 来源|科技向令说(xiangling0815) 二手车江湖走过喧嚣与无序,正在回归平静.理性和秩序. "2019年11月实现集团整体盈利,预计Q4集团将实现整体盈利", ...

最新文章

  1. pythonurllib模块-python爬虫之urllib模块和requests模块学习
  2. Python基础day05【函数应用:学生管理系统、拆包、今日总结】
  3. 2020CCPC(威海) - Caesar Cipher(线段树+哈希)
  4. 交流电的有效值rms值_交流电路中的电源
  5. 单片机c语言编程要点,第1章单片机的C语言编程_2015要点.ppt
  6. python--练习--for i in range(2,101)
  7. Java项目经验相关常见面试题
  8. matlab的1stopt,用1stOpt解出非线性方程组的解作为初值用Matlab求解,解不出来
  9. PowerPoint 消除所有动画VBA指令
  10. maven atuo import
  11. 内外网同时连接配置说明
  12. win10安装PHP环境
  13. Elasticsearch 聚合系列:adjacency matrix aggregation(邻接矩阵聚合)
  14. linux activemq 打印日志,Log4j.xml配置日志按级别过滤并将指定级别的日志发送到ActiveMQ...
  15. R语言—90分钟从入门到精通
  16. 网易邮箱添加html,在网易邮箱中实行添加标签窗口的详细步骤
  17. C语言基础学习——第1天(类型+操作符)
  18. Java实现蓝桥杯分金币
  19. 新手如何学习学嵌入式开发?
  20. 玩转Jetson Nano(一)烧写系统

热门文章

  1. 利用数组,实现回文数的判断
  2. oracle在分组内排序的方法,oracle 在分组内排序的方法(转载)
  3. Python------二进制/十进制/八进制/十六进制相互转换
  4. 期货资管分仓跟单系统的开发搭建
  5. ActPro在日本约350个旅游景点安装开拓性的日本产自动货币兑换机
  6. 人民币大涨出国游省钱 携程外币兑换成交量增长30%
  7. spirng: srping mvc配置(访问路径配置)搭建SpringMVC——最小化配置
  8. 关于PROFIBUS:生产工厂的通信骨干网
  9. list类模板与vetcor有何不同
  10. 鸿蒙OS麒麟659,搭载海思麒麟659的手机有哪些