本文示例使用kotlin语言,java也大同小异

问题描述

A服务作为下游服务,就需要扛住上游服务的各种调用方式;某天上游调用的方式优化改进上线后,A服务的某接口开始出现重复数据处理问题,针对此问题当时最直观的猜想是redis分布式锁未起作用,比如redis服务故障或者什么原因?排查后发现是spring事物与锁使用不当造成的问题

一、spring事物与锁的错误使用示例

    @Transactional
    override fun doBusinessLogic(params: Any): Any? {
  
        ... 
        
        val lock = redisLock.createLock("xxxx_key")
        lock.lock()
        try {

           ...
           
        } finally {
            lock.unlock()
        }

这种写法的问题在于:当锁释放的时候事物还没有结束,此时另一个请求进入锁同步区域时读不到事物未提交的数据(这里数据库隔离级别采用的是读已提交)

二、改进

1.主要问题

  • 事物与锁的力度问题:锁的范围应该大于事物的范围
  • 考虑spring动态代理方法的生效条件:@Transactional注解事物是通过代理实现

2.正确示例

代码如下(示例):

@Component
class LockTransactionalSupport {

    @Transactional(rollbackFor = [Exception::class])
    fun <R> wrapperWithTransactional(action: () -> R): R {
        return action.invoke()
    }

}
@SpringBootTest
class XxxTest {

    @Autowired
    lateinit var lockTransactionalSupport: LockTransactionalSupport

    @Autowired
    lateinit var redisLock: RedisLock

    @Test
    fun testWrapperWithTransactional() {
        val lock = redisLock.createLock("my_lock_key")

        lock.lock()
        try {
            lockTransactionalSupport.wrapperWithTransactional {
            
                ...
                
                // do db save ops
                
                ...
                
                // throw err and rollback
                throw Exception("err.")
            }
        } finally {
            lock.unlock()
        }

    }
}
  • 将锁的范围放在事物的外层
  • 通过LockTransactionalSupport定义的函数式编程方式,用以保证@Transactional注解可以生效(对于代理方法必须要外部直接调用,才能走真正的代理逻辑)

当然如何在锁中去执行事务操作也有其他的实现方案,比如说编码的方式自己写 开启事物、提交事物或者回滚事物等


总结

  • 需要注意锁与事物同时使用的情况
  • 需要注意spring代理生效的条件



相关文章:

redis分布式锁使用注意事项

Logo

GitCode 天启AI是一款由 GitCode 团队打造的智能助手,基于先进的LLM(大语言模型)与多智能体 Agent 技术构建,致力于为用户提供高效、智能、多模态的创作与开发支持。它不仅支持自然语言对话,还具备处理文件、生成 PPT、撰写分析报告、开发 Web 应用等多项能力,真正做到“一句话,让 Al帮你完成复杂任务”。

更多推荐