Dubbo源码学习--优雅停机原理及在SpringBoot中遇到的问题

相关文章:

Dubbo源码学习文章目录

前言

主要是前一阵子换了工作,第一个任务就是解决目前团队在 Dubbo 停机时产生的问题,同时最近又看了一下 Dubbo 的源码,想重新写一下 Dubbo 相关的文章。

优雅停机原理

对于一个 java 应用,如果想在关闭应用时,执行一些释放资源的操作一般是通过注册一个 ShutDownHook ,当关闭应用时,不是调用 kill -9 命令来直接终止应用,而是通过调用 kill -15 命令来触发这个 ShutDownHook 进行停机前的释放资源操作。

对于 Dubbo 来说,需要停机前执行的操作包括两部分:

  1. 对于服务的提供者,需要通知注册中心来把自己在服务列表中摘除掉。
  2. 根据所配置的协议,关闭协议的端口和连接。

而何为优雅停机呢?就是在集群环境下,有一个应用停机,并不会出现异常。下面来看一下 Dubbo 是怎么做的。

注册ShutDownHook

Duubo 在 AbstractConfig 的静态构造函数中注册了 JVM 的 ShutDownHook,而 ShutdownHook 主要是调用 ProtocolConfig.destroyAll() ,源码如下:

    static {
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
public void run() {
if (logger.isInfoEnabled()) {
logger.info("Run shutdown hook now.");
}
ProtocolConfig.destroyAll();
}
}, "DubboShutdownHook"));
}

ProtocolConfig.destroyAll()

先看一下 ProtocolConfig.destroyAll() 源码:


public static void destroyAll() {
if (!destroyed.compareAndSet(false, true)) {
return;
}
AbstractRegistryFactory.destroyAll(); //1. // Wait for registry notification
try {
Thread.sleep(ConfigUtils.getServerShutdownTimeout()); //2.
} catch (InterruptedException e) {
logger.warn("Interrupted unexpectedly when waiting for registry notification during shutdown process!");
} ExtensionLoader<Protocol> loader = ExtensionLoader.getExtensionLoader(Protocol.class);
for (String protocolName : loader.getLoadedExtensions()) {
try {
Protocol protocol = loader.getLoadedExtension(protocolName);
if (protocol != null) {
protocol.destroy(); //3.
}
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
}

ProtocolConfig.destroyAll() 有三个比较重要的操作:

  1. 在1这个点调用AbstractRegistryFactory.destroyAll(),其内部会对每个注册中心进行 destroy 操作,进而把注册到注册中心的服务取消注册。
  2. 2这个点是最近 Dubbo 版本新增的操作,用来增强 Dubbo 的优雅停机,在老版本的 Dubbo 其逻辑是直接摘除服务列表,关闭暴露的连接,因为服务取消注册,注册中心是异步的通知消费者变更其存放在自己内存中的提供者列表。因为是异步操作,当调用量比较大的应用时消费者会拿到已经关闭连接点的提供者进行调用,这时候就会产生大量的错误,而2这个点就是通过Sleep 来延迟关闭协议暴露的连接。
  3. 因为 Dubbo 的扩展机制 ,loader.getLoadedExtensions() 会获取到已使用的所有协议,遍历调用 destroy 方法来关闭其打开的端口和连接。

而在第3步会在 Exchange 层 对所有打开的连接进行判断其有没有正在执行的请求,如果有会自旋 Sleep 直到设置的 ServerShutdownTimeout 时间或者已经没有正在执行的请求了才会关闭连接,源码如下:

  public void close(final int timeout) {
startClose();
if (timeout > 0) {
final long max = (long) timeout;
final long start = System.currentTimeMillis();
if (getUrl().getParameter(Constants.CHANNEL_SEND_READONLYEVENT_KEY, true)) {
sendChannelReadOnlyEvent();
}
while (HeaderExchangeServer.this.isRunning() //判断是否还有正在处理的请求
&& System.currentTimeMillis() - start < max) { //判断是否超时
try {
Thread.sleep(10);
} catch (InterruptedException e) {
logger.warn(e.getMessage(), e);
}
}
}
doClose();
server.close(timeout); //正在的关闭连接
}

在 SpringBoot 应用中存在的问题

简单的描述一下问题:就是在应用停机时,瞬间会产生大量的报错,比如拿到的数据库连接已经关闭等问题。 其实一看就知道是在停机时还存在正在处理的请求,而这些请求所需要的资源被 Spring 容器所关闭导致的。原来在SpringBoot 启动时会在 refreshContext 操作也注册一个 ShotdownHook 来关闭Spring容器。

    private void refreshContext(ConfigurableApplicationContext context) {
this.refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
} catch (AccessControlException var3) {
}
}
}

而要解决这个问题就需要取消掉这个 ShutDownHook ,然后再 Dubbo 优雅停机执行后关闭 Spring 容器。具体的修改如下:

  1. 在启动Main方法中,修改SpringBoot 启动代码,取消注册ShutDownHook。
    public static void main(String[] args) {
SpringApplication app = new SpringApplication(XxxApplication.class);
app.setRegisterShutdownHook(false);
app.run(args);
}
  1. 注册一个Bean 来让 Dubbo 关闭后关闭Spring容器。
public class SpringShutdownHook {
private static final Logger logger = LoggerFactory.getLogger(SpringShutdownHook.class);
@Autowired
private ConfigurableApplicationContext configurableApplicationContext; public SpringShutdownHook() {
} @PostConstruct
public void registerShutdownHook() {
logger.info("[SpringShutdownHook] Register ShutdownHook....");
Thread shutdownHook = new Thread() {
public void run() {
try {
int timeOut = ConfigUtils.getServerShutdownTimeout();
logger.info("[SpringShutdownHook] Application need sleep {} seconds to wait Dubbo shutdown", (double)timeOut / 1000.0D);
Thread.sleep((long)timeOut);
this.configurableApplicationContext.close();
logger.info("[SpringShutdownHook] ApplicationContext closed, Application shutdown");
} catch (InterruptedException var2) {
SpringShutdownHook.logger.error(var2.getMessage(), var2);
} }
};
Runtime.getRuntime().addShutdownHook(shutdownHook);
}
}

