Java語言的優雅停機 - 第308篇

相關歷史文章(閱讀本文之前,您可能需要先看下之前的系列👇

 

國內最全的Spring Boot系列之三

水滿自溢「限流算法第四把法器:漏桶算法」- 第303篇

一分鐘get:緩存穿透、緩存擊穿、緩存雪崩 - 第304篇

布隆過濾器Bloom Filter竟然讓我解決了一個大廠的問題 - 第305篇

100G的文件如何讀取 - 第306篇

100G的文件如何讀取續集 - 第307篇

 

師傅:徒兒,在接下來的章節中,我們一起來學習下Spring Boot如何優雅停機。

悟纖:師傅,棒棒的。

 

師傅:來,你來說下何爲優雅?

悟纖:指人行爲舉止優美,自然且高雅。

師傅:歐侯,徒兒,你這個解釋,我竟無言以對。你能審下題不?

悟纖:師傅,你也沒有什麼定語吶,我就以爲是解釋下優雅的意思了。我哪知道你要的是優雅的停止服務的意思呢。

師傅:哎,大夥說下,我這容易嘛。那你說說看。

 

悟纖:這個… 這個….

師傅:我看你就是過過嘴癮呢。還得爲師和你講一下。

悟纖:徒兒虛心求教。

 

一、何爲優雅停機?

1.1 定義

什麼叫優雅停機?簡單說就是,在對應用進程發送停止指令之後,能保證正在執行的業務操作不受影響。

應用接收到停止指令之後的步驟應該是:停止接收訪問請求,等待已經接收到的請求處理完成,並能成功返回,這時才真正停止應用。

 

1.2 JVM Hook

       Runtime.getRuntime().addShutdownHook(Thread hook):這個方法的意思就是在jvm中增加一個關閉的鉤子,當jvm關閉的時候,會執行系統中已經設置的所有通過方法addShutdownHook添加的鉤子,當系統執行完這些鉤子後,jvm纔會關閉。所以這些鉤子可以在jvm關閉的時候進行內存清理、對象銷燬等操作。

       註冊一個JVM關閉的鉤子,這個鉤子可以在以下幾種場景被調用:

(1)程序正常退出;

(2)使用System.exit();

(3)終端使用Ctrl+C觸發的中斷;

(4)系統關閉;

(5)使用kill pid命令幹掉進程;

 

1.3 JVM Hook的小例子1

       編寫一個代碼繼承Thread,並且使用Runtime.getRuntime().addShutdownHook(this) 將此線程添加到JVM的shutdown鉤子中:

public class ShutdownHook extends Thread {

    @Override
    public void run() {
        System.out.println("Shut down signal received.");
        //do something.
        System.out.println("Shut down complete.");
    }

    public ShutdownHook() {
        Runtime.getRuntime().addShutdownHook(this);
    }
}

       寫個main來測試下:

public class TestMain {
	private ShutdownHook shutdownHook;

	public TestMain() {
		this.shutdownHook = new ShutdownHook();
	}
	
	public static void main(String[] args) {
		TestMain app = new TestMain();
		System.out.println("start of main()");
		//do something.
		System.out.println("End of main()");
	}
}

       使用run main運行下,查看控制檯:

start of main()

End of main()

Shut down signal received.

Shut down complete.

       這種情況就是第一種情況(1)程序正常退出:執行完成main方法之後,程序就結束退出了,那麼就會執行hook,也就是會執行線程的run()方法。

 

1.4 JVM Hook的小例子2

       那麼如何來模擬下使用kill pid的方式吶,很簡單隻要稍微修改下我們的代碼。

       我們一個核心的思路就是讓main方法不要結束。我們可以在ShutdownHook定義一個是否接收到了shutdown的信號變量isReceivedSignal,然後在main方法執行有一個方法,通過此參數來循環執行即可,看代碼:

ShutdownHook

public class ShutdownHook extends Thread {
	
	public boolean isReceivedSignal = false;
	
    @Override
    public void run() {
        System.out.println("Shut down signal received.");
        this.isReceivedSignal = true;
        //do something.
        System.out.println("Shut down complete.");
    }

    public ShutdownHook() {
        Runtime.getRuntime().addShutdownHook(this);
    }
}

       注意:這裏多了一個變量isReceivedSignal ,在接收到shutdown的信號的時候,會執行run方法,然後執行完成之後,將變量置爲true。

TestMain

public class TestMain {
	private ShutdownHook shutdownHook;

	public TestMain() {
		this.shutdownHook = new ShutdownHook();
	}
	
	public void execute() {
		while(!shutdownHook.isReceivedSignal) {
			System.out.println("I am sleep");
			try {
				TimeUnit.SECONDS.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("I am not sleep");
		}
		System.out.println("end execute");
	}
	
	
	public static void main(String[] args) {
		TestMain app = new TestMain();
		System.out.println("start of main()");
		//do something.
		app.execute();
		System.out.println("End of main()");
	}
}

 

說明: 在testmain類中添加了一個execute方法,此方法就是根據ShutdownHook中的變量來進行判斷是否進行while循環的。

       使用run main在運行下,然後使用kill pid的方式進行關閉(我是mac電腦,終端就可以使用kill了)。

       對於kill pid 實際上等同於kill -15 pid(kill -s 15 pid),使用kill -l可以查看到所有的信號意思:

$ kill -l
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL
 5) SIGTRAP	 6) SIGABRT	 7) SIGEMT	 8) SIGFPE
 9) SIGKILL	10) SIGBUS	11) SIGSEGV	12) SIGSYS
