1 import com.alibaba.fastjson.JSON;
2 import com.xuebusi.spring.study.http.BasicHttpUtil;
3 import org.springframework.beans.factory.InitializingBean;
4 import org.springframework.web.bind.annotation.GetMapping;
5 import org.springframework.web.bind.annotation.PathVariable;
6 import org.springframework.web.bind.annotation.RequestMapping;
7 import org.springframework.web.bind.annotation.RestController;
8 import org.springframework.web.context.request.async.DeferredResult;
9
10 import java.util.*;
11 import java.util.concurrent.ConcurrentHashMap;
12 import java.util.concurrent.ExecutorService;
13 import java.util.concurrent.Executors;
14
15 /**
16 * 模拟实现配置中心配置发生变化时秒级推送至客户端
17 * 操作步骤:
18 * 1.请求http://host:port/conf/add?key=&value=接口添加数据
19 * 2.请求http://host:port/conf/get?key=接口查看数据
20 * 3.请求http://host:port/conf/update?key=&value=接口修改数据
21 * 4.观察控制台日志会看到配置变化的通知
22 * <p>
23 * 原理:
24 * 1.项目启动时,模拟一个客户端循环调用(超时重试) /conf/monitor 接口请求数据,配置中心接受到请求之后创建DeferredResult对象,
25 * 以客户端ID为key,以DeferredResult对象为value放入 deferredResultMap;
26 * 2.当客户端调用修改接口时,就将修改后的数据放入 changedDataMap(这里为了测试方便仅用一个map存);
27 * 3.通过另一个线程定时(间隔1s)查看 changedDataMap 中变化的数据(如果数据存在数据库A表,则每隔1s去扫一下A表);
28 * 4.如果数据有变化就遍历deferredResultMap通过 deferredResult.setResult() 通知给所有的客户端,通知完的客户端以及数据要移除;
29 *
30 * @author syj
31 */
32 @RestController
33 @RequestMapping
34 public class ConfController implements InitializingBean {
35
36 private static final int TIME_OUT = 30;
37
38 private static ExecutorService executorService = Executors.newCachedThreadPool();
39
40 private static Map<String, ConfData> changedDataMap = new ConcurrentHashMap<>();
41
42 private static Map<String, List<ConfDataLog>> logDataMap = new ConcurrentHashMap<>();
43
44 private Map<String, DeferredResult> deferredResultMap = new ConcurrentHashMap();
45
46 // 模拟数据库存储配置
47 public static Map<String, ConfData> confDataMap = new ConcurrentHashMap<>();
48
49 /**
50 * 获取最新配置
51 * 使用 DeferredResult 返回异步结果
52 *
53 * @param clientId
54 * @return
55 */
56 @GetMapping("/conf/monitor")
57 public DeferredResult<String> monitor(String clientId) {
58 DeferredResult<String> result = new DeferredResult<>(TIME_OUT * 1000L, "超时啦");
59 deferredResultMap.put(clientId, result);
60 return result;
61 }
62
63 /**
64 * 查询所有配置
65 *
66 * @return
67 */
68 @GetMapping("/conf/list")
69 public Result<Map<String, ConfData>> list() {
70 return new Result<>(confDataMap);
71 }
72
73 /**
74 * 查询配置
75 *
76 * @param key
77 * @return
78 */
79 @GetMapping("/conf/get")
80 public Result<ConfData> get(String key) {
81 if (!confDataMap.containsKey(key)) {
82 return new Result<>(Result.FAIL_CODE, "key不存在");
83 } else {
84 return new Result<>(confDataMap.get(key));
85 }
86 }
87
88 /**
89 * 修改配置
90 *
91 * @param key
92 * @param value
93 * @return
94 */
95 @GetMapping("/conf/update")
96 public Result<String> update(String key, String value) {
97 if (!confDataMap.containsKey(key)) {
98 return new Result<>(Result.FAIL_CODE, "key不存在");
99 } else {
100 ConfData confData = confDataMap.get(key);
101 if (!confData.getValue().equals(value)) {
102 confData.setValue(value);
103 confDataMap.put(key, confData);
104 writeLog(key, value, 2);
105 // 同时放到 changedDataMap 中一份最新数据
106 changedDataMap.put(key, confData);
107 }
108 return Result.SUCCESS;
109 }
110 }
111
112 /**
113 * 新增配置
114 *
115 * @param key
116 * @param value
117 * @return
118 */
119 @GetMapping("/conf/add")
120 public Result<String> add(String key, String value) {
121 if (confDataMap.containsKey(key)) {
122 return new Result<>(Result.FAIL_CODE, "key已经存在了");
123 }
124 confDataMap.put(key, new ConfData(value));
125 writeLog(key, value, 1);
126 changedDataMap.put(key, new ConfData(value));
127 return Result.SUCCESS;
128 }
129
130 /**
131 * 移除配置
132 *
133 * @param key
134 * @return
135 */
136 @GetMapping("/conf/delete")
137 public Result<String> delete(String key) {
138 if (!confDataMap.containsKey(key)) {
139 return new Result<>(Result.FAIL_CODE, "key不存在");
140 }
141 update(key, "");
142 return Result.SUCCESS;
143 }
144
145 /**
146 * 查询所有配置修改日志
147 *
148 * @return
149 */
150 @GetMapping("/conf/log")
151 public Result<Collection<List<ConfDataLog>>> history() {
152 return new Result<>(logDataMap.values());
153 }
154
155 /**
156 * 查询指定配置修改日志
157 *
158 * @param key
159 * @return
160 */
161 @GetMapping("/conf/log/{key}")
162 public Result<List<ConfDataLog>> logByKey(@PathVariable("key") String key) {
163 if (logDataMap.containsKey(key)) {
164 return new Result<>(logDataMap.get(key));
165 } else {
166 return new Result<>(null);
167 }
168 }
169
170 @Override
171 public void afterPropertiesSet() throws Exception {
172
173 // 模拟客户端 http请求最新配置
174 executorService.execute(() -> {
175 String clientId = UUID.randomUUID().toString().replace("-", "");
176 while (true) {
177 String url = "http://localhost:8888/conf/monitor?clientId=" + clientId;
178 String result = BasicHttpUtil.get(url, TIME_OUT + 30);
179 System.out.println(Thread.currentThread().getName() + "----http调用结果:" + result);
180 }
181 });
182
183 // 配置中心启动线程监控配置变化
184 executorService.execute(() -> {
185 while (true) {
186 Collection<ConfData> values = changedDataMap.values();
187 if (values != null && values.size() > 0) {
188 for (String key : changedDataMap.keySet()) {
189 for (String clientId : deferredResultMap.keySet()) {
190 DeferredResult deferredResult = deferredResultMap.get(clientId);
191 String msg = "通知客户端" + clientId + "配置" + key + "发生变化:" + JSON.toJSONString(changedDataMap.get(key));
192 deferredResult.setResult(msg);
193 // 移除已经通知过的客户端
194 deferredResultMap.remove(clientId);
195 }
196 // 移除已经通知过的数据
197 changedDataMap.remove(key);
198 }
199 }
200 try {
201 Thread.sleep(1000L);
202 } catch (InterruptedException e) {
203 e.printStackTrace();
204 }
205 }
206 });
207 }
208
209 /**
210 * 记录修改日志
211 *
212 * @param key
213 * @param value
214 * @param optType 操作类型 1添加 2修改
215 */
216 private void writeLog(String key, String value, int optType) {
217 if (logDataMap.containsKey(key)) {
218 logDataMap.get(key).add(new ConfDataLog(key, value, optType, new Date()));
219 } else {
220 List<ConfDataLog> list = new ArrayList<>();
221 list.add(new ConfDataLog(key, value, optType, new Date()));
222 logDataMap.put(key, list);
223 }
224 }
225
226 /**
227 * 返回结果
228 *
229 * @param <T>
230 */
231 public static class Result<T> {
232
233 public static int SUCCESS_CODE = 200;
234 public static int FAIL_CODE = 500;
235 public static final Result<String> SUCCESS = new Result<>(SUCCESS_CODE, "操作成功");
236 public static final Result<String> FAIL = new Result<>(FAIL_CODE, "操作失败");
237
238 private int code;
239 private String msg;
240 private T data;
241
242 public Result(T data) {
243 this.code = SUCCESS_CODE;
244 this.msg = "操作成功";
245 this.data = data;
246 }
247
248 public Result(int code, String msg) {
249 this.code = code;
250 this.msg = msg;
251 }
252
253 public Result(int code, String msg, T data) {
254 this.code = code;
255 this.msg = msg;
256 this.data = data;
257 }
258
259 public int getCode() {
260 return code;
261 }
262
263 public void setCode(int code) {
264 this.code = code;
265 }
266
267 public String getMsg() {
268 return msg;
269 }
270
271 public void setMsg(String msg) {
272 this.msg = msg;
273 }
274
275 public T getData() {
276 return data;
277 }
278
279 public void setData(T data) {
280 this.data = data;
281 }
282 }
283
284 /**
285 * 配置
286 */
287 public class ConfData {
288
289 private String value;
290
291 public ConfData(String value) {
292 this.value = value;
293 }
294
295 public String getValue() {
296 return value;
297 }
298
299 public void setValue(String value) {
300 this.value = value;
301 }
302 }
303
304 /**
305 * 配置修改日志
306 */
307 public class ConfDataLog {
308
309 private String key;
310 private String value;
311 private int optType;// 1添加,2修改
312 private Date updateTime;
313
314 public ConfDataLog() {
315 }
316
317 public ConfDataLog(String key, String value, int optType, Date updateTime) {
318 this.key = key;
319 this.value = value;
320 this.optType = optType;
321 this.updateTime = updateTime;
322 }
323
324 public String getKey() {
325 return key;
326 }
327
328 public void setKey(String key) {
329 this.key = key;
330 }
331
332 public String getValue() {
333 return value;
334 }
335
336 public void setValue(String value) {
337 this.value = value;
338 }
339
340 public int getOptType() {
341 return optType;
342 }
343
344 public void setOptType(int optType) {
345 this.optType = optType;
346 }
347
348 public Date getUpdateTime() {
349 return updateTime;
350 }
351
352 public void setUpdateTime(Date updateTime) {
353 this.updateTime = updateTime;
354 }
355 }
356 }