graceful shutdown 方案设计文档
1. 现状分析
1.1 当前问题
- 引擎直接关闭:目前的引擎关闭操作是直接关闭,没有进行额外的收尾操作。
-
数据丢失风险:
- 应用更新和添加:在更新或添加应用时会导致容器重启,这对于一些应用(如 IoT)来说,由于无法及时消费队列中的数据,会导致数据丢失。
- Hazelcast 组网:多个节点直接关闭时,由于某一写分片来不及备份,可能导致数据丢失。
- 缺乏优雅关闭机制:当前的关闭操作是直接和突兀的,没有给业务一定的缓冲时间。
2. 改进方案和实现
2.1 配置 Spring 优雅关闭机制
通过配置 Spring 的优雅关闭机制,可以在 JVM 关闭时优雅地关闭 Spring 应用上下文,避免数据丢失。
- 检查容器配置中,是否配置了
terminationGracePeriodSeconds
字段,不配置(默认为30)或配置为30即可 - 需要在application-dev.properties文件里添加如下配置
配置文件(application.properties)
server.shutdown=graceful
spring.lifecycle.timeout-per-shutdown-phase=30s
SpringApplication 类
private void registerShutdownHook(ConfigurableApplicationContext context) {
if (this.registerShutdownHook) {
Thread shutdownHook = new Thread(() -> {
try {
context.close();
} catch (Exception ex) {
// Handle exception...
}
});
Runtime.getRuntime().addShutdownHook(shutdownHook);
}
}
通过 Runtime.getRuntime().addShutdownHook(shutdownHook);
监听 k8s 发送的 SIGTERM 信号。
2.2 实现 DisposableBean 接口
通过实现 DisposableBean
接口,可以在 Spring 容器关闭时执行一些自定义的销毁逻辑。
示例代码
@RestController
@CrossOrigin
public class RpcController implements DisposableBean {
...
@Override
public void destroy() throws Exception {
long start = System.currentTimeMillis();
logger.warn("\n\n\n ###################### iidp server destroy start at {}\n\n\n", start);
// 自定义销毁逻辑
...
long end = System.currentTimeMillis();
logger.warn("\n\n\n ###################### iidp server destroy finish at {}, cost: {} ms", end, end - start);
}
...
}
2.3 优雅关闭 Hazelcast
通过优雅关闭 Hazelcast 当前成员,避免数据丢失。
示例代码
// 优雅关闭 Hazelcast 当前成员,避免数据丢失
// 参考: https://docs.hazelcast.com/hazelcast/5.5/maintain-cluster/shutdown
Hazelcast.getInstance().shutdown();
2.4 触发销毁事件
在关闭容器时,给当前容器中的所有应用发送销毁事件。
示例代码
public void destroyEvent() {
// 给当前容器所有的 app 发送 destroy event
Meta meta = new Meta(Meta.SUPERUSER, new HashMap<>());
Map<String, AppDataInfo> appDataInfoMap = EngineContainer.getBussinessAppGroupContainer().getAppDataInfoMap();
Map<String, AppDataInfo> appDataInfoMap2 = EngineContainer.getBaseAppGroupContainer().getAppDataInfoMap();
List<AppDataInfo> appDataInfoList = new ArrayList<>(appDataInfoMap.values());
appDataInfoList.addAll(appDataInfoMap2.values());
for (AppDataInfo appInfo : appDataInfoList) {
Map<String, List<String>> events = appInfo.getEvents();
if (events == null || events.isEmpty()) {
continue;
}
List<String> methodList = events.get(EventType.DESTROY.getType());
if (methodList == null || methodList.isEmpty()) {
continue;
}
meta.addArgument("app", appInfo.getName());
meta.addArgument(MetaConstant.TAG, appInfo.getTag());
logger.info("============== App [{}] 销毁事件 {} 开始 ==============", appInfo.getName(), methodList);
for (String method : methodList) {
try {
String[] modelMethod = method.split(MetaConstant.METHOD_SEPARATOR);
if (modelMethod.length == 3 && "ASYNC".equals(modelMethod[2])) {
meta.get(modelMethod[0]).callAsync(modelMethod[1]);
} else {
meta.get(modelMethod[0]).call(modelMethod[1]);
}
} catch (Exception ex) {
logger.error(String.format("执行应用: %s 事件报错", appInfo.getNameTag()), ex);
}
}
logger.info("============== App [{}] 销毁事件 {} 结束 ==============", appInfo.getName(), methodList);
}
}
2.5 app定义destroy销毁事件
在app中的app.json文件中定义销毁事件
"events":{
"destroy": [
"trace_whitelist::destroy" # 新增destroy事件
],
"startUp": [
"trace_whitelist::register"
],
"register" : [
],
"login": [
]
}
3. 总结
通过以上改进措施,可以实现系统的优雅关闭,避免数据丢失,提高系统的可靠性和稳定性。这些措施包括配置 Spring 的优雅关闭机制、实现 DisposableBean
接口、优雅关闭 Hazelcast 以及触发销毁事件。通过这些改进,可以为业务提供一定的缓冲时间,确保数据的完整性和一致性。