基于Redis的Redisson分布式可重入公平锁

576 阅读2分钟

参考: github.com/redisson/re…

基于Redis的Redisson分布式可重入公平锁也是实现了java.util.concurrent.locks.Lock接口的一种RLock对象。

基于Redis的Redisson分布式可重入公平锁也是实现了java.util.concurrent.locks.Lock接口的一种RLock对象。同时还提供了异步(Async)反射式(Reactive)RxJava2标准的接口。它保证了当多个Redisson客户端线程同时请求加锁时,优先分配给先发出请求的线程,所有请求线程会在一个队列中排队。

如果负责储存这个分布式锁的Redis节点宕机以后,而且这个锁正好处于锁住的状态时,这个锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。

另外Redisson还通过加锁的方法提供了leaseTime的参数来指定加锁的时间。超过这个时间后锁便自动解开了。

1. 简单示例使用方法:
RLock fairLock = redisson.getFairLock("myLock");
// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = fairLock.tryLock(100, 10, TimeUnit.SECONDS);

if (res) {
   try {
     ...你的业务
   } finally {
       //释放锁
       lock.unlock();
   }
}
2. 工具封装
import com.yunhx.commons.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.redisson.RedissonMultiLock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
public class LockManager {
    
    @Resource
    private RedissonClient redissonClient;
    
    //公平锁(Fair Lock)
    private static final ThreadLocal<ConcurrentMap<String, RLock>> FAIR_LOCK_THREAD_LOCAL = new ThreadLocal<>();
    
    //联锁(MultiLock)
    private static final ThreadLocal<RedissonMultiLock> MULTI_LOCK_THREAD_LOCAL = new ThreadLocal<>();

    /**
     * 给KEY上分布式公平锁
     */
    public boolean fairTryLock(String key) {
        RLock fairLock = redissonClient.getFairLock(key);
        boolean tryLock = fairLock.tryLock();
        return setFairLockToThreadLocal(key, fairLock, tryLock);
    }

    /**
     * 给KEY上分布式公平锁
     *
     * @param leaseTime 秒 得到锁后超过这个时间会自动解锁
     */
    public boolean fairTryLock(String key, long leaseTime) {
        try {
            RLock fairLock = redissonClient.getFairLock(key);
            boolean tryLock = fairLock.tryLock(leaseTime, TimeUnit.SECONDS);
            return setFairLockToThreadLocal(key, fairLock, tryLock);
        } catch (Exception e) {
            log.error("{}获取公平锁失败", key, e);
            throw new BusinessException("获取公平锁失败");
        }
    }

    /**
     * 给KEY上分布式公平锁
     *
     * @param waitTime  秒  这个时间后还没获到锁得将放弃取锁
     * @param leaseTime 秒 得到锁后超过这个时间会自动解锁
     */
    public boolean fairTryLock(String key, long waitTime, long leaseTime) {
        try {
            RLock fairLock = redissonClient.getFairLock(key);
            boolean tryLock = fairLock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
            return setFairLockToThreadLocal(key, fairLock, tryLock);
        } catch (Exception e) {
            log.error("{}获取公平锁失败", key, e);
            throw new BusinessException("获取公平锁失败");
        }
    }

    /**
     * 解公平锁
     */
    public void fairUnlock(String key) {
        ConcurrentMap<String, RLock> map = FAIR_LOCK_THREAD_LOCAL.get();
        RLock rLock = map.get(key);
        if (rLock != null && rLock.isHeldByCurrentThread()) {
            rLock.unlock();
        }
        map.remove(key);
        if (map.size() < 1){
            FAIR_LOCK_THREAD_LOCAL.remove();
        }
    }

    /**
     * 解公平锁
     */
    public void fairUnlockAll() {
        for (RLock rLock : FAIR_LOCK_THREAD_LOCAL.get().values()) {
            if (rLock != null && rLock.isHeldByCurrentThread()) {
                rLock.unlock();
            }
        }
        FAIR_LOCK_THREAD_LOCAL.remove();
    }

    private boolean setFairLockToThreadLocal(String key, RLock fairLock, boolean tryLock) {
        if (tryLock) {
            ConcurrentMap<String, RLock> concurrentMap = FAIR_LOCK_THREAD_LOCAL.get();
            if (concurrentMap == null){
                concurrentMap = new ConcurrentHashMap<>(1);
                FAIR_LOCK_THREAD_LOCAL.set(concurrentMap);
            }
            concurrentMap.put(key, fairLock);
        }
        return tryLock;
    }


    /**
     * 给KEYS上分布式联锁
     */
    public boolean multiTryLock(String... keys) {
        RLock[] rLocks = new RLock[keys.length];
        for (int i = 0; i < keys.length; i++) {
            rLocks[i] = redissonClient.getLock(keys[i]);
        }
        RedissonMultiLock lock = new RedissonMultiLock(rLocks);
        boolean tryLock = lock.tryLock();
        return setMultiLockToThreadLocal(lock, tryLock);
    }

    /**
     * 给KEY上分布式联锁
     *
     * @param leaseTime 秒 得到锁后超过这个时间会自动解锁
     */
    public boolean multiTryLock(long leaseTime, String... keys) {
        try {
            RLock[] rLocks = new RLock[keys.length];
            for (int i = 0; i < keys.length; i++) {
                rLocks[i] = redissonClient.getLock(keys[i]);
            }
            RedissonMultiLock lock = new RedissonMultiLock(rLocks);
            boolean tryLock = lock.tryLock(leaseTime, TimeUnit.SECONDS);
            return setMultiLockToThreadLocal(lock, tryLock);
        } catch (Exception e) {
            log.error("{}获取锁失败", keys, e);
            throw new BusinessException("获取联锁失败");
        }
    }

