分布式环境下的锁机制:Redis与Redisson的应用探讨

打印 上一主题 下一主题

主题 883|帖子 883|积分 2649

目录
一、回首分布式锁
(一)理解分布式锁的定义
(二)分布式锁的约束条件
(三)分布式锁常见实现方式
基于数据库的分布式锁
基于缓存的分布式锁
基于分布式同等性算法的分布式锁
基于文件系统的分布式锁
基于消息队列的分布式锁
基于第三方服务的分布式锁
二、分布式锁Redis原理
(一)Redis分布式锁的根本原理总揽
(二)核心指令:加锁
示例加锁参数解析
(三)核心指令:解锁
(四)错误案例分析:setNx
(五)常见解锁方案:通过Lua脚本解锁+利用Redis事件功能
通过Lua脚本实验解锁
利用Redis事件功能
(六)重点题目关注
三、Redisson分布式锁
(一)Redisson分布式锁-可重入锁
(二)Redisson分布式锁-公平锁(Fair Lock)
(三)Redisson分布式锁-联锁
(四)Redisson分布式锁-红锁(RedLock)
(五)Redisson分布式锁-读写锁(ReadWriteLock)
四、总结

干货分享,感谢您的阅读!
在这个高度互联的数字时代,步调员们就像是在一场无尽的“抓娃娃机”游戏中,努力争取每一个想要的对象——无论是数据、资源还是服务。可是,和抓娃娃机一样,如果没有一些计谋,终极的结果常常是白手而归。于是,分布式锁如同一位英俊的骑士,骑着“Redis”这匹快速的战马,挺身而出,帮助我们在并发的世界中安然无恙。
想象一下,如果没有分布式锁,多个步调就像一群不速之客挤在同一间派对上,每个人都想要那个“终极的披萨”,结果只会引发一场激烈的争取战,而我们都知道,终极赢家通常是最爱抢食的那位——大概是派对的保安!而Redisson,则是我们掌控派对秩序的超等助手,确保每一位客人都能有序享用美食而不至于打乱氛围。
以是,准备好迎接这场关于分布式锁的奇妙路程了吗?让我们一同探讨Redis和Redisson的世界,发现怎样在步调的派对中优雅地舞动,确保没有人会错过任何鲜味的披萨!
一、回首分布式锁

(一)理解分布式锁的定义

分布式锁是一种在分布式盘算环境中用于控制多个节点(或多个进程)对共享资源的访问的机制。在分布式系统中,多个节点可能必要协调对共享资源的访问,以防止数据的不同等性或冲突。分布式锁允许多个节点在竞争访问共享资源时进行同步,以确保只有一个节点能够得到锁,从而制止冲突和数据损坏。以下是一些关键概念和理解:
   :锁是一种同步机制,它可以被获取和释放。当一个节点得到锁时,它可以实验必要访问共享资源的操作,其他节点必须等待直到锁被释放才气得到锁。
  分布式环境:在分布式系统中,多个节点分布在差异的物理位置或盘算机上,它们通过网络相互通信。这增长了在多个节点之间协调共享资源访问的复杂性。
  锁的种类
  

  • 互斥锁:在分布式环境中,互斥锁确保在任何给定时刻只有一个节点可以持有锁。其他节点必须等待锁被释放。
  • 读写锁:允许多个节点同时读取共享资源,但只允许一个节点写入共享资源。这可以提高并发性能,但必要更复杂的管理。
  锁的实现方式:分布式锁可以利用差异的实现方式,如基于数据库、基于缓存、基于分布式同等性算法(例如ZooKeeper或etcd)等。
  死锁和性能题目:在计划和利用分布式锁时,必要思量到死锁(当多个节点相互等待锁释放而无法继续实验)和性能题目(锁争取可能导致性能降落)。
  分布式锁的主要目标是确保在分布式系统中对共享资源的访问是有序和安全的,从而制止数据不同等性和冲突。然而,分布式锁的计划和管理必要仔细思量,以确保高可用性、性能和可伸缩性。在实际应用中,通常会根据详细的需求和环境选择得当的分布式锁实现方式。
(二)分布式锁的约束条件

在计划和实现分布式锁时,必要思量一些约束条件,以确保锁的正确性和可用性。以下是一些常见的分布式锁的约束条件:

差异的分布式锁实现方式(如基于数据库、基于缓存、基于分布式同等性算法等)可能在满意这些约束条件时有差异的优缺点。在选择分布式锁实现方式时,必要根据详细的应用需求和性能要求来衡量这些约束条件。同时,为了确保分布式锁的正确性,必要进行严格的测试和验证。
(三)分布式锁常见实现方式

分布式锁可以利用多种差异的实现方式,每种方式都有其适用的场景和特点。以下是一些常见的分布式锁实现方式:(也可以见分布式锁实现方式分析-CSDN博客)
基于数据库的分布式锁



  • 利用数据库的行级锁或乐观锁来实现分布式锁。
  • 优点:可靠性高,容易理解和管理。
  • 缺点:性能可能受到数据库访问的延迟影响,不适用于高并发场景
基于缓存的分布式锁



  • 利用分布式缓存(如Redis或Memcached)来存储锁状态
  • 优点:性能较高,适用于高并发场景
  • 缺点:可能存在缓存故障或数据不同等性题目。
基于分布式同等性算法的分布式锁



  • 利用分布式同等性算法(如ZooKeeper或etcd)来实现锁。
  • 优点:可靠性高,适用于复杂的分布式环境
  • 缺点:性能较低,不适用于高吞吐量的场景
基于文件系统的分布式锁



  • 利用共享文件系统(如NFS)或分布式文件系统(如HDFS)来创建锁文件
  • 优点:易于理解和维护。
  • 缺点:性能可能受到文件系统的延迟影响,不适用于高并发场景
基于消息队列的分布式锁



  • 利用分布式消息队列(如Kafka或RabbitMQ)来协调锁状态
  • 优点:支持分布式异步操作,适用于特定场景。
  • 缺点:必要谨慎处置惩罚消息队列中的消息重复和丢失题目
基于第三方服务的分布式锁



  • 利用专门的分布式锁服务(如Redlock、Curator等)来管理锁。
  • 优点:可靠性高,提供了一些高级功能。
  • 缺点:通常必要引入额外的依靠
差异的实现方式适用于差异的应用场景和性能要求。选择合适的分布式锁实现方式时,必要思量系统的可靠性、性能、复杂性和维护成本等因素。别的,在利用分布式锁时,也必要注意处置惩罚死锁、超时、主动释放等题目,以确保锁的正确性和可用性。
二、分布式锁Redis原理

Redis的分布式锁实现通常基于两个主要下令:SET和EXPIRE,团结一些原子性操作,如NX(只在键不存在时设置键的值)。
(一)Redis分布式锁的根本原理总揽

获取锁


  • 客户端利用SET下令实验在Redis中设置一个特定的键,这个键通常被视为锁的名称。
  • 为了确保锁是独占的,客户端通常会利用NX选项,只有在该键不存在时才气设置乐成。
  • 客户端可以在SET下令中设置一个带有超时时间的参数,这个时间决定了锁的有效期。
锁超机遇制


  • 为了制止锁被长时间持有,客户端在SET下令中设置了锁的超时时间
  • Redis允许利用EXPIRE下令来设置键的逾期时间,这样纵然客户端在释放锁时出现题目,也会在一段时间后主动释放锁。
释放锁


  • 当客户端完成对共享资源的操作后,它可以利用DEL下令来删除锁键,从而释放锁。
  • 由于DEL是一个原子操作,确保了释放锁的安全性。
处置惩罚竞争条件


  • 如果多个客户端同时实验获取锁,只有一个客户端能够乐成设置锁,其余客户端会失败。
  • 失败的客户端通常会通过轮询或其他方式等待锁的释放。
续约锁


  • 为了防止因为客户端实验时间过长导致锁的逾期,客户端可以定期续约锁
  • 客户端可以通过重置锁的超时时间(利用EXPIRE下令)来实现续约。
必要注意的是,Redis的分布式锁虽然简朴,但也有一些潜在的题目必要处置惩罚,例如:
   

  • 锁的逾期时间必要谨慎设置,以免长时间锁定资源。
  • 客户端在获取锁后发生崩溃或异常环境时,必要确保锁能够主动释放。
  • 客户端必要小心处置惩罚续约机制,以防止死锁或其他题目。
  总之,Redis分布式锁是一种轻量级的实现方式,适用于某些场景。但在高并发和复杂的分布式环境中,可能必要更复杂的分布式锁实现方式来满意更高的可靠性和性能要求。
(二)核心指令:加锁

   SET KEY VALUE [EX seconds] [PX milliseconds] [NX|XX]
  对于利用Redis实现分布式锁,你可以利用SET下令的以下选项来进行加锁操作:


  • KEY:锁的名称,通常是一个字符串。
  • VALUE:锁的值,通常是一个唯一标识符或随机字符串,用于标识持有锁的客户端。
  • EX seconds:可选参数,设置锁的逾期时间(以秒为单位)。锁在指定的秒数后会主动逾期释放。
  • PX milliseconds:可选参数,设置锁的逾期时间(以毫秒为单位)。锁在指定的毫秒数后会主动逾期释放。
  • NX:可选参数,表示只有在键不存在时才气设置乐成,用于确保锁是独占的
  • XX:可选参数,表示只有在键已经存在时才气设置乐成,用于更新锁的值或延续锁的逾期时间
示例加锁参数解析

  1. SET lock_name my_random_value NX PX 30000