Dubbo源码学习--优雅停机原理及在SpringBoot中遇到的问题的更多相关文章

  1. Dubbo源码学习(二)

    @Adaptive注解 在上一篇ExtensionLoader的博客中记录了,有两种扩展点,一种是普通的扩展实现,另一种就是自适应的扩展点,即@Adaptive注解的实现类. @Documented ...

  2. Dubbo源码学习--服务是如何引用的

    ReferenceBean 跟服务引用一样,Dubbo的reference配置会被转成ReferenceBean类,ReferenceBean实现了InitializingBean接口,直接看afte ...

  3. Dubbo源码学习--注册中心分析

    相关文章: Dubbo源码学习--服务是如何发布的 Dubbo源码学习--服务是如何引用的 注册中心 关于注册中心,Dubbo提供了多个实现方式,有比较成熟的使用zookeeper 和 redis 的 ...

  4. Dubbo源码学习--服务是如何发布的

    相关文章: Dubbo源码学习--服务是如何发布的 Dubbo源码学习--服务是如何引用的 ServiceBean ServiceBean 实现ApplicationListener接口监听Conte ...

  5. Dubbo源码学习--集群负载均衡算法的实现

    相关文章: Dubbo源码学习文章目录 前言 Dubbo 的定位是分布式服务框架,为了避免单点压力过大,服务的提供者通常部署多台,如何从服务提供者集群中选取一个进行调用, 就依赖Dubbo的负载均衡策 ...

  6. Dubbo源码学习文章目录

    目录 Dubbo源码学习--服务是如何发布的 Dubbo源码学习--服务是如何引用的 Dubbo源码学习--注册中心分析 Dubbo源码学习--集群负载均衡算法的实现

  7. Dubbo 源码分析 - 自适应拓展原理

    1.原理 我在上一篇文章中分析了 Dubbo 的 SPI 机制,Dubbo SPI 是 Dubbo 框架的核心.Dubbo 中的很多拓展都是通过 SPI 机制进行加载的,比如 Protocol.Clu ...

  8. Dubbo源码学习--服务发布(ServiceBean、ServiceConfig)

    前面讲过Dubbo SPI拓展机制,通过ExtensionLoader实现可插拔加载拓展,本节将接着分析Dubbo的服务发布过程. 以源码中dubbo-demo模块作为切入口一步步走进Dubbo源码. ...

  9. Dubbo源码学习之-Adaptive自适应扩展

    前言 最近三周基本处于9-10-6与9-10-7之间,忙碌的节奏机会丢失了自己.除了之前干施工的那段经历,只看参加软件开发以来,前段时间是最繁忙的了.忙的原因,不是要完成的工作量大,而是各种环境问题, ...

随机推荐

  1. vue+大文件分片上传

    最近公司在使用vue做工程项目,实现大文件分片上传. 网上找了一天,发现网上很多代码都存在很多问题,最后终于找到了一个符合要求的项目. 工程如下: 对项目的大文件上传功能做出分析,怎么实现大文件分片上 ...

  2. 深入浅出javascript(二)函数和this对象

    一.函数对象的属性和方法 函数是一种数据类型,也是对象,这一点很明确.因此,函数对象也可以添加属性和方法,但是这里的属性和方法是静态的,之所以这样说,就是为了区别构造函数. 示例如下: ①创建一个空的 ...

  3. C#-VS程序集

    程序集即代码组,可以是单个文件或多个文件,按一个整体部署,但可指定自身调用其他程序集的版本. 推出原因 为解决dll地狱而推出,也可解决其他问题.dll地狱,a应用使用dll版本1,b应用使用dll版 ...

  4. 使用PinYin4j.jar将汉字转换为拼音

    package com.Test.util; import net.sourceforge.pinyin4j.PinyinHelper; import net.sourceforge.pinyin4j ...

  5. Stringbuffer与substring

    1. Stringbuffer 有append()方法 Stringbuffer 其实是动态字符串数组 append()是往动态字符串数组添加,跟“xxxx”+“yyyy”相当那个‘+’号 跟Stri ...

  6. presto调研和json解析函数的使用

    presto简单介绍 presto是一个分布式的sql交互式查询引擎.可以达到hive查询效率的5到10倍.支持多种数据源的秒级查询. presto是基于内存查询的,这也是它为什么查询快的原因.除了基 ...

  7. 由异常掉电问题---谈xfs文件系统

    由异常掉电问题---谈xfs文件系统 本文皆是作者自己的学习总结或感悟(linux环境),如有不对,欢迎提出一起探讨!! 目录结构 一.相关知识 二.问题提出 三.处理方法 四.最终结果 一.相关知识 ...

  8. 6.Django扩展

    富文本编辑器 借助富文本编辑器,管理员能够编辑出来一个包含html的页面,从而页面的显示效果,可以由管理员定义,而不用完全依赖于前期开发人员 此处以tinymce为例,其它富文本编辑器的使用可以自行学 ...

  9. 【转】利用线程更新ListView (2014-09-28 08:25:20)

    http://blog.sina.com.cn/s/blog_44fa172f0102v2x0.html procedure TForm5.Button3Click(Sender: TObject); ...

  10. hdu 1.2.4

    采用异或... #include<stdio.h> int main() { //freopen("input.txt","r",stdin); i ...