性能文章>面试官:Dubbo服务重启下线,消费者继续调用,都异常了怎么办>

面试官:Dubbo服务重启下线,消费者继续调用,都异常了怎么办原创

295225

**本文首发于公众号【看点代码再上班】,建议关注公众号,及时阅读最新文章。**

一定要看的原文:https://mp.weixin.qq.com/s?__biz=MzIw……

 

大家好,我是tin,这是我的第20篇原创文章

 

我们有没有想过一个问题,在分布式系统中,服务节点重启的时候,消费者流量继续调用该节点,那么这部分调用全部异常,对于线上用户来说,系统故障?

 

针对这种情况,我们可以这么想:Dubbo服务重启下线的时候,消费者还会不会继续调用此服务节点?

 

答案当然是不会

 

今天就结合dubbo源码一起来捋一捋,先上一个目录:

 

一、操作系统的优雅停机

优雅停机是指在停止应用时,执行一系列"操作"保证应用正常关闭。这些操作包括拒绝新请求和新连接、关闭服务注册、等待已有请求执行完成、关闭线程池、关闭连接、释放资源等。

 

优雅停机可以避免非正常关闭程序可能造成的任务被抛弃、数据丢失、应用异常等问题。优雅停机本质上是进程即将关闭前执行的一些额外流程。

 

操作系统本身也有对优雅停机的思考。

 

我们停止一个进程是,会使用kill -9和kill -15命令。但是,我们一般不建议使用kill -9。

 

这是因为kill -9命令很强硬,它会向操作系统内核发出SIGKILL信号,该信号直接停止进程,不能被阻塞或者忽略。

 

kill -9显然不那么优雅,如果在发出SIGKILL信号的时候,进程还有未处理完成的任务,这时候任务就会被丢弃或者终止导致数据的不可预知错误。

 

使用kill -15就不一样了,这时候发给系统内核的是一个SIGTERM信号。

 

当进程接收到SIGTERM信号时,具体要如何处理是由进程自己决定,进程就可以拒绝外部新的任务并完成已有任务后再停止。

 

在Docker中,docker stop相当于kill -15,他会向容器内的进程发送SIGTERM信号,在10S之后(可通过参数指定)再发送SIGKILL信号。

 

而docker kill就像kill -9,直接发送SIGKILL信号。

 

二、JVM的优雅下线

我们Java应用运行时就是一个独立的进程,它的关闭也即是JVM的关闭。

 

JVM的关闭也分为正常关闭、强制关闭、异常关闭。

 

JVM的正常关闭是Java程序优雅停机的关键,正常关闭的过程中,JVM可以做一系列预善后工作,比如线程池、连接池任务的完成和资源的释放等。

 

JVM还预留了钩子机制,供我们开发自行处理一些特定操作。

 

这种钩子机制就是JDK中提供的shutdown hook,它有一个方法Java.Runtime.addShutdownHook(Thread hook),可以注册一个JVM关闭的钩子。

 

比如我们写一个关闭【看点代码再上班】书库的钩子,如下:

package com.tin.example.shutdown.hook;

/**
 * title: ShutdownHookTest
 * <p>
 * description:
 *
 * @author tin @公众号【看点代码再上班】 on 2022/4/5 下午12:27
 */
public class JVMShutdownHookTest {
    public static void main(String[] args) throws Exception {
        Runtime.getRuntime().addShutdownHook(new Thread(JVMShutdownHookTest::doSomething));

        while (true) {
            System.out.println("i am running...");
            Thread.sleep(500);
        }
    }

    /**
     * 停机前处理事项
     */
    private static void doSomething() {
        System.out.println("关闭【看点代码再上班】书库。");
    }
}

执行命令:

ericli@EricdeAir IntelliJIdea2020.1 % jps
5458 Jps
5394 KotlinCompileDaemon
2986 QuorumPeerMain
5451 Launcher
5452 JVMShutdownHookTest
3023 ZooKeeperMain
ericli@EricdeAir IntelliJIdea2020.1 % kill 5452

本地测试控制台结果输出如下:

 

从测试输出可以看出,当我执行kill pid(实际也是kill -15命令)命名时,我们的Java程序会先执行ShutDownHook钩子逻辑,最后才退出进程。

 

三、Spring容器的优雅下线

Spring的优雅下线也使用到了JVM的ShutdownHook。

 

首先,在application context被load时会注册一个ShutdownHook。这个ShutdownHook会在进程退出前执行销毁bean、容器的销毁等操作。

 

同时,Spring除了销毁bean等操作之外,还会发出一个ContextClosedEvent事件,很多基于Spring容器的三方框架都可以监听这个事件实现更多的优雅停机操作。

 

比如,我们可以监听Spring的事件:

@Override
    public void onApplicationContextEvent(ApplicationContextEvent event) {
        if (event instanceof ContextClosedEvent) {
            //自定一些容器关闭前业务要进行的操作
            onContextClosedEvent((ContextClosedEvent) event);
        }
    }

 

四、Dubbo的优雅停机

Dubbo的优雅停机从起初的2.5版本到当前的2.7,经历了多次优化。

 

最终版本也同样采用到了Spring的ApplicationContextEventListener接口,监听Spring容器的close事件。

 

