本文示例使用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代理生效的条件
相关文章: