需求: 某系统在启动或重启时需将未执行任务加载至redis缓存中,周期或定时执行缓存中的任务。当用户增加新任务时 需要把新的周期任务或定时任务添加进redis缓存,当用户删除任务需要同时删除redis缓存中的数据
(最先的方案是 只用java内存 不用redis ,但是此种方案存在高延时的风险,即当用户删除任务同时要清空内存中的任务数据时,如果线程在执行大批量任务命令 用户此时删除任务 用户会一直等待直到线程执行完毕
解决方案 使用俩个缓存redis+java内存hashmap<String,model> 首先库中未执行任务先缓存到redis中 然后 java内存hashmap<String,model>读取reids中的数据 线程循环java内存hashmap<String,model>中的数据 即使用户删除某些任务更新的是redis中的数据 不会影响java内存hashmap<String,model>中命令下发 同时给redis加锁(存在用户和java内存hashmap<String,model>并行操作redis情况)
但是这样的方案有个弊端 本就珍贵的内存中 我们存了双份的数据 一份是redis数据一份是java内存数据 ,所以只用一份内存来处理是最高效的,用ConcurrentHashMap来处理并发问题
ConcurrentHashMap的优势在于 不会锁住整个对象 它的锁的粒度是key 这样在对象层次上不存在锁 不会发生线程阻塞
设计结构:
初始化->redis->java内存->在java内存中处理所有待执行任务
1:初始化加载任务至redis

@PostConstruct public void init() { try { //初始化或重启服务 缓存待执行任务到redis里 ArrayList<TaskModel> a=taskMapper.getTasks(); for (int i=0;i<a.size();i++){ 初始化存redis redisUtil.set("task"+a.get(i).getTaskId(),a.get(i)); } System.err.println("待执行任务加载到缓存中 "+a.size()+" 条"); } catch (Exception e) { //错误输出 e.printStackTrace(); } }
2:定时任务将redis中数据专程map 存储在redis中
3:用户删除redis数据

删除redis缓存
redisUtil.del("task"+taskId);
4:概要设计流程图
5:优化成只用java内存来处理 去掉redis层 用ConcurrentHashMap做内存保存数据
任务工具类

public class TaskUtil { /** * 定时任务的java缓存 key:任务id value:任务对象 */ public static ConcurrentHashMap<String, TaskModel> hashMap=new ConcurrentHashMap<>(); public static ConcurrentHashMap<String, TaskModel> getHashMap() { return hashMap; } public static void setHashMap(ConcurrentHashMap<String, TaskModel> hashMap) { TaskUtil.hashMap = hashMap; } }
初始化加载数据至ConcurrentHashMap中

@PostConstruct public void init() { try { //初始化或重启服务 缓存待执行任务到redis里 ArrayList<TaskModel> a=taskMapper.getTasks(); for (int i=0;i<a.size();i++){ //初始化存java内存 TaskUtil.getHashMap().put(a.get(i).getTaskId(),a.get(i)); } System.err.println("待执行任务加载到缓存中 "+a.size()+" 条"); } catch (Exception e) { //错误输出 e.printStackTrace(); } }
开一个线程在内存中处理任务数据
(之前开发用的是java的Timer类 是一个线程处理一个任务,如果任务有上万了那就要开上万个线程 系统会崩溃 优化方案是只用一个线程就处理所有任务 用的是springboot的@Scheduled注解,此注解默认开的是一个线程)

@Scheduled(cron = "0 0/5 * * * ?") @RequestMapping("/taskM") public void taskCron() { try { //线程开始时间 Date now=new Date(); //java内存 ConcurrentHashMap<String, TaskModel> taskMap=TaskUtil.getHashMap(); //循环java内存中的任务 for (Map.Entry<String,TaskModel> listEntry: taskMap.entrySet()){ //任务类型 (0:立即执行,1:周期执行,2:定时执行) String taskTimeType=listEntry.getValue().getTaskTimeType(); //任务开始执行时间 String startTime=listEntry.getValue().getTaskTimer(); long btTime= TaskUtil.betweenTime(DateUtil.time4.parse(startTime),now); //周期任务 if (taskTimeType.equals(TaskUtil.TASK_1)){ long count=TaskUtil.getTask2Count(btTime,TaskUtil.PERIOD); if (count==0){ //执行命令 this.doCmd(listEntry,startTime,now); }else if (count<0){//start-now<0 //补时到当前周期 startTime=TaskUtil.backTime(startTime,now,listEntry.getValue().getTaskTimerPeriod()); long btTimeBack= TaskUtil.betweenTime(DateUtil.time4.parse(startTime),now); long countBack=TaskUtil.getTask2Count(btTimeBack,TaskUtil.PERIOD); if (countBack==0){ //执行命令 this.doCmd(listEntry,startTime,now); }else { //将下一次执行时间更新至内存 String takId=listEntry.getValue().getTaskId();//任务id //补时到下一个周期 startTime=TaskUtil.backTime2(startTime,now,listEntry.getValue().getTaskTimerPeriod()); if (TaskUtil.getHashMap().containsKey(takId)){ TaskUtil.getHashMap().get(takId).setTaskTimer(startTime); } } } } //定时任务 else if (taskTimeType.equals(TaskUtil.TASK_2)){ long count=TaskUtil.getTask2Count(btTime,TaskUtil.PERIOD); //倒计时器为0时 开始执行定时任务 错过执行时间周期则不执行 等待用户重新添加定时任务 if (count==0){ //执行命令 this.doCmd(listEntry,startTime,now); } } } }catch (Exception e){ e.printStackTrace(); } }