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

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

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

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

一、问题现象

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

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

简单说就是:

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

二、根因分析

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

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

三、Dubbo 2.6.2 的下线方式

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

java 复制代码
// Dubbo 2.6.2 的下线入口
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
    ProtocolConfig.destroyAll();
}));

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

这里有两个问题:

问题一: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 靠谱得多。

五、解决方案

两个方向:

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

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

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

java 复制代码
@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,或者干脆切到方案一直接升版本。

七、总结

这个问题本质上是:

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

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

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

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

相关推荐
东小西6 分钟前
【SAA实战】第 1 篇:ReactAgent 入门——先撸个"会调工具的助手"跑起来
java·人工智能·spring
mqiqe1 小时前
AgentScope Java 2.0 协议集成全景解析:A2A、AG-UI、Agent Protocol 三大开放协议实战指南
java·开发语言·ui
脉动数据行情1 小时前
Java 实现台股 TWSE/TPEx 行情采集(个股 + K 线)
java·开发语言·twse·tpex·台股
月华路3 小时前
G1 GC 对数组与大对象(Humongous)的处理
java·jvm·算法
liangshanbo12154 小时前
虚拟列表深度面试题整理
java·开发语言·前端
gugucoding4 小时前
59. 【Java】Spring Boot 入门:第一个 Web 应用
java·开发语言·spring boot
10mAh5 小时前
【Java】HashMap 的 put 到底做了什么?——冲突、扩容与树化实测
java·开发语言·hash
我命由我123455 小时前
人脸识别 - 勒克斯(光线照射到物体表面上的明亮程度)
android·java·学习·java-ee·人脸识别·学习方法·android runtime
TechLee6 小时前
跨语言加解密总对不上?这个纯 Go 神库让 AES/RSA 与 PHP、Java 100% 互通
java·后端·算法