环境说明
由于测试条件有限以下测试都是使用的单机redis,官方推荐使用红锁是需要5台master。
背景说明:
最近的电商项目C端用户在购买商品时可以使用多种货币(余额、券、卡、积分)支付,同时B端商户也可以多这些货币进行管理(如 余额撤回、卡券作废、充值卡作废等),为了保证金额的安全问题,那么首选考虑的就是加锁,但是由于是多种货币可以同时使用且要保证则加锁得多维度批量加锁、支持分布式(B端和C端是在不同服务中)、保证批量加锁的原子性。
基于以上分析和考虑,则选用了Redisson的multiLock。
代码实现(springboot+redis):
pom依赖
<!-- 导入 redission 依赖 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.16.4</version>
</dependency>
RedissonConfig(配置类):
@Configuration
public class RedissonConfig {
/**
* 单Redis节点模式配置方法
* 其他配置參數,看:
*
* @return
*/
@Bean
public RedissonClient redisson() {
Config config = new Config();
//单机模式 依次设置redis地址和密码
config.useSingleServer().setAddress("redis://192.168.40.130:6379").setPassword("123456");
return Redisson.create(config);
}
}
MultiLockService(加锁类):
@Component
public class MultiLockService {
@Autowired
private RedissonClient redissonClient;
public RLock tryMultiLock(List<String> list){
List<RLock> locks = new ArrayList<>();
list.forEach(key ->{
RLock rLock = redissonClient.getLock(key);
locks.add(rLock);
});
RLock[] array = locks.stream().toArray(RLock[]::new);
RLock multiLock = redissonClient.getMultiLock(array);
boolean isLook = multiLock.tryLock();
if(isLook){
return multiLock;
}
return null;
}
}
TestService(业务测试类)
@Component
@Slf4j
public class TestService {
@Autowired
private MultiLockService lockService;
@Async
public void multiLock(){
log.info("multiLock 尝试加锁...lock1、lock2、lock3");
List<String> list = new ArrayList<>();
list.add("lock1");
list.add("lock2");
list.add("lock3");
//加锁
RLock multiLock = lockService.tryMultiLock(list);
if(Objects.nonNull(multiLock)){
log.info("multiLock 加锁成功,多锁为:lock1、lock2、lock3");
try {
Thread.sleep(5000);
}catch (Exception e){
log.error("休眠异常",e);
} finally {
log.info("multiLock 开始解锁...");
multiLock.unlock();
}
} else {
log.info("multiLock 尝试加锁失败...");
}
}
@Async
public void tryLock(){
log.info("tryLock 尝试加锁...lock3、lock4、lock5");
List<String> list = new ArrayList<>();
list.add("lock3");
list.add("lock4");
list.add("lock5");
//加锁
RLock multiLock = lockService.tryMultiLock(list);
if(Objects.nonNull(multiLock)){
log.info("tryLock 加锁成功,多锁为:lock3、lock4、lock5");
try {
Thread.sleep(20000);
}catch (Exception e){
log.error("休眠异常",e);
}finally {
log.info("multiLock 开始解锁...");
multiLock.unlock();
}
} else {
log.info("tryLock 尝试加锁失败...");
}
}
}
测试步骤:
- 先执行multiLock()方法批量加锁 (lock1、lock2、lock3),在执行multiLock()加锁成功后5s内执行tryLock()尝试批量加锁(lock3、lock4、lock5),由于multiLock()中已经有lock3则tryLock批量加锁会全部失败,此时redis中会有3个key(lock1、lock2、lock3);
- 等待5s后multiLock()会执行解锁(此时redis中3个key也会同时被删掉),再执行tryLock()尝试批量加锁(lock3、lock4、lock5),则会加锁成功,此时redis中会有3个key(lock3、lock4、lock5);
- multiLock()加锁后等待10s,则会做解锁操作,同时redis中key(lock3、lock4、lock5)会删掉。
以上说明Redission的multiLock是完全支持批量加锁,并能保证批量加锁时的原子性(业务是在redis中用lua脚本实现);