\344\274\230\351\233\205shutdown.md
... ...
@@ -0,0 +1,146 @@
1
+# graceful shutdown 方案设计文档
2
+
3
+## 1. 现状分析
4
+
5
+### 1.1 当前问题
6
+1. **引擎直接关闭**:目前的引擎关闭操作是直接关闭,没有进行额外的收尾操作。
7
+2. **数据丢失风险**:
8
+ - **应用更新和添加**:在更新或添加应用时会导致容器重启,这对于一些应用(如 IoT)来说,由于无法及时消费队列中的数据,会导致数据丢失。
9
+ - **Hazelcast 组网**:多个节点直接关闭时,由于某一写分片来不及备份,可能导致数据丢失。
10
+3. **缺乏优雅关闭机制**:当前的关闭操作是直接和突兀的,没有给业务一定的缓冲时间。
11
+
12
+## 2. 改进方案和实现
13
+
14
+### 2.1 配置 Spring 优雅关闭机制
15
+
16
+通过配置 Spring 的优雅关闭机制,可以在 JVM 关闭时优雅地关闭 Spring 应用上下文,避免数据丢失。
17
+
18
+#### 配置文件(application.properties)
19
+```properties
20
+server.shutdown=graceful
21
+spring.lifecycle.timeout-per-shutdown-phase=30s
22
+```
23
+
24
+#### SpringApplication 类
25
+```java
26
+private void registerShutdownHook(ConfigurableApplicationContext context) {
27
+ if (this.registerShutdownHook) {
28
+ Thread shutdownHook = new Thread(() -> {
29
+ try {
30
+ context.close();
31
+ } catch (Exception ex) {
32
+ // Handle exception...
33
+ }
34
+ });
35
+ Runtime.getRuntime().addShutdownHook(shutdownHook);
36
+ }
37
+}
38
+```
39
+通过 `Runtime.getRuntime().addShutdownHook(shutdownHook);` 监听 k8s 发送的 SIGTERM 信号。
40
+
41
+### 2.2 实现 DisposableBean 接口
42
+
43
+通过实现 `DisposableBean` 接口,可以在 Spring 容器关闭时执行一些自定义的销毁逻辑。
44
+
45
+#### 示例代码
46
+```java
47
+@RestController
48
+@CrossOrigin
49
+public class RpcController implements DisposableBean {
50
+ ...
51
+ @Override
52
+ public void destroy() throws Exception {
53
+ long start = System.currentTimeMillis();
54
+ logger.warn("\n\n\n ###################### iidp server destroy start at {}\n\n\n", start);
55
+
56
+ // 自定义销毁逻辑
57
+ ...
58
+
59
+ long end = System.currentTimeMillis();
60
+ logger.warn("\n\n\n ###################### iidp server destroy finish at {}, cost: {} ms", end, end - start);
61
+ }
62
+
63
+ ...
64
+}
65
+```
66
+
67
+### 2.3 优雅关闭 Hazelcast
68
+
69
+通过优雅关闭 Hazelcast 当前成员,避免数据丢失。
70
+
71
+#### 示例代码
72
+```java
73
+// 优雅关闭 Hazelcast 当前成员,避免数据丢失
74
+// 参考: https://docs.hazelcast.com/hazelcast/5.5/maintain-cluster/shutdown
75
+Hazelcast.getInstance().shutdown();
76
+```
77
+
78
+### 2.4 触发销毁事件
79
+
80
+在关闭容器时,给当前容器中的所有应用发送销毁事件。
81
+
82
+#### 示例代码
83
+```java
84
+public void destroyEvent() {
85
+ // 给当前容器所有的 app 发送 destroy event
86
+
87
+ Meta meta = new Meta(Meta.SUPERUSER, new HashMap<>());
88
+ Map<String, AppDataInfo> appDataInfoMap = EngineContainer.getBussinessAppGroupContainer().getAppDataInfoMap();
89
+ Map<String, AppDataInfo> appDataInfoMap2 = EngineContainer.getBaseAppGroupContainer().getAppDataInfoMap();
90
+ List<AppDataInfo> appDataInfoList = new ArrayList<>(appDataInfoMap.values());
91
+ appDataInfoList.addAll(appDataInfoMap2.values());
92
+
93
+ for (AppDataInfo appInfo : appDataInfoList) {
94
+ Map<String, List<String>> events = appInfo.getEvents();
95
+ if (events == null || events.isEmpty()) {
96
+ continue;
97
+ }
98
+ List<String> methodList = events.get(EventType.DESTROY.getType());
99
+ if (methodList == null || methodList.isEmpty()) {
100
+ continue;
101
+ }
102
+
103
+ meta.addArgument("app", appInfo.getName());
104
+ meta.addArgument(MetaConstant.TAG, appInfo.getTag());
105
+
106
+ logger.info("============== App [{}] 销毁事件 {} 开始 ==============", appInfo.getName(), methodList);
107
+
108
+ for (String method : methodList) {
109
+ try {
110
+ String[] modelMethod = method.split(MetaConstant.METHOD_SEPARATOR);
111
+ if (modelMethod.length == 3 && "ASYNC".equals(modelMethod[2])) {
112
+ meta.get(modelMethod[0]).callAsync(modelMethod[1]);
113
+ } else {
114
+ meta.get(modelMethod[0]).call(modelMethod[1]);
115
+ }
116
+ } catch (Exception ex) {
117
+ logger.error(String.format("执行应用: %s 事件报错", appInfo.getNameTag()), ex);
118
+ }
119
+ }
120
+
121
+ logger.info("============== App [{}] 销毁事件 {} 结束 ==============", appInfo.getName(), methodList);
122
+ }
123
+}
124
+```
125
+
126
+### 2.5 app定义destroy销毁事件
127
+
128
+在app中的app.json文件中定义销毁事件
129
+```json
130
+ "events":{
131
+ "destroy": [
132
+ "trace_whitelist::destroy" # 新增destroy事件
133
+ ],
134
+ "startUp": [
135
+ "trace_whitelist::register"
136
+ ],
137
+ "register" : [
138
+ ],
139
+ "login": [
140
+ ]
141
+ }
142
+```
143
+
144
+## 3. 总结
145
+
146
+通过以上改进措施,可以实现系统的优雅关闭,避免数据丢失,提高系统的可靠性和稳定性。这些措施包括配置 Spring 的优雅关闭机制、实现 `DisposableBean` 接口、优雅关闭 Hazelcast 以及触发销毁事件。通过这些改进,可以为业务提供一定的缓冲时间,确保数据的完整性和一致性。
... ...
\ No newline at end of file