化被动为主动,周期任我控

项目场景:

推不动别人公司提供主动推送接口,丢一个文档,我们对接,自行取取数据,而且要保证数据的实时性。有些xx的话不想说出口,算了,都过去了。不是这样那样的磨难,我们又怎么会成长。正因为这些不欢而散,我们才想到强壮自己。
以下的优化方案也是在这样的情况下,一点点衍生,最后就不怕你要什么级别的实时性了,只要你坚信你的服务器能抗住,别怪我不厚道了。


问题描述

销售觉得实时性不够,同步数据周期太长。下面是原方案的流程图:
在这里插入图片描述
从流程图上可以看出,设备数据量越大周期越长。

原因分析:

首先可能想到的方案:自动任务 + 多线程
自动任务解决主动的周期,多线程解决周期内进度的加快。
想法是一个设备开一个线程取处理,但是我会告诉你,线程会耗尽,导致主线程假死。
原因:

  1. 2个Http的接口嵌套使用,执行时间受网络,http协议、返回数据量的影响,执行时间会在2-5s

  2. 设备有3w多个

    就算我加了多线程的拒绝策略依然扛不住,而且拒绝了,可能导致有些设备一致就没有机会去取最新的上报数据。

解决方案:

线程耗尽的血案其实在我上一篇自动任务与多线程的风花雪月分享之前就出现过了。
第一版优化,保证了不线程耗尽假死,但是同步数据变成了同步。
优化点:去掉了多线程,修改了自动任务的调度由fixedRate改为了fixedDelayString,看下入口代码吧。

@Scheduled(fixedDelayString = "${scheduling.zyUcView.syncDeviceRealData.fixedDelay}")
   public Result<?> deviceRealTimeDataFromUcViewWork() {
    
    
   log.info("--对接盈科设备实时数据获取任务(中烟)--");
   DeviceVo deviceVo = new DeviceVo();
   DeviceInfoDto deviceInfoDto = new DeviceInfoDto();
   deviceInfoDto.setIsDeleted(BusinessConstant.NO_DELETE);
   deviceInfoDto.setFactoryId(BusinessConstant.UC_DEVICE_FACTORY_ID);
   deviceInfoDto.setCreateTime(null);

   deviceVo.setDeviceInfoDto(deviceInfoDto);
   jobCallService.deviceRealTimeDataFromUcViewWork(zyUcViewBaseUrl, deviceVo);
   return ResultGenerator.genSuccessResult("--开线程对接盈科设备实时数据获取任务自动执行成功--");
   }

deviceRealTimeDataFromUcViewWork实现类就没有什么看的意义了,也涉密就不分享了。可以说下主要流程:
1、根据入参条件查询设备信息
2、进入流程图上所示的迭代部分

第二版就是将设备按照设备类型分组,分成多个自动任务
代码示例:

@Scheduled(fixedDelayString = "${scheduling.zyUcView.syncDeviceRealData.ele_window_uc.fixedDelay}")
  public Result<?> deviceRealTimeDataFromUcViewWork230101() {
    
    
    log.info("--对接盈科设备实时数据获取任务(中烟)--电动窗");
    DeviceVo deviceVo = new DeviceVo();
    DeviceInfoDto deviceInfoDto = new DeviceInfoDto();
    deviceInfoDto.setIsDeleted(BusinessConstant.NO_DELETE);
    deviceInfoDto.setFactoryId(BusinessConstant.UC_DEVICE_FACTORY_ID);
    deviceInfoDto.setCreateTime(null);

    deviceInfoDto.setDeviceTypeNo("230101");

    deviceVo.setDeviceInfoDto(deviceInfoDto);
    jobCallService.deviceRealTimeDataFromUcViewWork(zyUcViewBaseUrl, deviceVo);
    return ResultGenerator.genSuccessResult("--开线程对接盈科设备实时数据获取任务自动执行成功--");
  }

第三版优化就是衍生的分2个版本的优化设计,分别是基于队列版、redis版。(均未进入编码实现,因为实时性客户已经满意了<设计出来就是预备给销售,以及客户临时要进一步提升实时性>)
1、队列版
在这里插入图片描述
设计说明:
1、首次需要将相应设备查询到,顺序放入队列。
2、如果过程中由新设备加入,也需要放入队列。
3、队列循环取用也需要用之前再往队列里放一次。(重新如队列到最后)
4、从队列里取到设备信息,需要判断设置了锁定时长的RLock还是否存在
5、设置了锁定时长的RLock不存在,表示需要进行主动获取设备的最新上报数据了
6、设置了锁定时长的RLock存在,表示周期时间还没到,不需要获取
7、在从队列里取的过程中,如果发现队列空了,可以sleep(可能出现一直没有入口放入队列了),或者最好重新从数据库查询设备信息顺序放入(流程图没有体现重新查库放入)

2、redis版
在这里插入图片描述
设计说明:
1、首次需要将相应设备查询到,业务名称 + 设备编码作为key(根据情况设置特定含义的key)放入redis,过期时间设置为获取设备上报数据的周期
2、后面不需要自己开循环,只要保证key失效了,监听失效的方法里再次放入就行
3、而且这个不需要依赖redisson做周期锁了,因为本身设置key失效时间就是周期时间
4、同样的再后面新增的设备需要设置key放入redis
5、可以额外设置一个计数的key,一个目标设备数量缓存,每次进入key失效的监听方法取出计数key进行判断(或者统计特定开头的key存在数量),如果计数数量 < 目标设备数量,那么需要查库重新往redis放一次(注意:存在就过,不存在就放)【这点再流程图没有体现,临时想到的】
总结
做完第一版优化,实时性就得到了提升,已经可以满足客户了。但是为了精益求精,后面的优化方案还是画了流程图,首先是备忘,然后是预防突如其来的升级要求。
对于第三版优化方案
1、队列肯定会因为设备数量大导致高内存占用
2、队列还需要配合Redission的RLock使用,复杂度略高
3、redis也可能会因为设备数量大导致大内存占用(但是不会导致我们的服务崩溃,不过实际上redis挂了用户也登录不了)
4、redis失效key的监听实际上会消耗redis的一部分性能,而且redis的配置也需要改(不过其实消耗的性能也很微小)
好了,就分享到这里,希望能帮到大家。
最后说一句,任何设计没有固定的公式,加油。


猜你喜欢

转载自blog.csdn.net/zwrlj527/article/details/123742043