复制代码


  • lock_name,即分布式锁的名称,对于 Redis 而言,lock_name 就是 Key-Value 中的 Key且具有唯一性。
  • my_random_value,由客户端生成的一个随机字符串,它要保证在足够长的一段时间内,且在所有客户端的所有获取锁的请求中都是唯一的,用于唯一标识锁的持有者。
  • NX 表示只有当 lock_name(key) 不存在的时间才气 SET 乐成,从而保证只有一个客户端能得到锁,而其它客户端在锁被释放之前都无法得到锁。
  • PX 30000 表示这个锁节点有一个 30 秒的主动逾期时间(目的是为了防止持有锁的客户端故障后,无法主动释放锁而导致死锁,因此要求锁的持有者必须在逾期时间之内实验完相干操作并释放锁)。
(三)核心指令:解锁

   del lock_name
  

  • 在加锁时为锁设置逾期时间,当逾期时间到达,Redis 会主动删除对应的 Key-Value,从而制止死锁。
  • 正常实验完毕,未到达锁逾期时间,通过del lock_name主动释放锁。
  • 注意,这个逾期时间必要团结详细业务综合评估设置,以保证锁的持有者能够在逾期时间之内实验完相干操作并释放锁。
(四)错误案例分析:setNx

  1.         Jedis jedis = jedisPool.getResource();
  2.         // 如果锁不存在则进行加锁
  3.         Long lockResult = jedis.setnx(lockName, myRandomValue);
  4.         if (lockResult == 1) {
  5.             // 设置锁过期时间,加锁和设置过期时间是两步完成的,非原子操作
  6.             jedis.expire(lockName, expireTime);
  7.         }
复制代码
代码利用SETNX和EXPIRE下令来实现分布式锁的方式,虽然看似可行,但确实存在一定的题目,特别是在异常环境下,可能会导致死锁。让我重新梳理一下这个题目:
题目描述

  • 利用SETNX下令实验获取锁。
  • 如果SETNX乐成,表示锁被乐成获取,接着利用EXPIRE来设置锁的逾期时间。
  • 如果在设置逾期时间时发生异常,锁就会一直存在,无法主动释放。
这个题目的核心在于SETNX和EXPIRE两个下令并没有原子性地组合在一起。如果在第2步和第3步之间发生了异常,就会导致锁没有逾期时间,进而可能导致死锁。
解决方案
为了确保锁的安全性,必要将获取锁和设置逾期时间这两个操作原子化。可以利用SET下令的NX和EX选项来一次性完成这两个操作,以制止出现题目。下面是示例代码:
  1. Jedis jedis = jedisPool.getResource();
  2. String lockResult = jedis.set(lockName, myRandomValue, "NX", "EX", expireTime);
  3. if ("OK".equals(lockResult)) {
  4.     // 锁获取成功
  5.     // 进行业务操作
  6.     // ...
  7.     // 业务完成后,释放锁
  8.     jedis.del(lockName);
  9. } else {
  10.     // 锁获取失败
  11.     // 可以进行重试或其他处理
  12. }
复制代码
在这个示例中,SET下令的选项"NX"表示只有在键不存在时才气设置乐成,"EX"表示设置键的逾期时间。这样可以确保获取锁和设置逾期时间是一个原子操作,从而制止了在异常环境下出现死锁题目。
总之,确保分布式锁的获取和释放是原子操作是非常重要的,以确保锁的正确性和可用性。利用SET下令的组合选项可以简化代码并制止一些潜在的题目。
(五)常见解锁方案:通过Lua脚本解锁+利用Redis事件功能

通过Lua脚本实验解锁

要利用Lua脚本实现可靠的分布式锁解锁,可以编写一个Lua脚本,该脚本在实验时会查抄锁的值是否与预期值匹配,并且只有在匹配时才会删除锁。这确保了只有持有锁的客户端才气乐成解锁。
以下是一个示例Lua脚本,用于解锁:
  1. if redis.call("GET", KEYS[1]) == ARGV[1] then
  2.    return redis.call("DEL", KEYS[1])
  3. else
  4.    return 0
  5. end
复制代码
在这个脚本中,KEYS[1]表示锁的键,ARGV[1]表示你持有的锁的值。脚本起首查抄锁的值是否与预期值匹配(即查抄锁是否仍然由当前客户端持有),如果匹配,则利用DEL下令来删除锁,然后返回1表示解锁乐成,否则返回0表示解锁失败。
在Java中,可以利用Jedis库实验Lua脚本:
  1. import redis.clients.jedis.Jedis;
  2. import redis.clients.jedis.JedisPool;
  3. import redis.clients.jedis.JedisPoolConfig;
  4. public class RedisLockUnlock {
  5.     private static final String LOCK_KEY = "my_lock_key";
  6.     private static final String LOCK_VALUE = "my_lock_value";
  7.     public static void main(String[] args) {
  8.         JedisPool jedisPool = new JedisPool(new JedisPoolConfig(), "localhost", 6379);
  9.         try (Jedis jedis = jedisPool.getResource()) {
  10.             String luaScript = "if redis.call('GET', KEYS[1]) == ARGV[1] then " +
  11.                                "return redis.call('DEL', KEYS[1]) " +
  12.                                "else " +
  13.                                "return 0 " +
  14.                                "end";
  15.             
  16.             String result = (String) jedis.eval(luaScript, 1, LOCK_KEY, LOCK_VALUE);
  17.             if ("1".equals(result)) {
  18.                 System.out.println("Lock released successfully.");
  19.             } else {
  20.                 System.out.println("Failed to release lock. Lock may no longer be owned by this client.");
  21.             }
  22.         } catch (Exception e) {
  23.             e.printStackTrace();
  24.         } finally {
  25.             jedisPool.close();
  26.         }
  27.     }
  28. }
