redis+环绕aop 实现定时任务在集群中仅执行一次

16 篇文章 0 订阅
5 篇文章 0 订阅

场景说明:web服务原本仅有一台机器,现在因为用户量增加准备扩充为2台机器,那项目中的定时任务要求仅能单次在一个机器上执行。

解决:准备采用redis实现分布式锁功能,定时任务执行前先查看执行方法对应的key在redis中是否存在,如果不存在,则把key放入(相当于加锁),指定过期时间(防止方法执行失败导致的死锁),方法执行完成后移除key(释放锁);如果key已经存在,则直接跳过,方法不执行。

具体代码如下:
先声明一个接口:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface JobLock {
    // redis锁key的后缀,为空取method.toString结果;否则取设置的值;
    // 建议设置为空字符串
    String lockedSuffix() default "";
    // key在redis里存在的时间,秒
    int expireSecond() default 10;
}

切面类如下:

@Aspect
@Component
public class JobLockAspect {
    // 假设存在三个集群(每集群2机器)独立运行,但是使用相同的redis,会导致6台机器仅有一个机器执行,实际我需要每个集群中均有一个机器执行
    // token这里就是用来区分不同的集群
    @Value("${passport.token}")
    private String token;

    private static final String LOCK_VALUE = "locked";
    private static final String REDIS_START = "BI_";
    static org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(DataSourceAspect.class);

    @Autowired
    private ICacheService redisService;
 
    public void  cacheLockPoint(ProceedingJoinPoint joinPoint) {
        try {
            Signature signature = joinPoint.getSignature();
            MethodSignature methodSignature = (MethodSignature) signature;
            // 被拦截的方法
            Method method = methodSignature.getMethod();
            // 获取被拦截方法上面的 @JobLock注解的内容
            if (method.getAnnotation(JobLock.class) == null){
                joinPoint.proceed();
                return;
            }

            String lockKey = method.getAnnotation(JobLock.class).lockedSuffix();
            // 如果设置的key为空,则method.toString()作为key,实例如:public void com.ruisitech.bi.job.TestJob.monitorMigrate()
            if(StringUtils.isEmpty(lockKey)){
                lockKey = method.toString();
            }
            int timeOut = method.getAnnotation(JobLock.class).expireSecond();

            String redisKey = getRedisKey(lockKey);
            if (redisService.setnx(redisKey,LOCK_VALUE).intValue() == 1) {
                redisService.expire(redisKey,timeOut);
                log.info("method:" + lockKey +"获取锁:"+ redisKey+",开始运行!");
                joinPoint.proceed();
                // 移除
                redisService.del(redisKey);
                return;
            }else{
                log.info("method:" + lockKey + "获取锁:"+ redisKey + ",失败!");
                return ;
            }
        } catch (Exception e) {
            log.error("定时任务执行失败", e);
        } catch (Throwable e) {
            log.error("定时任务执行失败", e);
        }
    }

    public String getRedisKey(String lockKey){
        return REDIS_START + token + lockKey;
    }
}

aop配置如下:
 

   <bean id="jobLockAspect" class="com.ruisitech.bi.job.manage.JobLockAspect"></bean>
    <!--定义切面信息-->
    <aop:config>
        <!-- 切面类 -->
        <aop:aspect  ref="jobLockAspect">
            <!-- 切入点 -->
            <aop:pointcut expression="execution (* com.aaa.bb.job..*.*(..))" id="myPointcut" />
            <!-- 切入的方法 -->
            <aop:around method="cacheLockPoint" pointcut-ref="myPointcut" />
        </aop:aspect>
    </aop:config>


使用实例:com.ruisitech.bi.job包中某个类中包含如下两个定时方法
   

@Scheduled(cron = "0 */1 * * * ?")
    @JobLock(lockedSuffix ="test", expireSecond =10)
    public void test1(){
        System.out.println("countThreeDayNum");
    }

    @Scheduled(cron = "0 */1 * * * ?")
    @JobLock(lockedSuffix ="test", expireSecond =10)
    public void test2(){
        System.out.println("getDayMigrateTableMeta");
    }

那么test1和test2每分钟会仅有一个方法执行。

内部方法调用导致的 aop失效问题

见:https://blog.csdn.net/h2604396739/article/details/102610610

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值