在org.apache.dubbo.config.spring.extension.SpringExtensionFactory类中,就有关于ShutDownHook注册的相关代码:

 

特别有意思的是,这里有一个issue,看②处代码,它是一个关闭注册的动作,①处则是一个注册的动作。

 

既要注册又要注销,这是为什么呢?

 

Dubbo的开发者们已经发现,Spring容器下的Dubbo进程会触发两次关机钩子。

 

一次在org.apache.dubbo.config.bootstrap.DubboBootstrap中:

 

另一次是在org.apache.dubbo.config.spring.context.DubboBootstrapApplicationListener中通过监听spring的关闭事件来关闭dubbo服务:

 

这会导致什么后果?

 

答:会导致高并**况下,一些任务线程获取不到资源而抛异常。

 

这个issue中也讲得很明白了,如下:

dubbo没有关闭完,数据库连接池已经关闭,导致应用并发高的时候很多没跑完的dubbo线程拿不到数据库连接抛出异常。

 

If there is an exception, please attach the exception trace:

 

msg:DUBBO服务异常 remoteHost:xxx.xx.xxx.xxx service:com.xxx.service.xxxService method:xxx message:nested exception is org.apache.ibatis.exceptions.PersistenceException:

 

Error querying database. Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection; nested exception is java.sql.SQLException: HikariDataSource HikariDataSource (HikariPool-1) has been closed.

这也是Dubbo优化多次后做出的处理结果,以下文章描述得非常详细,值得一看:

https://www.cnkirito.moe/dubbo-gracefully-shutdown

 

我们再来看看Dubbo ShutdownHook都做了哪些事情。

 

org.apache.dubbo.config.DubboShutdownHook#doDestroy

1. 注册中心数据销毁:关闭注册中心中本节点对应的提供者地址以及订阅数据。

2. 协议流程数据销毁:销毁所有协议,包括所有已经暴露和引用的服务,释放协议所占用的所有资源,比如连接和端口。

 

Dubbo销毁注册中心的同时还需要注销Spring容器,这部分工作由Spring的ShutdownHook完成。

 

org.springframework.context.support.AbstractApplicationContext#doClose

 

发布close事件,就有利于我们应用层自己做一些特殊处理逻辑,比如Dubbo关闭注册中心。

 

在关闭Spring容器之前,Dubbo已经关闭注册中心上对应的服务注册,不再接收新请求进来,同时还会把本地未完成的任务做完,最后才销毁bean以及关闭进程。

结语

我是tin,一个在努力让自己变得更优秀的普通工程师。自己阅历有限、学识浅薄,如有发现文章不妥之处,非常欢迎加我提出,我一定细心推敲并加以修改。

 

看到这里请安排个“三连”(分享、点赞、在看)再走吧,坚持创作不容易,你的正反馈是我坚持输出的最强大动力,谢谢!

 

分类:标签:
请先登录,查看2条精彩评论吧
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步

为你推荐

字符串字面量长度是有限制的
前言 偶然在一次单元测试中写了一个非常长的字符串字面量。 正文 在一次单元测试中,我写了一个很长的字符串字面量,大概10万个字符左右,编译时,编译器给出了异常告警 `java: constant
多次字符串相加一定要用StringBuilder而不用-吗?
今天在写一个读取Java class File并进行分析的Demo时,偶然发现了下面这个场景(基于oracle jdk 1.8.0_144): ``` package test; public c
如何通过反射获得方法的真实参数名(以及扩展研究)
前段时间,在做一个小的工程时,遇到了需要通过反射获得方法真实参数名的场景,在这里我遇到了一些小小的问题,后来在部门老大的指导下,我解决了这个问题。通过解决这个问题,附带着我了解到了很多新的知识,我觉得
高吞吐、低延迟 Java 应用的 GC 优化实践
本篇原文作者是 LinkedIn 的 Swapnil Ghike,这篇文章讲述了 LinkedIn 的 Feed 产品的 GC 优化过程,虽然文章写作于 April 8, 2014,但其中的很多内容和
「每日五分钟,玩转 JVM」:久识你名,初居我心
聊聊 JVMJVM,一个熟悉又陌生的名词,从认识Java的第一天起,我们就会听到这个名字,在参加工作的前一两年,面试的时候还会经常被问到JDK,JRE,JVM这三者的区别。JVM可以说和我们是老朋友了
据说99.99%的人都会答错的类加载的问题
概述首先还是把问题抛给大家,这个问题也是我厂同学在做一个性能分析产品的时候碰到的一个问题。 同一个类加载器对象是否可以加载同一个类文件多次并且得到多个Class对象而都可以被java层使用吗请仔细注意
Java多线程——并发测试
编写并发程序时候,可以采取和串行程序相同的编程方式。唯一的难点在于,并发程序存在不确定性,这种不确定性会令程序出错的地方远比串行程序多,出现的方式也没有固定规则。那么如何在测试中,尽可能的暴露出这些问
Java多线程知识小抄集(一)
本文主要整理笔者遇到的Java多线程的相关知识点,适合速记,故命名为“小抄集”。本文没有特别重点,每一项针对一个多线程知识做一个概要性总结,也有一些会带一点例子,习题方便理解和记忆。 1.interr