Ruby 定时任务在容器化环境中的动态扩缩容配置方案

核心问题:避免重复执行

在动态扩缩容器环境下,多个容器实例同时运行定时任务会导致重复执行。解决方案需确保任务在集群中仅执行一次

基于 Whenever 的配置方案
# config/schedule.rb
require_relative "../lib/task_lock"

every :day, at: "4:30 am" do
  if TaskLock.acquire?
    runner "DataCleaner.perform"
  end
end

关键组件实现
  1. 分布式锁机制 (Redis 实现示例)
# lib/task_lock.rb
class TaskLock
  REDIS = Redis.new(url: ENV["REDIS_URL"])

  def self.acquire?(key: "task_lock", ttl: 300)
    return false unless ENV["CONTAINER_ROLE"] == "worker"
    
    REDIS.set(key, "locked", nx: true, ex: ttl)
  end
end

  1. 容器角色标识
# Dockerfile 片段
ENV CONTAINER_ROLE=worker

动态扩缩容策略
组件作用
环境变量通过 CONTAINER_ROLE 区分 worker/非 worker 节点
Redis 锁确保集群中只有一个实例获取执行权 (TTL 防止死锁)
健康检查在 Kubernetes 中配置 livenessProbe 确保故障节点自动替换
HPA 配置基于 CPU/Memory 的自动扩缩容策略
Kubernetes 部署要点
  1. StatefulSet 配置 (确保稳定网络标识)
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: worker
spec:
  serviceName: worker
  replicas: 3
  template:
    spec:
      containers:
      - name: app
        env:
        - name: CONTAINER_ROLE
          value: "worker"

  1. 水平扩缩容 (HPA)
kubectl autoscale statefulset worker --cpu-percent=70 --min=2 --max=10

执行流程
  1. 所有 worker 容器加载定时任务配置
  2. 任务触发时通过 Redis 分布式锁竞争执行权
  3. 获取锁的容器执行任务,其他容器跳过
  4. 任务完成自动释放锁 (TTL 兜底)
  5. 容器扩缩时自动加入/退出竞争
优化建议
  1. 锁粒度控制:按任务类型使用不同锁键
  2. 监控集成
    # 任务执行监控
    Datadog.increment("scheduled_tasks.runs", tags: ["task:cleaner"])
    

  3. 优雅退出:容器终止前完成正在执行的任务
  4. 配置热更新:使用 ConfigMap 动态更新 schedule.rb

关键优势
此方案在保持 Whenever 原生配置方式的同时,通过分布式锁实现动态扩缩容支持,无需修改核心业务代码,适用于 Kubernetes/Docker Swarm 等主流容器平台。

更多推荐