基于redis的spring task集群配置

  项目从单节点环境变为集群环境,这个时候要确保项目中的定时任务同一时刻只能在集群中的其中一个服务器中运行,但又不能写死在哪一台服务器上运行,怎样才能实现这样的需求?

思路:

可以做一个切面,扫描定时任务,在任务开始之前使用redis缓存判断任务是否启动,由于Redis是单线程按顺序执行命令,可以在每个定时任务执行前,使用setnx方法判断是否可以添加值,如果添加成功,说明这个定时任务没有被执行,设置key的过期时间并让定时任务执行,如果不可以添加值,说明该定时任务已经在其他服务器上跑了,方法之间返回。Key的过期时间要大于集群中方法执行setnx的总和,不大于定时任务的时间间隔。

1.定义一个自定义注解,用于配置定时任务的key,到期时间,存储库等信息

package com.study.task;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TaskRedisConfig {

    /**redis存储对应的key*/
    String key() default "";
    
    /**key对应的value值*/
    String value() default "1";
    
    /**选用第一个redis库存储该key*/
    String index() default "0";
    
    /**该key的过期时间*/
    String timeout() default "30";
    
    /**是否允许多节点并发运行 */
    boolean isConcurrent() default false;
    
}

2.定义一个切面,扫描定时任务,在定时任务执行之前用redis的setnx做判断

package com.study.aop;

import java.lang.reflect.Method;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;

import com.study.task.TaskRedisConfig;
import com.study.util.RedisUtil;

import redis.clients.jedis.Jedis;

@Aspect
@Component
@EnableAspectJAutoProxy
public class TaskAop {
    
    @Pointcut("@annotation(org.springframework.scheduling.annotation.Scheduled)")
    public void pointcut(){}
    
    /*@Before("pointcut()")
    public void taskBefore(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println(methodName);
        Method method = (Method)joinPoint.getThis();
        //Method method = (Method)joinPoint.getTarget();
        if(method.isAnnotationPresent(TaskRedisConfig.class)) {
            System.out.println("aaaaaaaaaa---------");
        }
    }*/
    @Around("pointcut()")
    public void taskBefore(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        System.out.println(methodName);
        MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();   
        Method method = methodSignature.getMethod();
        if(method.isAnnotationPresent(TaskRedisConfig.class)) {
            TaskRedisConfig tc = method.getAnnotation(TaskRedisConfig.class);
            String index = tc.index();
            Jedis jedis = RedisUtil.getJedis();
            jedis.select(Integer.parseInt(index));
            String key = tc.key();
            String value = tc.value();
            String timeout = tc.timeout();
            boolean isConcurrent = tc.isConcurrent();
            if(!isConcurrent) {
                //设置成功,返回 1 。 设置失败,返回 0 。
                long result = jedis.setnx(key, value);
                if(result==1) {
                    long exresult = jedis.expire(key, Integer.parseInt(timeout));
                    joinPoint.proceed();
                    jedis.close();
                }else {
                    return;
                }
            }
            
        }
    }

}

3.写一个定时任务类,用于测试

package com.study.task;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class SimpleTask {
    
    @Scheduled(cron = "0/10 * * * * ?")
    @TaskRedisConfig(key="simpleTask:methodOne",timeout="9",index="2")
    @Transactional
    public void methodOne() {
        System.out.println("methodOne----,时间:" + System.currentTimeMillis()/1000);
    }
    
    @Scheduled(cron = "0/20 * * * * ?")
    @TaskRedisConfig(key="simpleTask:methodTwo",timeout="18",index="2")
    public void methodTwo() {
        System.out.println("methodTwo----,时间:" + System.currentTimeMillis()/1000);
    }

}

可以向项目放到两个工作空间下,用两个eclipse进行分别启动该两个项目,查看结果

代码git地址:https://gitee.com/sjcq/redis.git

猜你喜欢

转载自www.cnblogs.com/sjcq/p/8973528.html