Dubbo 稳定性案例:Nacos 注册中心可用性问题复盘

问题描述

上周四晚刚回到家,就接到了软负载同学的电话,说是客户线上出了故障,我一听”故障“两个字,立马追问是什么情况,经过整理,还原出线上问题的原貌:

客户使用了 Dubbo,注册中心使用的是 Nacos,在下午开始不断有调用报错,查看日志,发现了 Nacos 心跳请求返回 502

1
2
2019-11-15 03:02:41.973 [com.alibaba.nacos.client.naming454] -ERROR [com.alibaba.nacos.naming.beat.sender] request xx.xx.xx.xx failed.
com.alibaba.nacos.api.exception.NacosException: failed to req API: xx.xx.xx.xx:8848/nacos/v1/ns/instance/beat. code:502 msg:

此时还没有大范围的报错。随后,用户对部分机器进行了重启,开始出现大规模的 Nacos 连接不上的报错,并且调用开始出现大量 no provider 的报错。

问题分析

Nacos 出现心跳报错,一般会有两种可能:

  • 用户机器出现问题,如网络不通
  • Nacos Server 宕机

但由于是大面积报错,所以很快定位到是 Nacos Server 本身出了问题:由于磁盘老旧导致 IO 效率急剧下降,Nacos Server 无法响应客户端的请求,客户端直接接收到 502 错误响应。这个事件本身并不复杂,是一起注册中心磁盘故障引发的血案,但从这起事件,却可以窥探到很多高可用的问题,下面来跟大家一起聊聊这当中的细节。

问题复现

Dubbo 版本:2.7.4

Nacos 版本:1.1.4

复现目标:在本地模拟 Nacos Server 宕机,检查 Dubbo 的调用是否会受到影响。

复现步骤

  1. 本地启动 Nacos Server、Provider、Consumer,触发 Consumer 调用 Provider
  2. kill -9 Nacos Server,模拟 Nacos Server 宕机,触发 Consumer 调用 Provider
  3. 重启 Consumer,触发 Consumer 调用 Provider

期望

3 个步骤均可以调用成功

实际结果:

1、2 调用成功,3 调用失败

问题成功复现,重启 Consumer 之后,没有调用成功,客户恰好遇到了这个问题。大家可能对这其中的细节还是有一些疑问,我设想了一些疑惑点,来和大家一起进行探讨。

为什么 Nacos 宕机后,仍然可以调用成功

我们都知道,一般聊到 Dubbo,有三个角色是必须要聊到的:服务提供者、服务消费者、注册中心。他们的关系不用我赘述,可以从下面的连通性列表得到一个比较全面的认识:

  • 注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小
  • 服务提供者向注册中心注册其提供的服务,此时间不包含网络开销
  • 服务消费者向注册中心获取服务提供者地址列表,并根据负载算法直接调用提供者,此时间包含网络开销
  • 注册中心,服务提供者,服务消费者三者之间均为长连接
  • 注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者
  • 注册中心宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表
  • 注册中心可选的,服务消费者可以直连服务提供者

重点关注倒数第二条,Dubbo 其实在内存中缓存了一份提供者列表,这样可以方便地在每次调用时,直接从本地内存拿地址做负载均衡,而不避免每次调用都访问注册中心。只有当服务提供者节点发生上下线时,才会推送到本地,进行更新。所以,Nacos 宕机后,Dubbo 仍然可以调用成功。

Nacos 宕机不影响服务调用,为什么日志中仍然有调用报错

宕机期间,已有的服务提供者节点可能突然下线,但由于注册中心无法通知给消费者,所以客户端调用到下线的 IP 就会出现报错。

对于此类问题,Dubbo 也可以进行兜底

  • Dubbo 会在连接级别进行心跳检测,当 channel 本身不可用时,即使没有注册中心通知,也会对其进行断连,并设置定时器,当该连接恢复后,再恢复其可用性
  • 在阿里云商业版的 Dubbo – EDAS 中,提供了「离群摘除」功能,可以在调用层面即时摘除部分有问题的节点,保证服务的可用性。

