Redis 分布式锁终极版

本文介绍了如何使用Redis实现分布式锁,重点在于加锁和解锁的详细步骤。加锁通过`set()`方法,利用NX参数确保唯一性,设置过期时间以避免死锁。解锁时,通过Lua脚本判断请求ID与锁的value匹配才删除锁,确保安全。然而,集群部署时可能存在同步延迟导致多客户端获取锁的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

先直接上代码

​
/**
 * 简单redis分布式实现 解决宕机死锁和解锁了别人的锁的问题
 * Created by ljb on 2017/12/26.
 */
@Service("simpleLockDistributeService")
public class SimpleLockDistributeService{

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

    private static final String LOCK_KEY_PREFIX = "lock.key:";//锁key的前缀

    private static final int EXPIRE_TIME_SECONDS_MIN = 3;//秒,设置单个锁对象的存活时间

    private static final int EXPIRE_TIME_SECONDS_MAX = 900;//秒,设置单个锁对象的存活时间

    private static final int WAIT_TRY_MAX_TIME_SECOND = 100;//秒,等待锁的最大时间

    private static final Long RELEASE_SUCCESS = 1L;

    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "EX";

    @Autowired
    private JedisConnectionFactory jedisConnectionFactory;

    public boolean tryLock(String key, String requestId, int waitSeconds, int expireSeconds) {
        if (StringUtils.isBlank(key)) {
            return false;
        }

        RedisConnection jedisConnection = null;
        try {
            key = LOCK_KEY_PREFIX + key;

            long beginTime = System.currentTimeMillis();//开始时间(毫秒)
            long waitTime = 0L;//等待时间(毫秒)
            if (waitSeconds > 0) {
                if (waitSeconds > WAIT_TRY_MAX_TIME_SECOND) {
                    waitSeconds = WAIT_TRY_MAX_TIME_SECOND;
                }
                waitTime = waitSeconds * 1000;
            }

            if (expireSeconds < EXPIRE_TIME_SECONDS_MIN) {
                expireSeconds = EXPIRE_TIME_SECONDS_MIN;
            } else if (expireSeconds > EXPIRE_TIME_SECONDS_MAX) {
                expireSeconds = EXPIRE_TIME_SECONDS_MAX;
            }

            jedisConnection = jedisConnectionFactory.getConnection();
            Jedis jedis = (Jedis) jedisConnection.getNativeConnection();

            do {
                logger.debug("try lock key: " + key);
                logger.debug("jedis.key:=== " + jedis.get(key));

                String result = jedis.set(key, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireSeconds);

                if (LOCK_SUCCESS.equals(result)) {
                    return true;
                }

                if (waitTime == 0) {
                    break;
                }
                Thread.sleep(200);
            } while ((System.currentTimeMillis() - beginTime) < waitTime);

            return Boolean.FALSE;
        } catch (JedisConnectionException je) {
            logger.error(je.getMessage(), je);
            //returnBrokenResource(jedis);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        } finally {
            returnConnection(jedisConnection);
        }
        return Boolean.FALSE;

    }

    public void unLock(String key, String requestId) {
        key = LOCK_KEY_PREFIX + key;
        RedisConnection jedisConnection = null;
        try {
            jedisConnection = jedisConnectionFactory.getConnection();
            Jedis jedis = (Jedis) jedisConnection.getNativeConnection();

            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(key), Collections.singletonList(requestId));
//            if (RELEASE_SUCCESS.equals(result)) {
//                return true;
//            }
//            return false;

        } catch (JedisConnectionException je) {
            logger.error(je.getMessage(), je);
            //returnBrokenResource(jedis);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        } finally {
            returnConnection(jedisConnection);
        }

    }

    private void closeJedis(Jedis jedis) {//jedis.close();

        try {
            if (jedis != null) {
                jedis.close();//此方法会判断Jedis的dataSource成员不为空,不为空则将连接放回连接池
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
    }

    /**
     * spring-data-redis pool回收链接
     *
     * @param jedisConnection
     */
    private void returnConnection(RedisConnection jedisConnection) {//jedis.close();

        try {
            if (jedisConnection != null) {
                jedisConnection.close();//这个close不是真正的关闭,而是将连接放回连接池
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
    }
}

​

加锁

加锁就一行代码:jedis.set(String key, String value, String nxxx, String expx, int time),这个set()方法一共有五个形参:

  • 第一个为key,我们使用key来当锁,因为key是唯一的。

  • 第二个为value,我们传的是requestId,很多童鞋可能不明白,有key作为锁不就够了吗,为什么还要用到value?原因就是我们在上面讲到可靠性时,分布式锁要满足第四个条件解铃还须系铃人,通过给value赋值为requestId,我们就知道这把锁是哪个请求加的了,在解锁的时候就可以有依据。requestId可以使用UUID.randomUUID().toString()方法生成。

  • 第三个为nxxx,这个参数我们填的是NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作;

  • 第四个为expx,这个参数我们传的是PX,意思是我们要给这个key加一个过期的设置,具体时间由第五个参数决定。

  • 第五个为time,与第四个参数相呼应,代表key的过期时间。

解锁

jedis.eval(script, Collections.singletonList(key), Collections.singletonList(requestId))

获取锁对应的value值,检查是否与requestId相等,如果相等则删除锁(解锁)

在eval命令执行Lua代码的时候,Lua代码将被当成一个命令去执行,并且直到eval命令执行完成,Redis才会执行其他命令

 

问题

   存在集群部署的时候 集群同步有延迟 会导致出现多客户端获取锁。可以尝试使用Redisson实现分布式锁,

 

参考资料:https://wudashan.cn/2017/10/23/Redis-Distributed-Lock-Implement/

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值