模拟实现配置中心配置发生变化时秒级推送至客户端代码思路

 

 

  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 }

 

posted @ 2019-05-03 23:17  xuebusi  阅读(1052)  评论(0编辑  收藏  举报