复制代码
示例中利用eval方法实验Lua脚本,通报锁的键(LOCK_KEY)和持有的锁的值(LOCK_VALUE)作为参数。脚本会实验解锁,如果解锁乐成,就会返回"1",否则返回"0"。根据返回值可以判断解锁是否乐成。
请注意,利用Lua脚本来解锁可以确保解锁操作是原子的,只有持有锁的客户端才气乐成解锁,这使得解锁更加可靠。
利用Redis事件功能

可以利用Redis的事件功能来实现可靠的分布式锁解锁。Redis事件利用MULTI、EXEC和WATCH下令来实验一系列下令,这些下令在EXEC中原子性地实验。通过利用WATCH下令,你可以监视某个键是否被修改,如果被修改,事件将被取消,从而确保解锁是可靠的。
以下是一个Java示例,演示怎样利用Redis事件来解锁:
  1. import redis.clients.jedis.Jedis;
  2. import redis.clients.jedis.JedisPool;
  3. import redis.clients.jedis.JedisPoolConfig;
  4. import redis.clients.jedis.Transaction;
  5. import redis.clients.jedis.exceptions.JedisException;
  6. public class RedisLockUnlock {
  7.     private static final String LOCK_KEY = "my_lock_key";
  8.     private static final String LOCK_VALUE = "my_lock_value";
  9.     public static void main(String[] args) {
  10.         JedisPool jedisPool = new JedisPool(new JedisPoolConfig(), "localhost", 6379);
  11.         try (Jedis jedis = jedisPool.getResource()) {
  12.             // 监视锁的键
  13.             jedis.watch(LOCK_KEY);
  14.             String lockValue = jedis.get(LOCK_KEY);
  15.             if (LOCK_VALUE.equals(lockValue)) {
  16.                 // 开启事务
  17.                 Transaction tx = jedis.multi();
  18.                 // 删除锁
  19.                 tx.del(LOCK_KEY);
  20.                 if (tx.exec() != null) {
  21.                     System.out.println("Lock released successfully.");
  22.                 } else {
  23.                     System.out.println("Failed to release lock. Lock may no longer be owned by this client.");
  24.                 }
  25.             } else {
  26.                 System.out.println("Lock is not owned by this client.");
  27.             }
  28.         } catch (JedisException e) {
  29.             e.printStackTrace();
  30.         } finally {
  31.             jedisPool.close();
  32.         }
  33.     }
  34. }
复制代码
示例中利用WATCH下令来监视锁的键(LOCK_KEY),然后获取锁的值。如果锁的值与预期的值雷同,表示锁仍然由当前客户端持有,我们就开启事件,利用DEL下令删除锁,然后通过EXEC来实验事件。如果事件实验乐成,说明解锁乐成;否则,说明锁可能已被其他客户端修改,解锁失败。
这种方法确保了解锁是原子的,并且只有持有锁的客户端才气乐成解锁。如果锁不再属于当前客户端,事件将被取消,这使得解锁操作更加可靠。
(六)重点题目关注

上面的方案在主从架构的Redis集群中,主节点和从节点之间的异步复制存在一定的延迟,这可能导致在主节点宕机并切换到从节点时,之前在主节点上获取的锁在从节点上尚未完全同步,从而引发多个客户端获取同一把锁的题目。这是一个在分布式锁中必要思量的常见题目。
为了解决这个题目,可以思量利用RedLock算法大概利用Redis Sentinel来增强锁的可用性和可靠性。还有一种更加可靠健壮且易用性更好的Redis锁实现方式------Redisson分布式锁实现(关于Redisson的分布式锁可见分布式锁和同步器 )。
三、Redisson分布式锁

Redisson(Redis + Java + Jackson)是一个用于Java应用步调的开源分布式Java对象的框架。Redisson提供了一组用于在分布式环境下处置惩罚常见使命的API,其中包括分布式锁。Redisson的分布式锁实现是基于Redis的,它具有高性能、可靠性和可扩展性,可以用于解决分布式应用步调中的并发控制题目。
起首,你必要在你的Java项目中导入Redisson的依靠。你可以通过Maven或Gradle来添加依靠。以下是一个示例Maven依靠的设置:
  1. <dependency>
  2.     <groupId>org.redisson</groupId>
  3.     <artifactId>redisson</artifactId>
  4.     <!-- 使用最新版本 -->
  5.     <version>3.16.1</version>
  6. </dependency>
