Redis 如何用SETNX实现分布式锁

利用Redis的单线程特性,在多个Redis客户端同时通过SETNX命令尝试获取锁,如果返回1表示获取锁成功,否则表示获取锁失败。只会有一个客户端成功获取到锁,而其他客户端则会失败。如果获取锁成功,则设置一个过期时间,防止该客户端挂了之后一直持有该锁。客户端释放锁的时候,需要先判断该锁是否仍然属于该客户端,如果是,则通过DEL命令释放锁。

public class RedisDistributedLock {
    private final JedisPool jedisPool;

    public RedisDistributedLock(JedisPool jedisPool) {
        this.jedisPool = jedisPool;
    }

    public boolean tryLock(String lockKey, String requestId, int expireTime) {
        try (Jedis jedis = jedisPool.getResource()) {
            //其中:
            //lockKey:锁的标识
            //requestId:请求 id
            //NX:只在键不存在时,才对键进行设置操作。 也就是SETNX命令
            //PX:设置键的过期时间为 millisecond 毫秒。
            //expireTime:过期时间
            String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
            return "OK".equals(result);
        }
    }

    public boolean unlock(String lockKey, String requestId) {
        try (Jedis jedis = jedisPool.getResource()) {
            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));
            return Long.parseLong(result.toString()) == 1L;
        }
    }
}

tryLock方法

接收三个参数,分别是锁的键值lockKey、加锁的请求标识requestId和锁的过期时间expireTime。该方法会尝试使用Redis的set命令加锁,如果加锁成功则返回true,否则返回false。其中NX表示只在锁的键不存在时设置锁,PX表示锁的过期时间为expireTime毫秒。

unlock方法

接收两个参数,分别是锁的键值lockKey和加锁的请求标识requestId。该方法会执行一个Lua脚本,判断当前锁的值是否等于请求标识requestId,如果是则删除锁并返回true,否则返回false。该方法使用eval命令执行Lua脚本,传入锁的键值和请求标识两个参数,返回值是执行结果。


优点

  1. 实现简单:SETNX命令实现简单,易于理解和使用。
  2. 性能较高:由于SETNX命令的执行原子性,保证了分布式锁的正确性,而且在Redis中,SETNX命令是单线程执行的,所以性能较高。

缺点

  1. 锁无法续期:如果加锁方在加锁后的执行时间较长,而锁的超时时间设置的较短,可能导致锁被误释放。
  2. 无法避免死锁:如果加锁方在加锁后未能及时解锁,且该客户端崩溃,可能导致死锁。
  3. 存在竞争:由于SETNX命令是对Key的操作,所以在高并发情况下,多个客户端之间仍可能存在竞争,从而影响性能。
RedisSETNX命令用于在键不存在时设置键值对,如果键已经存在,则不做任何操作。利用SETNX命令可以实现简单的分布式锁。以下是一个使用SETNX实现分布式锁的示例代码: ```python import redis import time def acquire_lock(lock_name, acquire_timeout=10, lock_timeout=10): # 创建redis连接 redis_conn = redis.Redis() # 生成唯一的锁标识 lock_key = f"lock:{lock_name}" # 获取当前时间戳,用于计算锁超时时间 timestamp = int(time.time()) + acquire_timeout # 循环尝试获取锁 while int(time.time()) < timestamp: # 尝试获取锁 if redis_conn.setnx(lock_key, "locked"): # 设置锁超时时间 redis_conn.expire(lock_key, lock_timeout) return True # 短暂休眠,避免频繁尝试获取锁 time.sleep(0.1) return False def release_lock(lock_name): # 创建redis连接 redis_conn = redis.Redis() # 生成唯一的锁标识 lock_key = f"lock:{lock_name}" # 删除锁 redis_conn.delete(lock_key) ``` 在上述示例代码中,`acquire_lock`函数用于获取分布式锁,`release_lock`函数用于释放分布式锁。具体实现过程如下: 1. 创建Redis连接。 2. 生成唯一的锁标识,一般以`lock:`为前缀加上具体的锁名。 3. 计算获取锁的超时时间戳,即当前时间戳加上获取锁的超时时间。 4. 循环尝试获取锁,如果成功获取到锁,则设置锁的超时时间,并返回True;如果超过超时时间仍未获取到锁,则返回False。 5. 释放锁的过程比较简单,直接删除对应的锁标识即可。 需要注意的是,分布式锁实现还需要考虑异常情况下的处理、防止锁被误释放等问题,上述代码仅作为示例,具体应用场景中可能需要根据实际需求进行适当修改。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值