redis分布式锁踩坑|spring 事务踩坑

在并发场景中,结合Redisson分布式锁和@Transactional可能会遇到问题,如线程A读取到线程B未提交的事务数据,引发错误。在房间打赏礼物的业务中,发现用户流水账目错误。解决方案包括分析日志、模拟业务场景,调整业务逻辑,以降低锁粒度,避免死锁风险,处理锁超时和确保锁的正确释放。

问题记录


问题记录:redisson分布式锁和@Transactional在并发场景中的Bug

详细描述:线程A抢占到线程B未提交事务但已释放的锁的,以及线程A读取到未提交的数据,导致线程A处理用户流水明细记录时,出现流水账目错误。

业务场景:房间内用户可以相互打赏礼物,在高频刷礼物的场景下,导致用户余额扣增错误

解决方法:

        1、通过生产环境的日志进行原因分析

        2、猜测可能出现的错误原因

  • 事务隔离机制导致的释放锁后,事务未提交,导致读取到旧数据
  • 其它线程把正在执行任务线程的锁给释放了

        3、模拟生产环境的业务代码,进行场景还原

        4、确定问题是因为抢到锁之后,读取到了未提交的事务数据,问题进行修改

问题解决方案

原业务逻辑伪代码:        

@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public Action consume() {
    
    //xxxx逻辑处理
    Business giftShareAmountMessage = new Business();
    serviceA.save(giftShareAmountMessage);

    serviceB.operaUserAmountPolymerization(giftShareAmountMessage);
}

@Service
public class serviceB{
    @Transactional(rollbackFor = Exception.class)
    public void operaUserAmountPolymerization(){
        boolean lock = redisLock.multiLock(lockKeys);
        if (!lock) {            
throw new ServiceException(ResponseCodeEnum.CHANGE_USER_AMOUNT_LOCK_FAIL);
        }
        
        selectUserAmountRecord();
        saveBatchUserAmountDetail();
        updateUserAmount();
    }
}

调整后伪代码

public Action consume() {
    
    //xxxx逻辑处理
    Business giftShareAmountMessage = new Business();
    serviceB.operaUserAmountPolymerization(giftShareAmountMessage);

   
}

@Service
public class serviceB{
    public void operaUserAmountPolymerization(){
        boolean lock = redisLock.multiLock(lockKeys);
        if (!lock) {            
throw new ServiceException(ResponseCodeEnum.CHANGE_USER_AMOUNT_LOCK_FAIL);
        }
        
        //手动开启事务
        serviceA.save(giftShareAmountMessage);
        selectUserAmountRecord();
        saveBatchUserAmountDetail();
        updateUserAmount();
        //手动提交或者回滚事务
    }
}

当Redisson分布式锁和事务一起使用时,可能会出现以下问题:

  1. 锁粒度过大:使用分布式锁时,锁的粒度通常是比较大的,即在一个事务中可能需要加锁的代码块比较多。这可能导致锁的粒度过大,造成了并发性能的损失。
  2. 死锁风险:在一些复杂的业务场景中,可能会出现多个并发事务之间相互等待对方释放锁的情况,从而导致死锁。这种情况下,需要谨慎设计事务和锁的顺序,以避免死锁风险。
  3. 锁超时问题:如果事务在加锁后执行时间过长,超过了锁的超时时间,那么其他事务可能会在此期间获取到锁并执行对应的操作,从而导致数据不一致的问题。为了避免这种情况,可以考虑增加锁的超时时间或者重新设计业务逻辑。
  4. 锁释放问题:在使用Redisson分布式锁时,需要确保在事务结束后正确释放锁。如果锁没有被释放,可能会导致其他事务无法获取到锁,从而影响系统的正常运行。