复制代码
在利用Redisson之前,你必要创建一个Redisson实例,用于毗连到Redis服务器。通常,你只必要创建一个全局的Redisson实例,并在整个应用步调中重复利用它。
  1. Config config = new Config();
  2. config.useSingleServer()
  3.       // 设置Redis服务器地址
  4.       .setAddress("redis://localhost:6379");
  5. RedissonClient redisson = Redisson.create(config);
复制代码
(一)Redisson分布式锁-可重入锁

基于Redis的Redisson分布式可重入锁RLock Java对象实现了java.util.concurrent.locks.Lock接口。同时还提供了异步(Async)、反射式(Reactive)和RxJava2标准的接口。
下面是一个详细的业务利用案例,演示怎样利用Redisson的RLock来管理并发访问。
业务场景:假设有一个电子商务网站,用户在购物时必要扣减商品的库存。由于多个用户可能同时购买雷同的商品,必要确保库存的扣减是线程安全的,同时制止超卖的题目。
  1. package org.zyf.javabasic.redisson;
  2. import org.redisson.Redisson;
  3. import org.redisson.api.RLock;
  4. import org.redisson.api.RedissonClient;
  5. import org.redisson.config.Config;
  6. /**
  7. * @program: zyfboot-javabasic
  8. * @description: 假设有一个电子商务网站,用户在购物时需要扣减商品的库存。
  9. * 由于多个用户可能同时购买相同的商品,需要确保库存的扣减是线程安全的,同时避免超卖的问题。
  10. * @author: zhangyanfeng
  11. * @create: 2023-10-03 14:18
  12. **/
  13. public class InventoryService {
  14.     private static final String PRODUCT_STOCK_KEY = "product:12345:stock";
  15.     public static void main(String[] args) {
  16.         // 创建Redisson客户端
  17.         Config config = new Config();
  18.         config.useSingleServer()
  19.                 .setAddress("redis://localhost:6379");
  20.         RedissonClient redisson = Redisson.create(config);
  21.         // 获取可重入锁
  22.         RLock lock = redisson.getLock(PRODUCT_STOCK_KEY);
  23.         try {
  24.             // 尝试获取锁,最多等待10秒
  25.             if (lock.tryLock(10, 10, java.util.concurrent.TimeUnit.SECONDS)) {
  26.                 // 获取锁成功,执行库存扣减操作
  27.                 int currentStock = getCurrentStock();
  28.                 if (currentStock > 0) {
  29.                     // 扣减库存
  30.                     currentStock--;
  31.                     updateStock(currentStock);
  32.                     System.out.println("库存扣减成功,当前库存:" + currentStock);
  33.                 } else {
  34.                     System.out.println("库存不足,无法扣减");
  35.                 }
  36.             } else {
  37.                 System.out.println("获取锁超时,无法扣减库存");
  38.             }
  39.         } catch (InterruptedException e) {
  40.             e.printStackTrace();
  41.         } finally {
  42.             // 释放锁
  43.             lock.unlock();
  44.         }
  45.         // 关闭Redisson客户端
  46.         redisson.shutdown();
  47.     }
  48.     private static int getCurrentStock() {
  49.         // 模拟从数据库或缓存中获取当前库存数量的操作
  50.         return 10;
  51.     }
  52.     private static void updateStock(int newStock) {
  53.         // 模拟更新数据库或缓存中库存数量的操作
  54.     }
  55. }
复制代码
在上述示例中利用了Redisson的RLock来保护库存扣减操作。主要步骤如下:

  • 创建Redisson客户端并获取可重入锁。
  • 实验获取锁,最多等待10秒。如果获取锁乐成,实验库存扣减操作。
  • 扣减库存并更新库存数量。
  • 末了释放锁。
这样,纵然多个用户同时访问库存扣减操作,也能确保只有一个线程能够乐成获取锁,从而保证库存操作的线程安全性。
这个示例展示了怎样在分布式环境下利用Redisson的RLock来处置惩罚并发控制题目。它能够轻松地解决雷同的并发题目,确保数据的同等性和可靠性。同时,Redisson还提供了异步、反射式和RxJava2标准的接口,可以根据项目需求选择最得当的方式来利用RLock。
(二)Redisson分布式锁-公平锁(Fair Lock)