为什么期望 Consumer 重启之后,调用成功

Nacos Server 宕机后,Consumer 依旧可以调用成功,这个大家应该都比较清楚。但是为什么期望 Consumer 重启之后,依旧调用成功,有些人可能就会有疑问了,注册中心都宕机了,重启之后一定连不上,理应调用失败,怎么会期望成功呢?这就要涉及到 Nacos 的本地缓存了。

Nacos 本地缓存的作用:当应用与服务注册中心发生网络分区或服务注册中心完全宕机后,应用进行了重启操作,内存里没有数据,此时应用可以通过读取本地缓存文件的数据来获取到最后一次订阅到的内容。

例如在 Dubbo 应用中定义了如下服务:

1
<dubbo:service interface="com.alibaba.edas.xml.DemoService" group="DUBBO" version="1.0.0" ref="demoService" />

可以在本机的 /home/${user}/​nacos/naming/ 下看到各个命名空间发布的所有服务的信息,其内容格式如下:

1
{"metadata":{},"dom":"DEFAULT_GROUP@@providers:com.alibaba.edas.xml.DemoService:1.0.0:DUBBO","cacheMillis":10000,"useSpecifiedURL":false,"hosts":[{"valid":true,"marked":false,"metadata":{"side":"provider","methods":"sayHello","release":"2.7.4","deprecated":"false","dubbo":"2.0.2","pid":"5275","interface":"com.alibaba.edas.xml.DemoService","version":"1.0.0","generic":"false","revision":"1.0.0","path":"com.alibaba.edas.xml.DemoService","protocol":"dubbo","dynamic":"true","category":"providers","anyhost":"true","bean.name":"com.alibaba.edas.xml.DemoService","group":"DUBBO","timestamp":"1575355563302"},"instanceId":"30.5.122.3#20880#DEFAULT#DEFAULT_GROUP@@providers:com.alibaba.edas.xml.DemoService:1.0.0:DUBBO","port":20880,"healthy":true,"ip":"30.5.122.3","clusterName":"DEFAULT","weight":1.0,"ephemeral":true,"serviceName":"DEFAULT_GROUP@@providers:com.alibaba.edas.xml.DemoService:1.0.0:DUBBO","enabled":true}],"name":"DEFAULT_GROUP@@providers:com.alibaba.edas.xml.DemoService:1.0.0:DUBBO","checksum":"69c4eb7e03c03d4b18df129829a486a","lastRefTime":1575355563862,"env":"","clusters":""}

为什么期望重启后调用成功?因为经过检查,发现线上出现问题的机器上,缓存文件一切正常。虽然 Nacos Server 宕机了,本地的缓存文件依旧可以作为一个兜底,所以期望调用成功。

为什么 Consumer 重启后,没有按照预期加载本地缓存文件

缓存文件正常,问题只有可能出现在读取缓存文件的逻辑上。

  • 可能是 nacos-client 出了问题
  • 可能是 Dubbo 的 nacos-registry 出了问题

一番排查,在 Nacos 研发的协助下,找到了 naocs-client 的一个参数: namingLoadCacheAtStart,该配置参数控制启动时是否加载缓存文件,默认值为 false。也就是说,使用 nacos-client,默认是不会加载本地缓存文件的。终于定位到线上问题的原因了:需要手动开启加载本地缓存,才能让 Nacos 加载本地缓存文件。

该参数设置为 true 和 false 的利弊:

  • 设置为 true,认为可用性 & 稳定性优先,宁愿接受可能出错的数据,也不能因为没有数据导致调用完全出错
  • 设置为 false,则认为 Server 的可用性较高,更能够接受没有数据,也不能接受错误的数据

无论是 true 还是 false,都是对一些极端情况的兜底,而不是常态。对于注册发现场景,设置成 true,可能更合适一点,这样可以利用 Nacos 的本地缓存文件做一个兜底。

Dubbo 传递注册中心参数

