返回

优雅停机

优雅停机ShutdownHook

1, ShutdownHook初识

在Java程序中可以通过添加关闭钩子,实现在程序退出时关闭资源、平滑退出的功能。 并且在以下几种场景将调用该钩子

  1. 程序正常退出
  2. 使用System.exit()
  3. 终端使用Ctrl+C触发的中断
  4. 系统关闭
  5. 使用Kill pid命令干掉进程

具体来讲Runtime.addShutdownHook 添加钩子到 ApplicationShutdownHooks中。

// Runtime添加钩子(钩子具体来讲就是一个要执行的线程任务)
public void addShutdownHook(Thread hook) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(new RuntimePermission("shutdownHooks"));
        }
        ApplicationShutdownHooks.add(hook);
    }

// Runtime去除钩子
 public boolean removeShutdownHook(Thread hook) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(new RuntimePermission("shutdownHooks"));
        }
        return ApplicationShutdownHooks.remove(hook);
    }

再看ApplicationShutdownHooks

class ApplicationShutdownHooks {
    /* The set of registered hooks */
    private static IdentityHashMap<Thread, Thread> hooks;
    static {
        try {
            Shutdown.add(1 /* shutdown hook invocation order */,
                false /* not registered if shutdown in progress */,
                new Runnable() {
                    public void run() {
                        runHooks();
                    }
                }
            );
            hooks = new IdentityHashMap<>();
        } catch (IllegalStateException e) {
            // application shutdown hooks cannot be added if
            // shutdown is in progress.
            hooks = null;
        }
    }


    private ApplicationShutdownHooks() {}

    /* Add a new shutdown hook.  Checks the shutdown state and the hook itself,
     * but does not do any security checks.
     */
    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);
    }

    /* Remove a previously-registered hook.  Like the add method, this method
     * does not do any security checks.
     */
    static synchronized boolean remove(Thread hook) {
        if(hooks == null)
            throw new IllegalStateException("Shutdown in progress");

        if (hook == null)
            throw new NullPointerException();

        return hooks.remove(hook) != null;
    }

    /* Iterates over all application hooks creating a new thread for each
     * to run in. Hooks are run concurrently and this method waits for
     * them to finish.
     */
    static void runHooks() {
        Collection<Thread> threads;
        synchronized(ApplicationShutdownHooks.class) {
            threads = hooks.keySet();
            hooks = null;
        }

        for (Thread hook : threads) {
            hook.start();
        }
        for (Thread hook : threads) {
            while (true) {
                try {
                    hook.join();
                    break;
                } catch (InterruptedException ignored) {
                }
            }
        }
    }
}

2, 自定义hooks

往往在应用程序里已经有了一些第三方注册好的hook, 当我们要对将自己自定义的hook放到其他hook之前执行,需要强制干预ApplicationShutdownHooks的hooks,可以通过反射来做

String className = "java.lang.ApplicationShutdownHooks";
Class<?> clazz = Class.forName(className);
Field field = clazz.getDeclaredField("hooks");
field.setAccessible(true);

// 先反射拿到其他的hook,
IdentityHashMap<Thread, Thread> otherHookMap = (IdentityHashMap<Thread, Thread>) field.get(clazz);

// 将otherHookMap 中 各个hook封装一个延时(比如3s)

// 再将自定义hook 加入
Runtime.getRuntime().addShutdownHook(new Thread(()->{ System.out.println("exec my hook"); }));

3, ShenYu中应用

ShenYu项目下线时,会将一些client服务,比如注册中心客户端,优雅停掉。

public class ShenyuClientShutdownHook {
		// 钩子名称
    private static String hookNamePrefix = "ShenyuClientShutdownHook";

    private static AtomicInteger hookId = new AtomicInteger(0);

    private static Properties props;

