1.为什么要使用DeferredResult?
建立一次连接,让他们等待尽可能长的时间。这样同时如果有新的数据到达服务器,服务器可以直接返回响应。通过这种方式,我们绝对可以减少所涉及的请求和响应周期的数量。
1.背景:有个大屏展示了上百个资产的状态,当这个资产有告警产生的时候,对应资产需要展示为红色,让运维人员知道该资产出了问题。
2.原来的实现:前端每隔10秒就请求一次后端,后端再去查询mysql,再把最新的数据返回给前端
3.存在的问题:理论上可能会存在最多10秒的延迟,前端展示就会慢了0-10秒。
4.改进方法:DeferredResult长轮询,后端有更新后马上返回给前端
2.DeferredResult执行逻辑
- 浏览器发起异步请求
- 请求到达服务端被挂起(使用浏览器查看请求状态,此时为pending)
- 向浏览器进行响应,分为两种情况:
3.1 调用DeferredResult.setResult()
,请求被唤醒,返回结果
3.2 超时,返回一个你设定的结果 - 浏览得到响应,再次重复1,处理此次响应结果
3.实际应用
下面代码不是使用demo,是实际的应用记录,没用过的同学可能看起来比较费劲。demo这个大神的比较详细:Spring Boot实现长轮询和短轮询(Short Polling & Long Polling)长连接和短连接_jake_Aaron(小湮没)的博客-CSDN博客_长轮询如何实现
下面代码逻辑是只要有告警状态的变更,就从数据库中获取所有设备的最新状态,然后和redis中保存的比较,如果有更新,就调setResult方法,把结果返回给客户端。如果没有更新,就在20秒后跳出循环。
@ApiOperation("长轮询判断是否有最新告警")
@GetMapping("/getWarnAndLevels")
public DeferredResult getWarnInfos(int counter) {
Map<String, Object> map = new HashMap<>();
long timeOutInMilliSec = 25000;//超时时间25秒
String timeOutResp = "超时了";
DeferredResult deferredResult = new DeferredResult<>(timeOutInMilliSec, timeOutResp);
Object warnAndLevel = redisUtil.get(StoConstant.warnAndLevel);//告警信息
if (warnAndLevel == null) {//第一次,直接调原接口
map.put(StoConstant.count, 0);
map.put("updata", true);
deferredResult.setResult(map);
return deferredResult;
}
String warnAndLevelString = (String) warnAndLevel;
List<GetWarnInfoResponseVo> warnAndLevelList = JSON.parseArray(warnAndLevelString, GetWarnInfoResponseVo.class);
List<String> list = new LinkedList<>();
long t1 = System.currentTimeMillis();
while (true) {
long t2 = System.currentTimeMillis();
if (t2 - t1 > 20 * 1000) { //hold住请求20秒
break;
}
warnAndLeveStatus.drainTo(list, 1);//一次只拿一个
if (list.size() > 0) {
List<GetWarnInfoResponseVo> warnInfoResponseVoList = warnInfoService.getWarnInfo();
if (!(warnInfoResponseVoList.size() == warnAndLevelList.size() && warnInfoResponseVoList.containsAll(warnAndLevelList))) {
//不相同
String warnAndLevelJson = JSON.toJSONString(warnInfoResponseVoList);
redisUtil.set(StoConstant.warnAndLevel, warnAndLevelJson);
int counterRedis = redisUtil.get(StoConstant.warnAndLevelCounter) == null ? 0 : Integer.parseInt(redisUtil.get(StoConstant.warnAndLevelCounter).toString());
map.put(StoConstant.count, ++counterRedis);
map.put("updata", true);
redisUtil.incr(StoConstant.warnAndLevelCounter, 1);//增加1
deferredResult.setResult(map);
return deferredResult;
}
}
int counterRedis = redisUtil.get(StoConstant.warnAndLevelCounter) == null ? 0 : Integer.parseInt(redisUtil.get(StoConstant.warnAndLevelCounter).toString());
if (counter < counterRedis) {
map.put(StoConstant.count, counterRedis);
map.put("updata", true);
deferredResult.setResult(map);
return deferredResult;
}
try {
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
}
map.put(StoConstant.count, counter);
map.put("updata", false);
deferredResult.setResult(map);//超时
return deferredResult;
}