Dubbo 中使用统一 URL 模型进行参数的传递,当我们需要在配置文件传递注册中心相关的配置参数时,可以通过键值对的形式进行拼接,当我们想要在 Dubbo 中开启加载注册中心缓存的开关时,可以如下配置:

1
<dubbo:registry address="nacos://127.0.0.1:8848?namingLoadCacheAtStart=true"/>

遗憾的是,最新版本的 Dubbo 只传递了部分参数给 Nacos Server,即使用户配置了 namingLoadCacheAtStart 也不会被服务端识别,进而无法加载本地缓存。我在本地修改了 Dubbo 2.7.5-SNAPSHOT,传递上述参数后,可以使得 1、2、3 三个阶段都调用成功,证明了 namingLoadCacheAtStart 的确可以使得 Dubbo 加载本地缓存文件。该问题将会在 Dubbo 2.7.5 得到修复,届时 Dubbo 中使用 Nacos 的稳定性将会得到提升。

问题总结

该线上问题反映出了 Nacos 注册中心可用性对 Dubbo 应用的影响,以及系统在某个组件宕机时,整体系统需要进行的一些兜底逻辑,不至于因为某个组件导致整个系统的瘫痪。

总结下现有代码的缺陷以及一些最佳实践:

  • Dubbo 传递注册中心参数给 Nacos 时,只能够识别部分参数,这会导致用户的部分配置失效,在接下来的版本会进行修复。
  • nacos-client 加载本地缓存文件的开关等影响到系统稳定性的参数最好设计成 -D 启动参数,或者环境变量参数,这样方便发现问题,及时止血。例如此次的事件,有缺陷的 Dubbo 代码仅仅依赖于参数的传递,无法加载本地缓存文件,而如果有 -D 参数,可以强行开始加载缓存,大大降低了问题的影响面。
  • namingLoadCacheAtStart 是否默认开启,还需要根据场景具体确定,但 nacos-server 宕机等极端场景下,开启该参数,可以尽可能地降低问题的影响面。顺带一提,Nacos 本身还提供了一个本地灾备文件,与本地缓存文件有一些差异,有兴趣的朋友也可以去了解一下。
分享到

一文聊透 Dubbo 优雅上线

1 前言

在此文之前,我写过一篇 《一文聊透 Dubbo 优雅停机》,这篇文章算是一个续集,优雅停机和优雅上线两者都是微服务生命周期中,开发者必须关心的环节。

优雅上线还有很多称呼:「无损上线」,「延迟发布」,「延迟暴露」。它们的对立面自然是:「有损上线」,「直接发布」。

我最近写的「一文聊透 Dubbo xx」系列文章,都有一个特点,即当你不注重文章中实践,你的 Dubbo 应用依旧可以正常运行,但总归在某些场景 case 下,你的系统会出现问题。做不到优雅上线,你的系统将会出现:在应用刚启动时,就有流量进入,而此时应用尚未初始化完毕,导致调用失败,在集群规模较大时,影响会变得很明显。

2 方案一:延迟发布

以 SpingBoot 下使用 Dubbo 为例,被 Dubbo 的 @Service 注解修饰的服务,会按照 Spring 中初始化 Bean 的顺序,串行执行发布逻辑。Dubbo 框架会完成一系列的操作:

  • 创建远程调用的 Proxy
  • 把代理对象注册到 ProviderConsumerRegTable,方便远程调用到来时寻找到对应的服务
  • 向注册中心注册

一旦服务信息注册到注册中心,在消费者看来该服务就是可以被调用的。然而,此时可能出现一些数据库、缓存资源尚未加载完毕的场景,这取决于你的系统有没有对应的组件,它们何时加载完毕,也完全取决于你的业务。如果你担心你的系统存在这种隐患,可以尝试多次重启集群中的任意一台机器,查看调用方是否存在报错,如果有报错,一种可能性是没有实现优雅停机,一种可能性是没有实现优雅上线。

Dubbo 服务暴露的起点一般是以 Spring 容器启动完毕后发出的 ContextRefreshedEvent 事件为准,Dubbo 的 ServiceBean 实现了 ApplicationListener<E extends ApplicationEvent> 接口,用以接收这一容器刷新事件。