13) SIGPIPE	14) SIGALRM	15) SIGTERM	16) SIGURG
17) SIGSTOP	18) SIGTSTP	19) SIGCONT	20) SIGCHLD
21) SIGTTIN	22) SIGTTOU	23) SIGIO	24) SIGXCPU
25) SIGXFSZ	26) SIGVTALRM	27) SIGPROF	28) SIGWINCH
29) SIGINFO	30) SIGUSR1	31) SIGUSR2	

       kill pid和kill -s 15 pid含義一樣,表示發送一個SIGTERM的信號給對應的程序。程序收到該信號後,將會發生以下事情:

(1)程序立刻停止;

(2)程序釋放相應資源後立刻停止;

(3)程序可能仍然繼續運行;

       大部分程序在接收到SIGTERM信號後,會先釋放自己的資源,然後再停止。但也有一些程序在收到信號後,做一些其他事情,並且這些事情是可以配置的。也就是說,SIGTERM多半是會被阻塞,忽略的。

       對於kill -9 pid是我們常見的,意思是SIGKILL,表示強制、儘量終止一個進程。

       來我們運行下kill pid(pid可以使用jps命令進行查看)。

查看進程ID:

$ jps
96512 Jps
96510 TestMain

關閉進程TestMain:kill 96510

       查看控制檯信息:

I am sleep

I am not sleep

I am sleep

Shut down signal received.

Shut down complete.

       觀察控制檯的打印,我們不難發現:

(1)execute並未執行完;

(2)main方法也並未執行完;

       所以這個kill之後,只能發出一個對於添加到了addShutdownHook的線程,對於主線程等同於就是直接結束了嗎??

       那麼我們是否可以等待主線程執行完畢吶,答案是可以的,主要使用線程的join方法即可。

 

1.5 JVM Hook的小例子3

       我們先猜想:對於主線程是否需是我們的ShuwdownHook的run方法執行太快了,然後JVM就認爲全部執行完成了,就釋放了資源,直接關閉了進程了吶。

       猜想歸猜想,可以簡單驗證下,在ShutdownHook的run方法,當接收到關閉信號的時候,先休眠個1秒:

@Override
    public void run() {
        System.out.println("Shut down signal received.");
        this.isReceivedSignal = true;
        //do something.
        try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
        System.out.println("Shut down complete.");
    }

 

       運行看一下效果:

I am sleep

I am not sleep

I am sleep //進入while循環了

