美文网首页
基于redis实现分布式锁

基于redis实现分布式锁

作者: wei_lu_lu | 来源:发表于2019-01-23 19:42 被阅读0次

    参考文章:http://www.importnew.com/27477.html#comment-721558
    分布式锁的要求:
    1,互斥性。在任意时刻,只有一个客户端能持有锁。
    2,不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
    3,具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
    4,加锁和解锁必须是同一个客户端

    示例代码:

    public class RedisTool {
     
        private static final String LOCK_SUCCESS = "OK";
        private static final String SET_IF_NOT_EXIST = "NX";
        private static final String SET_WITH_EXPIRE_TIME = "PX";
     
        /**
         * 尝试获取分布式锁
         * @param jedis Redis客户端
         * @param lockKey 锁
         * @param requestId 请求标识
         * @param expireTime 超期时间
         * @return 是否获取成功
         */
        public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
     
            String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
     
            if (LOCK_SUCCESS.equals(result)) {
                return true;
            }
            return false;
     
        }
     
    }
    

    说明:
    首先,set()加入了NX参数,可以保证如果已有key存在,则函数不会调用成功,也就是只有一个客户端能持有锁,满足互斥性。其次,由于我们对锁设置了过期时间,即使锁的持有者后续发生崩溃而没有解锁,锁也会因为到了过期时间而自动解锁(即key被删除),不会发生死锁。最后,因为我们将value赋值为requestId,代表加锁的客户端请求标识,那么在客户端在解锁的时候就可以进行校验是否是同一个客户端。
    两种错误示例:

    public static void wrongGetLock1(Jedis jedis, String lockKey, String requestId, int expireTime) {
     
        Long result = jedis.setnx(lockKey, requestId);
        if (result == 1) {
            // 若在这里程序突然崩溃,则无法设置过期时间,将发生死锁
            jedis.expire(lockKey, expireTime);
        }
     
    }
    
    public static boolean wrongGetLock2(Jedis jedis, String lockKey, int expireTime) {
     
        long expires = System.currentTimeMillis() + expireTime;
        String expiresStr = String.valueOf(expires);
     
        // 如果当前锁不存在,返回加锁成功
        if (jedis.setnx(lockKey, expiresStr) == 1) {
            return true;
        }
     
        // 如果锁存在,获取锁的过期时间
        String currentValueStr = jedis.get(lockKey);
        if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
            // 锁已过期,获取上一个锁的过期时间,并设置现在锁的过期时间
            String oldValueStr = jedis.getSet(lockKey, expiresStr);
            if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
                // 考虑多线程并发的情况,只有一个线程的设置值和当前值相同,它才有权利加锁
                return true;
            }
        }
     
        // 其他情况,一律返回加锁失败
        return false;
     
    }
    

    第二种方式会出现将他人的过期时间覆盖的问题。

    解锁代码:

    public class RedisTool {
     
        private static final Long RELEASE_SUCCESS = 1L;
     
        /**
         * 释放分布式锁
         * @param jedis Redis客户端
         * @param lockKey 锁
         * @param requestId 请求标识
         * @return 是否释放成功
         * 说明lua脚本会保证整个操作是一个原子性;同时也有身份判断
         */
        public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
     
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
     
            if (RELEASE_SUCCESS.equals(result)) {
                return true;
            }
            return false;
     
        }
     
    }
    

    两种错误的解锁方式:

    public static void wrongReleaseLock1(Jedis jedis, String lockKey) {
    //未判断del身份,会将他人的锁删除
        jedis.del(lockKey);
    }
    
    public static void wrongReleaseLock2(Jedis jedis, String lockKey, String requestId) {
     
        // 判断加锁与解锁是不是同一个客户端
        if (requestId.equals(jedis.get(lockKey))) {
            // 若在此时,这把锁突然不是这个客户端的,则会误解锁
            jedis.del(lockKey);
        }
     
    }
    

    相关文章

      网友评论

          本文标题:基于redis实现分布式锁

          本文链接:https://www.haomeiwen.com/subject/ddmsjqtx.html