1
2
3
4
5
6
7
public void onApplicationEvent(ContextRefreshedEvent event) {
// 是否有延迟导出 && 是否已导出 && 是不是已被取消导出
if (isDelay() && !isExported() && !isUnexported()) {
// 导出服务
export();
}
}

Dubbo 为服务提供了 delay 配置:

1
<dubbo:service delay="5000" />

如上配置后,Dubbo 服务将会在 Spring 容器启动后 5s,再执行暴露逻辑。这里 delay 的时长,取决于你系统资源初始化的耗时,没有一个经验值。如果不配置改值,Dubbo 将会在收到 ContextRefreshedEvent 事件后,立即执行发布逻辑。

Dubbo 2.6.5 版本对服务延迟发布逻辑进行了细微的调整,将需要延迟暴露(delay > 0)服务的倒计时动作推迟到了 Spring 初始化完成后进行。在此之前的版本的逻辑不太合理,如果想要让 2.6.5 之前的版本延迟到 Spring 初始化完成后,再暴露服务,可以这样配置:<dubbo:service delay=”-1” />

本节参考 Dubbo 官方文档

延迟暴露:http://dubbo.apache.org/zh-cn/docs/user/demos/delay-publish.html

3 方案二:QOS 命令上线

Dubbo 还为服务提供了另一个配置项:

1
<dubbo:service register="false" />

该配置项配置后,服务将不会发布到注册中心,可能很多 Dubbo 用户不会注意到这个配置,它的作用恰恰是 QOS 指令使用的。

Dubbo 2.5.8 及以上的版本,还提供了一些在线运维命令。为了演示该命令,我们准备一个 GreetingService 的 demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class DubboProvider {

public static void main(String[] args) throws Exception {
ServiceConfig<GreetingsService> service = new ServiceConfig<>();
service.setApplication(new ApplicationConfig("dubbo-provider"));
service.setRegistry(new RegistryConfig("nacos://127.0.0.1:8848"));
service.setInterface(GreetingsService.class);
service.setRef(new GreetingsServiceImpl());
service.setRegister(false);
service.export();

System.out.println("dubbo service started");
new CountDownLatch(1).await();
}
}

注意我们配置了不发布:service.setRegister(false),由于 QOS 配置是默认打开的,在本地的 22222 端口,可以进入 QOS 控制台。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
 krito git:(master) ✗ telnet localhost 22222
Trying ::1...
Connected to localhost.
Escape character is '^]'.
___ __ __ ___ ___ ____
/ _ \ / / / // _ ) / _ ) / __ \
/ // // /_/ // _ |/ _ |/ /_/ /
/____/ \____//____//____/ \____/
dubbo>ls
As Provider side:
+-------------------------------------+---+
| Provider Service Name |PUB|
+-------------------------------------+---+
|com.alibaba.edas.api.GreetingsService| N |
+-------------------------------------+---+
As Consumer side:
+---------------------+---+
|Consumer Service Name|NUM|
+---------------------+---+

dubbo>online
OK
dubbo>ls
As Provider side:
+-------------------------------------+---+
| Provider Service Name |PUB|
+-------------------------------------+---+
|com.alibaba.edas.api.GreetingsService| Y |
+-------------------------------------+---+
As Consumer side:
+---------------------+---+
|Consumer Service Name|NUM|
+---------------------+---+

dubbo>

如上图所示,我们首先查看到 com.alibaba.edas.api.GreetingsService 服务是未发布的,通过 online 命令手动将服务发布,再使用 ls 查看服务列表时,已经显示服务处于发布状态了。

除了使用 telnet,还可以通过 HTTP 访问:

1
curl localhost:22222/online

小 tips:在使用 SpringBoot 注解式声明一个 Service 时,register 属性会失效,在 xml 或者 API 方式下声明则运行正常,怀疑是 dubbo-spring-boot-starter 的一个 bug。

image-20191117205139227

image-20191117205207044

