如何正确关闭游戏服务器
一,如何正確的關閉游戲服務器
1,最簡單粗爆的方法
在Linux系統(tǒng)上,使用ps -aux|grep java?可以查到所有運行的java程序的pid,即進程號,然后使用kill - 9?進程號,殺死一個進程。
這樣做雖然簡單快速,但是會有一個問題,如果我們運行的服務器有緩存的數(shù)據(jù),還沒有來得及進行持久化存儲,那么這樣操作,內(nèi)存中的數(shù)據(jù)就會丟失。kill - 9是一個必殺命令,不管進程處于什么狀態(tài),都是殺無赦,它不會給進程留下任何善后的機會。那么該如何正確的關閉游戲服務器吧?
2,優(yōu)雅的關閉進程
優(yōu)雅的關閉進程,就是在收到關閉進程的命令后,進程進行一些數(shù)據(jù)處理,比如:
1,不再接收連接
2,不再接收數(shù)據(jù)
3,把未持久化的數(shù)據(jù)進行持久化
4,清理一些臨時文件等
5,執(zhí)行一些已經(jīng)提交到線程池中但未執(zhí)行的任務
? 3,Java進程如何接收進程停止命令
1,JVM?關閉鉤子
關閉鉤子本質(zhì)上是一個線程(也稱為Hook線程),用來監(jiān)聽JVM的關閉。通過使用Runtime的addShutdownHook(Thread hook)可以向JVM注冊一個關閉鉤子。Hook線程在JVM?正常關閉才會執(zhí)行,在強制關閉時不會執(zhí)行。
這個鉤子可以在一下幾種場景中被調(diào)用:
1.?程序正常退出
2.?使用System.exit()
3.?終端使用Ctrl+C觸發(fā)的中斷
4.?系統(tǒng)關閉
5.?OutOfMemory宕機
6.?使用Kill pid命令干掉進程(注:在使用kill -9 pid時,是不會被調(diào)用的)
對于一個JVM中注冊的多個關閉鉤子它們將會并發(fā)執(zhí)行,所以JVM并不能保證它的執(zhí)行順行。當所有的Hook線程執(zhí)行完畢后,如果此時runFinalizersOnExit為true,那么JVM將先運行終結(jié)器,然后停止。Hook線程會延遲JVM的關閉時間,這就要求在編寫鉤子過程中必須要盡可能的減少Hook線程的執(zhí)行時間。另外由于多個鉤子是并發(fā)執(zhí)行的,那么很可能因為代碼不當導致出現(xiàn)競態(tài)條件或死鎖等問題,為了避免該問題,強烈建議在一個鉤子中執(zhí)行一系列操作。
?
另外在使用關閉鉤子還要注意以下幾點:
1.?不能在鉤子調(diào)用System.exit(),否則卡住JVM的關閉過程,但是可以調(diào)用Runtime.halt()。
2.?不能再鉤子中再進行鉤子的添加和刪掉操作,否則將會拋出IllegalStateException。
3.?在System.exit()之后添加的鉤子無效。
4.?當JVM收到SIGTERM命令(比如操作系統(tǒng)在關閉時)后,如果鉤子線程在一定時間沒有完成,那么Hook線程可能在執(zhí)行過程中被終止。
5. Hool線程中同樣會拋出異常,如果拋出異常又不處理,那么鉤子的執(zhí)行序列就會被停止。
?
下面是一個簡單的示例:
?
| public class T { @SuppressWarnings("deprecation") public static void main(String[] args) throws Exception { //啟用退出JVM時執(zhí)行Finalizer Runtime.runFinalizersOnExit(true); MyHook hook1 = new MyHook("Hook1"); MyHook hook2 = new MyHook("Hook2"); MyHook hook3 = new MyHook("Hook3"); ? //注冊關閉鉤子 Runtime.getRuntime().addShutdownHook(hook1); Runtime.getRuntime().addShutdownHook(hook2); Runtime.getRuntime().addShutdownHook(hook3); ? //移除關閉鉤子 Runtime.getRuntime().removeShutdownHook(hook3); ? //Main線程將在執(zhí)行這句之后退出 System.out.println("Main Thread Ends."); } } ? class MyHook extends Thread { private String name; public MyHook (String name) { this.name = name; setName(name); } public void run() { System.out.println(name + " Ends."); } //重寫Finalizer,將在關閉鉤子后調(diào)用 protected void finalize() throws Throwable { System.out.println(name + " Finalize."); } } |
?
和(可能的)執(zhí)行結(jié)果(因為JVM不保證關閉鉤子的調(diào)用順序,因此結(jié)果中的第二、三行可能出現(xiàn)相反的順序):
?
Main Thread Ends.
Hook2 Ends.
Hook1 Ends.
Hook3 Finalize.
Hook2 Finalize.
Hook1 Finalize.
可以看到,main函數(shù)執(zhí)行完成,首先輸出的是Main Thread Ends,接下來執(zhí)行關閉鉤子,輸出Hook2 Ends和Hook1 Ends。這兩行也可以證實:JVM確實不是以注冊的順序來調(diào)用關閉鉤子的。而由于hook3在調(diào)用了addShutdownHook后,接著對其調(diào)用了removeShutdownHook將其移除,于是hook3在JVM退出時沒有執(zhí)行,因此沒有輸出Hook3 Ends。
另外,由于MyHook類實現(xiàn)了finalize方法,而main函數(shù)中第一行又通過Runtime.runFinalizersOnExit(true)打開了退出JVM時執(zhí)行Finalizer的開關,于是3個hook對象的finalize方法被調(diào)用,輸出了3行Finalize。
注意,多次調(diào)用addShutdownHook來注冊同一個關閉鉤子將會拋出IllegalArgumentException:
Exception in thread "main" java.lang.IllegalArgumentException: Hook previously registered
at java.lang.ApplicationShutdownHooks.add(ApplicationShutdownHooks.java:72)
at java.lang.Runtime.addShutdownHook(Runtime.java:211)
at T.main(T.java:12)
?
另外,從JavaDoc中得知:
?
? ?Once the shutdown sequence has begun it can be stopped only by invoking the halt method, which forcibly terminates the virtual machine.
? ?Once the shutdown sequence has begun it is impossible to register a new shutdown hook or de-register a previously-registered hook. Attempting either of these operations will cause an IllegalStateException to be thrown.
?
“一旦JVM關閉流程開始,就只能通過調(diào)用halt方法來停止該流程,也不可能再注冊或移除關閉鉤子了,這些操作將導致拋出IllegalStateException”。
?
如果在關閉鉤子中關閉應用程序的公共的組件,如日志服務,或者數(shù)據(jù)庫連接等,像下面這樣:
?
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
try {
LogService.this.stop();
} catch (InterruptedException ignored){
//ignored
}
}
});
?
由于關閉鉤子將并發(fā)執(zhí)行,因此在關閉日志時可能導致其他需要日志服務的關閉鉤子產(chǎn)生問題。為了避免這種情況,可以使關閉鉤子不依賴那些可能被應用程序或其他關閉鉤子關閉的服務。實現(xiàn)這種功能的一種方式是對所有服務使用同一個關閉鉤子(而不是每個服務使用一個不同的關閉鉤子),并且在該關閉鉤子中執(zhí)行一系列的關閉操作。這確保了關閉操作在單個線程中串行執(zhí)行,從而避免了在關閉操作之前出現(xiàn)競態(tài)條件或死鎖等問題。
二,在游戲服務器中添加關閉鉤子
?
| public class ShutDownService { private static CommonLog gameLogger = CommonLog.getInstance(); private static List<IShutDown> shutDownList = new ArrayList<>(); //注冊需要在關閉鉤子中執(zhí)行的任務 public static void registShutDown(IShutDown shutDownServer) { shutDownList.add(shutDownServer); } public static void startShutDownHook() { ? Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { gameLogger.warn(0, "開始關閉服務器,正在清理資源....."); for (IShutDown shutDown : shutDownList) { if (shutDown != null) { shutDown.shutDown(); while (!shutDown.isTerminated()) { ? } gameLogger.warn(0, "----關閉" + shutDown.getClass().getName() + "成功----"); } } gameLogger.warn(0, "###---服務器關閉成功---###"); } }); } } |
?
三,Linux腳本根據(jù)端口殺死一個進程
?
| #!/bin/bash echo "重新啟動服務" jar_name=GameLogicServer.jar PROCESS=`ps -ef|grep ${jar_name} |grep -v grep|grep -v PPID|awk '{ print $2}'` for i in $PROCESS do ?echo "######Kill the ${jar_name} process [ $i ] ########" ? ?kill -15 $i done ? ? while true ?do OLD_PROCESS=`ps -ef|grep ${jar_name} |grep -v grep|grep -v PPID|awk '{ print $2}'` if [ "${OLD_PROCESS}" = "" ] then echo "${PROCESS}進程已殺死成功,開始啟動新的進程" break else echo "正在等待${PROCESS}進程關閉....." sleep 1s fi ? ? ?done ? nohup java -server -agentpath:/usr/jprofiler9/jprofiler9/bin/linux-x64/libjprofilerti.so=port=8849,nowait -jar ${jar_name} > console.out 2>&1 & echo "服務器啟動完成" |
更多游戲技術資料請參照:游戲技術網(wǎng):http://www.youxijishu.com/
轉(zhuǎn)載于:https://www.cnblogs.com/youxijishu/p/6404241.html
《新程序員》:云原生和全面數(shù)字化實踐50位技術專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的如何正确关闭游戏服务器的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一维数组
- 下一篇: Apache常见功能实战详解