Redisson的公平锁(Fair Lock)是一种分布式可重入锁,它基于Redis实现,提供了Java的java.util.concurrent.locks.Lock接口,同时也支持异步、反射式和RxJava2标准的接口。公平锁确保当多个Redisson客户端线程同时请求加锁时,锁的获取顺序是公平的,即按照请求顺序分配锁。在公平锁中,所有请求线程会在一个队列中排队,等待获取锁。
业务场景:假设有一个共享资源,多个线程必要访问这个资源,但必要按照请求的先后顺序获取访问权,以保证公平性。
  1. package org.zyf.javabasic.redisson;
  2. import org.redisson.Redisson;
  3. import org.redisson.api.RLock;
  4. import org.redisson.api.RedissonClient;
  5. import org.redisson.config.Config;
  6. /**
  7. * @program: zyfboot-javabasic
  8. * @description: 假设有一个共享资源,多个线程需要访问这个资源,但需要按照请求的先后顺序获取访问权,以保证公平性。
  9. * @author: zhangyanfeng
  10. * @create: 2023-10-03 14:23
  11. **/
  12. public class SharedResourceService {
  13.     private static final String RESOURCE_KEY = "shared_resource";
  14.     public static void main(String[] args) {
  15.         // 创建Redisson客户端
  16.         Config config = new Config();
  17.         config.useSingleServer()
  18.                 .setAddress("redis://localhost:6379");
  19.         RedissonClient redisson = Redisson.create(config);
  20.         // 获取公平锁
  21.         RLock fairLock = redisson.getFairLock(RESOURCE_KEY);
  22.         try {
  23.             // 尝试获取锁
  24.             fairLock.lock();
  25.             // 执行需要访问共享资源的操作
  26.             System.out.println("Thread " + Thread.currentThread().getId() + " is accessing the shared resource.");
  27.             Thread.sleep(2000); // 模拟访问共享资源的耗时操作
  28.         } catch (InterruptedException e) {
  29.             e.printStackTrace();
  30.         } finally {
  31.             // 释放锁
  32.             fairLock.unlock();
  33.         }
  34.         // 关闭Redisson客户端
  35.         redisson.shutdown();
  36.     }
  37. }
复制代码
在上述示例中利用了Redisson的公平锁(RLock)来实现多个线程访问共享资源的公平竞争。主要步骤如下:

  • 创建Redisson客户端并获取公平锁。
  • 实验获取锁,如果有其他线程持有锁,当前线程将等待,直到获取到锁。
  • 实验必要访问共享资源的操作。这里我们模拟了一个耗时的操作。
  • 末了释放锁。
利用公平锁,多个线程会按照请求的顺序获取锁,确保了访问共享资源的公平性。这对于必要遵照先到先得原则的场景非常有效。
必要注意的是,公平锁可能会导致线程等待的时间较长,因为它会等待之前请求的线程释放锁。因此,在利用公平锁时,必要思量性能和公平性之间的衡量。如果对性能要求较高,可以思量利用非公平锁。不过,在某些场景下,公平锁是非常有代价的,例如必要遵照特定规则或优先级的应用步调。
(三)Redisson分布式锁-联锁

Redisson提供了RTransaction两种方式来实现雷同的分布式联锁行为。这两种方式都允许你在多个Redisson对象之间实验事件性操作,确保一组操作要么全部乐成,要么全部失败。
业务场景:假设有一个在线购物系统,用户下单时必要满意以下条件:

  • 商品库存充足。
  • 用户账户余额充足。
  • 付出渠道可用。
只有当以上三个条件都满意时,用户的订单才气乐成下单。
RTransaction是Redisson提供的事件管理方式,你可以将多个Redis下令包装在一个事件中,然后一起提交或回滚。这也可以用于模拟分布式联锁的行为。
  1. package org.zyf.javabasic.redisson;
  2. import org.redisson.Redisson;
  3. import org.redisson.api.*;
  4. import org.redisson.config.Config;
  5. /**
  6. * @program: zyfboot-javabasic
  7. * @description: 假设有一个在线购物系统,用户下单时需要满足以下条件:
  8. * 商品库存充足。 用户账户余额充足。 支付渠道可用。
  9. * 只有当以上三个条件都满足时,用户的订单才能成功下单。
  10. * @author: zhangyanfeng
  11. * @create: 2023-10-03 14:41
  12. **/
  13. public class OrdeRTransactionService {
  14.     private static final String PRODUCT_STOCK_KEY = "product:12345:stock";
  15.     private static final String USER_BALANCE_KEY = "user:1001:balance";
  16.     private static final String PAYMENT_CHANNEL_KEY = "payment:channel:available";
  17.     public static void main(String[] args) {
  18.         // 创建Redisson客户端
  19.         Config config = new Config();
  20.         config.useSingleServer()
  21.                 .setAddress("redis://localhost:6379");
  22.         RedissonClient redisson = Redisson.create(config);
  23.         // 获取各个锁
  24.         RLock productStockLock = redisson.getLock(PRODUCT_STOCK_KEY);
  25.         RLock userBalanceLock = redisson.getLock(USER_BALANCE_KEY);
  26.         RLock paymentChannelLock = redisson.getLock(PAYMENT_CHANNEL_KEY);
  27.         try {
  28.             // 创建事务
  29.             TransactionOptions options = TransactionOptions.defaults();
  30.             RTransaction transaction = redisson.createTransaction(options);
  31.             // 加锁并提交事务
  32.             productStockLock.lock();
  33.             userBalanceLock.lock();
  34.             paymentChannelLock.lock();
  35.             transaction.commit();
  36.             // 所有锁都成功加锁,执行订单下单操作
  37.             System.out.println("订单下单成功");
  38.         } catch (Exception e) {
  39.             e.printStackTrace();
  40.             // 事务失败时回滚锁
  41.             productStockLock.unlock();
  42.             userBalanceLock.unlock();
  43.             paymentChannelLock.unlock();
  44.         } finally {
  45.             // 关闭Redisson客户端
  46.             redisson.shutdown();
  47.         }
  48.     }
  49. }
