Java 应用优雅停机

我们首先考虑下,一般在什么场景下数据会丢失呢?

  • 升级服务时
  • pod重启时
  • 服务器断电时

因为服务器断电属于极端情况,我们暂且不考虑。那就只有 Java 退出时我们要保证数据的完整性了。在 Java 中,有一个方法可以实现应用退出时候的优雅停机:shutdown hookSpring boot把这个东西封装了一下,可以通过 @PreDestroy 注解实现。当 JVM 收到退出的信号时,会调用 shutdown hook 中的方法,完成清理操作。示例代码如下:

Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
System.out.println("Start to run shutdown hook.");
}
})

Shutdown hook 可以保证在我们代码主动调用 System.exit()OOM, 在终端执行 Ctrl+C,以及应用主动关闭等情况下时被调用。在实际的场景中,我们可以在上述的线程中执行清理操作。比如,停止 kafka 的数据消费,以及任务的及时处理等。

当我们使用 java -jar *.jar 运行 Java程序后,通过执行 kill $pid,可以发现程序确实可以优雅退出。但是当我把服务部署到 Kubernetes 时,发现这个逻辑并没有被执行,到底哪里出了问题?

在 Kubernetes 中优雅停机

当我们发送 delete 命令给 pod 时,Kubernetes 会使用优雅停机(默认30s时间),在优雅停机过程中,此 podAPI server 中会被更新为dead状态。当我们用kubectl 命令查看此pod时,它被展示为Terminating 的状态。当 Kubelet 看到 pod被标记为了 Terminating 状态时,它就会开始执行 podshutdown 程序。如果我们 pod 的容器定义了 preStop hook,那么这个 hook 会在容器中执行;与此同时,Kubelet 会向容器内发送一个TERM信号。Service也会将此 pod 从 endpoint 列表移除。当优雅停机时间过后,在 pod 里仍然存活的进程则会被SIGKILL命令杀掉。Kubelet会在 API server 里通过设置 grace period=0(立即删除)来完成 Pod 的删除操作。删除后此 Pod 会在API中消失,并且在客户端也不可见了。

以上,可以看出,我们的容器是会收到 TERM 信号的,按照常理,如果我们的 Java 进程收到了 TERM 信号是可以正常执行我们写的 shutdown hook 优雅退出的,但是这里却没有执行,很有可能是我们的 Java 进程根本就没有收到信号。

查看我们的 Dockerfile,发现我们定义的启动命令是执行一个 run.sh 的脚本,在 run.sh 脚本中,进一步执行了启动 Java 进程的命令。

# run.sh
...
sh start.sh start
...
while [1]
do
sleep 30
done

可以看到,我们在 run.sh 中进一步执行了 start.sh,Java 进程的启动逻辑在start.sh脚本中。我们可以执行 ps -ef 查看下当前容器中的进程

UID		PID		PPID	C 	STIME 		TTY 	TIME 		CMD
root 1 0 0 11:01 ? 00:00:00 bash ~/run.sh
root 4084 1 8 11:01 ? 00:15:00 java -Dname=test
root 14913 1 0 13:49 ? 00:00:00 sleep 30
root 14914 0 0 13:50 pts/0 00:00:00 bash
root 14955 14914 0 13:50 pts/0 00:00:00 ps -ef

可以看到,我们运行的 run.sh 的 PID 是 1,Java 进程的 PID 是 4084,Java 进程是 run.sh 进程的一个子进程。问题就出在这里,在 pod 被删除时,TERM 信号只会发送给 1号进程,而 run.sh 接收到此信号后并不会将其转发给 Java 进程,因此 Java 便无法触发 shutdown hook,无法实现优雅退出。最终,Java 是被 SIGKILL 信号杀掉的(强制退出)。所以,我们只需要让 Java 进程作为 1号进程就行了。改写下脚本,我们把启动 Java 进程的命令放到 run.sh

