1

时间追溯到2018年12月的某一天夜晚,那天我正准备上线一个需求完就回家,刚点下发布按钮,告警就响起,我擦,难道回不了家了?看着报错量只有一两个,断定只是偶发,稳住不要慌。

把剩下的机器发完,又出现了几个同样的错误,作为一名优(咸)秀(鱼)程序员,这种问题必须追查到底。

2

娴熟地查询到报错日志

org.apache.ibatis.exceptions.PersistenceException: ### Error querying database. Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is com.alibaba.druid.pool.DataSourceClosedException: dataSource already closed

看着异常信息,陷入了沉思

  • 表面上看报错是因为使用了已经关闭的数据源
  • 数据源什么时候会关闭呢?只有进程被杀死的时候
  • 莫非是应用关闭时不够平滑?发布时会先摘除流量的呀,应该不至于呀

天色已经很晚,漫无目的地拖动日志,疲惫地寻找新线索,突然报错日志中一个单词引入眼帘:rocketmq

精神抖擞,大概知道原因了,这应用中还有个兢兢业业的rocketmq consumer一直在消费消息,在应用关闭时,外部流量被摘除了,但没人通知rocketmq consumer,于是它抛异常了。

3

出于我对rocketmq不深刻甚至有点肤浅的理解,它的消费采用ack的方式,如果报错,消息稍后还会重试,不会丢消息,而且如果消费代码是幂等的,也不会有业务上的异常,总之这不重要,因为它也不是我写的代码。

瞅了一眼consumer的代码(这里就不贴代码了,反正贴了你也不会看),consumer注册了一个ShutdownHook,ShutdownHook里consumer执行了shutdown来优雅地退出,并且给这个shutdownThread设置了最高优先级,然而从实践看来,这个线程最高优先级并没有什么卵用。

而且从《ShutdownHook原理》这篇文章中也知道ShutdownHook是并发执行的,spring容器关闭也是一个ShutdownHook,他们之前没有先后顺序。

了解原因后,第一时间想到了类似dubbo摘流的方案,吭哧吭哧写了个优雅关闭rocketmq cosnumer的接口,在应用关闭脚本的kill之前调用该接口,完美解决问题,赶紧下班回家,不然要猝死了。

4

夜里入睡,梦到老板让我把所有的系统都改造掉,吓得我一机灵。

于是第二天又重新思考这个问题,总觉得在应用里实现一个接口并在stop脚本中去调用是一件非常不优雅的事,更重要的是这也没法复制到其他项目,我又陷入了沉思。

既然是spring容器关闭时bean的销毁顺序导致的问题,那么能不能利用spring的depend-on把顺序理顺了?说干就干。

起初我遇到是这样的依赖关系:

手把手在xml的每个bean中把depend-on关系都配上,似乎也起到了作用。

但当我打开第二个项目时,它的bean之间的依赖关系大致如下:

好家伙,26个字母差点不够用,当时我的心情是这样的

所以我觉得以当前的速度,改造完所有项目可能都到9102年了。

5

又过了一段时间,在github交友网站上突然看到了rocketmq官方实现的spring-boot-starter,于是点进去看了它的实现。好家伙,看完直呼666。

官方starter实现了spring的SmartLifecycle接口,它的start方法能在所有bean初始化完成后被调用,stop方法会在bean被销毁前调用,对rocketmq consumer来说简直完美。

顺便还复习了一下spring容器的关闭,代码在AbstractApplicationContext的doClose方法,这里我总结成一幅图:

通过上图能看到,销毁bean之前,有关闭lifecycle bean和发送ContextClosedEvent两个动作,官方starter选择了实现LifeCycle接口的方式。

6

到这里我该给老板汇报去了,之所以rocketmq consumer发布时不平滑是我们的使用姿势问题,虽然对业务没影响,但不优雅,解决方案有两个,老板你选吧:

  • 全都换成官方starter,依赖spring-boot,官方维护,改造成本很高,
  • 监听ContextClosedEvent来实现优雅关闭,这块可以封装一下,让业务方引入依赖即可


都看到这了,不点个关注吗?

