Redis系列学习文章分享---第六篇(Redis实战篇--Redis分布式锁+实现思路+误删问题+原子性+lua脚本+Redisson功能介绍+可重入锁+WatchDog机制+multiLock)

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: Redis系列学习文章分享---第六篇(Redis实战篇--Redis分布式锁+实现思路+误删问题+原子性+lua脚本+Redisson功能介绍+可重入锁+WatchDog机制+multiLock)

Redis的实战篇-分布式锁

1. 分布式锁-基本原理和不同实现方式对比

1.1 基本原理

分布式锁是用于在分布式环境中控制共享资源访问的一种机制。其基本原理是利用某种方式确保在同一时刻只有一个客户端能够获得锁,从而避免多个客户端同时修改共享资源导致的数据不一致问题。

1.2 不同实现方式对比

常见的分布式锁实现方式包括基于数据库、基于ZooKeeper、基于Redis等。各种实现方式在性能、可靠性、易用性等方面有所不同,需要根据具体场景选择适合的方式。

2. 分布式锁-Redis的分布式锁实现思路

2.1 实现思路

在Redis中,可以利用SETNX(SET if Not eXists)命令来实现分布式锁。该命令可以原子性地设置一个键的值,当键不存在时才会进行设置,因此可以用来实现锁的加锁操作。

2.2 代码示例

String lockKey = "lock_key";
String requestId = UUID.randomUUID().toString();
boolean lock = jedis.set(lockKey, requestId, "NX", "PX", expireTime) != null;

以上代码使用了Java的Jedis客户端来操作Redis。首先生成一个唯一的requestId作为锁的标识,然后使用set命令尝试给lockKey加锁,设置NX参数表示仅在键不存在时才会设置成功,设置PX参数表示设置过期时间。如果加锁成功,返回true;否则返回false。

3. 分布式锁-实现Redis分布式锁版本

3.1 实现方式

基于Redis的单机实例或集群实现分布式锁,通常会使用SET命令结合EXPIRE或PSETEX命令来设置锁的超时时间,以防止锁被永久占用。

3.2 示例代码

String lockKey = "lock_key";
String requestId = UUID.randomUUID().toString();
boolean lock = jedis.set(lockKey, requestId, "NX", "PX", expireTime) != null;

在上述代码中,expireTime是锁的过期时间,单位为毫秒。当加锁成功后,通过设置锁的过期时间,可以确保即使出现异常情况导致锁未能及时释放,也不会永久占用锁资源。

4. 分布式锁-Redis分布式锁误删问题

4.1 问题分析

在使用Redis实现分布式锁时,可能会遇到误删锁的问题。这种情况通常发生在锁的过期时间内,业务执行时间较长导致锁被自动释放,然后其他客户端又获取到了相同的锁。

4.2 解决方案

为了解决误删锁的问题,可以使用Lua脚本来确保释放锁的原子性,即在删除锁之前先检查锁的持有者是否为当前客户端。

4.3 代码示例

String lockKey = "lock_key";
String requestId = UUID.randomUUID().toString();
String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                  "   return redis.call('del', KEYS[1]) " +
                  "else " +
                  "   return 0 " +
                  "end";
Object result = jedis.eval(luaScript, Collections.singletonList(lockKey), Collections.singletonList(requestId));

上述代码使用Lua脚本来判断当前锁的持有者是否为当前请求的客户端,如果是则删除锁,否则不执行任何操作。这样可以确保释放锁的原子性,避免误删锁的问题。

5. 分布式锁-解决Redis分布式锁误删问题

5.1 解决方案

除了使用Lua脚本来确保释放锁的原子性外,还可以通过为锁设置唯一的标识符来避免误删锁的问题。每次获取锁时,都为锁设置一个唯一的标识符,释放锁时只有持有相同标识符的客户端才能释放锁。

5.2 代码示例

String lockKey = "lock_key";
String requestId = UUID.randomUUID().toString();
// 加锁
boolean lock = jedis.set(lockKey, requestId, "NX", "PX", expireTime) != null;
// 解锁
String currentRequestId = jedis.get(lockKey);
if (requestId.equals(currentRequestId)) {
    jedis.del(lockKey);
}

在上述代码中,加锁时为锁设置了唯一的requestId作为标识符,解锁时通过比较当前锁的标识符和请求的标识符来确保只有持有相同标识符的客户端才能释放锁。

6. 分布式锁-分布式锁的原子性问题

6.1 问题分析

在分布式环境下,多个客户端同时尝试获取同一个锁时,可能会出现竞争情况,导致锁的获取和释放不具备原子性,进而引发一系列问题。

6.2 解决方案

为了保证分布式锁的原子性,需要采用一种原子操作方式来实现锁的获取和释放。这里可以利用Redis的SETNX(SET if Not eXists)命令,该命令可以在键不存在时设置键的值,如果键已经存在,则不进行任何操作。

6.3 代码示例
String lockKey = "lock_key";
String requestId = UUID.randomUUID().toString();
// 尝试获取锁
boolean lockAcquired = jedis.setnx(lockKey, requestId) == 1;
// 设置锁的过期时间
if (lockAcquired) {
    jedis.expire(lockKey, expireTime);
}
// 处理业务逻辑
// 释放锁
if (lockAcquired) {
    jedis.del(lockKey);
}

