日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

JDK ShutdownHook - 优雅地停止服务

發(fā)布時間:2025/3/21 编程问答 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JDK ShutdownHook - 优雅地停止服务 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

一、什么是ShutdownHook?


在Java程序中可以通過添加關閉鉤子,實現(xiàn)在程序退出時關閉資源、平滑退出的功能。?
使用Runtime.addShutdownHook(Thread hook)方法,可以注冊一個JVM關閉的鉤子,這個鉤子可以在以下幾種場景被調用:?
1. 程序正常退出?
2. 使用System.exit()?
3. 終端使用Ctrl+C觸發(fā)的中斷?
4. 系統(tǒng)關閉?
5. 使用Kill pid命令干掉進程

?

Runtime.java中相關方法源碼

public void addShutdownHook(Thread hook) {SecurityManager sm = System.getSecurityManager();if (sm != null) {sm.checkPermission(new RuntimePermission("shutdownHooks"));}ApplicationShutdownHooks.add(hook); }public boolean removeShutdownHook(Thread hook) {SecurityManager sm = System.getSecurityManager();if (sm != null) {sm.checkPermission(new RuntimePermission("shutdownHooks"));}return ApplicationShutdownHooks.remove(hook); }


ApplicationShutdownHooks.java

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) {try {hook.join();} catch (InterruptedException x) { }}} }



二、java進程平滑退出的意義


很多時候,我們會有這樣的一些場景,比如說nginx反向代理若干個負載均衡的web容器,又或者微服務架構中存在的若干個服務節(jié)點,需要進行無間斷的升級發(fā)布。?
在重啟服務的時候,除非我們去變更nginx的配置,否則重啟很可能會導致正在執(zhí)行的線程突然中斷,本來應該要完成的事情只完成了一半,并且調用方出現(xiàn)錯誤警告。?
如果能有一種簡單的方式,能夠讓進程在退出時能執(zhí)行完當前正在執(zhí)行的任務,并且讓服務的調用方將新的請求定向到其他負載節(jié)點,這將會很有意義。?
自己注冊ShutdownHook可以幫助我們實現(xiàn)java進程的平滑退出。

?

