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 服务重启时注册中心下线不及时问题的排查与修复记录,感谢观看;

相关推荐
shehuiyuelaiyuehao3 小时前
算法46,分治快排,排序数组
java·算法·排序算法·排序
野生技术架构师3 小时前
1200 道 JAVA 面试题(各大企业常见面试题及答案)
java·开发语言
careathers3 小时前
【数据结构】栈
java·数据结构
邪修king3 小时前
Re:Linux系统篇(二十六):文件系统(二):Ext 文件系统底层详解:从 inode、块组到软硬链接,结合 Windows 讲透文件管理本质
android·java·linux
Yyyyyy~3 小时前
【java】类和对象
java·开发语言
重生之小比特4 小时前
【Java SE】抽象类和接口
java·开发语言
Lyyaoo.4 小时前
【设计模型】策略模式
java
譕痕4 小时前
Idea 集成 智能体开发工具“千问(qwen)”
java·ide·spring·语言模型
用户8181870627464 小时前
第27章 消息丢失/重复消费的全链路排查(生产者→Broker→消费者)
java·后端
索隆zoro4 小时前
Army 对 jOOQ
java·后端