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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

Spring Boot Dubbo 应用启停源码分析

發布時間:2025/3/21 javascript 17 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Spring Boot Dubbo 应用启停源码分析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

作者:張乎興 來源:Dubbo官方博客

背景介紹

Dubbo Spring Boot 工程致力于簡化 Dubbo RPC 框架在Spring Boot應用場景的開發。同時也整合了 Spring Boot 特性:

  • 自動裝配?(比如: 注解驅動, 自動裝配等).

  • Production-Ready?(比如: 安全, 健康檢查, 外部化配置等).

DubboConsumer啟動分析

你有沒有想過一個問題? incubator-dubbo-spring-boot-project中的 DubboConsumerDemo應用就一行代碼, main方法執行完之后,為什么不會直接退出呢?

@SpringBootApplication(scanBasePackages = "com.alibaba.boot.dubbo.demo.consumer.controller") public class DubboConsumerDemo {public static void main(String[] args) {SpringApplication.run(DubboConsumerDemo.class,args);}}

其實要回答這樣一個問題,我們首先需要把這個問題進行一個抽象,即一個JVM進程,在什么情況下會退出?

以Java 8為例,通過查閱JVM語言規范[1],在12.8章節中有清晰的描述:

A program terminates all its activity and exits when one of two things happens:

  • All the threads that are not daemon threads terminate.

  • Some thread invokes the?exit?method of class?Runtime?or class?System, and the?exitoperation is not forbidden by the security manager.