三、java進程平滑退出的思路


  • 在服務啟動時注冊自己的ShutdownHook
  • ShutdownHook在被運行時,首先不接收新的請求,或者告訴調用方重定向到其他節(jié)點
  • 等待當前的執(zhí)行線程運行完畢,如果五秒后仍在運行,則強制退出
  • ?

    四、如何屏敝第三方組件的ShutdownHook


    我們會發(fā)現(xiàn),有一些第三方組件在代碼中注冊了關閉自身資源的ShutdownHook,這些ShutdownHook對于我們的平滑退出有時候起了反作用。?
    比如dubbo,在static方法塊里面注冊了自己的關閉鉤子,完全不可控。在進程退出時直接就把長連接給斷開了,導致當前的執(zhí)行線程無法正常完成,源碼如下:

    static {Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {public void run() {if (logger.isInfoEnabled()) {logger.info("Run shutdown hook now.");}ProtocolConfig.destroyAll();}}, "DubboShutdownHook")); }


    從Runtime.java和ApplicationShutdownHooks.java的源碼中,我們看到并沒有一個可以遍歷操作shutdownHook的方法。?
    Runtime.java僅有的一個removeShutdownHook的方法,對于未寫線程名的匿名類來說,無法獲取對象的引用,也無法分辨出彼此。?
    ApplicationShutdownHooks.java不是public的,類中的hooks也是private的。?
    只有通過反射的方式才能獲取并控制它們。定義ExcludeIdentityHashMap類來幫助我們阻止非自己的ShutdownHook注入

    class ExcludeIdentityHashMap<K,V> extends IdentityHashMap<K,V> {public V put(K key, V value) {if (key instanceof Thread) {Thread thread = (Thread) key;if (!thread.getName().startsWith("My-")) {return value;}}return super.put(key, value);} }


    通過反射的方式注入自己的ShutdownHook并清除其他Thread

    String className = "java.lang.ApplicationShutdownHooks"; Class<?> clazz = Class.forName(className); Field field = clazz.getDeclaredField("hooks"); field.setAccessible(true);Thread shutdownThread = new Thread(new Runnable() {@Overridepublic void run() {// TODO} }); shutdownThread.setName("My-WebShutdownThread"); IdentityHashMap<Thread, Thread> excludeIdentityHashMap = new ExcludeIdentityHashMap<>(); excludeIdentityHashMap.put(shutdownThread, shutdownThread);synchronized (clazz) {IdentityHashMap<Thread, Thread> map = (IdentityHashMap<Thread, Thread>) field.get(clazz);for (Thread thread : map.keySet()) {Log.info("found shutdownHook: " + thread.getName());excludeIdentityHashMap.put(thread, thread);}field.set(clazz, excludeIdentityHashMap); }


    五、實現(xiàn)服務的平滑退出


    對于一般的微服務來說,有這幾種任務的入口:Http請求、dubbo請求、RabbitMQ消費、Quartz任務

    5.1 Http請求

    測試發(fā)現(xiàn)Jetty容器在stop的時候不能實現(xiàn)平滑退出,springboot默認使用的tomcat容器可以,以下是部分代碼示例:

    EmbeddedWebApplicationContext embeddedWebApplicationContext = (EmbeddedWebApplicationContext) applicationContext; EmbeddedServletContainer embeddedServletContainer = embeddedWebApplicationContext.getEmbeddedServletContainer(); if (embeddedServletContainer instanceof TomcatEmbeddedServletContainer) {Connector[] connectors = tomcatEmbeddedServletContainer.getTomcat().getService().findConnectors();for (Connector connector : connectors) {connector.pause();}for (Connector connector : connectors) {Executor executor = connector.getProtocolHandler().getExecutor();if (executor instanceof ThreadPoolExecutor) {try {ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;threadPoolExecutor.shutdown();if (!threadPoolExecutor.awaitTermination(5, TimeUnit.SECONDS)) {log.warn("Tomcat thread pool did not shutdown gracefully within 5 seconds. Proceeding with forceful shutdown");}} catch (InterruptedException e) {log.warn("TomcatShutdownHook interrupted", e);}}} }


    5.2 dubbo請求

    嘗試了許多次,看了相關的源碼,dubbo不支持平滑退出;解決方法只有一個,那就是修改dubbo的源碼,以下兩個地址有詳細介紹:?
    http://frankfan915.iteye.com/blog/2254097?
    https://my.oschina.net/u/1398931/blog/790709

    5.3 RabbitMQ消費

    以下是SpringBoot的示例,不使用Spring原理也是一樣的

    RabbitListenerEndpointRegistry rabbitListenerEndpointRegistry = applicationContext.getBean(RabbitListenerConfigUtils.RABBIT_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME,RabbitListenerEndpointRegistry.class); Collection<MessageListenerContainer> containers = rabbitListenerEndpointRegistry.getListenerContainers(); for (MessageListenerContainer messageListenerContainer : containers) {messageListenerContainer.stop(); }


    5.4 Quartz任務

    quartz也比較簡單

    Scheduler scheduler = applicationContext.getBean(Scheduler.class); scheduler.shutdown(true);


    六、為何重啟時有時會有ClassNotFoundException


    springboot通過java -jar example.jar的方式啟動項目,在使用腳本restart的時候,首先覆蓋舊的jar包,然后stop舊線程,啟動新線程,這樣就可能會出現(xiàn)此問題。因為在stop的時候,ShutdownHook線程被喚醒,在其執(zhí)行過程中,某些類(尤其是匿名類)還未加載,這時候就會通知ClassLoader去加載;ClassLoader持有的是舊jar包的文件句柄,雖然新舊jar包的名字路徑完全一樣,但是ClassLoader仍然是使用open著的舊jar包文件,文件已經(jīng)找不到了,所以類加載不了就ClassNotFound了。

    如何解決呢?也許有更優(yōu)雅的方式,但是我沒有找到;但是我們可以簡單地把順序調整一下,先stop、再copy覆蓋、最后start,這樣就OK了。
    ?

    總結

    以上是生活随笔為你收集整理的JDK ShutdownHook - 优雅地停止服务的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內容還不錯,歡迎將生活随笔推薦給好友。