本文介绍如何将任务调度应用优雅下线。
背景信息
在实际业务场景下,定时任务持续地按固定频率在应用进程中执行。当应用在发布重启时,进程需暂时下线。如果直接关闭应用,正在进行中的定时任务将被中断,可能导致数据不完整或其他问题。为避免该情况,SchedulerX实现了定时任务的优雅下线功能,在关闭应用前,需等待当前正在进行的任务执行完成后,然后再安全地关闭应用。
使用限制
客户端版本为1.10.8及以上。
优雅下线示意
如何配置
当前 SchedulerX 可同时支持单机任务、分布式任务多种执行模式下的优雅下线能力。另外,离线定时任务不同于实时在线业务场景,可能存在执行长耗时的特性,但是应用进程在停机时不会无限等待。您可以通过以下配置运行中的任务应用延迟停机的时间。
#优雅下线模式,WAIT_ALL:等待全部; WAIT_RUNNING:等待运行中。
#该参数不配置则不启用优雅下线功能(默认不开启优雅下线)。
spring.schedulerx2.graceShutdownMode=WAIT_ALL
#优雅下线等待超时时长(单位:秒),默认无超时。
spring.schedulerx2.graceShutdownTimeout=10
#是否开启http服务接口,默认false。
spring.schedulerx2.httpServerEnable=true
#http下线接口端口,默认51886。
spring.schedulerx2.httpServerPort=52333
下线模式 | 描述 |
等待全部( | (推荐)该模式下,待所有已接收的任务和子任务执行完成后,应用才退出。 |
等待运行中( | 该模式下,应用在退出时,将等待已分配线程并在处理中的任务或子任务执行完成,队列中的任务将被放弃执行。 |
下线实现方式
为了方便业务应用集成在各种部署形态下的发布流程,您可以使用以下几种方式实现优雅停机。
方式一:通过kill -15优雅停止
SchedulerX SDK 添加了JVM进程关闭时的钩子函数,实现了程序优雅退出的功能。用户在停止应用进程时可以采用kill -15 进程ID
的方式停止应用进程,运行中的应用进程将按照对应的配置策略完成任务执行后的优雅退出。
执行下线脚本停止业务应用时,不能直接采用kill -9
,否则该能力将失效。部分业务应用下线的脚本先采用kill -15
,监测并等待一段时间后,再执行kill -9
命令强制停止。建议根据应用任务的特征来合理设置下线等待的时间,防止发布过程过慢。
方式二:通过SpringBoot shutdown事件停止
如果通过 SpringBoot 方式初始化接入 SchedulerX,可以兼容SpringBoot 提供的actuator功能来实现优雅下线,它能在响应 Spring 容器关闭事件时进行定时任务的优雅下线处理。
开启SpringBoot actuator步骤如下:
在应用程序的
pom.xml
文件中添加依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
配置开启shutdown接口。
management.endpoint.shutdown.enabled=true
management.endpoints.web.exposure.include=shutdown
执行下线。
curl -X GET http://NodeIP:Port/actuator/shutdown
方式三:通过HTTP接口停止
通过配置项在当前业务的应用进程中开启一个HTTP接口服务,用于外部自定义请求实现当前应用进程中的定时任务优雅下线处理。应用参数配置如下:
#是否开启http服务接口,默认false。
spring.schedulerx2.httpServerEnable=true
#http下线接口端口,默认51886。
spring.schedulerx2.httpServerPort=51886
调用如下HTTP接口即可实现任务调度服务的优雅下线
curl -X GET http://NodeIP:51886/schedulerx/worker/shutdown
运用集成方案
通常需要将对应的优雅下线能力集成到日常发布的运维流程中,以规避应用重启时出现的定时任务业务有损。以下将介绍几种常见的集成形式。
方案一:自建部署流程集成
通常在自建CD流程中会有一个应用进程停止的节点,该节点可通过构建一个stop.sh脚本用于应用进程的停止退出。脚本内容需包含应用优雅下线的相关逻辑处理,可采用上述章节下线实现方式的任意一种方式即可。
自建CD流程如图所示:
停止应用进程的脚本案例:
#应用启用成功后进程ID信息会写入app.pid文件
PID="{应用部署路径}/app.pid"
FORCE=1
if [ -f ${PID} ]; then
TARGET_PID=`cat ${PID}`
kill -15 ${TARGET_PID}
loop=1
while(( $loop<=5 ))
do
#health 检查当前应用进程确实已经结束,可根据应用特征自定义
health
if [ $? == 0 ]; then
echo "check $loop times, current app has not stop yet."
sleep 5s
let "loop++"
else
FORCE=0
break
fi
done
if [ $FORCE -eq 1 ]; then
echo "App(pid:${TARGET_PID}) stop timeout, forced termination."
kill -9 ${TARGET_PID}
if
rm -rf ${PID}
echo "App(pid:${TARGET_PID}) stopped successful."
fi
方案二:K8s容器化部署PreStop方式
利用K8s Pod生命周期中销毁时的preStop事件处理来触发应用的优雅下线,同时preStep hook可通过exec执行脚本和HTTP请求方式来实现优雅下线逻辑处理。
Exec脚本方式:直接通过停止应用进程,或者调用提前预设的
stop.sh
脚本实现应用进程退出。
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app-container
image: my-app-image:latest
lifecycle:
preStop:
exec:
# command: ["/bin/sh", "-c", "kill -15 PID && sleep 30"]
command: ["/bin/sh", "-c", "脚本路径/stop.sh"]
HTTP接口方式:
对于启用SpringBoot actuator shutdown 能力时可配置Path为
/actuator/shutdown
。非Spring应用可在再开启SchedulerX SDK优雅下线HTTP接口后,配置Path为
/schedulerx/worker/shutdown
。
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app-container
image: my-app-image:latest
lifecycle:
preStop:
httpGet:
path: /schedulerx/worker/shutdown
port: 51886
scheme: HTTP
方案三:阿里云上应用发布平台自动集成
如果业务应用已经采用了公有云上EDAS或者MSE和ACK发布部署时,在对应的平台上开启了无损下线能力即可自动集成SchedulerX定时任务的优雅下线能力。具体操作,请参见EDAS应用发布开启应用无损下线和MSE+ACK部署形态下应用无损下线。