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分布式锁仍然是首选方案。

相关推荐
BestAns8 分钟前
一文带你吃透 Java 反射机制
java·后端
wasp52016 分钟前
AgentScope Java 核心架构深度解析
java·开发语言·人工智能·架构·agentscope
2501_9167665425 分钟前
【Springboot】数据层开发-数据源自动管理
java·spring boot·后端
自在极意功。42 分钟前
MyBatis 动态 SQL 详解:从基础到进阶实战
java·数据库·mybatis·动态sql
sxlishaobin1 小时前
MySQL- explain
数据库·mysql
软件管理系统1 小时前
基于Spring Boot的便民维修管理系统
java·spring boot·后端
源代码•宸1 小时前
Leetcode—620. 有趣的电影&&Q3. 有趣的电影【简单】
数据库·后端·mysql·算法·leetcode·职场和发展
百***78751 小时前
Step-Audio-2 轻量化接入全流程详解
android·java·gpt·php·llama
快乐肚皮2 小时前
MySQL递归CTE
java·数据库·mysql·递归表达式