Shut down signal received. //接收到關閉信號,後sleep了.

I am not sleep //main thread繼續執行

end execute //execute執行完畢

End of main() //跳出execute方法,回到main方法.

Shut down complete. //run方法執行完畢。

       這種方式main是可以執行完畢了,但是我們很多時候,並不知道main方法還需要執行多久才能執行完畢,那麼這個時候thread.join方法就派上用場了。

1.6 JVM Hook的小例子6

       我們這個例子改造的核心思路就是,我們需要將主線程作爲參數傳到ShutdownHook類中,然後在ShutdownHook的run方法裏執行Thread.join等待主線程執行完畢。

ShutdownHook:

public class ShutdownHook extends Thread {
	
	public boolean isReceivedSignal = false;
	private Thread mainThread;
	
	
	
    @Override
    public void run() {
        System.out.println("Shut down signal received.");
        this.isReceivedSignal = true;
        //do something.
//        try {
//			Thread.sleep(1000);
//		} catch (InterruptedException e) {
//			e.printStackTrace();
//		}
        
        try {
			mainThread.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
        System.out.println("Shut down complete.");
    }

    public ShutdownHook(Thread mainThread) {
    	this.mainThread = mainThread;
        Runtime.getRuntime().addShutdownHook(this);
    }
}

說明:構建方法接收一個mainThread,在run方法使用join等待主線程執行完畢。

TestMain

public class TestMain {
	private ShutdownHook shutdownHook;
	
	public TestMain() {
		this.shutdownHook = new ShutdownHook(Thread.currentThread());
	}
	
	public void execute() {
		while(!shutdownHook.isReceivedSignal) {
			System.out.println("I am sleep");
			try {
				TimeUnit.SECONDS.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("I am not sleep");
		}
		System.out.println("end execute");
	}
	
	
	public static void main(String[] args) {
		TestMain app = new TestMain();
		System.out.println("start of main()");
		//do something.
		app.execute();
		System.out.println("End of main()");
	}
}

說明:TestMain沒什麼特殊之處,就是把當前自己所屬的線程傳給了ShutdownHook。

       來run一下吧,使用kill pid關閉:

I am sleep

I am not sleep

I am sleep

Shut down signal received.

I am not sleep

end execute

End of main()

Shut down complete.

 

 

二、悟纖小結

師傅:我們今天就先介紹到這裏,下節我們來說說Spring Boot的優雅停機方式。

悟纖:師傅棒棒的,剩下的就讓徒兒來給師傅總結下。

(1)優雅停機?簡單說就是,在對應用進程發送停止指令之後,能保證正在執行的業務操作不受影響。

(2)Runtime.getRuntime().addShutdownHook(Thread hook):這個方法的意思就是在jvm中增加一個關閉的鉤子,當jvm關閉的時候,會執行系統中已經設置的所有通過方法addShutdownHook添加的鉤子,當系統執行完這些鉤子後,jvm纔會關閉。所以這些鉤子可以在jvm關閉的時候進行內存清理、對象銷燬等操作。

(3)對於kill指令-s 9和15是有區別的:-s 9:強制關閉進程;-s 15:發出終止信號。

 

我就是我,是顏色不一樣的煙火。
我就是我,是與衆不同的小蘋果。

學院中有Spring Boot相關的課程:

à悟空學院:https://t.cn/Rg3fKJD

SpringBoot視頻:http://t.cn/A6ZagYTi

Spring Cloud視頻:http://t.cn/A6ZagxSR

SpringBoot Shiro視頻:http://t.cn/A6Zag7IV

SpringBoot交流平臺:https://t.cn/R3QDhU0

SpringData和JPA視頻:http://t.cn/A6Zad1OH

SpringSecurity5.0視頻:http://t.cn/A6ZadMBe

Sharding-JDBC分庫分表實戰:http://t.cn/A6ZarrqS

分佈式事務解決方案「手寫代碼」:http://t.cn/A6ZaBnIr

JVM內存模型和性能調優:http://t.cn/A6wWMVqG

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章