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

复制Ruby on Rails容器时Sidekiq会重复运行吗?如何避免定时任务重复执行

Sidekiq定时任务多容器部署问题解答

多容器实例会导致重复执行定时任务吗?

会的。每个Rails应用容器启动时都会注册Sidekiq定时任务,要是你部署多个实例,每个实例里的Sidekiq调度器(比如用sidekiq-scheduler的场景)都会独立触发任务,同一个定时任务就会被多次执行。

怎么让任务只在单个容器执行?

推荐两种靠谱的解决办法,结合你提供的Docker Compose配置来调整:

方法一:拆分独立的Sidekiq调度器容器

专门创建一个只负责运行Sidekiq调度器的容器,其他应用容器只处理Web请求或者常规Sidekiq任务,不启动调度功能。

修改你的docker-compose.yml,新增两个Sidekiq相关服务(一个处理常规任务,一个负责调度):

version: '3.8'
services:

  thedatabase:
    image: postgres:12
    volumes:
      - ./database:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: theuser
      POSTGRES_PASSWORD: thepass
    networks: 
      - excnetwork

  theapi:
    build: 
      context: ./dex-api
      dockerfile: Dockerfile
    volumes:
      - ./dex-api:/web
    env_file: 
      - ./dex-api/.env
    ports:
      - "8081:3000"
    depends_on:
      - thedatabase
      - redis
    networks: 
      - excnetwork
    # 让web容器只跑Rails服务器,不启动Sidekiq
    command: bundle exec rails server -b 0.0.0.0 -p 3000

  sidekiq-worker:
    build: 
      context: ./dex-api
      dockerfile: Dockerfile
    volumes:
      - ./dex-api:/web
    env_file: 
      - ./dex-api/.env
    depends_on:
      - thedatabase
      - redis
    networks: 
      - excnetwork
    # 只运行常规Sidekiq任务处理器,不启动调度器
    command: bundle exec sidekiq -C config/sidekiq.yml

  sidekiq-scheduler:
    build: 
      context: ./dex-api
      dockerfile: Dockerfile
    volumes:
      - ./dex-api:/web
    env_file: 
      - ./dex-api/.env
    # 强制只跑一个实例,避免重复调度
    deploy:
      replicas: 1
    depends_on:
      - thedatabase
      - redis
    networks: 
      - excnetwork
    # 启动Sidekiq调度器,负责定时任务的触发
    command: bundle exec sidekiq -C config/sidekiq.yml
    # 添加环境变量,让代码只在这个容器启动调度器
    environment:
      - ENABLE_SCHEDULER=true

  redis:
    image: redis
    command: 'redis-server --requirepass "yourpassword"'
    volumes:
      - ./redis:/var/lib/redis/data
    networks: 
      - excnetwork

volumes:
  database:
  dex-api:
  redis:
  
networks: 
  excnetwork:
    driver: bridge

然后在Rails的Sidekiq初始化代码里,通过环境变量控制调度器是否启用:

# config/initializers/sidekiq.rb
if ENV['ENABLE_SCHEDULER'] == 'true'
  require 'sidekiq-scheduler'
  Sidekiq::Scheduler.enabled = true
else
  Sidekiq::Scheduler.enabled = false
end

这样只有sidekiq-scheduler容器会触发定时任务,其他实例不会,彻底解决重复执行问题。

方法二:用Redis分布式锁控制任务执行

如果不想拆分容器,可以给定时任务加个分布式锁,只有抢到锁的实例才会执行任务。

示例代码如下:

class MyScheduledJob
  include Sidekiq::Job

  def perform
    # 生成唯一锁键,设置过期时间(要比任务最长执行时间长)
    lock_key = "scheduled_job:#{self.class.name}"
    # 只有当锁不存在时才设置成功,返回true
    acquired_lock = Sidekiq.redis { |conn| conn.set(lock_key, "locked", nx: true, ex: 3600) }

    return unless acquired_lock

    begin
      # 这里写你的任务逻辑
    ensure
      # 任务执行完不管成功失败都释放锁
      Sidekiq.redis { |conn| conn.del(lock_key) }
    end
  end
end

这种方式的缺点是如果任务执行时间超过锁的过期时间,可能会导致重复执行,所以要根据任务实际情况调整过期时间。

总结

优先选方法一,拆分容器的方式架构更清晰,维护起来也更简单,从根源上避免了重复执行的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 11:50:45