rocketmq优雅停机往事的更多相关文章

  1. JAVA优雅停机的实现

    最近在项目中需要写一个数据转换引擎服务,每过5分钟同步一次数据.具体实现是启动engine server后会初始化一个ScheduledExecutorService和一个ThreadPoolExec ...

  2. dubbo-2.5.6优雅停机研究

    不优雅的停机: 当进程存在正在运行的线程时,如果直接执行kill -9 pid时,那么这个正在执行的线程被中断,就好像一个机器运行中突然遭遇断电的情况,所导致的结果是造成服务调用的消费端报错,也有可能 ...

  3. 哦,这就是java的优雅停机?(实现及原理)

    优雅停机? 这个名词我是服的,如果抛开专业不谈,多好的名词啊! 其实优雅停机,就是在要关闭服务之前,不是立马全部关停,而是做好一些善后操作,比如:关闭线程.释放连接资源等. 再比如,就是不会让调用方的 ...

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

    Dubbo源码学习--优雅停机原理及在SpringBoot中遇到的问题 相关文章: Dubbo源码学习文章目录 前言 主要是前一阵子换了工作,第一个任务就是解决目前团队在 Dubbo 停机时产生的问题 ...

  5. spring cloud shutdown graceful 优雅停机

    spring cloud shutdown graceful 优雅停机 当一个服务启动后,会注册到eureka中,其他的服务也可以从eureka获取到新注册的服务.但当我们要停止一个服务的时候,如果直 ...

  6. dubbo之优雅停机

    优雅停机 Dubbo 是通过 JDK 的 ShutdownHook 来完成优雅停机的,所以如果用户使用 kill -9 PID 等强制关闭指令,是不会执行优雅停机的,只有通过 kill PID 时,才 ...

  7. Spring Boot 内嵌容器 Tomcat / Undertow / Jetty 优雅停机实现

    Spring Boot 内嵌容器 Tomcat / Undertow / Jetty 优雅停机实现 Anoyi 精讲JAVA 精讲JAVA 微信号 toooooooozi 功能介绍 讲解java深层次 ...

  8. ShutdownHook- Java 优雅停机解决方案

    想象一下,如果你现在刚好在 word 上写需求文档,电脑突然重启.等待开机完成,你可能会发现写了一个小时文档没有保存,就这么没了... 一个正在运行 Java 应用如果突然将其停止,影响不止数据丢失, ...

  9. spring cloud 优雅停机

    spring cloud 优雅停机 大部分部署项目如果要停掉项目一般都是用kill -9 来杀进程 但是由于Eureka采用心跳的机制来上下线服务,会导致服务消费者调用已经kill的服务提供者然后出错 ...

随机推荐

  1. GoLang设计模式3 - 抽象工厂模式

    之前我们介绍了工厂设计模式,现在我们再看一下抽象工厂设计模式.抽象工程模式顾名思义就是对工厂模式的一层抽象,也是创建型模式的一种,通常用来创建一组存在相关性的对象. UML类图大致如下: 类图比较复杂 ...

  2. PyQT5基础布局管理

    绝对定位布局 使用move(x, y)可以对窗口进行布局,以窗口左上角为原点,向右为 x 轴正方向,向下为 y 轴正方向,移动(x,y); import sys from PyQt5.QtGui im ...

  3. Docker(34)- 如何修改 docker 容器的目录映射

    如果你还想从头学起 Docker,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1870863.html 问题背景 docker run ...

  4. 斐波那契数(Java)

    斐波那契数,通常用 F(n) 表示,形成的序列称为 斐波那契数列 .该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和.也就是: F(0) = 0,F(1) = 1 F(n) = F(n ...

  5. VUE005. 在data中使用 / 改变data,或在data中调用method函数

    使用三方UI库时经常会遇到在data中写入方法的场景,如Element-UI的级联选择器(动态加载part)需要在data中写入lazyLoad. 但后端总会给出意想不到的需求: 通过接口调取一串数据 ...

  6. Dapr实战(一) 基础概念与环境搭建

    什么是Dapr Dapr 是一个可移植的.事件驱动的运行时,可运行在云平台或边缘计算中.支持多种编程语言和开发框架. 上面是官方对Dapr的介绍.有点难以理解,大白话可以理解为:Dapr是一个运行时, ...

  7. Loadrunner拼装唯一值方法

    由于Loadrunner函数有限性,唯一值需要几个函数的字符串进行拼装,可实现流水号.订单号等等数值的唯一性.具体可见下列方法: 方法一: char OraderID[15];srand(time{N ...

  8. ecshop后台设置模板的地方显示自己新建模板的操作界面

    我建立了一个叫test.dwt文件怎样在后台设置模板里面出现呢.1首先找到ecshop目录下的languages\zh_cn\admin/template.php 这个php文件 当然如果这只是简体中 ...

  9. Shell系列(28)- 条件判断之字符串判断

    字符串判断 $变量时要用双引号引起来,即"$变量" 测试选项 作用 -z 字符串 判断字符串是否为空(为空返回真) -n 字符串 判断字符串是否为非空(非空返回真) 字符串1 == ...

  10. 华为云计算IE面试笔记-FusionCompute上的虚拟机A和虚拟机B不能正常通信,请描述可能的故障点和排除方法(分析虚拟机A和虚拟机B不通)

    *快速定位故障点:(考到) ARP:同一二层内可以通过ARP -a命令查询到要访问的主机IP(ARP表项中记录了同一二层中的IP和MAC的对应关系),若查询不到,说明二层出现问题(Vlan 间不通)或 ...