复制代码
在这个示例中创建了一个RTransaction对象,将多个锁的加锁操作放入事件中,然后提交事件。如果事件中的任何操作失败会回滚锁。
(四)Redisson分布式锁-红锁(RedLock)

红锁(RedLock)是一种分布式锁算法,旨在提供高可用性和可靠性的分布式锁。Redisson库提供了RedissonRedLock对象来实现这种算法,允许你将多个RLock对象关联为一个红锁,其中每个RLock可以来自差异的Redisson实例,以增强锁的可靠性和高可用性。
下面是一个关于怎样利用RedissonRedLock的业务利用案例分析:
场景背景: 假设有一个在线购物平台,用户在购买商品时必要锁定商品的库存,并且必要扣减用户的余额。同时,必要在扣减余额时也锁定用户的余额,以防止并发题目。这个场景必要确保库存锁和余额锁同时乐成,否则不能完成购买操作。
  1. package org.zyf.javabasic.redisson;
  2. import org.redisson.Redisson;
  3. import org.redisson.api.RLock;
  4. import org.redisson.api.RedissonClient;
  5. import org.redisson.api.RedissonRedLock;
  6. import org.redisson.config.Config;
  7. /**
  8. * @program: zyfboot-javabasic
  9. * @description: 使用RedissonRedLock的业务场景
  10. * @author: zhangyanfeng
  11. * @create: 2023-10-03 15:24
  12. **/
  13. public class PurchaseRedService {
  14.     private static final String PRODUCT_STOCK_KEY = "product:12345:stock";
  15.     private static final String USER_BALANCE_KEY = "user:1001:balance";
  16.     private static final int LOCK_TIMEOUT = 10; // 锁超时时间,秒
  17.     public static void main(String[] args) {
  18.         // 创建Redisson客户端连接多个Redis节点
  19.         Config config1 = new Config();
  20.         config1.useSingleServer()
  21.                 .setAddress("redis://host1:6379");
  22.         Config config2 = new Config();
  23.         config2.useSingleServer()
  24.                 .setAddress("redis://host2:6379");
  25.         Config config3 = new Config();
  26.         config3.useSingleServer()
  27.                 .setAddress("redis://host3:6379");
  28.         RedissonClient redisson1 = Redisson.create(config1);
  29.         RedissonClient redisson2 = Redisson.create(config2);
  30.         RedissonClient redisson3 = Redisson.create(config3);
  31.         // 获取商品库存锁、用户余额锁
  32.         RLock productStockLock = redisson1.getLock(PRODUCT_STOCK_KEY);
  33.         RLock userBalanceLock = redisson2.getLock(USER_BALANCE_KEY);
  34.         // 创建红锁,关联多个锁
  35.         RedissonRedLock redLock = new RedissonRedLock(productStockLock, userBalanceLock);
  36.         try {
  37.             // 尝试获取红锁,等待10秒,锁超时时间为10秒
  38.             if (redLock.tryLock(LOCK_TIMEOUT, LOCK_TIMEOUT)) {
  39.                 // 获取红锁成功,执行购买操作
  40.                 // 检查库存是否足够
  41.                 int currentStock = getCurrentStock();
  42.                 if (currentStock > 0) {
  43.                     // 扣减库存
  44.                     currentStock--;
  45.                     updateStock(currentStock);
  46.                     // 扣减用户余额
  47.                     double currentBalance = getCurrentBalance();
  48.                     double purchaseAmount = 100.0; // 假设购买商品价格为100
  49.                     if (currentBalance >= purchaseAmount) {
  50.                         currentBalance -= purchaseAmount;
  51.                         updateBalance(currentBalance);
  52.                         System.out.println("购买成功,剩余库存:" + currentStock + ",剩余余额:" + currentBalance);
  53.                     } else {
  54.                         System.out.println("余额不足,购买失败");
  55.                     }
  56.                 } else {
  57.                     System.out.println("库存不足,购买失败");
  58.                 }
  59.             } else {
  60.                 System.out.println("获取红锁失败,购买失败");
  61.             }
  62.         } catch (InterruptedException e) {
  63.             e.printStackTrace();
  64.         } finally {
  65.             // 释放红锁
  66.             redLock.unlock();
  67.             // 关闭Redisson客户端
  68.             redisson1.shutdown();
  69.             redisson2.shutdown();
  70.             redisson3.shutdown();
  71.         }
  72.     }
  73.     private static int getCurrentStock() {
  74.         // 模拟从数据库或缓存中获取当前库存数量的操作
  75.         return 10;
  76.     }
  77.     private static void updateStock(int newStock) {
  78.         // 模拟更新数据库或缓存中库存数量的操作
  79.     }
  80.     private static double getCurrentBalance() {
  81.         // 模拟从数据库或缓存中获取当前用户余额的操作
  82.         return 500.0;
  83.     }
  84.     private static void updateBalance(double newBalance) {
  85.         // 模拟更新数据库或缓存中用户余额的操作
  86.     }
  87. }