在上述代码中,通过SETNX命令尝试获取锁,并使用expire命令设置锁的过期时间,确保即使在获取锁后发生异常或程序意外退出时,锁也能够自动释放,避免死锁情况的发生。

7. 分布式锁-Lua脚本解决多条命令原子性问题

7.1 解决方案

为了保证多条命令的原子性操作,可以使用Lua脚本来将多个命令封装成一个原子操作,确保在执行期间不会被其他客户端中断。

7.2 代码示例
String lockKey = "lock_key";
String requestId = UUID.randomUUID().toString();
String luaScript = "if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then " +
                   "   redis.call('expire', KEYS[1], ARGV[2]) " +
                   "   return true " +
                   "else " +
                   "   return false " +
                   "end";
Boolean lockAcquired = (Boolean) jedis.eval(luaScript, Collections.singletonList(lockKey),
                                            Arrays.asList(requestId, String.valueOf(expireTime)));

在上述代码中,通过Lua脚本封装了SETNX和EXPIRE命令,确保了获取锁和设置过期时间的原子性操作,从而避免了多个命令执行过程中的竞争情况。

8. 分布式锁-Java调用1ua脚本改造分布式锁

8.1 解决方案

在Java中,可以通过使用Jedis客户端执行Lua脚本来实现对Redis中分布式锁的获取和释放操作。这样可以保证多个Redis命令的原子性执行,进而确保分布式锁的正确性和可靠性。

8.2 代码示例
String lockKey = "lock_key";
String requestId = UUID.randomUUID().toString();
String luaScript = "if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then " +
                   "   redis.call('expire', KEYS[1], ARGV[2]) " +
                   "   return true " +
                   "else " +
                   "   return false " +
                   "end";
// 调用Lua脚本执行分布式锁获取操作
Boolean lockAcquired = (Boolean) jedis.eval(luaScript, Collections.singletonList(lockKey),
                                            Arrays.asList(requestId, String.valueOf(expireTime)));
// 处理业务逻辑
// 释放锁
if (lockAcquired) {
    jedis.del(lockKey);
}

在上述代码中,通过调用jedis.eval方法执行Lua脚本,实现了对Redis中分布式锁的获取和释放操作,并确保了原子性执行。

9. 分布式锁-Redisson功能介绍

9.1 Redisson简介

Redisson是一个基于Redis的Java驻内存数据网格(In-Memory Data Grid)和分布式锁服务的框架,提供了丰富的分布式对象和服务支持。它封装了Redis的分布式对象和服务,简化了Java应用程序对Redis的操作。

9.2 Redisson功能特点
  • 提供了分布式对象(Distributed Objects)和服务(Distributed Services)的API,包括分布式锁、分布式集合、分布式映射等。
  • 内置了多种分布式锁实现,包括可重入锁、公平锁、联锁等,满足不同场景下的需求。
  • 支持异步和响应式编程模型,提供了丰富的异步操作API。
  • 提供了基于事件通知机制的分布式消息队列,支持发布/订阅模式和点对点模式。
9.3 Redisson快速入门
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.15.5</version>
</dependency>
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
RLock lock = redisson.getLock("myLock");
lock.lock();
try {
    // 执行业务逻辑
} finally {
    lock.unlock();
}

在上述代码中,首先通过Maven添加Redisson依赖,然后创建Redisson客户端实例,通过调用getLock方法获取分布式锁实例,最后通过lock和unlock方法实现对分布式锁的获取和释放操作。

10. 分布式锁-Redisson快速入门

10.1 Redisson简介

Redisson是一个基于Redis的Java驻内存数据网格(In-Memory Data Grid)和分布式锁服务的框架,提供了丰富的分布式对象和服务支持。它封装了Redis的分布式对象和服务,简化了Java应用程序对Redis的操作。

10.2 Redisson功能特点
  • 提供了分布式对象(Distributed Objects)和服务(Distributed Services)的API,包括分布式锁、分布式集合、分布式映射等。
  • 内置了多种分布式锁实现,包括可重入锁、公平锁、联锁等,满足不同场景下的需求。
  • 支持异步和响应式编程模型,提供了丰富的异步操作API。
  • 提供了基于事件通知机制的分布式消息队列,支持发布/订阅模式和点对点模式。
10.3 Redisson快速入门
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.15.5</version>
</dependency>
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
RLock lock = redisson.getLock("myLock");
lock.lock();
try {
    // 执行业务逻辑
} finally {
    lock.unlock();
}

在上述代码中,首先通过Maven添加Redisson依赖,然后创建Redisson客户端实例,通过调用getLock方法获取分布式锁实例,最后通过lock和unlock方法实现对分布式锁的获取和释放操作。

