You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免Rails并发请求触发重复的第三方外部API调用

问题解决方案

你遇到的是典型的Rails并发请求下重复执行逻辑的竞态条件问题,可按你的项目技术栈选择以下方案处理:

1. 数据库唯一约束 + 异常捕获(适合创建逻辑先写库再调API的场景)

这是成本最低、可靠性最高的防重复插入方案,首先给profiles表的user_id字段加唯一索引,从数据库底层禁止单用户重复生成档案:

# 执行命令生成迁移文件
rails g migration AddUniqueUserIdIndexToProfiles user_id:uniq
# 执行迁移
rails db:migrate

然后修改get_user方法,捕获唯一约束冲突异常即可:

def get_user
  @user = User.find(params[:id])
  return if @user.profile.present?

  begin
    @user.create_profile
  rescue ActiveRecord::RecordNotUnique
    # 其他请求已经完成创建,重新加载档案即可
    @user.reload.profile
  end
end

注意:如果你的create_profile逻辑是先调用第三方API、再把结果写入数据库,该方案只能避免重复存库,还是会触发两次API调用,这种情况建议用下面两种方案。

2. 数据库行锁(适合中小流量项目)

在查询用户的时候加行锁,让后续同用户的查询请求等待第一个请求的事务完成后再执行,从源头避免重复进入创建逻辑:

def get_user
  # 加行锁,同ID的用户查询会串行执行
  @user = User.lock.find(params[:id])
  @user.create_profile if @user.profile.nil?
end

该方案不需要额外引入第三方组件,实现成本极低,适合并发量不高的项目。

3. 分布式锁(适合高并发、已接入Redis的项目)

用Redis实现分布式锁,只有拿到锁的请求才能执行档案创建逻辑,完全避免重复调用第三方API:

def get_user
  @user = User.find(params[:id])
  return if @user.profile.present?

  lock_key = "profile_create:#{@user.id}"
  # 锁过期时间按第三方API的最大响应时间设置,这里示例为10秒
  if Redis.current.set(lock_key, 'locked', nx: true, ex: 10)
    begin
      @user.create_profile
    ensure
      # 执行完成后主动释放锁
      Redis.current.del(lock_key)
    end
  else
    # 未拿到锁的请求轮询等待创建完成,最多等待3秒
    6.times do
      break if @user.reload.profile.present?
      sleep 0.5
    end
  end
end

额外提示:不要直接用Rails自带的find_or_create_by方法处理该场景,它内部是先查询再插入,中间存在竞态窗口,并发场景下依然会出现重复创建的问题。

内容的提问来源于stack exchange,提问作者Prem Anand

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 20:06:02