redis分布式锁实战

我们设置key的时候,将value设置为一个随机值r,并且存在当前线程ThreadLocal。当释放锁,也就是删除key的时候,不是直接删除,而是先判断该key对应的value是否等于先前存在当前线程的随机值,只有当前当前线程持有锁,才删除该key,由于每个客户端产生的随机值是不一样的,这样一来就不会误释放别的客户端申请的锁了

public class RedisLock {

    private static Logger logger = LoggerFactory.getLogger(RedisLock.class);

    private static OnecachePlugin oneCache;

    static {
        oneCache = SpringUtil.getBean(OnecachePlugin.class);
    }


    /**
     * 基础有效时间
     */
    private static final int BASE_VAILD_TIME = 10;

    /**
     * 锁的基本等待时间10s
     */
    private static final int BASE_WAIT_TIME = 4;

    /**
     * 随机数
     */
    private static Random random = new Random();

    private static ThreadLocal> threadLocal = new ThreadLocal<>();


    private static void currentThreadSleep() throws InterruptedException {
        Thread.sleep((long) (200), random.nextInt(5000));
    }

    public static boolean easyLock(String key) throws Exception {
        return easyLock(key, BASE_WAIT_TIME, BASE_VAILD_TIME);
    }

    //加锁
    public static boolean easyLock(String key, Integer waitTime, Integer expireTime) throws Exception {
        if (ObjectUtil.hasEmpty(waitTime)) {
            waitTime = BASE_WAIT_TIME;
        }
        if (ObjectUtil.hasEmpty(expireTime)) {
            expireTime = BASE_VAILD_TIME;
        }
        Long signTime = System.nanoTime();
        Long holdTime = TimeUnit.NANOSECONDS.convert(waitTime, TimeUnit.SECONDS);
        String state = UUIDGenerator.getUUID();

        while ((System.nanoTime() - signTime) < holdTime) {
            //从redis获取key 如果不存在,则将key存入redis
            RString rs = oneCache.getRString(key);
            //原子性加锁
            if (rs.setnx(state, Time.seconds(expireTime))) {
                logger.info(Thread.currentThread().getId()+" 获取锁成功! key = "+key);
                if(ObjectUtil.hasEmpty(threadLocal.get())){
                    threadLocal.set(Sets.newHashSet());
                }
                threadLocal.get().add(state);
                return true;
            } else {
                try {
                    currentThreadSleep();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }
        throw new Exception( "系统正忙,稍后重试");
    }

    /**
     * redis简单分布式锁,业务执行完毕之后必须try finally 调用释放锁方法easyUnLock
     *
     * @param key 锁
     */
    public static void easyUnLock(String key) {
        logger.info(Thread.currentThread().getId()+" 准备解锁! key = "+key);

        //是否是当前线程持有锁,以免释放其他线程加的锁
        if (isHoldEasyLock(key)) {
            logger.info(Thread.currentThread().getId()+" 开始解锁! key = "+key);
            try {
                //从redis删除key
                RString rs = oneCache.getRString(key);
                String state = rs.get();
                rs.delete();
                threadLocal.get().remove(state);
                logger.info(Thread.currentThread().getId()+" 解锁成功! key = "+key);
            } catch (Exception e) {
                logger.info("RedisLockUtils easyLock解锁异常 ->" + e);
            }
        }
    }


    /**
     * redis简单分布式锁,判断线程是否持有锁
     */
    public static boolean isHoldEasyLock(String key) {
        if (ObjectUtil.hasEmpty(threadLocal.get())) {
            return false;
        }
        if (threadLocal.get().contains(oneCache.getRString(key).get())) {
            return true;
        } else {
            return false;
        }
    }

}

实际上,这样还是有一点问题,释放锁不是原子性,很有可能在查询完,redis也刚过期,再删除就把别的线程的锁释放了。

image.png

对以上问题解决办法,就是使用lua脚本,参考
https://www.jianshu.com/p/0e5d592197c1。

至此,还没有完,就是过期时间的的问题,如果高并发下,某个线程被阻塞,导致超时,那么redis过期了,就导致并发问题了,如果说过期时间设置太长,如果服务重启了,那么key就释放不了了,
因此,稳妥一点的解决办法,就是锁续命。
就是在加锁后,异步起一个线程,每隔几秒去判断一下,redis锁是否还在或者过期,给重新设置锁的过期时间,这样会完美解决上述问题。
具体实现有现成框架redisson
我们项目并发量一般,所以普通加锁就能满足

你可能感兴趣的:(redis分布式锁实战)