    private static AtomicBoolean delay = new AtomicBoolean(false);
		//等待加入延时,但是还没有加入的hooks
    private static IdentityHashMap<Thread, Thread> delayHooks = new IdentityHashMap<>();
		//已经做了延时的hook
    private static IdentityHashMap<Thread, Thread> delayedHooks = new IdentityHashMap<>();

    /**
     * Add shenyu client shutdown hook.
     *
     * @param result ShenyuClientRegisterRepository
     * @param props  Properties
     */
    public static void set(final ShenyuClientRegisterRepository result, final Properties props) {
        String name = hookNamePrefix + "-" + hookId.incrementAndGet();
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            result.close();
        }, name));
        log.info("Add hook {}", name);
        ShenyuClientShutdownHook.props = props;
    }

    /**
     * Delay other shutdown hooks.// 将其他的hooks做延时处理
     */
    public static void delayOtherHooks() {
        if (!delay.compareAndSet(false, true)) {
            return;
        }
        TakeoverOtherHooksThread thread = new TakeoverOtherHooksThread();
        thread.start();
    }

    /**
     * Delay other shutdown hooks thread. 
     * 延时处理的线程,  1,反射拿到其他hooks
    	*				 2,遍历hooks,通过线程包装原先的hook线程任务,并在其中加3s的延时 
     *				3,将原先的hook线程任务重新加入到ApplicationShutdownHooks中
     */
    private static class TakeoverOtherHooksThread extends Thread {
        @SneakyThrows
        @Override
        public void run() {
            int shutdownWaitTime = Integer.parseInt(props.getProperty("shutdownWaitTime", "3000"));
            int delayOtherHooksExecTime = Integer.parseInt(props.getProperty("delayOtherHooksExecTime", "2000"));
            Class<?> clazz = Class.forName(props.getProperty("applicationShutdownHooksClassName", "java.lang.ApplicationShutdownHooks"));
            Field field = clazz.getDeclaredField(props.getProperty("applicationShutdownHooksFieldName", "hooks"));
            field.setAccessible(true);
            IdentityHashMap<Thread, Thread> hooks = (IdentityHashMap<Thread, Thread>) field.get(clazz);
            long s = System.currentTimeMillis();
            while (System.currentTimeMillis() - s < delayOtherHooksExecTime) {
                for (Iterator<Thread> iterator = hooks.keySet().iterator(); iterator.hasNext();) {
                    Thread hook = iterator.next();
                    if (hook.getName().startsWith(hookNamePrefix)) {// 为当前自己自定义的hook 就不做延时
                        continue;
                    }
                    if (delayHooks.containsKey(hook) || delayedHooks.containsKey(hook)) {
                        continue;
                    }
                    Thread delayHook = new Thread(() -> {// 通过线程包装原先的hook线程任务,并在其中加3s的延时
                        log.info("sleep {}ms", shutdownWaitTime);
                        try {
                            TimeUnit.MILLISECONDS.sleep(shutdownWaitTime);
                        } catch (InterruptedException ex) {
                            ex.printStackTrace();
                        }
                        hook.run();
                    }, hook.getName());
                    delayHooks.put(delayHook, delayHook);// 加入的待加入延时集合
                    iterator.remove();// 从原来的ApplicationShutdownHooks的hooks集合中去掉
                }

                for (Iterator<Thread> iterator = delayHooks.keySet().iterator(); iterator.hasNext();) {
                    Thread delayHook = iterator.next();
                    Runtime.getRuntime().addShutdownHook(delayHook);// 加入到ApplicationShutdownHooks的hooks集合中去掉
                    delayedHooks.put(delayHook, delayHook);
                    iterator.remove();
                    log.info("hook {} will sleep {}ms when it start", delayHook.getName(), shutdownWaitTime);
                }
                TimeUnit.MILLISECONDS.sleep(100);
            }

            hookNamePrefix = null;
            hookId = null;
            props = null;
            delayHooks = null;
            delayedHooks = null;
        }
    }
}

Built with Hugo
Theme Stack designed by Jimmy
本站访问量:   您是本站第 位访问者