一. 前言
项目初期我们可以使用kill -9 pid的方法来杀死后台服务进程,然后重新部署。
但是随着时间发展,这种简单粗暴的方法会有一些问题:
-
如何在退出时清理一些资源?
-
如果某个请求执行操作到一半,直接被退出了,就会造成脏数据。
-
如何给客户端正确的反馈?
二. Java虚拟机退出钩子
虚拟机允许在退出的时候执行钩子程序,那么我们可以在这个方法里面作一些释放资源的操作,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
System.out.println(
"add hook..."
);
Thread shutdownThread =
new
Thread(
new
Runnable() {
@Override
public
void
run() {
System.out.println(
"hook running.."
+ System.currentTimeMillis());
try
{
Thread.sleep(
10000
);
}
catch
(InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(
"hook sleep done..."
+ System.currentTimeMillis());
}
});
Runtime.getRuntime().addShutdownHook(shutdownThread);
|
三. tomcat的unloadDelay属性
这个属性的意思是tomcat留给请求执行的时间,默认只有2秒,也就是执行./shundown.sh之后,请求处理只有2秒的时间。如果没有及时返回给客户端,那么就会返回客户端503错误。
apache-tomcat-9.0.0.M22-src/java/org/apache/catalina/core/StandardWrapper.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
/**
* Wait time for servlet unload in ms.
*/
protected
long
unloadDelay =
2000
;
public
synchronized
void
unload()
throws
ServletException {
// Nothing to do if we have never loaded the instance
if
(!singleThreadModel && (instance ==
null
)) {
log.info(
"instance run..."
);
return
;
}
unloading =
true
;
log.info(
"countAllocated.get(): "
+ countAllocated.get());
// Loaf a while if the current instance is allocated
// (possibly more than once if non-STM)
if
(countAllocated.get() >
0
) {
int
nRetries =
0
;
long
delay = unloadDelay /
20
;
System.out.println(
"unloadDelay: "
+ unloadDelay);
log.info(
"unloadDelay 2222: "
+ unloadDelay);
while
((nRetries <
21
) && (countAllocated.get() >
0
)) {
if
((nRetries %
10
) ==
0
) {
log.info(sm.getString(
"standardWrapper.waiting"
,
countAllocated.toString(),
getName()));
}
try
{
Thread.sleep(delay);
}
catch
(InterruptedException e) {
// Ignore
}
nRetries++;
}
}
}
|
这段代码它会把unloadDelay分成20份,然后循环20次检查请求是否已经结束。
unloadDelay默认是2000ms,也就是2秒钟.
这样写的好处是即使配置的unloadDelay时间很长,但是如果请求很快结束,那么它也不会等待配置的unloadDelay时间,它可以提前退出服务。
如果请求在unloadDelay时间内处理完请求,那么客户端就可以接受到正常的结果。
四. unloadDelay配置
在tomcat的conf目录下的context.xml中可以配置,如下:
1
2
3
4
|
<
Context
unloadDelay
=
"20000"
>
...
</
Context
>
|
五. springboot发送http请求关闭服务
在pom.xml中配置
1
2
3
|
<
dependency
>
<
groupId
>org.springframework.boot</
groupId
>
<
artifactId
>spring-boot-starter-actuator</
artifactId
></
dependency
>
|
在application.properties中配置
1
2
3
4
5
|
#启用
shutdownendpoints.shutdown.enabled=true
#禁用密码验证
endpoints.shutdown.sensitive=false
|
发送http关闭命令
1
|
curl -X POST host:port/shutdown
|
得到的返回消息如下:
1
|
{"message":"Shutting down, bye..."}
|
发送http消息后,它会拒绝新的请求,2秒内让已有请求执行完毕,清理资源。
六. 权限
如果没有权限,那么任何人发送curl -X POST host:port/shundown都可以关闭服务。
所以,需要添加权限
1.pom.xml中添加申明
1
2
3
4
5
6
7
8
9
|
<
dependency
>
<
groupId
>org.springframework.boot</
groupId
>
<
artifactId
>spring-boot-starter-security</
artifactId
>
</
dependency
>
<
dependency
>
<
groupId
>org.springframework.boot</
groupId
>
<
artifactId
>spring-boot-starter-actuator</
artifactId
>
</
dependency
>
|
spring-boot-starter-actuator的作用参考这篇帖子:
2.appliaction.properties文件中添加用户名和密码
1
2
3
4
|
endpoints.shutdown.sensitive=true
security.user.name=admin
security.user.password=admin
management.security.role=SUPERUSER
|
3.发起的http指令中携带用户名和密码
1
|
curl -uadmin:admin -X POST http://host:port/shutdown
|
4.不过这样会导致正常的请求也需要用户名和密码,所以application.properties还得再加一句:
1
|
security.basic.enabled=false
|
5.IP地址验证
如果想另外加上IP地址验证,那么全部配置如下:
1
2
3
4
5
6
7
8
|
endpoints.shutdown.enabled=true
endpoints.shutdown.sensitive=true
security.basic.enabled=false #不影响正常请求
security.user.name=admin
security.user.password=admin
management.security.role=SUPERUSER
management.port=3081 #管理的端口,需要和server.port不同(正常的请求端口)
management.address=127.0.0.1 #只允许本机发送http shutdown命令
|
发送的指令如下:
curl -uadmin:admin -X POST http://localhost:3081/shutdown