假如我们从 kafka 拉取数据然后生成任务处理数据,在服务退出时,如何保证内存中的数据能被正常处理完不丢失呢?假如服务是部署在 Kubernetes 中又该如何处理?

Java 应用优雅停机

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

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

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

Runtime.getRuntime().addShutdownHook(new Thread() {@Overridepublic 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 startgrace_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】优雅停机时的一点思考

    1.概述 转载:http://cxytiandi.com/blog/detail/15386 转载自:徐靖峰 Kirito的技术分享 最近瞥了一眼项目的重启脚本,发现运维一直在使用 kill -9 的 ...

  2. java停机保存数据_哦,这就是java的优雅停机?(实现及原理)

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

  3. java唱歌打分系统原理_哦,这就是java的优雅停机?(实现及原理)

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

  4. Java应用的优雅停机

    一. 优雅停机的概念 优雅停机一直是一个非常严谨的话题,但由于其仅仅存在于重启.下线这样的部署阶段,导致很多人忽视了它的重要性,但没有它,你永远不能得到一个完整的应用生命周期,永远会对系统的健壮性持怀 ...

  5. 95-22-010-停止-优雅停机

    文章目录 1.优雅停机 2. 调用方法 1.优雅停机 Netty的优雅停机三部曲: 不再接收新消息 退出前的预处理操作 资源的释放操作 ​ Java的优雅停机通常通过注册JDK的ShutdownHoo ...

  6. dubbo protocol port 消费者端_Dubbo 优雅停机演进之路

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

  7. 微服务架构—优雅停机方案

    1 介绍 微服务架构中的应用优雅停机主要是指应用实例有计划而平滑(即不产生需要处理的事故)的退出.应用服务器的停机主要分为两类:主动停机和被动停机,而其中主动停机和大部分的被动停机都是可以实现优雅停机 ...

  8. 中台化实践——优雅停机方案

    1 介绍 微服务架构中的应用优雅停机主要是指应用实例有计划而平滑(即不产生需要处理的事故)的退出.应用服务器的停机主要分为两类:主动停机和被动停机,而其中主动停机和大部分的被动停机都是可以实现优雅停机 ...

  9. java signal handler_JAVA优雅停机的实现

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

最新文章

  1. tomcat 启动报栈溢出 解决方法
  2. java word表格_Java 添加Word表格行或列
  3. @PropertySource@ImportResource@Bean
  4. 手把手教你在Linux上搭建BitTorrent服务器
  5. 五十种巧妙优化SQL Server数据库
  6. Nacos源码HostReactor
  7. NWERC 2018——B.Brexit Negotiations
  8. 6个步骤卸载wine
  9. Java类class cast()方法及示例
  10. 我就是互联网的老不死
  11. 8.5. JdbcTemplate
  12. 同步异步线程进程的一些思考
  13. grub2 引导光盘
  14. 计算机网络技术提纲,计算机网络技术复习提纲
  15. java实现车牌头像识别_LPR java车牌图像处理 输入一个车牌照片(不是整车的照片) - 下载 - 搜珍网...
  16. windows11百度网盘下载,win11iso镜像百度云下载
  17. Solidity函数中pure、view、constant的用法
  18. 中文分词算法python_简单的中文分词算法
  19. 密西根大学张阳教授受聘中国上海交通大学客座教授(图)
  20. W800/W801学习记录网络部分(一):WIFI的扫描和连接

热门文章

  1. linux nfs root无权限,nfs root 无权限_如何允许root用户访问NFS?
  2. SSM+Java体育用品库存管理系统 毕业设计-附源码211712
  3. 直播软件源码如何在Android端实现多人视频通话
  4. 麦块我的世界怎么用java_我的世界从进入游戏到多人游戏 生存要点 Java下载一套龙教程【含麦块使用教程】...
  5. Tomcat在指定JDK版本启动
  6. 黑客:iPhone的这些脑残功能逼我去越狱 苹果:乖,招安是我强项!
  7. HTML 为元素设置边框
  8. 最好用的17个安全漏洞检查工具
  9. Stata:无条件分位数回归及应用
  10. 还原android系统文件夹,如何从Android的内存中恢复文件-万兴恢复专家