大家在 SpringBoot 下使用 Dubbo 需要留意类似的问题,之前有过一些属性在 SpringBoot 注解中未解析或为提供注解配置的案例,在使用时需要注意。

本节参考 Dubbo 官方文档

服务配置说明:http://dubbo.apache.org/zh-cn/docs/user/references/xml/dubbo-service.html

QOS:http://dubbo.apache.org/zh-cn/docs/user/references/qos.html

最佳实践

本文介绍了两种 Dubbo 的机制:

  • 方案一:延迟发布(delay=5000)
  • 方案二:不发布 + QOS 指令发布(register=false)

想要实现优雅上线,可以采取适合你系统的方式。方案一延迟发布的优势在于实现简单,但具体 delay 多少秒,比较依赖系统维护者的经验。方案二使用 QOS 指令,一般依靠于发布系统,当发布系统检测到固定的资源加载完毕这样一个信号时,自动触发上线命令,更加灵活。

当你系统遇到应用启动时流量有所损失时,就应该考虑一下优雅上线的问题了,更多 Dubbo 使用的注意点,请持续关注公众号。

img

「技术分享」某种程度上,是让作者和读者,不那么孤独的东西。欢迎关注我的微信公众号:Kirito的技术分享」

分享到

一文聊透 Dubbo 元数据中心

前言

如果让你在本地构建一个 Dubbo 应用,你会需要额外搭建哪些中间件呢?如果没猜错的话,你的第一反应应该是注册中心,类 Dubbo 的大多数服务治理框架都有注册中心的概念。你可以部署一个 Zookeeper,或者一个 Nacos,看你的喜好。但在 Apache Dubbo 的 2.7 版本后,额外引入了两个中间件:元数据中心和配置中心。

在今年年初 Dubbo 2.7 刚发布时,我就写了一篇文章 《Dubbo 2.7 三大新特性详解》,介绍了包含元数据中心改造在内的三大新特性,但一些细节介绍没有详细呈现出来,在这篇文章中,我将会以 Dubbo 为例,跟大家一起探讨一下服务治理框架中元数据中心的意义与集成细节。

Dubbo 2.7 架构

查看更多

分享到

Linux 环境写文件如何稳定跑满磁盘 I/O 带宽?

  • 准备
    • 要求
    • 机器配置
    • 测试磁盘 IO 性能
  • 实验一: Buffer IO 写入
  • 实验二: 4K 单次 Direct IO 写入
  • 实验三: mmap 写入
  • 实验四: 改进的 mmap 写入
  • 结论

查看更多

分享到

一个看板娘入住你的个人博客只需要三步

最近在浏览别人博客时,一个萌物给了我意外的惊喜,原来博客还可以这么玩

小恶魔

于是果断审查元素,发现这个萌萌哒的看板娘背后使用的是一个叫 live2d 的技术,并且凭借 Google 迅速找到对应的开源代码:https://github.com/xiazeyu/live2d-widget.js,https://github.com/EYHN/hexo-helper-live2d

你可以在我的博客中先目睹下它的实际效果:https://www.cnkirito.moe/,点击会有音效哦~

在浏览 live2-widget.js 的说明文档时,发现它对 hexo 的支持非常友好,恰好我的博客是通过 hexo 搭建的,所以本文会介绍一下如何为 hexo 集成一只看板娘。

查看更多

分享到

一文聊透 Dubbo 优雅停机

1 前言

一年之前,我曾经写过一篇《研究优雅停机时的一点思考》,主要介绍了 kill -9,kill -15 两个 Linux 指令的含义,并且针对性的聊到了 Spring Boot 应用如何正确的优雅停机,算是本文的前置文章,如果你对上述概念不甚了解,建议先去浏览一遍,再回头来看这篇文章。这篇文章将会以 Dubbo 为例,既聊架构设计,也聊源码,聊聊服务治理框架要真正实现优雅停机,需要注意哪些细节。

