### 关于乐观锁的重试
锁相关的知识可以参考 @zamia 的这篇文章 https://ruby-china.org/topics/28963
这篇文章算是一点小小的补充
### 如何本地复现乐观锁冲突
在访问量大的线上环境,使用了乐观锁的地方是有可能出现 StaleObjectError 的异常的,本地如何复现呢?这个有个小方法(可能比较 low)
使用 byebug 这个 gem,在锁的事务中加上 byebug 调试,例:
假设这是一个库存的对象,库存比较容易出现抢锁的情况
def unforzen
self.with_lock do
byebug
self.frozen_num -= 1
self.save!
end
end
开两个 console,获取两个对象(stock_a 和 stock_b)
调用 stock_a.unforzen,会进入到如下调试模式,可以看到已经进入到事务中了:
然后调用 stock_b.unforzen,跟 a 相同,也进入到了事务,这时将 a 的代码执行完,next 是单步执行,continue 是执行到下一个断点。
执行 b 的代码,这时你会发现 b 的 unforzen 方法抛出了 StaleObjectError 的异常
###准备工作结束,如何重试
值得注意的一点是,reload 需要给参数 lock: true,否则 lock_version 是不会更新的
def unforzen
optimistic_lock_retry_times = 3
begin
self.with_lock do
byebug
self.frozen_num -= 1
self.save!
end
rescue ActiveRecord::StaleObjectError => e
optimistic_lock_retry_times -= 1
if optimistic_lock_retry_times > 0
self.reload(lock: true)
retry
else
raise e
end
rescue => e
raise e
end
end
### 一点疑问
其实实际的代码要比例子复杂一点,这个释放库存的功能是通过一个 service 调用的,异常统一在 service 中处理,但是 service 不能直接获取到出现锁异常的 record,我尝试过 error.record.reload(lock: true),如下:
rescue ActiveRecord::StaleObjectError => e
p e.record.lock_version
e.record.reload(lock: true)
p e.record.lock_version
retry
end
发现 lock_version 是变了的,但是重新 retry 的代码中的 record 的 lock_version 还是原来的。这块还没仔细研究原理,懂得同学也可以分享一下~