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

本文涉及的产品
云原生内存数据库 Tair,内存型 2GB
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Redis 版,经济版 1GB 1个月
简介: 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
相关文章
|
2天前
|
存储 NoSQL Redis
Redis系列学习文章分享---第十六篇(Redis原理1篇--Redis数据结构-动态字符串,insert,Dict,ZipList,QuickList,SkipList,RedisObject)
Redis系列学习文章分享---第十六篇(Redis原理1篇--Redis数据结构-动态字符串,insert,Dict,ZipList,QuickList,SkipList,RedisObject)
10 1
|
2天前
|
存储 NoSQL 安全
Redis系列学习文章分享---第十五篇(Redis最佳实践--设计优雅的key+合适的数据结构+持久化如何配置+慢查询问题解决)
Redis系列学习文章分享---第十五篇(Redis最佳实践--设计优雅的key+合适的数据结构+持久化如何配置+慢查询问题解决)
7 1
|
2天前
|
缓存 负载均衡 NoSQL
Redis系列学习文章分享---第十四篇(Redis多级缓存--封装Http请求+向tomcat发送http请求+根据商品id对tomcat集群负载均衡)
Redis系列学习文章分享---第十四篇(Redis多级缓存--封装Http请求+向tomcat发送http请求+根据商品id对tomcat集群负载均衡)
8 1
|
2天前
|
NoSQL Java Redis
Redis系列学习文章分享---第十八篇(Redis原理篇--网络模型,通讯协议,内存回收)
Redis系列学习文章分享---第十八篇(Redis原理篇--网络模型,通讯协议,内存回收)
10 0
|
2天前
|
存储 消息中间件 缓存
Redis系列学习文章分享---第十七篇(Redis原理篇--数据结构,网络模型)
Redis系列学习文章分享---第十七篇(Redis原理篇--数据结构,网络模型)
7 0
|
5天前
|
NoSQL Redis
redis分布式锁redisson
底层会尝试去加锁,如果加锁失败,会睡眠,自旋加锁,直到获取到锁为止。
11 1
|
3天前
|
NoSQL 算法 Java
技术好文:Redis实现分布式锁的7种方案
技术好文:Redis实现分布式锁的7种方案
|
16天前
|
NoSQL 算法 Java
探讨redis分布式锁
探讨redis分布式锁
19 1
|
23天前
|
缓存 NoSQL 安全
玩转Redis!非常强大的Redisson分布式集合,少写60%代码
Redisson是Java的Redis客户端,提供实时数据平台服务,简化了分布式环境下的数据管理。它包含RList、RSet、RMap等分布式集合,支持ConcurrentMap和Set接口,确保线程安全和数据一致性。例如,RMap实现了本地缓存和监听器功能,允许数据监听和本地加速读取。此外,还提供了RSet的排序和去重功能,以及RQueue和RBlockingQueue等队列实现,支持阻塞操作。通过Redisson,开发者能轻松处理分布式系统的数据同步和操作。
|
2天前
|
NoSQL Java Redis
通过Redis 实现分布式锁_利用Jedis 客户端
通过Redis 实现分布式锁_利用Jedis 客户端