    /**
     * 给KEY上分布式联锁
     *
     * @param waitTime  秒  这个时间后还没获到锁得将放弃取锁
     * @param leaseTime 秒 得到锁后超过这个时间会自动解锁
     */
    public boolean multiTryLock(long waitTime, long leaseTime, String... keys) {
        try {
            RLock[] rLocks = new RLock[keys.length];
            for (int i = 0; i < keys.length; i++) {
                rLocks[i] = redissonClient.getLock(keys[i]);
            }
            RedissonMultiLock lock = new RedissonMultiLock(rLocks);
            boolean tryLock = lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
            return setMultiLockToThreadLocal(lock, tryLock);
        } catch (Exception e) {
            log.error("{}获取锁失败", keys, e);
            throw new BusinessException("获取联锁失败");
        }
    }


    /**
     * 解联锁
     */
    public void multiUnlock() {
        RedissonMultiLock rLock = MULTI_LOCK_THREAD_LOCAL.get();
        if (rLock != null) {
            rLock.unlock();
        }
        MULTI_LOCK_THREAD_LOCAL.remove();
    }

    private boolean setMultiLockToThreadLocal(RedissonMultiLock fairLock, boolean tryLock) {
        if (tryLock) {
            MULTI_LOCK_THREAD_LOCAL.set(fairLock);
        }
        return tryLock;
    }

}
3. 工具使用
@Component
public class LockManagerTest {

    @Resource
    private  LockManager lockManager;

    public void synTerminalTransQueryFromStartTime() {
        //一直阻塞尝试获取锁,10s后还未获得锁,则放弃取锁,返回false
        String key = "myLock";
        boolean res = lockManager.fairTryLock(key,10,60);
        try {
            if(res){
                业务逻辑开始
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //释放锁
            lockManager.fairUnlock(key);
        }
    }
}
4. 使用AOP获取锁,aop的几个注解执行流程自己度娘一下,这里不做介绍
4.1 FairLockAspect切面
import lombok.extern.log4j.Log4j2;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;

import java.util.concurrent.TimeUnit;

@Log4j2
@Aspect
public class FairLockAspect {

    @Resource
    private RedissonClient redissonClient;

    @Around("@annotation(fairLockAnn)")
    public Object fairTryLock(ProceedingJoinPoint pjp, LockFair fairLockAnn) throws Throwable {
        String key = fairLockAnn.key();
        final int index = fairLockAnn.keyParamIndex();
        final boolean keyParam = fairLockAnn.paramKey();
        if (keyParam) {
            Object[] args = pjp.getArgs();
            if (index < 0 || args == null || args.length < (index + 1) || args[index] == null) {
                log.warn(">> 未找到参数或参数值为空,无法进行上锁: {}, keyParam=true,keyParamIndex={}", pjp, index);
                throw new RuntimeException("未找到参数或参数值为空,无法进行上锁");
            } else if (isValidKeyParamType(args[index])) {
                key = key + args[index];
            } else {
                log.warn(">> 设置的参数不是string/long/int/short/byte类型, 无法进行上锁: {}", pjp);
                throw new RuntimeException("设置的参数类型不正确,无法进行上锁");
            }
        }
        RLock fairLock = redissonClient.getFairLock(key);
        //秒  这个时间后还没获到锁得将放弃取锁
        final long waitTime = fairLockAnn.waitTime();
        //秒 得到锁后超过这个时间会自动解锁
        final long leaseTime = fairLockAnn.leaseTime();
        boolean tryLock = fairLock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
        if(!tryLock){
            throw new RuntimeException("获取公平锁失败,请稍后再试");
        }
        try {
            return pjp.proceed();
        } finally {
            if (fairLock.isHeldByCurrentThread()){
                fairLock.unlock();
            }
        }

    }


    private boolean isValidKeyParamType(Object param) {
        return (param instanceof String) || (param instanceof Long) || (param instanceof Integer) || (param instanceof Short)
                || (param instanceof Byte);
    }
}
4.2 LockFair注解

import java.lang.annotation.*;

/**
 * 公平锁注解
 */
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LockFair {
    /**
     * 缓存key
     *
     * @return
     */
    String key();

    /**
     * 秒  这个时间后还没获到锁得将放弃取锁
     */
    long waitTime() default 5;

    /**
     * 秒 得到锁后超过这个时间会自动解锁
     */
    long leaseTime() default 10;

    /**
     * 是否使用方法的参数作为缓存key的组成
     */
    boolean paramKey() default false;

    /**
     * 要用来作为缓存key组成的参数索引(从0开始),
     * 该索引对应的参数必须为string/Long/Integer/Short/Byte,
     * 仅当{@link #paramKey}为{@code true}时有用
     */
    int keyParamIndex() default 0;

}
4.3 注解的使用示例
@Component
public class LockManagerTest {

    @LockFair(key = CacheKeys.ADAPAY_PAYMENT_NOTIFY, waitTime = 20, leaseTime = 20,paramKey = true)
    public void synTerminalTransQueryFromStartTime() {
        //你的业务逻辑 TODO
    }
}