模拟实现配置中心配置发生变化时秒级推送至客户端代码思路
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 }