主页 > 互联网  > 

13天--Redis中如何实现分布式锁?Redis的RedLock是什么?你了解吗?Redis实现分布式锁时可能

13天--Redis中如何实现分布式锁?Redis的RedLock是什么?你了解吗?Redis实现分布式锁时可能
Redis 中如何实现分布式锁? 1. 基于 SETNX 命令实现

原理:

使用 SETNX 命令(SET if Not eXists)尝试获取锁。如果键不存在,则设置成功,表示获取锁;如果键已存在,则设置失败,表示锁已被其他客户端持有。为锁设置过期时间(EX 或 PX 参数),以防止死锁。

实现代码:

import redis.clients.jedis.Jedis; public class RedisLockExample { public static void main(String[] args) { Jedis jedis = new Jedis("localhost", 6379); String lockKey = "lock_key"; String requestId = UUID.randomUUID().toString(); // 尝试获取锁 String result = jedis.set(lockKey, requestId, "NX", "EX", 10); if ("OK".equals(result)) { try { // 执行业务逻辑 } finally { // 释放锁 if (requestId.equals(jedis.get(lockKey))) { jedis.del(lockKey); } } } else { // 获取锁失败,重试或返回 } } } 2. 基于 Redisson 客户端实现

原理:

Redisson 是一个基于 Java 的 Redis 客户端,提供了高级的分布式锁功能。使用 RLock 接口实现分布式锁,支持可重入锁、锁续期(Watch Dog)等功能。

实现代码:

import org.redisson.Redisson; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.redisson.config.Config; public class RedissonLockExample { public static void main(String[] args) { Config config = new Config(); config.useSingleServer().setAddress("redis://localhost:6379"); RedissonClient redisson = Redisson.create(config); RLock lock = redisson.getLock("myLock"); try { // 尝试获取锁,最多等待 10 秒,锁持有时间为 10 秒 if (lock.tryLock(10, 10, TimeUnit.SECONDS)) { try { // 执行业务逻辑 } finally { // 释放锁 lock.unlock(); } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } 3. 基于 RedLock 算法实现

原理:

RedLock 算法通过多个独立的 Redis 主节点来实现分布式锁,确保即使部分节点故障,锁仍然安全。客户端尝试从多个 Redis 主节点获取锁,只有在大多数节点(超过半数)获取成功时,才认为获取锁成功。

实现步骤:

客户端获取当前时间戳 T1。依次向多个 Redis 主节点发起加锁请求,每个请求设置超时时间。如果在大多数节点上加锁成功,则认为获取锁成功。如果获取锁失败,释放已获取的锁。

实现代码:

import org.redisson.Redisson; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.redisson.config.Config; public class RedLockExample { public static void main(String[] args) { Config config = new Config(); config.useSingleServer().setAddress("redis://localhost:6379"); RedissonClient redisson = Redisson.create(config); RLock lock = redisson.getLock("myLock"); try { if (lock.tryLock(10, 10, TimeUnit.SECONDS)) { try { // 执行业务逻辑 } finally { // 释放锁 lock.unlock(); } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } 4. 基于 Lua 脚本实现

原理:

使用 Lua 脚本确保加锁和解锁操作的原子性,避免竞态条件。

实现代码:

import redis.clients.jedis.Jedis; public class LuaLockExample { public static void main(String[] args) { Jedis jedis = new Jedis("localhost", 6379); String lockKey = "lock_key"; String requestId = UUID.randomUUID().toString(); // 加锁 Lua 脚本 String lockScript = "if redis.call('exists', KEYS[1]) == 0 then " + "redis.call('hset', KEYS[1], ARGV[1], 1);" + "redis.call('pexpire', KEYS[1], ARGV[2]);" + "return nil;" + "elseif redis.call('hexists', KEYS[1], ARGV[1]) == 1 then " + "redis.call('hincrby', KEYS[1], ARGV[1], 1);" + "redis.call('pexpire', KEYS[1], ARGV[2]);" + "return nil;" + "end;" + "return redis.call('pttl', KEYS[1]);"; // 释放锁 Lua 脚本 String unlockScript = "if redis.call('hexists', KEYS[1], ARGV[1]) == 0 then " + "return nil;" + "end;" + "local counter = redis.call('hincrby', KEYS[1], ARGV[1], -1);" + "if counter > 0 then " + "redis.call('pexpire', KEYS[1], ARGV[2]);" + "return 0;" + "else " + "redis.call('del', KEYS[1]);" + "return 1;" + "end;" + "return nil;"; // 加锁 jedis.eval(lockScript, Collections.singletonList(lockKey), Arrays.asList(requestId, "10000")); try { // 执行业务逻辑 } finally { // 释放锁 jedis.eval(unlockScript, Collections.singletonList(lockKey), Arrays.asList(requestId, "10000")); } } } 总结 简单实现:使用 SETNX 命令结合过期时间,适用于简单场景。高级实现:使用 Redisson 客户端或 RedLock 算法,适用于复杂场景,提供更高的可靠性和安全性。原子性保证:使用 Lua 脚本确保加锁和解锁操作的原子性,避免竞态条件。

根据具体业务需求和场景选择合适的实现方式。

Redis 的 Red Lock 是什么?你了解吗? 1. Red Lock 的核心思想

Red Lock 的核心思想是通过多个独立的 Redis 实例来实现分布式锁,以提高锁的可靠性和安全性。具体步骤如下:

多个独立的 Redis 实例:假设部署了 5 个独立的 Redis 主节点,这些节点之间没有主从关系,也不进行数据同步。客户端尝试加锁:客户端依次向这些 Redis 实例发送加锁请求,每个请求设置一个较短的超时时间(远小于锁的有效时间)。多数节点加锁成功:如果客户端在大多数节点(例如 3 个或更多)上成功获取锁,并且获取锁的总时间小于锁的有效时间,则认为加锁成功。释放锁:客户端在任务完成后,向所有 Redis 实例发送释放锁的请求,无论这些节点是否成功加锁。 2. Red Lock 的实现步骤 获取当前时间戳:客户端获取当前时间戳 T1。依次向 Redis 实例发起加锁请求:客户端依次向多个 Redis 实例发送加锁请求,每个请求设置超时时间(例如 50 毫秒)。如果某个节点加锁失败(如锁被占用或网络超时),立即向下一个节点发起请求。计算获取锁的总耗时:如果客户端在大多数节点上成功获取锁,获取当前时间戳 T2,计算总耗时 T2 - T1。判断加锁是否成功:如果总耗时小于锁的有效时间,则加锁成功;否则加锁失败,客户端需要释放已获取的锁。释放锁:客户端在任务完成后,向所有 Redis 实例发送释放锁的请求,无论这些节点是否成功加锁。 3. Red Lock 的优势 高可靠性:通过多个独立的 Redis 实例,避免了单点故障的问题,即使部分节点宕机,锁仍然有效。容错性:不依赖单点 Redis 实例,避免了单点故障导致锁不可用的问题。时间一致性:使用锁的有效期来限制锁的持有时间,即使客户端异常退出,锁也会自动释放。 4. Red Lock 的劣势 复杂性:需要维护多个 Redis 实例,部署成本较高。网络延迟:加锁和释放锁的操作需要同时与多个 Redis 实例通信,延迟较单实例锁更高。时钟漂移:Red Lock 假设 Redis 节点的时钟一致。如果节点时钟漂移较大,可能导致锁的有效期计算错误。 5. Red Lock 的实际应用

Red Lock 适用于需要高可靠性和高可用性的分布式锁场景,例如分布式事务、定时任务调度、库存扣减等。在实际应用中,可以根据业务需求和系统架构,灵活地运用 Red Lock 来保护关键资源的访问。

6. Red Lock 的实现示例

以下是一个简单的 Red Lock 实现示例(使用 Lua 脚本):

-- 加锁脚本 if redis.call("SET", KEYS[1], ARGV[1], "NX", "PX", ARGV[2]) then return 1 else return 0 end -- 解锁脚本 if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) else return 0 end 7. 注意事项 锁有效期与任务执行时间:锁的有效期应比预估任务执行时间稍长,防止锁过期导致多个客户端同时持有锁。多数节点原则:Red Lock 假定大多数节点是正常工作的。如果超过一半节点同时故障,加锁可能失效。网络分区:如果发生网络分区,某些 Redis 节点不可用,加锁和解锁的成功率会降低。

Red Lock 是一种在分布式系统中实现高可靠分布式锁的解决方案,通过多个独立的 Redis 实例协同工作,确保锁的安全性和可靠性。

Redis 实现分布式锁时可能遇到的问题有哪些? 1. 锁过期与业务未完成(锁续期问题) 问题:客户端业务处理时间超过锁过期时间,导致锁提前释放,其他客户端获得锁,引发数据不一致。解决方案: 看门狗机制:启动后台线程定期(如每隔10秒)续期锁的过期时间。合理设置过期时间:根据业务处理耗时动态调整锁的过期时间(如预估时间+缓冲时间)。 2. 锁误删(非原子性释放) 问题:非原子性操作(先 GET 再 DEL)可能导致释放其他客户端的锁。解决方案:使用 Lua 脚本保证验证与删除的原子性。 3. 主从切换导致锁丢失 问题:Redis 主节点宕机后,从节点可能未同步锁信息,新主节点上锁丢失。解决方案: RedLock 算法:向多个独立 Redis 实例申请锁,当多数节点(如5个中的3个)加锁成功时,认为锁获取成功。集群模式:使用 Redis Cluster 或 Sentinel 保障高可用性。 4. 时钟漂移问题 问题:如果 Redis 服务器的时钟向前跳跃,会使锁的 Key 过早超时失效。例如,客户端 1 获取到锁后,设置 Key 的过期时间是 10:02 分,但 Redis 服务器的时钟比客户端快了 2 分钟,那么 Key 可能在 10:00 就失效了。要是此时客户端 1 还没释放锁,就会出现多个客户端同时持有同一把锁的情况。解决方案:使用 NTP 服务同步服务器时钟,减少时钟漂移的影响。 5. 单点安全问题 问题:当 Redis 集群采用单 Master 模式时,如果这台 Master 服务器宕机,所有客户端都可能无法获取到锁。为了提高可用性,会给 Master 引入 Slave 节点。但由于 Redis 的主从同步是异步进行的,可能会出现这样的情况:客户端 1 设置好锁后,Master 突然挂掉,Slave 晋升为新的 Master,而由于异步复制的特性,客户端 1 设置的锁丢失了。这时,客户端 2 也能成功设置锁,导致客户端 1 和 2 同时拥有同一把锁。解决方案:使用 Redis Cluster 或 Sentinel 保障高可用性。 6. 锁阻塞问题 问题:在实际业务中,为了实现一个分布式锁,独立部署多个 Redis 实例,整体成本直线上升。实现复杂,整体加锁效率有所降低。解决方案:使用 Redis Cluster 或 Sentinel 保障高可用性,减少独立部署多个 Redis 实例的成本。 7. 锁重入问题 问题:在秒杀场景中,假设库存有 2000 个商品可以供用户秒杀。为了防止出现超卖,通常会对库存加锁。如果有 1W 的用户竞争同一把锁,显然系统吞吐量会非常的低。解决方案:将库存分段,例如分为 100 段,每段有 20 个商品可以参与秒杀。在秒杀过程中,先获取用户 ID 的 Hash 值,然后除以 100 取模。模为 1 的用户访问第 1 段库存,模为 2 的用户访问第 2 段库存,以此类推,最后模为 100 的用户访问第 100 段库存。这样在多线程环境中,就可以大大减少锁的冲突。 8. 锁超时失效或锁提前过期问题 问题:如果线程 A 加锁成功,但由于某些原因执行耗时过长,超过锁的超时时间,这时 Redis 会自动释放线程 A 加的锁。但线程 A 还没执行完,还在对共享数据进行访问。如果此时线程 B 尝试加锁,那么也可以加锁成功,并对共享数据进行访问。这样就出现了多个线程对共享数据进行操作的问题。解决方案:如果达到了超时时间,但业务代码还没执行完,则需要给锁自动续期。可以使用 TimerTask 类来实现自动续期的功能,比如获取锁之后自动开启一个定时任务,每隔 10 秒自动刷新一次过期时间。 9. 原子操作问题 问题:使用 SETNX 命令加锁和设置锁的超时时间是分开的,并非原子操作。假如加锁成功,但设置超时时间失败了,该 lockKey 就变成永不失效。极端情况下,获取锁的客户端如果宕机了,那么就没法释放锁了。解决方案:使用 SET 命令的 NX 和 PX 选项,可以在一个原子操作中完成获取锁和设置过期时间的两个步骤。 10. 加锁失败的处理 问题:如果有两个线程同时上传文件到 SFTP,上传文件前先要创建目录,假设两个线程需要创建的目录名都是当天的日期。如果不做任何控制,直接并发创建目录,第二个线程必然会失败。如果加一个 Redis 分布式锁后在目录不存在时才进行创建,那么第二个请求加锁失败时,是返回失败,还是返回成功?解决方案:在规定的时间内,通过自旋 + 睡眠去尝试加锁。比如在规定的 500 毫秒内,不断自旋尝试加锁。如果成功,则直接返回。如果失败,则睡眠 50 毫秒,再发起新一轮加锁的尝试。如果到了超时时间还未成功加锁,则直接返回失败。 11. 锁竞争问题 问题:在高并发场景下,多个客户端同时尝试获取同一把锁,可能导致锁竞争激烈,影响系统性能。解决方案:使用读写锁或分段锁,减少锁的冲突。例如,将库存分段,每个段使用独立的锁,这样可以提高系统的并发性能。 12. RedLock 算法的问题 问题:RedLock 算法虽然提高了锁的可靠性,但实现过程复杂,且依赖多个 Redis 实例的时钟同步。如果某个实例的时钟发生漂移,可能导致锁提前失效,进而引发并发问题。解决方案:使用 Redis Cluster 或 Sentinel 保障高可用性,减少对多个 Redis 实例的依赖。

通过以上解决方案,可以有效应对 Redis 分布式锁在实际应用中可能遇到的问题,提高系统的稳定性和可靠性。

标签:

13天--Redis中如何实现分布式锁?Redis的RedLock是什么?你了解吗?Redis实现分布式锁时可能由讯客互联互联网栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“13天--Redis中如何实现分布式锁?Redis的RedLock是什么?你了解吗?Redis实现分布式锁时可能