联系方式
Java服务器开发群:66728073
游戏开发者高级群:398808948
Unity3d游戏开发:286114103
龙井,铁观音,茶叶
如何正确关闭游戏服务器
2017-02-12 22:43浏览数:1120 

一,如何正确的关闭游戏服务器

1,最简单粗爆的方法

Linux系统上,使用ps -aux|grep java 可以查到所有运行的java程序的pid,即进程号,然后使用kill - 9 进程号,杀死一个进程。

这样做虽然简单快速,但是会有一个问题,如果我们运行的服务器有缓存的数据,还没有来得及进行持久化存储,那么这样操作,内存中的数据就会丢失。kill - 9是一个必杀命令,不管进程处于什么状态,都是杀无赦,它不会给进程留下任何善后的机会。那么该如何正确的关闭游戏服务器吧?

2,优雅的关闭进程

优雅的关闭进程,就是在收到关闭进程的命令后,进程进行一些数据处理,比如:

1,不再接收连接

2,不再接收数据

3,把未持久化的数据进行持久化

4,清理一些临时文件等

5,执行一些已经提交到线程池中但未执行的任务

  3Java进程如何接收进程停止命令

1JVM 关闭钩子

关闭钩子本质上是一个线程(也称为Hook线程),用来监听JVM的关闭。通过使用RuntimeaddShutdownHook(Thread hook)可以向JVM注册一个关闭钩子。Hook线程在JVM 正常关闭才会执行,在强制关闭时不会执行。

这个钩子可以在一下几种场景中被调用:

1. 程序正常退出

2. 使用System.exit()

3. 终端使用Ctrl+C触发的中断

4. 系统关闭

5. OutOfMemory宕机

6. 使用Kill pid命令干掉进程(注:在使用kill -9 pid时,是不会被调用的)

对于一个JVM中注册的多个关闭钩子它们将会并发执行,所以JVM并不能保证它的执行顺行。当所有的Hook线程执行完毕后,如果此时runFinalizersOnExittrue,那么JVM将先运行终结器,然后停止。Hook线程会延迟JVM的关闭时间,这就要求在编写钩子过程中必须要尽可能的减少Hook线程的执行时间。另外由于多个钩子是并发执行的,那么很可能因为代码不当导致出现竞态条件或死锁等问题,为了避免该问题,强烈建议在一个钩子中执行一系列操作。

另外在使用关闭钩子还要注意以下几点:

1. 不能在钩子调用System.exit(),否则卡住JVM的关闭过程,但是可以调用Runtime.halt()

2. 不能再钩子中再进行钩子的添加和删掉操作,否则将会抛出IllegalStateException

3. System.exit()之后添加的钩子无效。

4. JVM收到SIGTERM命令(比如操作系统在关闭时)后,如果钩子线程在一定时间没有完成,那么Hook线程可能在执行过程中被终止。

5. Hool线程中同样会抛出异常,如果抛出异常又不处理,那么钩子的执行序列就会被停止。

下面是一个简单的示例:

public class T {

@SuppressWarnings("deprecation")

public static void main(String[] args) throws Exception {

//启用退出JVM时执行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线程将在执行这句之后退出

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,将在关闭钩子后调用

protected void finalize() throws Throwable {

System.out.println(name + " Finalize.");

}

}



和(可能的)执行结果(因为JVM不保证关闭钩子的调用顺序,因此结果中的第二、三行可能出现相反的顺序):

Main Thread Ends.

Hook2 Ends.

Hook1 Ends.

Hook3 Finalize.

Hook2 Finalize.

Hook1 Finalize.

可以看到,main函数执行完成,首先输出的是Main Thread Ends,接下来执行关闭钩子,输出Hook2 EndsHook1 Ends。这两行也可以证实:JVM确实不是以注册的顺序来调用关闭钩子的。而由于hook3在调用了addShutdownHook后,接着对其调用了removeShutdownHook将其移除,于是hook3JVM退出时没有执行,因此没有输出Hook3 Ends

另外,由于MyHook类实现了finalize方法,而main函数中第一行又通过Runtime.runFinalizersOnExit(true)打开了退出JVM时执行Finalizer的开关,于是3hook对象的finalize方法被调用,输出了3Finalize

注意,多次调用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关闭流程开始,就只能通过调用halt方法来停止该流程,也不可能再注册或移除关闭钩子了,这些操作将导致抛出IllegalStateException”。

如果在关闭钩子中关闭应用程序的公共的组件,如日志服务,或者数据库连接等,像下面这样:

Runtime.getRuntime().addShutdownHook(new Thread() {

public void run() {

try {

LogService.this.stop();

} catch (InterruptedException ignored){

//ignored

}

}

});

由于关闭钩子将并发执行,因此在关闭日志时可能导致其他需要日志服务的关闭钩子产生问题。为了避免这种情况,可以使关闭钩子不依赖那些可能被应用程序或其他关闭钩子关闭的服务。实现这种功能的一种方式是对所有服务使用同一个关闭钩子(而不是每个服务使用一个不同的关闭钩子),并且在该关闭钩子中执行一系列的关闭操作。这确保了关闭操作在单个线程中串行执行,从而避免了在关闭操作之前出现竞态条件或死锁等问题。

二,在游戏服务器中添加关闭钩子


public class ShutDownService {

private static CommonLog gameLogger = CommonLog.getInstance();

private static List<IShutDown> shutDownList = new ArrayList<>();

//注册需要在关闭钩子中执行的任务

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脚本根据端口杀死一个进程

#!/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 "服务器启动完成"