Dubbo服务重启时注册中心下线不及时问题排查与修复记录
阿昌 Java小菜鸡

Dubbo服务重启时注册中心下线不及时问题排查与修复记录

Hi,我是阿昌,今天记录一个服务重启时反复出现的 JDBC 报错问题;

排查到最后发现根因是 Dubbo 2.6.2 的注册中心下线机制有坑。

一、问题现象

服务发布重启时,短时间内出现多次 JDBC 连接报错。

image

看日志发现在容器已经销毁后,注册中心里该服务的节点还在,导致仍有请求被路由到这个已经挂掉的实例上。

简单说就是:

服务实例已经没了,但注册中心以为它还在。

二、根因分析

直接原因很清楚:注册中心下线不及时

继续往下追,就得看 Dubbo 是怎么做下线的。项目用的是 Dubbo 2.6.2。

三、Dubbo 2.6.2 的下线方式

翻源码发现,2.6.2 是在类加载时注册了一个 JVM shutdown 钩子:

1
2
3
4
// Dubbo 2.6.2 的下线入口
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
ProtocolConfig.destroyAll();
}));

真正执行下线的是 ProtocolConfig.destroyAll()

image

这里有两个问题:

问题一:kill -9 时钩子不执行。 这是最主要的原因。发布系统销毁容器时如果走了强制 kill,JVM 的 shutdown hook 根本不会触发,注册中心就不会收到下线通知。

问题二:多线程保证不了执行顺序。 即使钩子顺利执行,shutdown hook 的线程调度顺序也不可控,可能出现资源先于 Dubbo 协议被销毁的情况。

四、Dubbo 2.6.3 的改进

2.6.3 版本对下线机制做了明显的优化:

  1. 新增 DubboShutdownHook 类,把主要的下线逻辑收拢进去
  2. 2.6.2 原有的下线方法被移除,只保留了兼容调用
  3. 新增 DubboApplicationListener,通过监听 Spring 容器的 ContextClosedEvent 来触发下线

这意味着在 Spring 容器正常关闭时(比如 kill -15),Listener 能拿到关闭事件主动去注销服务,比只靠 JVM shutdown hook 靠谱得多。

image

image

image

五、解决方案

两个方向:

方案一:升级 Dubbo 版本到 2.6.3 以上。 直接用官方的改进,最省事。

方案二:仿照 2.6.3 自己加一个 Listener。 不升级版本的情况下,手动补上这个能力。

我当时的做法是方案二,直接写了一个 ShutdownHookListener

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
@Configuration
public class ShutdownHookListener {

private static Logger log = LoggerFactory.getLogger(ShutdownHookListener.class);

@Bean
DubboShutdownListener dubboShutdownListener() {
return new DubboShutdownListener();
}

public static class DubboShutdownListener implements ApplicationListener, PriorityOrdered {

@Override
public void onApplicationEvent(ApplicationEvent event) {
log.info("销毁开始");
if (event instanceof ContextClosedEvent) {
log.info("开始注销注册中心");
ProtocolConfig.destroyAll();
log.info("服务下线完成");
}
}

@Override
public int getOrder() {
return 0;
}
}
}

思路很简单:

监听 ContextClosedEvent,容器关闭时主动调用 ProtocolConfig.destroyAll() 把服务从注册中心摘掉。

getOrder() 返回 0 保证这个 Listener 在关闭链中尽量早执行,先下线再销毁其他资源。

六、这个方案的一个隐患

上面用了 ProtocolConfig.destroyAll(),这个方法在 Dubbo 2.6.3 源码里有这样一段注释:

“Just for compatibility” —— 仅仅为了兼容

“It should be deleted in the next major version, say 2.7.x.” —— 在 2.7.x 主要版本中会被删除

也就是说,如果以后升级到 Dubbo 3,这个方法就没了,编译直接报错

所以在方案二的代码里,后续升级 Dubbo 时需要记得换成高版本的对应下线 API,或者干脆切到方案一直接升版本。

image

七、总结

这个问题本质上是:

JVM shutdown hook 不可靠 + 发布系统的容器销毁方式 = 注册中心感知不到服务下线。

短期方案是自己加个 Spring Listener 兜底,长期方案是升级 Dubbo 版本。

写代码的时候顺手加一行注释提醒未来升级时注意 API 兼容性,省得后面踩坑。

以上就是这次 Dubbo 服务重启时注册中心下线不及时问题的排查与修复记录,感谢观看;

 请作者喝咖啡