目录
一、回顾分布式锁
(一)理解分布式锁的定义
(二)分布式锁的约束条件
(三)分布式锁常见实现方式
基于数据库的分布式锁
基于缓存的分布式锁
基于分布式一致性算法的分布式锁
基于文件系统的分布式锁
基于消息队列的分布式锁
基于第三方服务的分布式锁
二、分布式锁Redis原理
(一)Redis分布式锁的基本原理总揽
(二)核心指令:加锁
示例加锁参数解析
(三)核心指令:解锁
(四)错误案例分析:setNx
(五)常见解锁方案:通过Lua脚本解锁+使用Redis事务功能
通过Lua脚本执行解锁
使用Redis事务功能
(六)重点问题关注
三、Redisson分布式锁
(一)Redisson分布式锁-可重入锁
(二)Redisson分布式锁-公平锁(Fair Lock)
(三)Redisson分布式锁-联锁
(四)Redisson分布式锁-红锁(RedLock)
(五)Redisson分布式锁-读写锁(ReadWriteLock)
分布式锁是一种在分布式计算环境中用于控制多个节点(或多个进程)对共享资源的访问的机制。在分布式系统中,多个节点可能需要协调对共享资源的访问,以防止数据的不一致性或冲突。分布式锁允许多个节点在竞争访问共享资源时进行同步,以确保只有一个节点能够获得锁,从而避免冲突和数据损坏。以下是一些关键概念和理解:
锁:锁是一种同步机制,它可以被获取和释放。当一个节点获得锁时,它可以执行需要访问共享资源的操作,其他节点必须等待直到锁被释放才能获得锁。
分布式环境:在分布式系统中,多个节点分布在不同的物理位置或计算机上,它们通过网络相互通信。这增加了在多个节点之间协调共享资源访问的复杂性。
锁的种类:
- 互斥锁:在分布式环境中,互斥锁确保在任何给定时刻只有一个节点可以持有锁。其他节点必须等待锁被释放。
- 读写锁:允许多个节点同时读取共享资源,但只允许一个节点写入共享资源。这可以提高并发性能,但需要更复杂的管理。
锁的实现方式:分布式锁可以使用不同的实现方式,如基于数据库、基于缓存、基于分布式一致性算法(例如ZooKeeper或etcd)等。
死锁和性能问题:在设计和使用分布式锁时,需要考虑到死锁(当多个节点相互等待锁释放而无法继续执行)和性能问题(锁争夺可能导致性能下降)。
分布式锁的主要目标是确保在分布式系统中对共享资源的访问是有序和安全的,从而避免数据不一致性和冲突。然而,分布式锁的设计和管理需要仔细考虑,以确保高可用性、性能和可伸缩性。在实际应用中,通常会根据具体的需求和环境选择适当的分布式锁实现方式。
在设计和实现分布式锁时,需要考虑一些约束条件,以确保锁的正确性和可用性。以下是一些常见的分布式锁的约束条件:
不同的分布式锁实现方式(如基于数据库、基于缓存、基于分布式一致性算法等)可能在满足这些约束条件时有不同的优缺点。在选择分布式锁实现方式时,需要根据具体的应用需求和性能要求来权衡这些约束条件。同时,为了确保分布式锁的正确性,需要进行严格的测试和验证。
分布式锁可以使用多种不同的实现方式,每种方式都有其适用的场景和特点。以下是一些常见的分布式锁实现方式:(也可以见分布式锁实现方式分析-CSDN博客)
不同的实现方式适用于不同的应用场景和性能要求。选择合适的分布式锁实现方式时,需要考虑系统的可靠性、性能、复杂性和维护成本等因素。此外,在使用分布式锁时,也需要注意处理死锁、超时、自动释放等问题,以确保锁的正确性和可用性。
Redis的分布式锁实现通常基于两个主要命令:SET
和EXPIRE
,结合一些原子性操作,如NX
(只在键不存在时设置键的值)。
获取锁
SET
命令尝试在Redis中设置一个特定的键,这个键通常被视为锁的名称。NX
选项,只有在该键不存在时才能设置成功。SET
命令中设置一个带有超时时间的参数,这个时间决定了锁的有效期。锁超时机制
SET
命令中设置了锁的超时时间。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
:可选参数,表示只有在键已经存在时才能设置成功,用于更新锁的值或延续锁的过期时间。SET lock_name my_random_value NX PX 30000
del lock_name
Jedis jedis = jedisPool.getResource();
// 如果锁不存在则进行加锁
Long lockResult = jedis.setnx(lockName, myRandomValue);
if (lockResult == 1) {
// 设置锁过期时间,加锁和设置过期时间是两步完成的,非原子操作
jedis.expire(lockName, expireTime);
}
代码使用SETNX
和EXPIRE
命令来实现分布式锁的方式,虽然看似可行,但确实存在一定的问题,特别是在异常情况下,可能会导致死锁。让我重新梳理一下这个问题:
问题描述
SETNX
命令尝试获取锁。SETNX
成功,表示锁被成功获取,接着使用EXPIRE
来设置锁的过期时间。这个问题的核心在于SETNX
和EXPIRE
两个命令并没有原子性地组合在一起。如果在第2步和第3步之间发生了异常,就会导致锁没有过期时间,进而可能导致死锁。
解决方案
为了确保锁的安全性,需要将获取锁和设置过期时间这两个操作原子化。可以使用SET
命令的NX
和EX
选项来一次性完成这两个操作,以避免出现问题。下面是示例代码:
Jedis jedis = jedisPool.getResource();
String lockResult = jedis.set(lockName, myRandomValue, "NX", "EX", expireTime);
if ("OK".equals(lockResult)) {
// 锁获取成功
// 进行业务操作
// ...
// 业务完成后,释放锁
jedis.del(lockName);
} else {
// 锁获取失败
// 可以进行重试或其他处理
}
在这个示例中,SET
命令的选项"NX"
表示只有在键不存在时才能设置成功,"EX"
表示设置键的过期时间。这样可以确保获取锁和设置过期时间是一个原子操作,从而避免了在异常情况下出现死锁问题。
总之,确保分布式锁的获取和释放是原子操作是非常重要的,以确保锁的正确性和可用性。使用SET
命令的组合选项可以简化代码并避免一些潜在的问题。
要利用Lua脚本实现可靠的分布式锁解锁,可以编写一个Lua脚本,该脚本在执行时会检查锁的值是否与预期值匹配,并且只有在匹配时才会删除锁。这确保了只有持有锁的客户端才能成功解锁。
以下是一个示例Lua脚本,用于解锁:
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
在这个脚本中,KEYS[1]
表示锁的键,ARGV[1]
表示你持有的锁的值。脚本首先检查锁的值是否与预期值匹配(即检查锁是否仍然由当前客户端持有),如果匹配,则使用DEL
命令来删除锁,然后返回1表示解锁成功,否则返回0表示解锁失败。
在Java中,可以使用Jedis库执行Lua脚本:
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class RedisLockUnlock {
private static final String LOCK_KEY = "my_lock_key";
private static final String LOCK_VALUE = "my_lock_value";
public static void main(String[] args) {
JedisPool jedisPool = new JedisPool(new JedisPoolConfig(), "localhost", 6379);
try (Jedis jedis = jedisPool.getResource()) {
String luaScript = "if redis.call('GET', KEYS[1]) == ARGV[1] then " +
"return redis.call('DEL', KEYS[1]) " +
"else " +
"return 0 " +
"end";
String result = (String) jedis.eval(luaScript, 1, LOCK_KEY, LOCK_VALUE);
if ("1".equals(result)) {
System.out.println("Lock released successfully.");
} else {
System.out.println("Failed to release lock. Lock may no longer be owned by this client.");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
jedisPool.close();
}
}
}
示例中使用eval
方法执行Lua脚本,传递锁的键(LOCK_KEY
)和持有的锁的值(LOCK_VALUE
)作为参数。脚本会尝试解锁,如果解锁成功,就会返回"1",否则返回"0"。根据返回值可以判断解锁是否成功。
请注意,使用Lua脚本来解锁可以确保解锁操作是原子的,只有持有锁的客户端才能成功解锁,这使得解锁更加可靠。
可以使用Redis的事务功能来实现可靠的分布式锁解锁。Redis事务使用MULTI
、EXEC
和WATCH
命令来执行一系列命令,这些命令在EXEC
中原子性地执行。通过使用WATCH
命令,你可以监视某个键是否被修改,如果被修改,事务将被取消,从而确保解锁是可靠的。
以下是一个Java示例,演示如何使用Redis事务来解锁:
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Transaction;
import redis.clients.jedis.exceptions.JedisException;
public class RedisLockUnlock {
private static final String LOCK_KEY = "my_lock_key";
private static final String LOCK_VALUE = "my_lock_value";
public static void main(String[] args) {
JedisPool jedisPool = new JedisPool(new JedisPoolConfig(), "localhost", 6379);
try (Jedis jedis = jedisPool.getResource()) {
// 监视锁的键
jedis.watch(LOCK_KEY);
String lockValue = jedis.get(LOCK_KEY);
if (LOCK_VALUE.equals(lockValue)) {
// 开启事务
Transaction tx = jedis.multi();
// 删除锁
tx.del(LOCK_KEY);
if (tx.exec() != null) {
System.out.println("Lock released successfully.");
} else {
System.out.println("Failed to release lock. Lock may no longer be owned by this client.");
}
} else {
System.out.println("Lock is not owned by this client.");
}
} catch (JedisException e) {
e.printStackTrace();
} finally {
jedisPool.close();
}
}
}
示例中使用WATCH
命令来监视锁的键(LOCK_KEY
),然后获取锁的值。如果锁的值与预期的值相同,表示锁仍然由当前客户端持有,我们就开启事务,使用DEL
命令删除锁,然后通过EXEC
来执行事务。如果事务执行成功,说明解锁成功;否则,说明锁可能已被其他客户端修改,解锁失败。
这种方法确保了解锁是原子的,并且只有持有锁的客户端才能成功解锁。如果锁不再属于当前客户端,事务将被取消,这使得解锁操作更加可靠。
上面的方案在主从架构的Redis集群中,主节点和从节点之间的异步复制存在一定的延迟,这可能导致在主节点宕机并切换到从节点时,之前在主节点上获取的锁在从节点上尚未完全同步,从而引发多个客户端获取同一把锁的问题。这是一个在分布式锁中需要考虑的常见问题。
为了解决这个问题,可以考虑使用RedLock算法或者使用Redis Sentinel来增强锁的可用性和可靠性。还有一种更加可靠健壮且易用性更好的Redis锁实现方式------Redisson分布式锁实现(关于Redisson的分布式锁可见分布式锁和同步器 )。
Redisson(Redis + Java + Jackson)是一个用于Java应用程序的开源分布式Java对象的框架。Redisson提供了一组用于在分布式环境下处理常见任务的API,其中包括分布式锁。Redisson的分布式锁实现是基于Redis的,它具有高性能、可靠性和可扩展性,可以用于解决分布式应用程序中的并发控制问题。
首先,你需要在你的Java项目中导入Redisson的依赖。你可以通过Maven或Gradle来添加依赖。以下是一个示例Maven依赖的配置:
org.redisson
redisson
3.16.1
在使用Redisson之前,你需要创建一个Redisson实例,用于连接到Redis服务器。通常,你只需要创建一个全局的Redisson实例,并在整个应用程序中重复使用它。
Config config = new Config();
config.useSingleServer()
// 设置Redis服务器地址
.setAddress("redis://localhost:6379");
RedissonClient redisson = Redisson.create(config);
基于Redis的Redisson分布式可重入锁RLock Java对象实现了java.util.concurrent.locks.Lock接口。同时还提供了异步(Async)、反射式(Reactive)和RxJava2标准的接口。
下面是一个具体的业务使用案例,演示如何使用Redisson的RLock
来管理并发访问。
业务场景:假设有一个电子商务网站,用户在购物时需要扣减商品的库存。由于多个用户可能同时购买相同的商品,需要确保库存的扣减是线程安全的,同时避免超卖的问题。
package org.zyf.javabasic.redisson;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
/**
* @program: zyfboot-javabasic
* @description: 假设有一个电子商务网站,用户在购物时需要扣减商品的库存。
* 由于多个用户可能同时购买相同的商品,需要确保库存的扣减是线程安全的,同时避免超卖的问题。
* @author: zhangyanfeng
* @create: 2023-10-03 14:18
**/
public class InventoryService {
private static final String PRODUCT_STOCK_KEY = "product:12345:stock";
public static void main(String[] args) {
// 创建Redisson客户端
Config config = new Config();
config.useSingleServer()
.setAddress("redis://localhost:6379");
RedissonClient redisson = Redisson.create(config);
// 获取可重入锁
RLock lock = redisson.getLock(PRODUCT_STOCK_KEY);
try {
// 尝试获取锁,最多等待10秒
if (lock.tryLock(10, 10, java.util.concurrent.TimeUnit.SECONDS)) {
// 获取锁成功,执行库存扣减操作
int currentStock = getCurrentStock();
if (currentStock > 0) {
// 扣减库存
currentStock--;
updateStock(currentStock);
System.out.println("库存扣减成功,当前库存:" + currentStock);
} else {
System.out.println("库存不足,无法扣减");
}
} else {
System.out.println("获取锁超时,无法扣减库存");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
lock.unlock();
}
// 关闭Redisson客户端
redisson.shutdown();
}
private static int getCurrentStock() {
// 模拟从数据库或缓存中获取当前库存数量的操作
return 10;
}
private static void updateStock(int newStock) {
// 模拟更新数据库或缓存中库存数量的操作
}
}
在上述示例中使用了Redisson的RLock
来保护库存扣减操作。主要步骤如下:
这样,即使多个用户同时访问库存扣减操作,也能确保只有一个线程能够成功获取锁,从而保证库存操作的线程安全性。
这个示例展示了如何在分布式环境下使用Redisson的RLock
来处理并发控制问题。它能够轻松地解决类似的并发问题,确保数据的一致性和可靠性。同时,Redisson还提供了异步、反射式和RxJava2标准的接口,可以根据项目需求选择最适合的方式来使用RLock
。
Redisson的公平锁(Fair Lock)是一种分布式可重入锁,它基于Redis实现,提供了Java的java.util.concurrent.locks.Lock
接口,同时也支持异步、反射式和RxJava2标准的接口。公平锁确保当多个Redisson客户端线程同时请求加锁时,锁的获取顺序是公平的,即按照请求顺序分配锁。在公平锁中,所有请求线程会在一个队列中排队,等待获取锁。
业务场景:假设有一个共享资源,多个线程需要访问这个资源,但需要按照请求的先后顺序获取访问权,以保证公平性。
package org.zyf.javabasic.redisson;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
/**
* @program: zyfboot-javabasic
* @description: 假设有一个共享资源,多个线程需要访问这个资源,但需要按照请求的先后顺序获取访问权,以保证公平性。
* @author: zhangyanfeng
* @create: 2023-10-03 14:23
**/
public class SharedResourceService {
private static final String RESOURCE_KEY = "shared_resource";
public static void main(String[] args) {
// 创建Redisson客户端
Config config = new Config();
config.useSingleServer()
.setAddress("redis://localhost:6379");
RedissonClient redisson = Redisson.create(config);
// 获取公平锁
RLock fairLock = redisson.getFairLock(RESOURCE_KEY);
try {
// 尝试获取锁
fairLock.lock();
// 执行需要访问共享资源的操作
System.out.println("Thread " + Thread.currentThread().getId() + " is accessing the shared resource.");
Thread.sleep(2000); // 模拟访问共享资源的耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
fairLock.unlock();
}
// 关闭Redisson客户端
redisson.shutdown();
}
}
在上述示例中使用了Redisson的公平锁(RLock
)来实现多个线程访问共享资源的公平竞争。主要步骤如下:
使用公平锁,多个线程会按照请求的顺序获取锁,确保了访问共享资源的公平性。这对于需要遵循先到先得原则的场景非常有用。
需要注意的是,公平锁可能会导致线程等待的时间较长,因为它会等待之前请求的线程释放锁。因此,在使用公平锁时,需要考虑性能和公平性之间的权衡。如果对性能要求较高,可以考虑使用非公平锁。不过,在某些场景下,公平锁是非常有价值的,例如需要遵循特定规则或优先级的应用程序。
Redisson提供了RTransaction
两种方式来实现类似的分布式联锁行为。这两种方式都允许你在多个Redisson对象之间执行事务性操作,确保一组操作要么全部成功,要么全部失败。
业务场景:假设有一个在线购物系统,用户下单时需要满足以下条件:
只有当以上三个条件都满足时,用户的订单才能成功下单。
RTransaction
是Redisson提供的事务管理方式,你可以将多个Redis命令包装在一个事务中,然后一起提交或回滚。这也可以用于模拟分布式联锁的行为。
package org.zyf.javabasic.redisson;
import org.redisson.Redisson;
import org.redisson.api.*;
import org.redisson.config.Config;
/**
* @program: zyfboot-javabasic
* @description: 假设有一个在线购物系统,用户下单时需要满足以下条件:
* 商品库存充足。 用户账户余额充足。 支付渠道可用。
* 只有当以上三个条件都满足时,用户的订单才能成功下单。
* @author: zhangyanfeng
* @create: 2023-10-03 14:41
**/
public class OrdeRTransactionService {
private static final String PRODUCT_STOCK_KEY = "product:12345:stock";
private static final String USER_BALANCE_KEY = "user:1001:balance";
private static final String PAYMENT_CHANNEL_KEY = "payment:channel:available";
public static void main(String[] args) {
// 创建Redisson客户端
Config config = new Config();
config.useSingleServer()
.setAddress("redis://localhost:6379");
RedissonClient redisson = Redisson.create(config);
// 获取各个锁
RLock productStockLock = redisson.getLock(PRODUCT_STOCK_KEY);
RLock userBalanceLock = redisson.getLock(USER_BALANCE_KEY);
RLock paymentChannelLock = redisson.getLock(PAYMENT_CHANNEL_KEY);
try {
// 创建事务
TransactionOptions options = TransactionOptions.defaults();
RTransaction transaction = redisson.createTransaction(options);
// 加锁并提交事务
productStockLock.lock();
userBalanceLock.lock();
paymentChannelLock.lock();
transaction.commit();
// 所有锁都成功加锁,执行订单下单操作
System.out.println("订单下单成功");
} catch (Exception e) {
e.printStackTrace();
// 事务失败时回滚锁
productStockLock.unlock();
userBalanceLock.unlock();
paymentChannelLock.unlock();
} finally {
// 关闭Redisson客户端
redisson.shutdown();
}
}
}
在这个示例中创建了一个RTransaction
对象,将多个锁的加锁操作放入事务中,然后提交事务。如果事务中的任何操作失败会回滚锁。
红锁(RedLock)是一种分布式锁算法,旨在提供高可用性和可靠性的分布式锁。Redisson库提供了RedissonRedLock
对象来实现这种算法,允许你将多个RLock
对象关联为一个红锁,其中每个RLock
可以来自不同的Redisson实例,以增强锁的可靠性和高可用性。
下面是一个关于如何使用RedissonRedLock的业务使用案例分析:
场景背景: 假设有一个在线购物平台,用户在购买商品时需要锁定商品的库存,并且需要扣减用户的余额。同时,需要在扣减余额时也锁定用户的余额,以防止并发问题。这个场景需要确保库存锁和余额锁同时成功,否则不能完成购买操作。
package org.zyf.javabasic.redisson;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.api.RedissonRedLock;
import org.redisson.config.Config;
/**
* @program: zyfboot-javabasic
* @description: 使用RedissonRedLock的业务场景
* @author: zhangyanfeng
* @create: 2023-10-03 15:24
**/
public class PurchaseRedService {
private static final String PRODUCT_STOCK_KEY = "product:12345:stock";
private static final String USER_BALANCE_KEY = "user:1001:balance";
private static final int LOCK_TIMEOUT = 10; // 锁超时时间,秒
public static void main(String[] args) {
// 创建Redisson客户端连接多个Redis节点
Config config1 = new Config();
config1.useSingleServer()
.setAddress("redis://host1:6379");
Config config2 = new Config();
config2.useSingleServer()
.setAddress("redis://host2:6379");
Config config3 = new Config();
config3.useSingleServer()
.setAddress("redis://host3:6379");
RedissonClient redisson1 = Redisson.create(config1);
RedissonClient redisson2 = Redisson.create(config2);
RedissonClient redisson3 = Redisson.create(config3);
// 获取商品库存锁、用户余额锁
RLock productStockLock = redisson1.getLock(PRODUCT_STOCK_KEY);
RLock userBalanceLock = redisson2.getLock(USER_BALANCE_KEY);
// 创建红锁,关联多个锁
RedissonRedLock redLock = new RedissonRedLock(productStockLock, userBalanceLock);
try {
// 尝试获取红锁,等待10秒,锁超时时间为10秒
if (redLock.tryLock(LOCK_TIMEOUT, LOCK_TIMEOUT)) {
// 获取红锁成功,执行购买操作
// 检查库存是否足够
int currentStock = getCurrentStock();
if (currentStock > 0) {
// 扣减库存
currentStock--;
updateStock(currentStock);
// 扣减用户余额
double currentBalance = getCurrentBalance();
double purchaseAmount = 100.0; // 假设购买商品价格为100
if (currentBalance >= purchaseAmount) {
currentBalance -= purchaseAmount;
updateBalance(currentBalance);
System.out.println("购买成功,剩余库存:" + currentStock + ",剩余余额:" + currentBalance);
} else {
System.out.println("余额不足,购买失败");
}
} else {
System.out.println("库存不足,购买失败");
}
} else {
System.out.println("获取红锁失败,购买失败");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放红锁
redLock.unlock();
// 关闭Redisson客户端
redisson1.shutdown();
redisson2.shutdown();
redisson3.shutdown();
}
}
private static int getCurrentStock() {
// 模拟从数据库或缓存中获取当前库存数量的操作
return 10;
}
private static void updateStock(int newStock) {
// 模拟更新数据库或缓存中库存数量的操作
}
private static double getCurrentBalance() {
// 模拟从数据库或缓存中获取当前用户余额的操作
return 500.0;
}
private static void updateBalance(double newBalance) {
// 模拟更新数据库或缓存中用户余额的操作
}
}
在这个示例中,首先创建了三个不同的Redisson客户端连接到不同的Redis节点。然后,我们获取了商品库存锁和用户余额锁,并使用RedissonRedLock
将它们关联为一个红锁。在购买过程中,我们使用红锁来确保在库存锁和余额锁都成功加锁时才能执行购买操作。如果任何一个锁获取失败,购买操作将被视为失败。
Redisson的分布式锁库也支持读写锁(ReadWriteLock),可以在分布式环境中更有效地管理读取和写入操作的并发性。读写锁允许多个线程同时读取数据,但只允许一个线程写入数据,并且写入数据时会阻塞读取操作。
业务使用案例:
假设我们有一个简单的文章发布系统,多个用户可以同时读取文章,但只能有一个用户同时进行编辑和发布文章的写入操作。
package org.zyf.javabasic.redisson;
import org.redisson.Redisson;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
/**
* @program: zyfboot-javabasic
* @description: 使用Redisson的ReadWriteLock的业务场景
* @author: zhangyanfeng
* @create: 2023-10-03 15:28
**/
public class ArticleService {
private static final String ARTICLE_LOCK_KEY = "article:lock";
private static final String ARTICLE_CONTENT_KEY = "article:content";
public static void main(String[] args) {
// 创建Redisson客户端
Config config = new Config();
config.useSingleServer()
.setAddress("redis://localhost:6379");
RedissonClient redisson = Redisson.create(config);
// 获取读写锁
RReadWriteLock rwLock = redisson.getReadWriteLock(ARTICLE_LOCK_KEY);
try {
// 获取读锁
rwLock.readLock().lock();
// 读取文章内容
String articleContent = getArticleContent();
System.out.println("文章内容:" + articleContent);
// 模拟读取操作耗时
Thread.sleep(1000);
// 释放读锁
rwLock.readLock().unlock();
// 获取写锁
rwLock.writeLock().lock();
// 编辑和发布文章
editAndPublishArticle();
System.out.println("文章编辑和发布成功");
// 释放写锁
rwLock.writeLock().unlock();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 关闭Redisson客户端
redisson.shutdown();
}
}
private static String getArticleContent() {
// 模拟从数据库或缓存中获取文章内容的操作
return "这是一篇文章的内容";
}
private static void editAndPublishArticle() {
// 模拟编辑和发布文章的操作
}
}
在这个示例中,首先创建了Redisson客户端,然后获取了一个读写锁(RReadWriteLock)。在代码中,首先获取了读锁,并读取文章内容,模拟了多个用户同时读取文章的场景。然后获取了写锁,并模拟了编辑和发布文章的操作。写锁在编辑和发布文章时保证了写入的原子性,并且会阻塞读取操作,直到写锁被释放。
具体原理源码可见
Redisson 实现分布式锁原理分析 - 知乎
Redis系列(二)Redisson分布式锁源码解析_redisson源码分析_白垩纪往事的博客-CSDN博客