感谢您阅读本文,希望对您了解Redis在分布式锁实现方面有所帮助。如有任何问题或建议,请随时在评论区留言。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
打赏
0
0
0
0
55
分享
相关文章
Redis 实操要点:Java 最新技术栈的实战解析
本文介绍了基于Spring Boot 3、Redis 7和Lettuce客户端的Redis高级应用实践。内容包括:1)现代Java项目集成Redis的配置方法;2)使用Redisson实现分布式可重入锁与公平锁;3)缓存模式解决方案,包括布隆过滤器防穿透和随机过期时间防雪崩;4)Redis数据结构的高级应用,如HyperLogLog统计UV和GeoHash处理地理位置。文章提供了详细的代码示例,涵盖Redis在分布式系统中的核心应用场景,特别适合需要处理高并发、分布式锁等问题的开发场景。
124 38
高并发秒杀系统实战(Redis+Lua分布式锁防超卖与库存扣减优化)
秒杀系统面临瞬时高并发、资源竞争和数据一致性挑战。传统方案如数据库锁或应用层锁存在性能瓶颈或分布式问题,而基于Redis的分布式锁与Lua脚本原子操作成为高效解决方案。通过Redis的`SETNX`实现分布式锁,结合Lua脚本完成库存扣减,确保操作原子性并大幅提升性能(QPS从120提升至8,200)。此外,分段库存策略、多级限流及服务降级机制进一步优化系统稳定性。最佳实践包括分层防控、黄金扣减法则与容灾设计,强调根据业务特性灵活组合技术手段以应对高并发场景。
332 7
|
2月前
|
redis分布式锁在高并发场景下的方案设计与性能提升
本文探讨了Redis分布式锁在主从架构下失效的问题及其解决方案。首先通过CAP理论分析,Redis遵循AP原则,导致锁可能失效。针对此问题,提出两种解决方案:Zookeeper分布式锁(追求CP一致性)和Redlock算法(基于多个Redis实例提升可靠性)。文章还讨论了可能遇到的“坑”,如加从节点引发超卖问题、建议Redis节点数为奇数以及持久化策略对锁的影响。最后,从性能优化角度出发,介绍了减少锁粒度和分段锁的策略,并结合实际场景(如下单重复提交、支付与取消订单冲突)展示了分布式锁的应用方法。
175 3
|
2月前
|
从扣减库存场景来讲讲redis分布式锁中的那些“坑”
本文从一个简单的库存扣减场景出发,深入分析了高并发下的超卖问题,并逐步优化解决方案。首先通过本地锁解决单机并发问题,但集群环境下失效;接着引入Redis分布式锁,利用SETNX命令实现加锁,但仍存在死锁、锁过期等隐患。文章详细探讨了通过设置唯一标识、续命机制等方法完善锁的可靠性,并最终引出Redisson工具,其内置的锁续命和原子性操作极大简化了分布式锁的实现。最后,作者剖析了Redisson源码,揭示其实现原理,并预告后续关于主从架构下分布式锁的应用与性能优化内容。
113 0
Redis的Lua脚本有什么作用?
Redis Lua脚本用于减少网络开销、实现原子操作及扩展指令集。它能合并操作降低网络延迟,保证原子性,替代不支持回滚的事务。通过脚本,代码复用率提高,且可自定义指令,如实现分布式锁,增强Redis功能和灵活性。
405 1
【📕分布式锁通关指南 03】通过Lua脚本保证redis操作的原子性
本文介绍了如何通过Lua脚本在Redis中实现分布式锁的原子性操作,避免并发问题。首先讲解了Lua脚本的基本概念及其在Redis中的使用方法,包括通过`eval`指令执行Lua脚本和通过`script load`指令缓存脚本。接着详细展示了如何用Lua脚本实现加锁、解锁及可重入锁的功能,确保同一线程可以多次获取锁而不发生死锁。最后,通过代码示例演示了如何在实际业务中调用这些Lua脚本,确保锁操作的原子性和安全性。
279 6
【📕分布式锁通关指南 03】通过Lua脚本保证redis操作的原子性
Redis 功能扩展 Lua 脚本 对Redis扩展 eval redis.call redis.pcall
通过本文的介绍,我们详细讲解了 Lua 脚本在 Redis 中的作用、`eval` 命令的使用方法以及 `redis.call` 和 `redis.pcall` 的区别和用法。通过合理使用 Lua 脚本,可以实现复杂的业务逻辑,确保操作的原子性,并减少网络开销,从而提高系统的性能和可靠性。
179 13
|
7月前
|
公司用什么软件监控电脑:Lua 脚本在监控软件扩展功能的应用
在企业环境中,电脑监控软件对保障信息安全、提升效率至关重要。Lua 脚本在此类软件中用于扩展功能,如收集系统信息、监控软件使用时长及文件操作,向指定服务器发送数据,支持企业管理和运营。
111 6
大数据-43 Redis 功能扩展 Lua 脚本 对Redis扩展 eval redis.call redis.pcall
大数据-43 Redis 功能扩展 Lua 脚本 对Redis扩展 eval redis.call redis.pcall
102 2
如何使用 Lua 脚本进行更复杂的网络请求,比如 POST 请求?
如何使用 Lua 脚本进行更复杂的网络请求,比如 POST 请求?

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等