美文网首页
SpringBoot开发之路——服务集群,基于redis实现的-

SpringBoot开发之路——服务集群,基于redis实现的-

作者: 我是顾子晨 | 来源:发表于2019-10-26 22:40 被阅读0次

    今天开发的时候遇到一个需求:
    在集群部署服务时,需要处理定时任务不能同一时间执行。
    后面实现的思路是这样的:

    • 每个定时任务都询问一下定时任务管理器,是否能执行。
    • 定时任务管理器把每一次可以执行的定时任务,当前执行时间+执行间隔=下次执行时间。并保存到redis 中
    • 此时需要采用Redis中的乐观锁,如果同一类任务,同时调用管理器,需要做到只能由一个定时任务抢到任务。

    项目地址:[example](https://gitee.com/97wx/example/tree/master/example-redis

    )

    基础类

    //接口、用于服务集群时,一个服务部署多份时多个定时任务全在跑,如何控制同一个定时器只能由一个定时器执行,若执行定时器的服务挂掉了则由另一个定时器补上
    TaskManagerService
    
    //接口、此信息用于定时任务管理器,key 用于区分那个定时任务,每个定时任务执行间隔时间,及时间单位由此统计出一类定时器由谁可以执行。
    ITaskInformationHelper
    
    //枚举、定时任务执行的时间单位
    TaskTimeUnit                    
    
    //枚举、TaskManagerService 的实现:定时任务常量
    TaskConstants
    

    任务管理器

    • 任务管理器核心代码在于,统计是否已经到了可以执行的时间
    1. now.after(offsetTime) || now.compareTo(offset)==0
    • 另外一个因素是,如果执行过程中跨网络请求的话需要考虑,网络花费的时间偏差
    • 如下为代码:
    package org.nm.south.redis.task.impl;
    
    import cn.hutool.core.date.DateUtil;
    import cn.hutool.core.util.StrUtil;
    import cn.hutool.json.JSONUtil;
    import org.nm.south.redis.task.ITaskInformationHelper;
    import org.nm.south.redis.task.TaskManagerService;
    import org.nm.south.redis.task.entity.TaskBean;
    import org.nm.south.redis.task.enums.TaskConstants;
    import org.springframework.beans.factory.SmartInitializingSingleton;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.dao.DataAccessException;
    import org.springframework.data.redis.core.RedisOperations;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.core.SessionCallback;
    import org.springframework.stereotype.Service;
    
    import java.util.Date;
    
    /***
     * 定时任务管理器
     * @Auther: guzhangwen
     * @BusinessCard https://blog.csdn.net/gu_zhang_w
     * @Date: 2019/8/26
     * @Time: 17:45
     * @version : V1.0
     *
     */
    @Service
    public class TaskManagerServiceImpl implements TaskManagerService, SmartInitializingSingleton {
    
        @Autowired
        private RedisTemplate<String, Object> redisTemplate;
    
    
        @Override
        public boolean hashExecute(ITaskInformationHelper helper) {
            Object value = redisTemplate.opsForValue().get(helper.getKey());
            if (value == null || StrUtil.isBlank(value.toString())) {
                TaskBean taskBean = new TaskBean();
                taskBean.setCheckedTime(new Date());
                redisTemplate.opsForValue().set(helper.getKey(), JSONUtil.toJsonStr(taskBean));
            }
            boolean flag = redisTemplate.execute(new SessionCallback<Boolean>() {
                @Override
                public Boolean execute(RedisOperations redisOperations) throws DataAccessException {
                    //开户监控
                    redisOperations.watch(helper.getKey());
                    Object value1 = redisOperations.opsForValue().get(helper.getKey());
                    //开启事务
                    redisOperations.multi();
                    Date now = new Date();
                    TaskBean taskBean = value1 == null ? null : JSONUtil.toBean(value1.toString(), TaskBean.class);
                    Date offsetDate = getOffsetTime(helper, now);
                    //这里设置两秒时间差,主要是为了避免由于网络引起的差距
                    now = DateUtil.offsetSecond(now, 2);
                    boolean flag = false;
                    if (taskBean == null) {
                        flag = true;
                        taskBean = new TaskBean();
                        taskBean.setCheckedTime(offsetDate);
                    } else if (now.after(taskBean.getCheckedTime()) || now.compareTo(taskBean.getCheckedTime()) == 0) {
                        flag = true;
                        taskBean.setCheckedTime(offsetDate);
                    }
                    if (flag) {
                        //执行事务,如果提交成功则>0;否则就可以认为读到脏数据了
                        redisOperations.opsForValue().set(helper.getKey(), JSONUtil.toJsonStr(taskBean));
                        int size = redisOperations.exec().size();
                        if (size <= 0) {
                            flag = false;
                        }else{
                            System.out.println(now.getTime());
                            System.out.println(taskBean.getCheckedTime().getTime());
                        }
                    }
                    return flag;
                }
            });
            return flag;
        }
    
        private Date getOffsetTime(ITaskInformationHelper helper, Date now) {
            Date offsetDate = now;
            switch (helper.getUnit()) {
                case DAY:
                    offsetDate = DateUtil.offsetDay(now, helper.getTime());
                    break;
                case HOUR:
                    offsetDate = DateUtil.offsetHour(now, helper.getTime());
                    break;
                case MINUTE:
                    offsetDate = DateUtil.offsetMinute(now, helper.getTime());
                    break;
                case SECOND:
                    offsetDate = DateUtil.offsetSecond(now, helper.getTime());
                    break;
            }
            return offsetDate;
        }
    
        @Override
        public void afterSingletonsInstantiated() {
            for (TaskConstants constants : TaskConstants.values()) {
                redisTemplate.delete(constants.getKey());
            }
        }
    }
    
    
    

    相关文章

      网友评论

          本文标题:SpringBoot开发之路——服务集群,基于redis实现的-

          本文链接:https://www.haomeiwen.com/subject/qnjjvctx.html