也就是說,導致JVM的退出只有2種情況:

  • 所有的非daemon進程完全終止

  • 某個線程調用了?System.exit()或?Runtime.exit()

  • 因此針對上面的情況,我們判斷,一定是有某個非daemon線程沒有退出導致。我們知道,通過jstack可以看到所有的線程信息,包括他們是否是daemon線程,可以通過jstack找出那些是非deamon的線程。

    ? ?jstack 57785 | grep tid | grep -v "daemon" "container-0" #37 prio=5 os_prio=31 tid=0x00007fbe312f5800 nid=0x7103 waiting on condition ?[0x0000700010144000] "container-1" #49 prio=5 os_prio=31 tid=0x00007fbe3117f800 nid=0x7b03 waiting on condition ?[0x0000700010859000] "DestroyJavaVM" #83 prio=5 os_prio=31 tid=0x00007fbe30011000 nid=0x2703 waiting on condition ?[0x0000000000000000] "VM Thread" os_prio=31 tid=0x00007fbe3005e800 nid=0x3703 runnable "GC Thread#0" os_prio=31 tid=0x00007fbe30013800 nid=0x5403 runnable "GC Thread#1" os_prio=31 tid=0x00007fbe30021000 nid=0x5303 runnable "GC Thread#2" os_prio=31 tid=0x00007fbe30021800 nid=0x2d03 runnable "GC Thread#3" os_prio=31 tid=0x00007fbe30022000 nid=0x2f03 runnable "G1 Main Marker" os_prio=31 tid=0x00007fbe30040800 nid=0x5203 runnable "G1 Conc#0" os_prio=31 tid=0x00007fbe30041000 nid=0x4f03 runnable "G1 Refine#0" os_prio=31 tid=0x00007fbe31044800 nid=0x4e03 runnable "G1 Refine#1" os_prio=31 tid=0x00007fbe31045800 nid=0x4d03 runnable "G1 Refine#2" os_prio=31 tid=0x00007fbe31046000 nid=0x4c03 runnable "G1 Refine#3" os_prio=31 tid=0x00007fbe31047000 nid=0x4b03 runnable "G1 Young RemSet Sampling" os_prio=31 tid=0x00007fbe31047800 nid=0x3603 runnable "VM Periodic Task Thread" os_prio=31 tid=0x00007fbe31129000 nid=0x6703 waiting on condition

    此處通過grep tid 找出所有的線程摘要,通過grep -v找出不包含daemon關鍵字的行

    通過上面的結果,我們發現了一些信息:

    • 有兩個線程?container-0,?container-1非??梢?#xff0c;他們是非daemon線程,處于wait狀態

    • 有一些GC相關的線程,和VM打頭的線程,也是非daemon線程,但他們很有可能是JVM自己的線程,在此暫時忽略。

    綜上,我們可以推斷,很可能是因為 container-0和 container-1導致JVM沒有退出?,F在我們通過源碼,搜索一下到底是誰創建的這兩個線程。

    通過對spring-boot的源碼分析,我們在 org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer的 startDaemonAwaitThread找到了如下代碼

    ? ?private void startDaemonAwaitThread() {Thread awaitThread = new Thread("container-" + (containerCounter.get())) {@Overridepublic void run() {TomcatEmbeddedServletContainer.this.tomcat.getServer().await();}};awaitThread.setContextClassLoader(getClass().getClassLoader());awaitThread.setDaemon(false);awaitThread.start();}

    在這個方法加個斷點,看下調用堆棧:

    initialize:115, TomcatEmbeddedServletContainer (org.springframework.boot.context.embedded.tomcat) <init>:84, TomcatEmbeddedServletContainer (org.springframework.boot.context.embedded.tomcat) getTomcatEmbeddedServletContainer:554, TomcatEmbeddedServletContainerFactory (org.springframework.boot.context.embedded.tomcat) getEmbeddedServletContainer:179, TomcatEmbeddedServletContainerFactory (org.springframework.boot.context.embedded.tomcat) createEmbeddedServletContainer:164, EmbeddedWebApplicationContext (org.springframework.boot.context.embedded) onRefresh:134, EmbeddedWebApplicationContext (org.springframework.boot.context.embedded) refresh:537, AbstractApplicationContext (org.springframework.context.support) refresh:122, EmbeddedWebApplicationContext (org.springframework.boot.context.embedded) refresh:693, SpringApplication (org.springframework.boot) refreshContext:360, SpringApplication (org.springframework.boot) run:303, SpringApplication (org.springframework.boot) run:1118, SpringApplication (org.springframework.boot) run:1107, SpringApplication (org.springframework.boot) main:35, DubboConsumerDemo (com.alibaba.boot.dubbo.demo.consumer.bootstrap)

    可以看到,spring-boot應用在啟動的過程中,由于默認啟動了Tomcat暴露HTTP服務,所以執行到了上述方法,而Tomcat啟動的所有的線程,默認都是daemon線程,例如監聽請求的Acceptor,工作線程池等等,如果這里不加控制的話,啟動完成之后JVM也會退出。因此需要顯式地啟動一個線程,在某個條件下進行持續等待,從而避免線程退出。Spring Boot 2.x 啟動全過程源碼分析(全),這篇文章推薦大家看下。

    下面我們在深挖一下,在Tomcat的 this.tomcat.getServer().await()這個方法中,線程是如何實現不退出的。這里為了閱讀方便,去掉了不相關的代碼。

    public void await() {// ...if( port==-1 ) {try {awaitThread = Thread.currentThread();while(!stopAwait) {try {Thread.sleep( 10000 );} catch( InterruptedException ex ) {// continue and check the flag}}} finally {awaitThread = null;}return;}// ...}

    在await方法中,實際上當前線程在一個while循環中每10秒檢查一次 stopAwait這個變量,它是一個 volatile?類型變量,用于確保被另一個線程修改后,當前線程能夠立即看到這個變化。如果沒有變化,就會一直處于while循環中。這就是該線程不退出的原因,也就是整個spring-boot應用不退出的原因。

    因為Springboot應用同時啟動了8080和8081(management port)兩個端口,實際是啟動了兩個Tomcat,因此會有兩個線程 container-0和 container-1。

    接下來,我們再看看,這個Spring-boot應用又是如何退出的呢?

    DubboConsumer退出分析

    在前面的描述中提到,有一個線程持續的在檢查 stopAwait這個變量,那么我們自然想到,在Stop的時候,應該會有一個線程去修改 stopAwait,打破這個while循環,那又是誰在修改這個變量呢?

    通過對源碼分析,可以看到只有一個方法修改了 stopAwait,即 org.apache.catalina.core.StandardServer#stopAwait,我們在此處加個斷點,看看是誰在調用。

    注意,當我們在Intellij IDEA的Debug模式,加上一個斷點后,需要在命令行下使用 kill-s INT $PID或者 kill-s TERM $PID才能觸發斷點,點擊IDE上的Stop按鈕,不會觸發斷點。這是IDEA的bug。在 IDEA 中調試 Bug,真是太厲害了!這個推薦大家看下。

    可以看到有一個名為 Thread-3的線程調用了該方法:

    stopAwait:390, StandardServer (org.apache.catalina.core) stopInternal:819, StandardServer (org.apache.catalina.core) stop:226, LifecycleBase (org.apache.catalina.util) stop:377, Tomcat (org.apache.catalina.startup) stopTomcat:241, TomcatEmbeddedServletContainer (org.springframework.boot.context.embedded.tomcat) stop:295, TomcatEmbeddedServletContainer (org.springframework.boot.context.embedded.tomcat) stopAndReleaseEmbeddedServletContainer:306, EmbeddedWebApplicationContext (org.springframework.boot.context.embedded) onClose:155, EmbeddedWebApplicationContext (org.springframework.boot.context.embedded) doClose:1014, AbstractApplicationContext (org.springframework.context.support) run:929, AbstractApplicationContext$2 (org.springframework.context.support)

    通過源碼分析,原來是通過Spring注冊的 ShutdownHook來執行的

    @Override public void registerShutdownHook() {if (this.shutdownHook == null) {// No shutdown hook registered yet.this.shutdownHook = new Thread() {@Overridepublic void run() {synchronized (startupShutdownMonitor) {doClose();}}};Runtime.getRuntime().addShutdownHook(this.shutdownHook);}}

    通過查閱Java的API文檔[2], 我們可以知道ShutdownHook將在下面兩種情況下執行

    The Java virtual machine shuts down in response to two kinds of events:

    • The program?exits?normally, when the last non-daemon thread exits or when the?exit(equivalently,?System.exit) method is invoked, or

    • The virtual machine is?terminated?in response to a user interrupt, such as typing?^C, or a system-wide event, such as user logoff or system shutdown.

  • 調用了System.exit()方法

  • 響應外部的信號,例如Ctrl+C(其實發送的是SIGINT信號),或者是?SIGTERM信號(默認?kill $PID發送的是?SIGTERM信號)

  • 因此,正常的應用在停止過程中( kill-9$PID除外),都會執行上述ShutdownHook,它的作用不僅僅是關閉tomcat,還有進行其他的清理工作,在此不再贅述。

    總結

  • 在?DubboConsumer啟動的過程中,通過啟動一個獨立的非daemon線程循環檢查變量的狀態,確保進程不退出

  • 在?DubboConsumer停止的過程中,通過執行spring容器的shutdownhook,修改了變量的狀態,使得程序正常退出

  • 問題

    在DubboProvider的例子中,我們看到Provider并沒有啟動Tomcat提供HTTP服務,那又是如何實現不退出的呢?我們將在下一篇文章中回答這個問題。

    彩蛋

    在 IntellijIDEA中運行了如下的單元測試,創建一個線程執行睡眠1000秒的操作,我們驚奇的發現,代碼并沒有線程執行完就退出了,這又是為什么呢?(被創建的線程是非daemon線程)

    @Test public void test() {new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(1000000);} catch (InterruptedException e) {e.printStackTrace();}}}).start(); }

    [1] https://docs.oracle.com/javase/specs/jls/se8/html/jls-12.html#jls-12.8

    [2] https://docs.oracle.com/javase/8/docs/api/java/lang/Runtime.html#addShutdownHook

    總結

    以上是生活随笔為你收集整理的Spring Boot Dubbo 应用启停源码分析的全部內容,希望文章能夠幫你解決所遇到的問題。

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