笔者的项目需要改造成支持优雅停机,停止服务时不在接受新的请求(不接受请求所指,不是拒绝请求,而是客户端不再往服务端分发),但是已经接受的请求,需要在处理完,再停止服务。
目前大致的思路是:微服务框架实现优雅停机的功能,在destroy方法里微服务框架先把本地服务从zk上下线、等待5秒(等待处理中的业务结束),后续再判断业务是否结束,如果未结束根据配置确定是否再等待,然后关闭连接。
为此,需要对系统进行一些重构,将系统部署的服务器进行分组,分为现网业务、MQ、定时任务3组
通过流控(nginx)确保现网用户的流量流量指向现网业务组,MQ和定时任务组不接受现网流量
对MQ和定时任务设计统一的配置中心,各个模块暴露启停接口,通过接受配置中心的通知来控制启停
MQ和定时任务需要中间状态监控和停机后的续传业务,以确保停机后可以继续执行中断的业务。
shutdownHook主要用于:
Application正常退出,在退出时执行特定的业务逻辑,或者关闭资源等操作。- 虚拟机非正常退出,比如用户按下ctrl+c、OOM宕机、操作系统关闭(kill pid)等。在退出时执行必要的挽救措施。
以下博文来自:https://www.cnblogs.com/shuo1208/p/5871224.html
JDK在1.3之后提供了Java Runtime.addShutdownHook(Thread hook)方法,可以注册一个JVM关闭的钩子,这个钩子可以在以下几种场景被调用:
- 1)程序正常退出
- 2)使用System.exit()
- 3)终端使用Ctrl+C触发的中断
- 4)系统关闭
- 5)使用Kill pid命令干掉进程
注:在使用kill -9 pid是不会JVM注册的钩子不会被调用。
在JDK中方法的声明:
public void addShutdownHook(Thread hook)
参数
hook -- 一个初始化但尚未启动的线程对象,注册到JVM钩子的运行代码。
异常
IllegalArgumentException -- 如果指定的钩已被注册,或如果它可以判定钩已经运行或已被运行
IllegalStateException -- 如果虚拟机已经是在关闭的过程中
SecurityException -- 如果存在安全管理器并且它拒绝的RuntimePermission(“shutdownHooks”)
代码示例:
使用Timer模拟一个工作线程,该线程重复工作十次,使用System.exit()退出,在清理现场代码CleanWorkThread 中,取消timer运行,并输出必要的日志信息。
1 package com.netease.test.java.lang;
2
3 import java.util.Timer;
4 import java.util.TimerTask;
5 import java.util.concurrent.atomic.AtomicInteger;
6
7 /**
8 * Date: 14-6-18
9 * Time: 11:01
10 * 测试ShutdownHook
11 */
12 public class TestShutdownHook {
13
14 //简单模拟干活的
15 static Timer timer = new Timer("job-timer");
16
17 //计数干活次数
18 static AtomicInteger count = new AtomicInteger(0);
19
20 /**
21 * hook线程
22 */
23 static class CleanWorkThread extends Thread{
24 @Override
25 public void run() {
26 System.out.println("clean some work.");
27 timer.cancel();
28 try {
29 Thread.sleep(2 * 1000);//sleep 2s
30 } catch (InterruptedException e) {
31 e.printStackTrace();
32 }
33 }
34 }
35 public static void main(String[] args) throws InterruptedException {
36 //将hook线程添加到运行时环境中去
37 Runtime.getRuntime().addShutdownHook(new CleanWorkThread());
38 System.out.println("main class start ..... ");
39 //简单模拟
40 timer.schedule(new TimerTask() {
41 @Override
42 public void run() {
43 count.getAndIncrement();
44 System.out.println("doing job " + count);
45 if (count.get() == 10) { //干了10次退出
46 System.exit(0);
47 }
48 }
49 }, 0, 2 * 1000);
50
51 }
52 }
运行后,可以模拟以上五种场景进行测试,只有kill -9 pid不会执行Hook里面的代码。
使用shutdownHook需注意:
1.不一定总是被执行,例如,JVM崩溃了。
2.shutdownHook可以在完成前强行停止
虽然shutdownHook开始执行,但是在操作系统关闭的情况下,任然可以在完成之前终止它。在这种情况下,一旦SIGTERM被提供,O/S等待一个进程终止指定的时间。如果进程在该时间限制内没有终止,则O/S通过发出SIGTERM(或Windows中的对等方)强制终止进程。所以有可能这是在shutdownHook中途执行时发生的。
需要保证其调用的服务不会被其他Hook影响。否则会出现当前Hook所依赖的服务被另外一个Hook终止了的情况。
因此,建议谨慎地编写shutdownHook,确保它们快速完成,并且不会造成死锁等情况。另外特别注意的是,不应该执行长时间计算或等待用户I/O操作在钩子。
3.可以有多个shutdownHook,但其执行顺序无法保证,因为它是存储在IdentityHashMap中的,JVM并不能保证其执行顺序。但是可以同时执行所有的shutdownHook。
4.关闭顺序开始后,无法注册/取消注册shutdownHook
一旦关闭顺序是由JVM发起的,将不在允许添加或删除任何现有的shutdownHook,否则抛出IllegalStateException异常。
5.关闭顺序开始后,只能由Runtime.halt()停止
关闭顺序开始后,只能通过Runtime.halt()(强制终止JVM),可以停止关闭顺序的执行(外部影响除外,如SIGKILL)。
6.使用shutdownHook需要安全权限
如果我们使用Java Security Managers,则执行添加/删除shutdownHook的代码需要在运行时具有shutdownHooks权限。否则会导致SecurityException。
在程序当中保持事务原子性的一个思路:
在开发中,遇到这种情况,多个线程同时工作,突然一个线程遇到了fetal的错误,需要立即终止程序,等人工排查解决了问题之后重新启动。但是这样会有一个问题,程序终止时,其他线程可能正在进行重要操作,比如发一个message到另一个模块,并更新数据库状态。突然终止,可能会让这个操作只完成一半,从而导致数据不一致。
解决方案是:参考数据库Transaction原子性的概念,将这一系列重要操作看作一个整体,要么全部完成,要么全部不完成。为方便表述,我们把这一系列重要操作记为操作X。
当程序即将退出时,查看当前是否有操作X在执行中,
- 如果有,等待其完成然后退出。且期间不再接受新的操作X。如果操作X执行之间过长,终止并回滚所有状态。
- 如果没有,则可以立即退出。
在程序退出的时候,做一些Check,保证已经开始的操作X的原子性,这里就用到了Runtime.ShutdownHook。
假如在系统中通过 Runtime.getRuntime().exec(String command) 或 new ProcessBuilder(List<String> command) 启动了子进程(Process),这个子进程一直在运行中,在当前进程退出时,子进程未必会退出,但此时业务上希望将它退出,就可以利用 ShutdownHook 。
Java的Signal在
Linux下支持的信号:
SEGV, ILL, FPE, BUS, SYS, CPU, FSZ, ABRT, INT, TERM, HUP, USR1, USR2, QUIT, BREAK, TRAP, PIPE
Windows下支持的信号:
SEGV, ILL, FPE, ABRT, INT, TERM, BREAK


浙公网安备 33010602011771号