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

Rails中如何配置仅针对单用户的Mutex(互斥锁)?

在Rails中实现针对单一用户的互斥锁方案

当然可以实现,下面提供几种适配不同部署场景的方案,既能保证同一用户的操作互斥,又不会影响其他用户的并发执行:

一、内存级用户专属互斥锁(单进程场景适用)

如果你的Rails应用是单进程部署(比如Puma单进程模式、单Worker的Unicorn),可以维护一个线程安全的哈希表,为每个用户分配独立的Mutex实例,只有同一用户的并发请求会被阻塞,不同用户互不干扰。

代码示例:

# 在Entry模型中定义线程安全的锁哈希
USER_MUTEXES = Concurrent::Hash.new { |hash, user_id| hash[user_id] = Mutex.new }

def self.update(user, day, segment, category_id)
  # 获取当前用户对应的锁
  mutex = USER_MUTEXES[user.id]
  mutex.synchronize do
    entry = find(user, day, segment)
    
    entry ||= Entry.new(user: user, date: day, segment: segment)
    entry[key_for_id(category_id)] = category_id
    entry.save!
  end
end

注意事项:

  • 依赖concurrent-ruby gem(Rails 5+默认已包含),确保哈希表的线程安全。
  • 仅在单进程环境有效,多进程部署时每个进程有独立内存空间,锁无法跨进程生效。

二、数据库层面原子操作(全场景通用)

多进程或分布式部署下,内存锁失效,必须依赖数据库的原子性来保证同一用户的操作互斥,同时不影响其他用户。

方案1:唯一索引 + 异常捕获

首先给entries表添加联合唯一索引,确保同一用户、日期、segment的记录唯一:

# 生成数据库迁移文件并添加索引
def change
  add_index :entries, [:user_id, :date, :segment], unique: true
end

然后修改方法,利用数据库的唯一约束处理并发创建,捕获冲突异常后重新查询:

def self.update(user, day, segment, category_id)
  begin
    entry = Entry.find_or_create_by!(user: user, date: day, segment: segment) do |e|
      e[key_for_id(category_id)] = category_id
    end
  rescue ActiveRecord::RecordNotUnique
    # 并发创建冲突时,直接查询已存在的记录
    entry = Entry.find_by!(user: user, date: day, segment: segment)
  end

  entry[key_for_id(category_id)] = category_id
  entry.save!
end

方案2:使用Rails的upsert方法(推荐)

Rails 6+支持upsert方法,对应数据库的INSERT ... ON CONFLICT DO UPDATE(PostgreSQL)或INSERT ... ON DUPLICATE KEY UPDATE(MySQL),完全原子化处理插入/更新,无需手动捕获异常:

def self.update(user, day, segment, category_id)
  Entry.upsert(
    {
      user_id: user.id,
      date: day,
      segment: segment,
      key_for_id(category_id) => category_id
    },
    unique_by: [:user_id, :date, :segment] # 指定唯一约束的字段
  )
end

优势:

  • 完全原子性,数据库层面保证并发安全。
  • 适配所有部署场景,性能优于内存锁(无需进程内同步)。

三、数据库悲观锁(适用于复杂更新逻辑)

如果你的更新逻辑更复杂,需要在获取记录后执行多步操作,可以结合with_lock实现悲观锁,确保同一用户的并发请求串行执行:

def self.update(user, day, segment, category_id)
  # 先原子化获取或创建记录
  entry = Entry.find_or_create_by(user: user, date: day, segment: segment)
  # 对记录加排他锁,同一用户的并发请求会等待锁释放
  entry.with_lock do
    # 这里执行复杂的更新逻辑
    entry[key_for_id(category_id)] = category_id
    entry.save!
  end
end

注意:同样需要先添加联合唯一索引,避免并发创建重复记录。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 06:16:03