<think>嗯,用户现在想了解spring-data-redis的相关信息,包括作用使用方法。结合之前的对话历史,用户之前询问过Fastjson的用途,现在转向了Spring生态中操作Redis的工具库,说明ta可能在开发中遇到了缓存或数据存储的需求。 用户提供的5个引用内容非常有价值。引用[1][4]都强调了spring-data-redis的核心价值:用面向对象的方式操作Redis,并封装了不同客户端(Jedis/Lettuce)的差异。引用[2][5]则提到关键组件RedisTemplate序列化配置,特别是引用[5]给出了具体的依赖配置示例。引用[3]补充了SpringBoot集成场景,还提到了@Cacheable注解这个重要特性。 用户可能是个Java后端开发者,当前正在搭建或优化Redis集成模块。ta的提问比较开放,可能需要从基础概念到实操的完整指引。考虑到ta之前关注过JSON序列化(Fastjson),这次要特别注意强调spring-data-redis的序列化配置——这正是容易的地方。 需要重点覆盖四个维度:1) 库的定位(为什么不用原生客户端)2) 核心功能(Template/Repositories/Cache抽象)3) 序列化机制(对比Jackson/Fastjson的选择)4) SpringBoot集成范例。其中序列化部分要展开说明默认JDK序列化的缺陷,以及如何切换为JSON方案——这正好用户之前问的Fastjson知识关联起来。 在给出代码示例时,要区分传统Spring配置SpringBoot自动配置两种方式。对于注解驱动缓存这块,虽然引用[3]提到但未展开,可以补充@Cacheable的典型用法,毕竟这是实际开发的高频特性。 最后生成的"相关问题"要有递进性:从基础(配置调优)到进阶(事务/集群),再到生态整合(Spring Cache)。这样无论用户是初学者还是需要深度优化,都能找到延伸方向。 </think>Spring Data RedisSpring 框架中用于简化 Redis 操作的**核心模块**,它通过高度封装底层客户端(如 Jedis、Lettuce),提供统一的、面向对象的 API 来访问 Redis 数据库。以下是其核心作用与使用方法的系统说明: --- ### **一、核心作用** 1. **统一客户端抽象** 屏蔽不同 Redis 客户端(Jedis、Lettuce、JRedis)的差异[^4],开发者无需关心底层实现切换,代码兼容性强。 2. **简化操作接口** 提供 `RedisTemplate` `StringRedisTemplate` 等模板类,封装了键值操作、事务、管道、发布订阅等 Redis 功能[^2][^5]。 3. **无缝集成 Spring 生态** - 支持 Spring 声明式缓存注解(如 `@Cacheable`),实现方法级缓存[^3]。 - 与 Spring Boot 深度整合,通过 `spring-boot-starter-data-redis` 实现自动配置[^3][^5]。 4. **灵活的序列化机制** 提供多种序列化策略(JDK、JSON、String 等),并支持自定义序列化器[^3][^5]。 --- ### **二、使用方法详解** #### **步骤 1:添加依赖(Spring Boot)** ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> ``` > 默认使用 Lettuce 客户端(基于 Netty,线程安全)[^5]。 #### **步骤 2:配置连接参数** ```properties # application.properties spring.redis.host=192.168.0.24 spring.redis.port=6379 spring.redis.password=yourpassword spring.redis.database=0 spring.redis.timeout=1000 # 连接超时(ms) spring.redis.lettuce.pool.max-active=200 # 连接池最大连接数 ``` > Lettuce 支持连接池共享,减少资源消耗[^5]。 #### **步骤 3:使用 RedisTemplate 操作数据** ```java @Autowired private RedisTemplate<String, Object> redisTemplate; // 写入字符串 redisTemplate.opsForValue().set("key", "value"); // 写入对象(需配置序列化) User user = new User("Alice", 25); redisTemplate.opsForValue().set("user:1", user); // 读取数据 String value = (String) redisTemplate.opsForValue().get("key"); User cachedUser = (User) redisTemplate.opsForValue().get("user:1"); ``` #### **步骤 4:自定义序列化(以 JSON 为例)** ```java @Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); // 使用 Fastjson 序列化值 GenericFastJsonRedisSerializer serializer = new GenericFastJsonRedisSerializer(); template.setValueSerializer(serializer); template.setKeySerializer(new StringRedisSerializer()); return template; } } ``` > 替换默认 JDK 序列化,避免可读性差兼容性问题[^3]。 #### **步骤 5:声明式缓存(通过注解)** ```java @Service public class UserService { @Cacheable(value = "users", key = "#id") public User getUserById(String id) { // 模拟数据库查询 return new User(id, "User" + id); } } ``` > 需在启动类添加 `@EnableCaching` 注解[^3]。 --- ### **三、关键优势** | 特性 | 说明 | |---------------------|----------------------------------------------------------------------| | **生产级可靠性** | 内置连接池管理、异常转换机制,避免资源泄漏[^2] | | **高性能** | Lettuce 基于 Netty 实现异步 I/O,支持响应式编程[^5] | | **扩展性强** | 支持 Redis 集群、哨兵模式、事务、Lua 脚本等高级特性[^4] | | **开发效率高** | 减少样板代码,通过注解快速实现缓存逻辑[^3] | --- ### **典型应用场景** 1. **分布式缓存**:加速数据库查询结果缓存(如 `@Cacheable`) 2. **会话存储**:替代 Tomcat Session 实现分布式会话 3. **消息队列**:通过 `ListOperations` 实现轻量级队列 4. **实时计数**:利用 `incr` 命令实现点赞/访问量统计 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值