复制代码
在这个示例中,起首创建了三个差异的Redisson客户端毗连到差异的Redis节点。然后,我们获取了商品库存锁和用户余额锁,并利用RedissonRedLock将它们关联为一个红锁。在购买过程中,我们利用红锁来确保在库存锁和余额锁都乐成加锁时才气实验购买操作。如果任何一个锁获取失败,购买操作将被视为失败。
(五)Redisson分布式锁-读写锁(ReadWriteLock)

Redisson的分布式锁库也支持读写锁(ReadWriteLock),可以在分布式环境中更有效地管理读取和写入操作的并发性。读写锁允许多个线程同时读取数据,但只允许一个线程写入数据,并且写入数据时会阻塞读取操作。
业务利用案例:
假设我们有一个简朴的文章发布系统,多个用户可以同时读取文章,但只能有一个用户同时进行编辑和发布文章的写入操作。
  1. package org.zyf.javabasic.redisson;
  2. import org.redisson.Redisson;
  3. import org.redisson.api.RReadWriteLock;
  4. import org.redisson.api.RedissonClient;
  5. import org.redisson.config.Config;
  6. /**
  7. * @program: zyfboot-javabasic
  8. * @description: 使用Redisson的ReadWriteLock的业务场景
  9. * @author: zhangyanfeng
  10. * @create: 2023-10-03 15:28
  11. **/
  12. public class ArticleService {
  13.     private static final String ARTICLE_LOCK_KEY = "article:lock";
  14.     private static final String ARTICLE_CONTENT_KEY = "article:content";
  15.     public static void main(String[] args) {
  16.         // 创建Redisson客户端
  17.         Config config = new Config();
  18.         config.useSingleServer()
  19.                 .setAddress("redis://localhost:6379");
  20.         RedissonClient redisson = Redisson.create(config);
  21.         // 获取读写锁
  22.         RReadWriteLock rwLock = redisson.getReadWriteLock(ARTICLE_LOCK_KEY);
  23.         try {
  24.             // 获取读锁
  25.             rwLock.readLock().lock();
  26.             // 读取文章内容
  27.             String articleContent = getArticleContent();
  28.             System.out.println("文章内容:" + articleContent);
  29.             // 模拟读取操作耗时
  30.             Thread.sleep(1000);
  31.             // 释放读锁
  32.             rwLock.readLock().unlock();
  33.             // 获取写锁
  34.             rwLock.writeLock().lock();
  35.             // 编辑和发布文章
  36.             editAndPublishArticle();
  37.             System.out.println("文章编辑和发布成功");
  38.             // 释放写锁
  39.             rwLock.writeLock().unlock();
  40.         } catch (InterruptedException e) {
  41.             e.printStackTrace();
  42.         } finally {
  43.             // 关闭Redisson客户端
  44.             redisson.shutdown();
  45.         }
  46.     }
  47.     private static String getArticleContent() {
  48.         // 模拟从数据库或缓存中获取文章内容的操作
  49.         return "这是一篇文章的内容";
  50.     }
  51.     private static void editAndPublishArticle() {
  52.         // 模拟编辑和发布文章的操作
  53.     }
  54. }
复制代码
在这个示例中,起首创建了Redisson客户端,然后获取了一个读写锁(RReadWriteLock)。在代码中,起首获取了读锁,并读取文章内容,模拟了多个用户同时读取文章的场景。然后获取了写锁,并模拟了编辑和发布文章的操作。写锁在编辑和发布文章时保证了写入的原子性,并且会阻塞读取操作,直到写锁被释放。
四、总结

在本日的数字化世界中,分布式系统的广泛应用使得我们面临着前所未有的并发挑衅。在这场技术的盛宴中,分布式锁成为我们维护数据同等性和防止资源争用的重要工具。通过对Redis和Redisson的深入探索,我们了解到分布式锁不但可以有效解决并发访问的题目,还能极大提拔系统的稳固性和可靠性。
从根本的锁机制到先进的锁管理计谋,Redisson为我们提供了一种灵活且高效的解决方案,让开发者能够轻松应对复杂的分布式环境。通过公道设置和有效利用分布式锁,我们能够在保证系统性能的同时,制止因资源竞争而导致的数据紊乱。
末了,尽管分布式锁不是“银弹”,但它无疑是我们在分布式系统中保障数据安全与同等性的一把利器。未来,随着技术的不停进步,分布式锁的应用和优化将愈发重要,值得每一位开发者深入理解和实践。让我们继续在这个领域探索,寻找更多提高系统性能和稳固性的创新方法,为我们的应用带来更大的代价!


详细原理源码可见
Redisson 实现分布式锁原理分析 - 知乎
Redis系列(二)Redisson分布式锁源码解析_redisson源码分析_白垩纪往事的博客-CSDN博客




免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

民工心事

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表