<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>

          【小技巧】微服務項目優(yōu)雅上線、下線,你學廢了嗎?

          共 5554字,需瀏覽 12分鐘

           ·

          2021-01-25 01:08

          由于公眾號文章推送規(guī)則的改變,大家能準時收到我們的文章推送,請將公眾號:?JAVA?設為星標~這樣就不會錯過每一篇精彩的推送啦~

          來源:fredal.xin/graceful-soa-updown

          對于微服務來說,服務的優(yōu)雅上下線是必要的。就上線來說,如果組件或者容器沒有啟動成功,就不應該對外暴露服務,對于下線來說,如果機器已經停機了,就應該保證服務已下線,如此可避免上游流量進入不健康的機器。

          優(yōu)雅下線

          基礎下線(Spring/SpringBoot/內置容器)

          首先JVM本身是支持通過shutdownHook的方式優(yōu)雅停機的。

          Runtime.getRuntime().addShutdownHook(new?Thread()?{
          ????@Override
          ????public?void?run()?{
          ????????close();
          ????}
          });

          此方式支持在以下幾種場景優(yōu)雅停機:

          1.程序正常退出

          2.使用System.exit()

          3.終端使用Ctrl+C

          4.使用Kill pid干掉進程

          那么如果你偏偏要kill -9?程序肯定是不知所措的。

          而在Springboot中,其實已經幫你實現好了一個shutdownHook,支持響應Ctrl+c或者kill -15 TERM信號。

          隨便啟動一個應用,然后Ctrl+c一下,觀察日志就可知, 它在AnnotationConfigEmbeddedWebApplicationContext這個類中打印出了疑似Closing...的日志,真正的實現邏輯在其父類

          AbstractApplicationContext中(這個其實是spring中的類,意味著什么呢,在spring中就支持了對優(yōu)雅停機的擴展)。

          public?void?registerShutdownHook()?{????if?(this.shutdownHook?==?nulpublic?void?registerShutdownHook()?{
          ????if?(this.shutdownHook?==?null)?{
          ????????this.shutdownHook?=?new?Thread()?{
          ????????????public?void?run()?{
          ????????????????synchronized(AbstractApplicationContext.this.startupShutdownMonitor)?{
          ????????????????????AbstractApplicationContext.this.doClose();
          ????????????????}
          ????????????}
          ????????};
          ????????Runtime.getRuntime().addShutdownHook(this.shutdownHook);
          ????}
          ?
          }
          ?
          public?void?destroy()?{
          ????this.close();
          }
          ?
          public?void?close()?{
          ????Object?var1?=?this.startupShutdownMonitor;
          ????synchronized(this.startupShutdownMonitor)?{
          ????????this.doClose();
          ????????if?(this.shutdownHook?!=?null)?{
          ????????????try?{
          ????????????????Runtime.getRuntime().removeShutdownHook(this.shutdownHook);
          ????????????}?catch?(IllegalStateException?var4)?{
          ????????????????;
          ????????????}
          ????????}
          ?
          ????}
          }
          ?
          protected?void?doClose()?{
          ????if?(this.active.get()?&&?this.closed.compareAndSet(false,?true))?{
          ????????if?(this.logger.isInfoEnabled())?{
          ????????????this.logger.info("Closing?"?+?this);
          ????????}
          ?
          ????????LiveBeansView.unregisterApplicationContext(this);
          ?
          ????????try?{
          ????????????this.publishEvent((ApplicationEvent)(new?ContextClosedEvent(this)));
          ????????}?catch?(Throwable?var3)?{
          ????????????this.logger.warn("Exception?thrown?from?ApplicationListener?handling?ContextClosedEvent",?var3);
          ????????}
          ?
          ????????if?(this.lifecycleProcessor?!=?null)?{
          ????????????try?{
          ????????????????this.lifecycleProcessor.onClose();
          ????????????}?catch?(Throwable?var2)?{
          ????????????????this.logger.warn("Exception?thrown?from?LifecycleProcessor?on?context?close",?var2);
          ????????????}
          ????????}
          ?
          ????????this.destroyBeans();
          ????????this.closeBeanFactory();
          ????????this.onClose();
          ????????this.active.set(false);
          ????}
          ?
          }

          我們能對它做些什么呢,其實很明顯,在doClose方法中它發(fā)布了一個ContextClosedEvent的方法,不就是給我們擴展用的么。于是我們可以寫個監(jiān)聽器監(jiān)聽ContextClosedEvent,在發(fā)生事件的時候做下線邏輯,對微服務來說即是從注冊中心中注銷掉服務。

          @Component
          public?class?GracefulShutdownListener?implements?ApplicationListener<ContextClosedEvent>?{
          ????
          ????@Override
          ????public?void?onApplicationEvent(ContextClosedEvent?contextClosedEvent){
          ???????//注銷邏輯
          ???????zookeeperRegistry.unregister(mCurrentServiceURL);
          ???????...
          ????}
          }

          可能會有疑問的是,微服務中一般來說,注銷服務往往是優(yōu)雅下線的第一步,接著才會執(zhí)行停機操作,那么這個時候流量進來怎么辦呢?

          個人會建議是,在注銷服務之后就可開啟請求擋板拒絕流量了,通過微服務框架本身的故障轉移功能去處理被拒絕的流量即可。

          Docker中的下線

          好有人說了,我用docker部署服務,支不支持優(yōu)雅下線。

          那來看看docker的一些停止命令都會干些啥:

          一般來說,正常人可能會用docker stop或者docker kill 命令去關閉容器(當然如果上一步注冊了USR2自定義信息,可能會通過docker exec kill -12去關閉)。

          對于docker stop來說,它會發(fā)一個SIGTERM(kill -15 term信息)給容器的PID1進程,并且默認會等待10s,再發(fā)送一個SIGKILL(kill -9 信息)給PID1。

          那么很明顯,docker stop允許程序有個默認10s的反應時間去做一下優(yōu)雅停機的操作,程序只要能對kill -15 信號做些反應就好了,如上一步描述。那么這是比較良好的方式。

          當然如果shutdownHook方法執(zhí)行了個50s,那肯定不優(yōu)雅了。可以通過docker stop -t 加上等待時間。

          外置容器的shutdown腳本(Jetty)

          如果非要用外置容器方式部署(個人認為浪費資源并提升復雜度)。那么能不能優(yōu)雅停機呢。

          可以當然也是可以的,這里有兩種方式:

          首先RPC框架本身提供優(yōu)雅上下線接口,以供調用來結束整個應用的生命周期,并且提供擴展點供開發(fā)者自定義服務下線自身的停機邏輯。

          同時調用該接口的操作會封裝成一個preStop操作固化在jetty或者其他容器的shutdown腳本中,保證在容器停止之前先調用下線接口結束掉整個應用的生命周期。shutdown腳本中執(zhí)行類發(fā)起下線服務 -> 關閉端口 -> 檢查下線服務直至完成 -> 關閉容器的流程。

          而更簡單的另一種方法是直接在腳本中加入kill -15命令。

          優(yōu)雅上線

          優(yōu)雅上線相對來說可能會更加困難一些,因為沒有什么默認的實現方式,但是總之呢,一個原則就是確保端口存在之后才上線服務。

          springboot內置容器優(yōu)雅上線

          這個就很簡單了,并且業(yè)界在應用層面的優(yōu)雅上線均是在內置容器的前提下實現的,并且還可以配合一些列健康檢查做文章。

          參看sofa-boot的健康檢查的源碼,它會在程序啟動的時候先對springboot的組件做一些健康檢查,然后再對它自己搞得sofa的一些中間件做健康檢查,整個健康檢查的流程完畢之后(sofaboot 目前是沒法對自身應用層面做健康檢查的,它有寫相關接口,但是寫死了port is ready...)才會暴露服務或者說優(yōu)雅上線,那么它健康檢查的時機是什么時候呢:

          @Override
          public?void?onApplicationEvent(ContextRefreshedEvent?event)?{
          ????healthCheckerProcessor.init();
          ????healthIndicatorProcessor.init();
          ????afterHealthCheckCallbackProcessor.init();
          ????publishBeforeHealthCheckEvent();
          ????readinessHealthCheck();
          }

          可以看到它是監(jiān)聽了ContextRefreshedEvent這個事件。在內置容器模式中,內置容器模式的start方法是在refreshContext方法中,方法執(zhí)行完成之后發(fā)布一個ContextRefreshedEvent事件,也就是說在監(jiān)聽到這個事件的時候,內置容器必然是啟動成功了的。

          但ContextRefreshedEvent這個事件,在一些特定場景中由于種種原因,ContextRefreshedEvent會被監(jiān)聽到多次,沒有辦法保證當前是最后一次event,從而正確執(zhí)行優(yōu)雅上線邏輯。

          在springboot中還有一個更加靠后的事件,叫做ApplicationReadyEvent,它的發(fā)布藏在了afterRefresh還要后面的那一句listeners.finished(context, null)中,完完全全可以保證內置容器 端口已經存在了,所以我們可以監(jiān)聽這個事件去做優(yōu)雅上線的邏輯,甚至可以把中間件相關的健康檢查集成在這里。

          @Component
          public?class?GracefulStartupListener?implements?ApplicationListener<ApplicationReadyEvent>?{????
          ????@Override
          ????public?void?onApplicationEvent(ApplicationReadyEvent?applicationReadyEvent){
          ???????//注冊邏輯?優(yōu)雅上線
          ???????apiRegister.register(urls);
          ???????...
          ????}
          }

          外置容器(Jetty)優(yōu)雅上線

          目前大多數應用的部署模式不管是jetty部署模式還是docker部署模式(同樣使用jetty鏡像),本質上用的都是外置容器。那么這個情況就比較困難了,至少在應用層面無法觀察到外部容器的運行狀態(tài),并且容器本身沒有提供什么hook給你實現。

          那么和優(yōu)雅上線一樣,需要RPC框架提供優(yōu)雅上線接口來初始化整個應用的生命周期,并且提供擴展點給開發(fā)者供執(zhí)行自定義的上線邏輯(上報版本探測信息等)。

          同樣將調用這個接口封裝成一個postStart操作,固化在jetty等外置容器的startup腳本中,保證應用在容器啟動之后在上線。容器執(zhí)行類似啟動容器 -> 健康檢查 -> 上線服務邏輯 -> 健康上線服務直至完成?的流程。




          最近有讀者想要分布式的項目,還有想要商城的,還有想要springboot,springcloud,k8s等等,這次直接分享幾乎涵蓋了我們java程序員的大部分技術桟,可以說真的非常全面了。強烈建議大家都上手做一做,而且以后肯定用的上。資料包含高清視頻+課件+源碼……

          掃以下二維碼并回復“99”即可獲取


          掃描上方二維碼,關注并回復【99】馬上獲取

          瀏覽 56
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  精品久久成人无码片 | 日韩视频中文字幕 | 欧美精品黄片 | 日韩无码观看 | 大鸡吧久久久 |