1.前言
emm,又又遇到问题啦,现有业务系统应用上线存在窗口期,不能满足正常任务迭代上线。在非窗口期上线容易导致数据库、mq、jsf等线程中断,进而导致需要手动修单问题。故而通过添加优雅停机功能进行优化,令其在上线前选择优雅停机后,会优先断掉新流量的涌入,并预留一定时间处理现存连接,最后完全下线,可有效扩大上线预留窗口时间并降低上线期间线程中断,进而降低手动修单。可是什么是优雅停机呢?为什么现有的系统技术没有原生的优雅停机机制呢?通过调研整理文章如下。
2.何为优雅停机?
? 优雅停机是指为确保应用关闭时,通知应用进程释放所占用的资源。
? 线程池
,shutdown(不接受新任务等待处理完)还是shutdownNow(调用Thread.interrupt进行中断)。
? socket链接,比如:netty、jmq、fmq
。(需要着重处理)
? 告知注册中心快速下线,比如jsf
。(需要着重处理)
? 清理临时文件。
? 各种堆内堆外内存释放。
总之,进程强行终止会带来数据丢失或者终端无法恢复到正常状态,在分布式环境下可能导致数据不一致的情况。
3.导致优雅停机不优雅的元凶之-kill命令
? kill指令
? kill -15 :kill指令默认就是-15,知识发送一个SIGTERM
信号通知进程终止,由进程自行决定
怎么做,即进程不一定终止。一般不直接使用kill -15,不一定能够终止进程。
? kill -9:强制终止进程,进程会被立刻终止。kill -9 过于暴力,往往会出现事务执行、业务处理中断的情况,导致数据库中存在脏数据
、系统中存在残留文件等情况。如果要使用kill -9,尽量先使用kill -15给进程一个处理善后的机会。该命令可以模拟一次系统宕机,系统断电等极端情况。
? kill -2:类似Ctrl + C退出,会先保存相关数据再终止进程。kill -2立刻终止正在执行的代码
->保存数据
->终止进程
,只是在进程终止之前会保存相关数据,依然会出现事务执行、业务处理中断的情况,做不到优雅停机。
4.引申问题:jvm如何接受处理linux信号量的?
? 在jvm启动时就加载了自定义SingalHandler
,关闭jvm时触发对应的handle。
public interface SignalHandler {
SignalHandler SIG_DFL = new NativeSignalHandler(0L);
SignalHandler SIG_IGN = new NativeSignalHandler(1L);
void handle(Signal var1);
}
class Terminator {
private static SignalHandler handler = null;
Terminator() {
}
//jvm设置SignalHandler,在System.initializeSystemClass中触发
static void setup() {
if (handler == null) {
SignalHandler var0 = new SignalHandler() {
public void handle(Signal var1) {
Shutdown.exit(var1.getNumber() + 128);//调用Shutdown.exit
}
};
handler = var0;
try {
Signal.handle(new Signal("INT"), var0);//中断时
} catch (IllegalArgumentException var3) {
}
try {
Signal.handle(new Signal("TERM"), var0);//终止时
} catch (IllegalArgumentException var2) {
}
}
}
}
? Runtime.addShutdownHook。在了解Shutdown.exit
之前,先看Runtime.getRuntime().addShutdownHook(shutdownHook)
;则是为jvm中增加一个关闭的钩子,当jvm关闭
的时候调用。
public class Runtime {
public void addShutdownHook(Thread hook) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("shutdownHooks"));
}
ApplicationShutdownHooks.add(hook);
}
}
class ApplicationShutdownHooks {
/* The set of registered hooks */
private static IdentityHashMap<Thread, Thread> hooks;
static synchronized void add(Thread hook) {
if(hooks == null)
throw new IllegalStateException("Shutdown in progress");
if (hook.isAlive())
throw new IllegalArgumentException("Hook already running");
if (hooks.containsKey(hook))
throw new IllegalArgumentException("Hook previously registered");
hooks.put(hook, hook);
}
}
//它含数据结构和逻辑管理虚拟机关闭序列
class Shutdown {
/* Shutdown 系列状态*/
private static final int RUNNING = 0;
private static final int HOOKS = 1;
private static final int FINALIZERS = 2;
private static int state = RUNNING;
/* 是否应该运行所以finalizers来exit? */
private static boolean runFinalizersOnExit = false;
// 系统关闭钩子注册一个预定义的插槽.
// 关闭钩子的列表如下:
// (0) Console restore hook
// (1) Application hooks
// (2) DeleteOnExit hook
private static final int MAX_SYSTEM_HOOKS = 10;
private static final Runnable[] hooks = new Runnable[MAX_SYSTEM_HOOKS];
// 当前运行关闭钩子的钩子的索引
private static int currentRunningHook = 0;
/* 前面的静态字段由这个锁保护 */
private static class Lock { };
private static Object lock = new Lo