# run.sh
...
exec java $JAVA_OPTS -jar ./*.jar --server.port=8080
...
while [1]
do
sleep 30
done

exec 的作用是被执行的命令行替换掉当前的 shell 进程。测试发现 OK,此时我们实现了优雅停机。但是,这足够优雅吗?

更优雅地停机

在上一步,我们实现了优雅停机,但是其实这并不是最优方案。我在看 start.sh 脚本中,发现此脚本定义了 start, restart, stop, status 4个方法,而且这个脚本中定义了很多额外的变量,如果我们要把之前的功能都实现的话,就需要把逻辑都搬到 run.sh 中。这无疑会增大工作量,这是不优雅的原因之一。

其次,一般是不推荐把 Java 进程作为1号进程的。因为在 Linux中,1号进程有特殊作用:1号进程会作为孤儿进程的父进程,它需要对自己的子进程进行清理回收,避免系统产生僵尸进程。bash可以很好地处理这种清理工作,我们一般自己写的 Java 程序是不会考虑这种东西的。

那么,就需要我们在 shell 中接收到 TERM 信号后把信号传递给 Java 进程了。这需要怎么做呢?我们需要使用trap命令。trap 命令的作用是捕捉信号和其他事件并执行命令。

# run.sh
...
sh start.sh start grace_exit() {
echo 'grace exit started'
sh start.sh stop &
wait $!
echo 'grace exit finished'
}
trap 'grace_exit' TERM INT
...
while [1]
do
sleep 30
done

在脚本中,我们使用 trap 捕捉 TERMKubelet 发送的信号) 和 INT(快速关闭,当用户输入 Control-C时由终端程序发送) 信号,捕捉到了以后,我们执行了 grace_exit 方法,在此方法中,调用了 start.sh 脚本的 stop 方法,其实这个 stop 方法就是找到了 Java 进程,然后给其发送了 kill 命令,我们直接在 grace_exit 中执行相同逻辑也是可以的,这里是为了复用逻辑。我们还使用了 & 保证 stop 方法在后台运行,这样方便我们获取其进程号($!会返回shell最后运行的后台进程的 PID),等待其执行结束。 这样,当我们 delete``pod 时,Kubelet 发送 TERM 信号后,我们就能传达给 Java 进程,进而让 Java 进程进行优雅停机了。


标题你的Kubernetes Java应用优雅停机了吗?

作者末日没有进行曲

链接你的Kubernetes Java应用优雅停机了吗?

时间:2021-01-15

声明:本博客所有文章均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

你的Kubernetes Java应用优雅停机了吗?的更多相关文章

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

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

  2. Java优雅停机

    Java的优雅停机通常通过注册JDK的ShootDownHook实现,当系统接受到退出指令后,首先标记系统处于退出状态,不再接受新的消息,然后将积压的消息处理完,最后调用资源回收接口将资源销毁,最后各 ...

  3. dubbo-2.5.6优雅停机研究

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

  4. Dubbo 优雅停机演进之路

    一.前言 在 『ShutdownHook- Java 优雅停机解决方案』 一文中我们聊到了 Java 实现优雅停机原理.接下来我们就跟根据上面知识点,深入 Dubbo 内部,去了解一下 Dubbo 如 ...

  5. JAVA优雅停机的实现

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

  6. Java 技术栈中间件优雅停机方案设计与实现全景图

    欢迎关注公众号:bin的技术小屋,阅读公众号原文 本系列 Netty 源码解析文章基于 4.1.56.Final 版本 本文概要 在上篇文章 我为 Netty 贡献源码 | 且看 Netty 如何应对 ...

  7. Spring Boot 2.3.0正式发布:优雅停机、配置文件位置通配符新特性一览

    当大潮退去,才知道谁在裸泳..关注公众号[BAT的乌托邦]开启专栏式学习,拒绝浅尝辄止.本文 https://www.yourbatman.cn 已收录,里面一并有Spring技术栈.MyBatis. ...

  8. Spring Boot 系列:最新版优雅停机详解

    爱生活,爱编码,本文已收录架构技术专栏关注这个喜欢分享的地方. 开源项目: 分布式监控(Gitee GVP最有价值开源项目 ):https://gitee.com/sanjiankethree/cub ...

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

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

随机推荐

  1. Redis篇:持久化、淘汰策略,缓存失效策略

    关注公众号,一起交流,微信搜一搜: 潜行前行 redis 持久化 redis 的数据是保存再系统内存里面的.持久化就是把内存的数据转移到磁盘中,redis 的持久化策略有两种:RDB.AOF RDB ...

  2. Java的对象与类,继承

    Java的对象与类,继承 题目1.Java类的建立与使用 设计一个用来描述汽车的类,使用类的非静态成员变量来表示汽车的车主姓名.当前的速率和当前方向盘的转向角度,使用类的非静态成员方法来表示改变汽车的 ...

  3. python 学生信息管理系统

    python与数据库的例子 初始化数据库 链接数据库创建库和表并插入数据 init.py import pymysql sql_base='create database school;' sql_t ...

  4. 【九度OJ】题目1107:搬水果 解题报告

    [九度OJ]题目1107:搬水果 解题报告 标签(空格分隔): 九度OJ http://ac.jobdu.com/problem.php?pid=1107 题目描述: 在一个果园里,小明已经将所有的水 ...

  5. 【LeetCode】39. Combination Sum 解题报告(Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 方法一:递归 方法二:回溯法 日期 题目地址:[htt ...

  6. Java学到什么程度可以面试工作?

    ​先说结论: 1 大多数公司,对于Java初级开发的要求是,会用Spring Boot+JPA做增删改查 2 所以零基础的Java小白,无需学太多的内容,只要掌握Spring Boot+JPA做增删改 ...

  7. Proximal Algorithms 1 介绍

    目录 定义 解释 图形解释 梯度解释 一个简单的例子 Proximal Algorithms 定义 令\(f: \mathrm{R}^n \rightarrow \mathrm{R} \cup \{+ ...

  8. BL8810|USB 2.0单芯片解决方案闪存读卡器|BL8810替代GL823K

    创惟GL823K是一款USB 2.0单LUN读卡器控制器,可支持SD/MMC/MSPRO闪存卡.它支持USB 2.0高速传输,将Digital TM(SD).SDHC.SDXC.Mini DTM.Mi ...

  9. 使用 jQuery 基本选择器获取页面元素,然后利用 jQuery 对象的 css() 方法动态设置 <span> 和 <a> 标签的样式

    查看本章节 查看作业目录 需求说明: 使用 jQuery 基本选择器获取页面元素,然后利用 jQuery 对象的 css() 方法动态设置 <span> 和 <a> 标签的样式 ...

  10. 关于linux的一点好奇心(一):linux启动过程

    一直很好奇,操作系统是如何工作的?我们知道平时编程,是如何让代码跑起来的,但那些都是比较高层次的东西.越往后,你会越觉得,像是空中楼阁,或者说只是有人帮你铺平了许多道理,而你却对此一无所知. 1. 操 ...