场景背景
在礼品卡管理系统中,激活礼品卡是一个关键业务操作。该操作需要确保数据一致性,防止因并发操作导致的数据异常。传统的做法是使用Redisson分布式锁来防止并发,但随着业务发展,特别是面对大量礼品卡批量激活的场景,传统分布式锁方案暴露出了一些问题。
为什么不使用Redisson锁
1. 性能问题
当需要激活大量礼品卡(如10万条记录)时,使用Redisson分布式锁会遇到以下性能问题:
- 大量Redis连接开销:需要为每张礼品卡创建一个分布式锁,导致大量Redis操作
- 内存占用过高:需要维护大量的锁对象引用
- 网络延迟累积:每个锁操作都需要网络往返,大量操作会造成显著延迟
- Redis服务器压力:大量锁操作会给Redis服务器带来巨大压力
Redisson锁适用场景
Redisson分布式锁更适合用于锁定一段"业务流程",而不是具体的数据记录修改。典型的适用场景包括:
1. 房间消费结算流程
在房间消费结算过程中,需要锁定整个结算流程,防止在结算期间添加新的消费项目:
java
// 示例:房间消费结算时锁定业务流程
String lockKey = HealthRedisKeyUtil.getLockKey(HealthRedisKeyUtil.consumeRoom, roomId.toString());
RLock rlock = redissonClient.getLock(lockKey);
try{
rlock.
lock();
// 执行结算流程
// 此时不允许再添加新的消费项目
}finally{
if(rlock.
isHeldByCurrentThread()){
rlock.
unlock();
}
}
2. 订单创建流程
在创建订单的过程中,需要确保订单创建的原子性,防止重复创建:
java
// 示例:订单创建时锁定业务流程
String lockKey = HealthRedisKeyUtil.getLockKey(HealthRedisKeyUtil.order, orderId.toString());
RLock rlock = redissonClient.getLock(lockKey);
try{
rlock.
lock();
// 执行订单创建流程
}finally{
if(rlock.
isHeldByCurrentThread()){
rlock.
unlock();
}
}
3. 会员充值、消费、权益扣减等复合操作
这些操作通常涉及多个表的修改,需要保证整个业务流程的原子性:
java
// 示例:会员充值流程
String lockKey = HealthRedisKeyUtil.getLockKey(HealthRedisKeyUtil.memberRecharge, memberId.toString());
RLock rlock = redissonClient.getLock(lockKey);
try{
rlock.
lock();
// 1. 更新会员账户余额
// 2. 创建充值记录
// 3. 发放相关权益
// 整个流程需要保持原子性
}finally{
if(rlock.
isHeldByCurrentThread()){
rlock.
unlock();
}
}
为什么礼品卡激活适合使用版本号
1. 操作特性匹配
礼品卡激活操作具有以下特点:
- 单一数据修改:主要是更新gift_card_info表中的状态字段
- 可独立处理:每张礼品卡的激活可以独立进行,不需要强关联性
- 非高频操作:相对于实时交易,属于低频操作
2. 版本号机制优势
使用版本号(乐观锁)机制处理礼品卡激活有以下优势:
- 无锁开销:不需要维护分布式锁,降低系统复杂性
- 高并发性:允许多张不相关的礼品卡同时激活
- 精确冲突检测:能准确识别并处理并发冲突
- 良好的可重试性:失败的操作可以针对性重试
- 优秀的扩展性:无论是10条还是10万条记录都可以统一处理
3. 实现方案
sql
-- 数据库表结构修改
ALTER TABLE gift_card_info
ADD COLUMN version INT DEFAULT 0;
java
// GiftCardInfo实体类添加版本号字段
@TableField("version")
private Integer version = 0;
// 更新时使用版本号进行乐观锁控制,并结合状态机验证
UpdateWrapper<GiftCardInfo> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("id", giftCardInfo.getId());
updateWrapper.eq("version", giftCardInfo.getVersion()); // 版本号检查
updateWrapper.eq("giftUseStatus", GiftUseStatus.unactivated); // 状态机验证:只有未激活的才能激活
updateWrapper.eq("status", Status.enable); // 状态机验证:只有启用状态的才能激活
updateWrapper.set("giftUseStatus", GiftUseStatus.activated);
updateWrapper.set("effectiveTime", new Date()); // 激活时需要设置生效时间
updateWrapper.setSql("version = version + 1"); // 版本号递增
总结
在礼品卡激活这类场景中,使用版本号乐观锁替代Redisson分布式锁是更为合适的选择。这种方案既保证了数据一致性,又避免了分布式锁的各种性能和可靠性问题。而对于需要锁定整个业务流程的场景,如房间消费结算、订单创建等,Redisson分布式锁仍然是首选方案。