redis分布式锁解决定时任务重复执行的问题

问题:项目部署到两台服务器上定时任务重复执行造成数据问题

解决办法:使用redis锁的形式进行解决,每次只允许一台服务器执行

reids锁+AOP切面,将加锁部分抽象出来,然后利用自定义注解的形式方便以后对其他地方进行加锁处理。

上代码:

切面类:

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;

/**
 * redis 分布式锁
 */
@Aspect
@Slf4j
@Component
public class CacheLockAspect{

    @Resource
    private RedisUtil<String> redisUtil;
    /**
     * 分布式锁的key
     */
    private static final String KEY_PREFIX_LOCK = "CACHE_LOCK_ASPECT:";

    @Around("@annotation(com.xxx.xxxx.xxxx.CacheLock)")
    public void  cacheLockPoint(ProceedingJoinPoint pjp) {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method cacheMethod = signature.getMethod();
        if(null == cacheMethod){
            log.info("未获取到使用方法 pjp: {}",pjp);
            return;
        }
        String lockKey = cacheMethod.getAnnotation(CacheLock.class).lockedKey();
        Integer timeOut = cacheMethod.getAnnotation(CacheLock.class).expireTime();
        boolean release = cacheMethod.getAnnotation(CacheLock.class).release();
        if(StringUtils.isBlank(lockKey)){
            log.error("method:{}, 锁名称为空!",cacheMethod);
            return;
        }
        try {
            //这里使用jedis的setnx方法,不存在则添加,成功返回1,不成功返回0
            if (redisUtil.setnx(KEY_PREFIX_LOCK+lockKey, lockKey, timeOut).equals(1L)){
                //对这个key加上有效期
                redisUtil.expire(KEY_PREFIX_LOCK+lockKey, timeOut);
                log.info("method:{} 获得锁:{},开始运行!",Thread.currentThread(),KEY_PREFIX_LOCK+lockKey);
                pjp.proceed();
                return;
            }
            log.info("method:{} 未获取锁:{},运行失败!",Thread.currentThread(),KEY_PREFIX_LOCK+lockKey);
            //不需要释放锁
            release = false;
        } catch (Throwable e) {
            log.error("method:{},运行错误!",cacheMethod,e);
        }finally {
            if(release){
                log.info("method:{} 执行完成释放锁:{}",cacheMethod,KEY_PREFIX_LOCK+lockKey);
                redisUtil.delete(KEY_PREFIX_LOCK+lockKey);
            }
        }
    }
}

自定义注解:

import java.lang.annotation.*;

/**自定义注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheLock {
    String lockedKey() default "";   //redis锁key
    int expireTime() default 100;    //key在redis里存在的时间 单位:秒
    boolean release() default false; //是否在方法执行完成之后释放锁
}

测试:在需要加锁的方法上加上自定义注解即可

//测试服务类
@Slf4j
@Component
public class TestServiceImpl {
	
    @CacheLock(lockedKey = "testLock", expireTime = 50)
    @Scheduled(cron = "0 0/1 * * * ? ")
    public void test1() {
        log.info("方法1开始执行!!!!");
    }
    @CacheLock(lockedKey = "testLock", expireTime = 50)
    @Scheduled(cron = "0 0/1 * * * ? ")
    public void test2() {
        log.info("方法2执行了====");
    }
}

补充: 

redis工具类,实现setnx方法

public class RedisUtil{

    @Autowired
    public RedisTemplate redisTemplate;


    public boolean setnx(String key, String val, Long expireTime){
        return (Boolean) redisTemplate.execute((RedisCallback) connection -> {
            Boolean aBoolean = connection.setNX(key.getBytes(), val.getBytes());
            if (aBoolean){
                // 设置过期时间
                this.expire(key, expireTime);
                return true;
            }
            Long expireTime1 = this.getExpireTime(key);
            if (expireTime1 == -1L) {
                // 过期时间为-1, 删除该key
                this.deleteObject(key);
            }
            return false;
        });
    }
}

参考文章:利用Redis分布式锁解决集群服务器定时任务重复执行问题__陈同学_的博客-CSDN博客_redis分布式锁定时任务

猜你喜欢

转载自blog.csdn.net/cccsssrrr/article/details/128168337