新手问题 Rails 锁 --- 能不能对一段逻辑代码加锁,高并发情况下总被反复执行

return · October 30, 2017 · Last by yfractal replied at November 07, 2017 · 3775 hits

逻辑代码如下:

...
...
...
project = Project.create!(:name => "name", ...)
api = Api.client.update(...)  # 这是一个调用第三方api的一个更新操作
if project.present?
  User.update_column(...)
end
...
...
...

现在的问题是在高并发下,这段代码可能会被执行多次,有方法对这整过过程加锁吗?类似于 lock/unlock

如果仅对 Project 加锁,

roject.lock.create!(...)

加锁,后面的 api...这段代码应该还是有可能执行多次吧? Rails Guide 对锁的介绍好抽象,望大神能帮解惑,非常感谢!

@huacnlee @lgn21st @Rei @hooopo 如果有时间的话,麻烦帮解答下,非常感谢~

Redis::Lock.new("#{cache_key}").lock do
  project = Project.create!(:name => "name", ...)
  api     = Api.client.update(...) # 这是一个调用第三方api的一个更新操作
end

抛一个思路

  1. 想清楚这段的代码的静态资源实体是什么?是 project 实例?还是业务逻辑?为什么会出现高并发呢?
  2. 数据库能够提供给你怎样的工具?行锁?事务?
  3. 涉及到外部网络请求的时候,会对锁造成什么影响?能否调整一下实现思路,例如异步地去请求?

2 楼、3 楼的两位,只是单纯地给楼主几行 COPY 代码而已,能解决问题,但不一定是最好的方案。

  1. 对外接口,加 rate limit
  2. Project 加唯一索引

给 project name 加唯一索引,然后 update 在 after_create 里触发。

Reply to pynix

这种写法怎么都感觉最后会很惨

Reply to 42thcoder

非常感谢~

这段代码是用户点击一个按钮然后后台会执行这系列的操作,而且这些步骤不能少,必须在这个点击操作中完成 具体代码如下:

project = Project.where(...).first
if project.present?
  redirct_to project_path(...)
else
 ActiveRecord::Base.transaction do
     project = Project.create!(:name => "name", ...)
    api = Api.client.update(...)  # 这是一个调用第三方api的一个更新操作
    User.update_column(...)
 end
end

所以一旦用户量多了,出现大并发情况,用户多次点击,就很有可能多次走 else,数据就会被多创,之前用数据库唯一索引(validates_uniqueness_of),是可以保证不会重复创建,但是重创的时候抛出的异常体验很不好 我想的是能不能通过锁能不能解决这个问题: 比如:

project = Project.where(...).lock(true).first

如果这样是不是就能解决问题,但是这个锁是不是只对 projects 表锁定,并发情况下还会走 else 吗?会出现多次创建的问题吗? 模拟并发环境好麻烦 对于锁我看了官方 guid,但是感觉好抽象,请指教,感谢!

Reply to hging

为何?

Reply to pynix

after_create 多了你根本控制不住。最后就是 GG

Reply to return

可以先考虑在前端用户点击层,先进行防止短时间快速重复点击的过滤。 然后,数据库加唯一索引其实是挺好的操作,不喜欢报错异常可以捕捉异常。 使用悲观锁会降低数据库并发能力,还不如唯一索引报错 赞同楼上说的,after 这种操作习惯性多了之后,基本就“失控了” 能用别的方法解决问题的,我不会首选用锁的机制。除了这显然用锁是最好的情况

Reply to return

不明白抛出异常怎么就用户体验不好了,捕捉异常处理一下给个友好的用户提示不可以么?

可能还是需要使用锁(悲观锁) 发现一个很奇怪的问题:validates_uniqueness_of 在资源竞争比较激烈的时候不能起到作用 还是会有重复的记录 比较疑惑,这是什么鬼... @pathbox

validates_uniqueness_of 的官方解释,不理解为什么还是用重复记录被创建

# When the record is created, a check is performed to make sure that no record exists in the database
 # with the given value for the specified attribute (that maps to a column). When the record is updated,
 # the same check is made but disregarding the record itself.
 #

@huacnlee 求助,tks

validates_uniqueness_of 这个失效的问题 我遇到过,两个参数相同的请求相差几毫秒,竟然通过了 validates_uniqueness_of 的验证。后来我在数据库表加唯一索引,来避免重复记录的情况,还是相信数据库的性能足够强大

其实就 https://github.com/leandromoreira/redlock-rb 就好,比数据库的锁灵活

@return 因为高并发的情况第一条数据通过 validate 但是还未在 DB 层面 commit,接着第二条在很短的时间内也通过了验证,这样 validates_uniqueness_of 就没什么卵用。所以只能从有原子性操作的地方入手,数据库唯一索引稳稳的解决你的问题 也可以通过 Redis SETNX lock 处理

@return 感觉这样的操作,可以放到队列中去处理,后端直接返回结果,队列保证执行成功

Reply to return

重创的时候后台处理抛出的异常,进行 retry。

谢谢热情的你们,问题已解决~🎀 🎀

这种只能通过唯一索引解决。。。知道数据库多牛逼没。。

Reply to return

有时间分享一下解决方案?

You need to Sign in before reply, if you don't have an account, please Sign up first.