本文的写作思路是从 Dubbo 2.5.x 开始,围绕优雅停机这个优化点,一直追溯到最新的 2.7.x。先对 Dubbo 版本做一个简单的科普:2.7.x 和 2.6.x 是目前官方推荐使用的版本,其中 2.7.x 是捐献给 Apache 的版本,具备了很多新的特性,目前最新的 release 版本是 2.7.4,处于生产基本可用的状态;2.6.x 处于维护态,主要以 bugfix 为主,但经过了很多公司线上环境的验证,所以求稳的话,可以使用 2.6.x 分支最新的版本。至于 2.5.x,社区已经放弃了维护,并且 2.5.x 存在一定数量的 bug,本文介绍的 Dubbo 优雅停机特性便体现了这一点。

查看更多

分享到

使用 JMeter 进行 Dubbo 性能测试

1 前言

说道性能测试工具,你会立刻联想到哪一个?ab(ApacheBench)、JMeter、LoadRunner、wrk…可以说市面上的压测工具实在是五花八门。那如果再问一句,对 Dubbo 进行性能压测,你会 pick 哪一个?可能大多数人就懵逼了。可以发现,大多数的压测工具对开放的协议支持地比较好,例如:HTTP 协议,但对于 Dubbo 框架的私有协议:dubbo,它们都显得力不从心了。

如果不从通用的压测工具上解决 Dubbo 的压测需求问题,可以自己写 Dubbo 客户端,自己统计汇总结果,但总归不够优雅,再加上很多开发同学没有丰富的测试经验,很容易出现一些偏差。说到底,还是压测工具靠谱,于是便引出了本文的主角 —— jmeter-plugins-for-apache-dubbo。这是一款由 Dubbo 社区 Commiter – 凝雨 同学开发的 JMeter 插件,可以非常轻松地对 Dubbo 实现性能测试。

查看更多

分享到

华为云 TaurusDB 性能挑战赛赛题总结

1 前言

image-20190902204538276

回顾第一次参加性能挑战赛 – 第四届阿里中间件性能挑战赛,那时候真的是什么都不会,只有一腔热情,借着比赛学会了 Netty、学会了文件 IO 的最佳实践,到了这次华为云举办的 TaurusDB 性能挑战赛,已经是第三次参加比赛了,同时也是最“坎坷”的一次比赛。经过我和某位不愿意透露姓名的 96 年小迷妹的不懈努力,最终跑分排名为第 3 名。

查看更多

分享到

日本东京游记 || 内含秋叶原、动漫打卡地攻略

由于表弟是个狂热的二次元爱好者,受我小姨之托,带他去日本游玩了一趟。趁着这个机会,打算给大家分享一下日本旅游的一些攻略,以祭奠我逝去的年假。这是我第二次去日本了,上一次还是大二时跟我初中舍友一起去的,所以这次去已经有了一些经验了,很多朋友表示想去日本,期待我能写一篇攻略,所以这篇攻略将会偏小白向,如果你是第一次去日本,那这篇攻略想必不会让你失望。

查看更多

分享到

Dubbo 中的 http 协议

太阳红彤彤,花儿五颜六色,各位读者朋友好,又来到了分享 Dubbo 知识点的时候了。说到 Dubbo 框架支持的协议,你的第一反应是什么?大概会有 Dubbo 默认支持的 dubbo 协议,以及老生常谈的由当当贡献给 Dubbo 的 rest 协议,或者是今天的主角 http。截止到目前,Dubbo 最新版本演进到了 2.7.3,已经支持了:dubbo,hessain,http,injvm,jsonrpc,memcached,native-thrift,thrift,redis,rest,rmi,webservice,xml 等协议,有些协议的使用方式还没有补全到官方文档中。原来 Dubbo 支持这么多协议,是不是有点出乎你的意料呢?

这么多 RPC 协议,可能有人会产生如下的疑问:rest,jsonrpc,webservice 不都是依靠 http 通信吗?为什么还单独有一个 http 协议?先不急着回答这个问题,而是引出今天的话题,先来介绍下 Dubbo 框架中所谓的 http 协议。

查看更多

分享到