<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          別亂用了,這才是 SpringBoot 停機(jī)的正確方式!?。?/h1>

          共 31225字,需瀏覽 63分鐘

           ·

          2022-07-27 20:29

          往期熱門(mén)文章:
          1、不好意思, Maven 該換了!
          2、面試官 | Spring Boot 項(xiàng)目如何統(tǒng)一結(jié)果,統(tǒng)一異常,統(tǒng)一日志?
          3、基于SpringBoot+MyBatis+Vue的音樂(lè)網(wǎng)站
          4、聊聊接口優(yōu)化的幾種方法
          5、多線(xiàn)程使用不當(dāng)導(dǎo)致的 OOM

          來(lái)源:blog.csdn.net/alex_xfboy/article/details/90404691/


          再談為了提醒明知故犯(在一坑里迭倒兩次不是不多見(jiàn)),由于業(yè)務(wù)系統(tǒng)中大量使用了spring Boot embedded tomcat的模式運(yùn)行,在一些運(yùn)維腳本中經(jīng)??吹絃inux 中 kill 指令,然而它的使用也有些講究,要思考如何能做到優(yōu)雅停機(jī)。

          何為優(yōu)雅關(guān)機(jī)

          就是為確保應(yīng)用關(guān)閉時(shí),通知應(yīng)用進(jìn)程釋放所占用的資源

          • 線(xiàn)程池,shutdown(不接受新任務(wù)等待處理完)還是shutdownNow(調(diào)用 Thread.interrupt進(jìn)行中斷)
          • socket 鏈接,比如:netty、mq
          • 告知注冊(cè)中心快速下線(xiàn)(靠心跳機(jī)制客服早都跳起來(lái)了),比如:eureka
          • 清理臨時(shí)文件,比如:poi
          • 各種堆內(nèi)堆外內(nèi)存釋放

          總之,進(jìn)程強(qiáng)行終止會(huì)帶來(lái)數(shù)據(jù)丟失或者終端無(wú)法恢復(fù)到正常狀態(tài),在分布式環(huán)境下還可能導(dǎo)致數(shù)據(jù)不一致的情況。

          基于 Spring Boot + MyBatis Plus + Vue & Element 實(shí)現(xiàn)的后臺(tái)管理系統(tǒng) + 用戶(hù)小程序,支持 RBAC 動(dòng)態(tài)權(quán)限、多租戶(hù)、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能。

          項(xiàng)目地址:https://github.com/YunaiV/ruoyi-vue-pro

          kill指令

          kill -9 pid 可以模擬了一次系統(tǒng)宕機(jī),系統(tǒng)斷電等極端情況,而kill -15 pid 則是等待應(yīng)用關(guān)閉,執(zhí)行阻塞操作,有時(shí)候也會(huì)出現(xiàn)無(wú)法關(guān)閉應(yīng)用的情況(線(xiàn)上理想情況下,是bug就該尋根溯源)

          #查看jvm進(jìn)程pid
          jps
          #列出所有信號(hào)名稱(chēng)
          kill -l
           

          > 基于微服務(wù)的思想,構(gòu)建在 B2C 電商場(chǎng)景下的項(xiàng)目實(shí)戰(zhàn)。核心技術(shù)棧,是 Spring Boot + Dubbo 。未來(lái),會(huì)重構(gòu)成 Spring Cloud Alibaba 。
          >
          > 項(xiàng)目地址:<https://github.com/YunaiV/onemall>

          # Windows下信號(hào)常量值
          # 簡(jiǎn)稱(chēng)  全稱(chēng)    數(shù)值 
          # INT   SIGINT     2       Ctrl+C中斷
          # ILL   SIGILL     4       非法指令
          # FPE   SIGFPE     8       floating point exception(浮點(diǎn)異常)
          # SEGV  SIGSEGV    11      segment violation(段錯(cuò)誤)
          # TERM  SIGTERM    5       Software termination signal from kill(Kill發(fā)出的軟件終止)
          # BREAK SIGBREAK   21      Ctrl-Break sequence(Ctrl+Break中斷)
          # ABRT  SIGABRT    22      abnormal termination triggered by abort call(Abort)
           
          #linux信號(hào)常量值
          # 簡(jiǎn)稱(chēng)  全稱(chēng)  數(shù)值  
          # HUP   SIGHUP      1    終端斷線(xiàn)  
          # INT   SIGINT      2    中斷(同 Ctrl + C)        
          # QUIT  SIGQUIT     3    退出(同 Ctrl + \)         
          # KILL  SIGKILL     9    強(qiáng)制終止         
          # TERM  SIGTERM     15    終止         
          # CONT  SIGCONT     18    繼續(xù)(與STOP相反, fg/bg命令)         
          # STOP  SIGSTOP     19    暫停(同 Ctrl + Z)        
          #....
           
          #可以理解為操作系統(tǒng)從內(nèi)核級(jí)別強(qiáng)行殺死某個(gè)進(jìn)程
          kill -9 pid 
          #理解為發(fā)送一個(gè)通知,等待應(yīng)用主動(dòng)關(guān)閉
          kill -15 pid
          #也支持信號(hào)常量值全稱(chēng)或簡(jiǎn)寫(xiě)(就是去掉SIG后)
          kill -l KILL

          思考:jvm是如何接受處理linux信號(hào)量的?

          當(dāng)然是在jvm啟動(dòng)時(shí)就加載了自定義SignalHandler,關(guān)閉jvm時(shí)觸發(fā)對(duì)應(yīng)的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設(shè)置SignalHandler,在System.initializeSystemClass中觸發(fā)
              static void setup() {
                  if (handler == null) {
                      SignalHandler var0 = new SignalHandler() {
                          public void handle(Signal var1) {
                              Shutdown.exit(var1.getNumber() + 128);//調(diào)用Shutdown.exit
                          }
                      };
                      handler = var0;
           
                      try {
                          Signal.handle(new Signal("INT"), var0);//中斷時(shí)
                      } catch (IllegalArgumentException var3) {
                          ;
                      }
           
                      try {
                          Signal.handle(new Signal("TERM"), var0);//終止時(shí)
                      } catch (IllegalArgumentException var2) {
                          ;
                      }
           
                  }
              }
          }

          Runtime.addShutdownHook

          在了解Shutdown.exit之前,先看Runtime.getRuntime().addShutdownHook(shutdownHook);則是為jvm中增加一個(gè)關(guān)閉的鉤子,當(dāng)jvm關(guān)閉的時(shí)候調(diào)用。

          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);
              }
          }
          //它含數(shù)據(jù)結(jié)構(gòu)和邏輯管理虛擬機(jī)關(guān)閉序列
          class Shutdown {
              /* Shutdown 系列狀態(tài)*/
              private static final int RUNNING = 0;
              private static final int HOOKS = 1;
              private static final int FINALIZERS = 2;
              private static int state = RUNNING;
              /* 是否應(yīng)該運(yùn)行所以finalizers來(lái)exit? */
              private static boolean runFinalizersOnExit = false;
              // 系統(tǒng)關(guān)閉鉤子注冊(cè)一個(gè)預(yù)定義的插槽.
              // 關(guān)閉鉤子的列表如下:
              // (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];
              // 當(dāng)前運(yùn)行關(guān)閉鉤子的鉤子的索引
              private static int currentRunningHook = 0;
              /* 前面的靜態(tài)字段由這個(gè)鎖保護(hù) */
              private static class Lock { };
              private static Object lock = new Lock();
           
              /* 為native halt方法提供鎖對(duì)象 */
              private static Object haltLock = new Lock();
           
              static void add(int slot, boolean registerShutdownInProgress, Runnable hook) {
                  synchronized (lock) {
                      if (hooks[slot] != null)
                          throw new InternalError("Shutdown hook at slot " + slot + " already registered");
           
                      if (!registerShutdownInProgress) {//執(zhí)行shutdown過(guò)程中不添加hook
                          if (state > RUNNING)//如果已經(jīng)在執(zhí)行shutdown操作不能添加hook
                              throw new IllegalStateException("Shutdown in progress");
                      } else {//如果hooks已經(jīng)執(zhí)行完畢不能再添加hook。如果正在執(zhí)行hooks時(shí),添加的槽點(diǎn)小于當(dāng)前執(zhí)行的槽點(diǎn)位置也不能添加
                          if (state > HOOKS || (state == HOOKS && slot <= currentRunningHook))
                              throw new IllegalStateException("Shutdown in progress");
                      }
           
                      hooks[slot] = hook;
                  }
              }
              /* 執(zhí)行所有注冊(cè)的hooks
               */

              private static void runHooks() {
                  for (int i=0; i < MAX_SYSTEM_HOOKS; i++) {
                      try {
                          Runnable hook;
                          synchronized (lock) {
                              // acquire the lock to make sure the hook registered during
                              // shutdown is visible here.
                              currentRunningHook = i;
                              hook = hooks[i];
                          }
                          if (hook != null) hook.run();
                      } catch(Throwable t) {
                          if (t instanceof ThreadDeath) {
                              ThreadDeath td = (ThreadDeath)t;
                              throw td;
                          }
                      }
                  }
              }
              /* 關(guān)閉JVM的操作
               */

              static void halt(int status) {
                  synchronized (haltLock) {
                      halt0(status);
                  }
              }
              //JNI方法
              static native void halt0(int status);
              // shutdown的執(zhí)行順序:runHooks > runFinalizersOnExit
              private static void sequence() {
                  synchronized (lock) {
                      /* Guard against the possibility of a daemon thread invoking exit
                       * after DestroyJavaVM initiates the shutdown sequence
                       */

                      if (state != HOOKS) return;
                  }
                  runHooks();
                  boolean rfoe;
                  synchronized (lock) {
                      state = FINALIZERS;
                      rfoe = runFinalizersOnExit;
                  }
                  if (rfoe) runAllFinalizers();
              }
              //Runtime.exit時(shí)執(zhí)行,runHooks > runFinalizersOnExit > halt
              static void exit(int status) {
                  boolean runMoreFinalizers = false;
                  synchronized (lock) {
                      if (status != 0) runFinalizersOnExit = false;
                      switch (state) {
                      case RUNNING:       /* Initiate shutdown */
                          state = HOOKS;
                          break;
                      case HOOKS:         /* Stall and halt */
                          break;
                      case FINALIZERS:
                          if (status != 0) {
                              /* Halt immediately on nonzero status */
                              halt(status);
                          } else {
                              /* Compatibility with old behavior:
                               * Run more finalizers and then halt
                               */

                              runMoreFinalizers = runFinalizersOnExit;
                          }
                          break;
                      }
                  }
                  if (runMoreFinalizers) {
                      runAllFinalizers();
                      halt(status);
                  }
                  synchronized (Shutdown.class{
                      /* Synchronize on the class object, causing any other thread
                       * that attempts to initiate shutdown to stall indefinitely
                       */

                      sequence();
                      halt(status);
                  }
              }
              //shutdown操作,與exit不同的是不做halt操作(關(guān)閉JVM)
              static void shutdown() {
                  synchronized (lock) {
                      switch (state) {
                      case RUNNING:       /* Initiate shutdown */
                          state = HOOKS;
                          break;
                      case HOOKS:         /* Stall and then return */
                      case FINALIZERS:
                          break;
                      }
                  }
                  synchronized (Shutdown.class{
                      sequence();
                  }
              }
          }

          spring 3.2.12

          在spring中通過(guò)ContextClosedEvent事件來(lái)觸發(fā)一些動(dòng)作(可以拓展),主要通過(guò)LifecycleProcessor.onClose來(lái)做stopBeans。由此可見(jiàn)spring也基于jvm做了拓展。

          public abstract class AbstractApplicationContext extends DefaultResourceLoader {
           public void registerShutdownHook() {
            if (this.shutdownHook == null) {
             // No shutdown hook registered yet.
             this.shutdownHook = new Thread() {
              @Override
              public void run() {
               doClose();
              }
             };
             Runtime.getRuntime().addShutdownHook(this.shutdownHook);
            }
           }
           protected void doClose() {
            boolean actuallyClose;
            synchronized (this.activeMonitor) {
             actuallyClose = this.active && !this.closed;
             this.closed = true;
            }
           
            if (actuallyClose) {
             if (logger.isInfoEnabled()) {
              logger.info("Closing " + this);
             }
           
             LiveBeansView.unregisterApplicationContext(this);
           
             try {
              //發(fā)布應(yīng)用內(nèi)的關(guān)閉事件
              publishEvent(new ContextClosedEvent(this));
             }
             catch (Throwable ex) {
              logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
             }
           
             // 停止所有的Lifecycle beans.
             try {
              getLifecycleProcessor().onClose();
             }
             catch (Throwable ex) {
              logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
             }
           
             // 銷(xiāo)毀spring 的 BeanFactory可能會(huì)緩存單例的 Bean.
             destroyBeans();
           
             // 關(guān)閉當(dāng)前應(yīng)用上下文(BeanFactory)
             closeBeanFactory();
           
             // 執(zhí)行子類(lèi)的關(guān)閉邏輯
             onClose();
           
             synchronized (this.activeMonitor) {
              this.active = false;
             }
            }
           } 
          }
          public interface LifecycleProcessor extends Lifecycle {
           /**
            * Notification of context refresh, e.g. for auto-starting components.
            */

           void onRefresh();
           
           /**
            * Notification of context close phase, e.g. for auto-stopping components.
            */

           void onClose();
          }

          spring boot

          到這里就進(jìn)入重點(diǎn)了,spring boot中有spring-boot-starter-actuator 模塊提供了一個(gè) restful 接口,用于優(yōu)雅停機(jī)。執(zhí)行請(qǐng)求 curl -X POST http://127.0.0.1:8088/shutdown ,待關(guān)閉成功則返回提示。

          注:線(xiàn)上環(huán)境該url需要設(shè)置權(quán)限,可配合 spring-security使用或在nginx中限制內(nèi)網(wǎng)訪(fǎng)問(wèn)

          #啟用shutdown
          endpoints.shutdown.enabled=true
          #禁用密碼驗(yàn)證
          endpoints.shutdown.sensitive=false
          #可統(tǒng)一指定所有endpoints的路徑
          management.context-path=/manage
          #指定管理端口和IP
          management.port=8088
          management.address=127.0.0.1

          #開(kāi)啟shutdown的安全驗(yàn)證(spring-security)
          endpoints.shutdown.sensitive=true
          #驗(yàn)證用戶(hù)名
          security.user.name=admin
          #驗(yàn)證密碼
          security.user.password=secret
          #角色
          management.security.role=SUPERUSER

          spring boot的shutdown原理也不復(fù)雜,其實(shí)還是通過(guò)調(diào)用AbstractApplicationContext.close實(shí)現(xiàn)的。

          @ConfigurationProperties(
              prefix = "endpoints.shutdown"
          )
          public class ShutdownMvcEndpoint extends EndpointMvcAdapter {
              public ShutdownMvcEndpoint(ShutdownEndpoint delegate) {
                  super(delegate);
              }
              //post請(qǐng)求
              @PostMapping(
                  produces = {"application/vnd.spring-boot.actuator.v1+json""application/json"}
              )
              @ResponseBody
              public Object invoke() {
                  return !this.getDelegate().isEnabled() ? new ResponseEntity(Collections.singletonMap("message""This endpoint is disabled"), HttpStatus.NOT_FOUND) : super.invoke();
              }
          }
          @ConfigurationProperties(
              prefix = "endpoints.shutdown"
          )
          public class ShutdownEndpoint extends AbstractEndpoint<Map<StringObject>> implements ApplicationContextAware {
              private static final Map<String, Object> NO_CONTEXT_MESSAGE = Collections.unmodifiableMap(Collections.singletonMap("message""No context to shutdown."));
              private static final Map<String, Object> SHUTDOWN_MESSAGE = Collections.unmodifiableMap(Collections.singletonMap("message""Shutting down, bye..."));
              private ConfigurableApplicationContext context;
           
              public ShutdownEndpoint() {
                  super("shutdown"truefalse);
              }
              //執(zhí)行關(guān)閉
              public Map<String, Object> invoke() {
                  if (this.context == null) {
                      return NO_CONTEXT_MESSAGE;
                  } else {
                      boolean var6 = false;
           
                      Map var1;
           
                      class NamelessClass_1 implements Runnable {
                          NamelessClass_1() {
                          }
           
                          public void run() {
                              try {
                                  Thread.sleep(500L);
                              } catch (InterruptedException var2) {
                                  Thread.currentThread().interrupt();
                              }
                              //這個(gè)調(diào)用的就是AbstractApplicationContext.close
                              ShutdownEndpoint.this.context.close();
                          }
                      }
           
                      try {
                          var6 = true;
                          var1 = SHUTDOWN_MESSAGE;
                          var6 = false;
                      } finally {
                          if (var6) {
                              Thread thread = new Thread(new NamelessClass_1());
                              thread.setContextClassLoader(this.getClass().getClassLoader());
                              thread.start();
                          }
                      }
           
                      Thread thread = new Thread(new NamelessClass_1());
                      thread.setContextClassLoader(this.getClass().getClassLoader());
                      thread.start();
                      return var1;
                  }
              }
          }

          最近熱文閱讀:

          1、不好意思, Maven 該換了!
          2、面試官 | Spring Boot 項(xiàng)目如何統(tǒng)一結(jié)果,統(tǒng)一異常,統(tǒng)一日志?
          3、基于SpringBoot+MyBatis+Vue的音樂(lè)網(wǎng)站
          4、聊聊接口優(yōu)化的幾種方法
          5、面試官 | Spring Boot 項(xiàng)目如何統(tǒng)一結(jié)果,統(tǒng)一異常,統(tǒng)一日志?
          6、為什么不建議使用ON DUPLICATE KEY UPDATE?
          7、Java8 Stream,過(guò)分絲滑!
          8、8 種最坑SQL語(yǔ)法,工作中踩過(guò)嗎?
          9、Java 語(yǔ)言“坑爹” TOP 10
          10、你還不明白如何解決分布式Session?看這篇就夠了!
          關(guān)注公眾號(hào),你想要的Java都在這里

          瀏覽 37
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)

          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  人妻无码第23页 | 色秘 乱码一区二区三区男奴-百度 | 欧美中文字幕在线 | 日韩一区二区三区在线视频 | 久久成人免费 |