Redisson分布式锁 和 乐观锁的使用场景

场景背景

在礼品卡管理系统中,激活礼品卡是一个关键业务操作。该操作需要确保数据一致性,防止因并发操作导致的数据异常。传统的做法是使用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分布式锁仍然是首选方案。

相关推荐
零匠学堂202532 分钟前
移动学习系统,如何提升企业培训效果?
java·开发语言·spring boot·学习·音视频
小杨快跑~39 分钟前
从装饰者到桥接再到工厂:模式组合的艺术
java·开发语言·设计模式
饕餮争锋42 分钟前
Spring内置的Bean作用域介绍
java·后端·spring
却话巴山夜雨时i42 分钟前
394. 字符串解码【中等】
java·数据结构·算法·leetcode
豆苗学前端1 小时前
面试复盘:谈谈你对 原型、原型链、构造函数、实例、继承的理解
前端·javascript·面试
张人大 Renda Zhang1 小时前
Java 虚拟线程 Virtual Thread:让“每请求一线程”在高并发时代复活
java·jvm·后端·spring·架构·web·虚拟线程
一勺菠萝丶1 小时前
解决 SLF4J 警告问题 - 完整指南
java·spring boot·后端
济南壹软网络科技有限公司1 小时前
架构深潜:通霸IM——私有化部署、全链路开源的高可用企业级即时通讯技术基座
java·架构·开源·即时通讯源码·即时通讯im
小股虫1 小时前
手搓限流第二版:限流算法与动态阈值的深度整合
java