Rails 8 使用 SolidQueue 替代 Redis 架构分析

一、背景与目标

1. 项目背景
    A. 业务场景
    Rails 8 从默认技术栈中移除了 Redis。新的功能组件——SolidQueue(任务队列)、SolidCache(缓存)、SolidCable(实时消息传递)——完全运行在应用程序现有的关系数据库服务上。对于大多数 Rails 应用,可以不再依赖 Redis。

    B. 痛点分析
    Redis 虽然是快速、可靠的基础设施,但它增加了系统复杂性:
    - 需要单独部署、版本管理、补丁和监控服务器软件
    - 需要配置持久化策略(RDB 快照、AOF 日志或两者)
    - 需要设置内存限制和淘汰策略
    - 需要维护 Rails 与 Redis 之间的网络连接和防火墙规则
    - 需要配置 Redis 客户端认证
    - 需要构建和维护高可用 Redis 集群
    - 需要协调 Sidekiq 进程的生命周期管理

2. 设计目标
    A. 功能目标
    - 使用关系数据库替代 Redis 实现任务队列功能
    - 支持任务调度、并发控制、重试机制
    - 提供监控和管理界面

    B. 非功能目标
    - 降低运维复杂性
    - 减少基础设施组件数量
    - 保持足够的性能水平

二、Redis 运维成本分析

1. 直接成本
    A. 服务器部署
    - Redis 服务器软件安装与配置
    - 版本升级与补丁管理
    - 服务器资源监控

    B. 持久化配置
    - RDB 快照:内存快照持久化
    - AOF 日志:追加文件日志
    - 混合策略:结合两者优势

2. 间接成本
    A. 网络与安全
    - 防火墙规则配置
    - Redis 客户端认证管理
    - 高可用集群部署

    B. 进程管理
    - Sidekiq 进程编排
    - 跨部署的生命周期管理
    - 进程崩溃恢复

3. 调试复杂性
    当任务出现问题时,需要同时调试 Redis 和 RDBMS 两个具有不同语义的数据存储,在不同的查询语言和工具之间切换上下文。还需要维护两套独立的备份策略。

三、SolidQueue 工作原理

1. 核心技术:SKIP LOCKED

    PostgreSQL 9.5 增强了 SQL 的 `FOR UPDATE` 子句,添加了 `SKIP LOCKED` 选项。`FOR UPDATE` 创建排他行锁,`SKIP LOCKED` 进一步跳过当前被锁定的行。

    这使得基于数据库的任务队列成为可能,即使在规模化场景下也能正常工作。

2. 任务获取流程

    工作进程获取任务的查询:
    ```sql
    SELECT * FROM solid_queue_ready_executions
    WHERE queue_name = 'default'
    ORDER BY priority DESC, job_id ASC
    LIMIT 1
    FOR UPDATE SKIP LOCKED
    ```

    自由的工作进程始终获取下一个可用任务。

    这个数据库优化解决了早期数据库队列实现面临的根本问题:**锁竞争**。工作进程从不等待另一个进程,也从不阻塞。多个工作进程可以同时查询,PostgreSQL 保证每个进程获得唯一的任务。

3. 数据库表结构

    SolidQueue 架构围绕三个核心表:

    ```mermaid
    graph TD
        subgraph SolidQueue 数据库表结构
            A[solid_queue_jobs<br/>任务元数据表]
            B[solid_queue_scheduled_executions<br/>定时任务表]
            C[solid_queue_ready_executions<br/>就绪任务表]
        end

        D[新任务] -->|已调度| B
        D -->|立即执行| C
        B -->|到达执行时间| C
        C -->|工作进程获取| E[执行中]
        E -->|完成| F[删除执行记录]
        E -->|失败| G[失败记录表]
    ```

    <img src="https://static.op123.ren/static/d9/d9ea6cf58fd285b4.svg" alt="SolidQueue 数据库表结构" width="700">

    A. solid_queue_jobs
    存储所有任务元数据,包括任务名称、Ruby 类、开始和结束时间戳。默认情况下,每个排队请求都会记录在此表中,并在任务完成后永久保留。

    B. solid_queue_scheduled_executions
    定时任务等待直到其预定时间到达。

    C. solid_queue_ready_executions
    准备立即运行的任务排队在此,工作进程从中获取任务。

    PostgreSQL 的 MVCC 设计通过内置的 autovacuum 进程很好地处理了任务表的快速和稳定 churn(大量插入和删除)。无需特殊调优。

4. 进程架构

    ```mermaid
    graph LR
        subgraph SolidQueue 进程组件
            A[Supervisor<br/>监督进程]
            B[Workers<br/>工作进程]
            C[Dispatchers<br/>调度进程]
            D[Schedulers<br/>定时任务进程]
        end

        A --> B
        A --> C
        A --> D

        B -->|轮询 0.1-0.2s| C
        D -->|每秒轮询| C
        C --> B
    ```

    <img src="https://static.op123.ren/static/c1/c1af6fa9474e338b.svg" alt="SolidQueue 进程架构" width="600">

    A. Workers(工作进程)
    轮询 solid_queue_ready_executions,间隔可配置(高优先级队列最快可达 0.1 秒)

    B. Dispatchers(调度进程)
    每秒轮询 solid_queue_scheduled_executions,将到期任务移至就绪表

    C. Schedulers(定时任务进程)
    按定义的时间表入队任务,管理周期性任务

    D. Supervisor(监督进程)
    监控所有进程,跟踪心跳并重启崩溃的进程

    这些分离的关注点是 SolidQueue 最优雅的特性之一。每种进程类型在不同的表上操作,使用针对其工作负载优化的不同轮询间隔。

四、定时任务功能

1. 配置方式

    SolidQueue 内置了 cron 风格的周期性任务支持,无需额外库。编辑 config/recurring.yml:

    ```yaml
    # config/recurring.yml
    production:
      cleanup_old_sessions:
        class: CleanupSessionsJob
        schedule: every day at 2am
        queue: maintenance

      send_daily_digest:
        class: DailyDigestJob
        schedule: every day at 9am
        queue: mailers

      refresh_cache:
        class: CacheWarmupJob
        schedule: every hour
        queue: default
    ```

2. 工作原理

    ```mermaid
    sequenceDiagram
        participant S as Scheduler
        participant J as Job Queue
        participant W as Worker

        Note over S: 8:00 AM
        S->>J: 入队 refresh_cache 任务
        W->>J: 获取并执行任务
        S->>S: 调度下次运行(9:00 AM)

        Note over S: 9:00 AM
        S->>J: 入队 refresh_cache 任务
        W->>J: 获取并执行任务
        S->>S: 调度下次运行(10:00 AM)
    ```

    <img src="https://static.op123.ren/static/02/02509fa484a93fe2.svg" alt="定时任务调度流程" width="600">

    A. 调度器运行时,找到到期的任务并入队执行
    B. 同时,调度器还会为下一次出现的时间入队新任务
    C. 这种模式具有崩溃恢复能力,因为调度是确定性的

五、并发控制功能

1. 并发限制声明

    SolidQueue 免费提供了 Sidekiq Enterprise 付费版才有的并发限制功能:

    ```ruby
    class ProcessUserOnboardingJob < ApplicationJob
      limits_concurrency to: 1,
        key: ->(user) { user.id },
        duration: 15.minutes

      def perform(user)
        # 复杂的入职工作流
      end
    end
    ```

    `limits_concurrency to: 1` 确保在任何时候每个用户只运行一个 ProcessUserOnboardingJob 任务。

2. 信号量实现

    ```mermaid
    graph TD
        A[新任务请求] --> B{检查信号量}
        B -->|未达到限制| C[获取信号量]
        B -->|已达到限制| D[加入阻塞队列]
        C --> E[执行任务]
        E --> F[释放信号量]
        F --> G[触发调度器]
        G --> D
        D --> H[解除阻塞,准备执行]
    ```

    <img src="https://static.op123.ren/static/f1/f106831493a715c1.svg" alt="并发控制流程" width="600">

    A. solid_queue_semaphores
    跟踪并发限制

    B. solid_queue_blocked_executions
    保存等待信号量释放的任务

    当任务完成时,它释放信号量并触发调度器解除下一个等待任务的阻塞。

六、监控与管理:Mission Control

1. 功能对比

    | 功能 | Sidekiq Pro/Enterprise | Mission Control Jobs |
    |-----|----------------------|---------------------|
    价格 | $949-$1699/年 | 免费 |
    实时任务状态 | 支持 | 支持 |
    失败任务检查 | 支持 | 支持,带完整堆栈 |
    批量操作 | 支持 | 支持 |
    定时任务可视化 | 支持 | 支持 |
    SQL 查询 | 不支持 | 支持 |

2. 配置方式

    ```ruby
    # config/routes.rb
    mount MissionControl::Jobs::Engine, at: "/jobs"
    ```

3. SQL 集成优势

    Mission Control 可以直接查询数据库:

    ```sql
    SELECT j.queue_name, COUNT(*) as failed_count
    FROM solid_queue_failed_executions fe
    JOIN solid_queue_jobs j ON j.id = fe.job_id
    WHERE fe.created_at > NOW() - INTERVAL '1 hour'
    GROUP BY j.queue_name;
    ```

    SQL 是你已知的语言,在你已使用的工具中运行。无需外部解析,无需时间戳算术。

七、迁移路径:从 Sidekiq 到 SolidQueue

1. 步骤概览

    ```mermaid
    graph LR
        A[Sidekiq + Redis] --> B[更改队列适配器]
        B --> C[安装 SolidQueue]
        C --> D[转换定时任务配置]
        D --> E[更新 Procfile]
        E --> F[移除旧依赖]
    ```

    <img src="https://static.op123.ren/static/89/89ddc87ed6c8a030.svg" alt="迁移步骤" width="600">

2. 详细步骤

    A. 更改 Rails 队列适配器
    ```ruby
    # config/environments/production.rb
    config.active_job.queue_adapter = :solid_queue
    ```

    B. 安装 SolidQueue
    ```bash
    $ bundle add solid_queue
    $ rails solid_queue:install
    $ rails db:migrate
    ```

    C. 转换定时任务配置
    ```yaml
    # 旧配置:config/sidekiq.yml
    :schedule:
      cleanup_job:
        cron: '0 2 * * *'
        class: CleanupJob

    # 新配置:config/recurring.yml
    production:
      cleanup_job:
        class: CleanupJob
        schedule: every day at 2am
    ```

    D. 更新 Procfile
    ```yaml
    web: bundle exec puma -C config/puma.rb
    jobs: bundle exec rake solid_queue:start
    ```

    E. 移除旧依赖
    ```ruby
    # Gemfile - DELETE
    # gem "redis"
    # gem "sidekiq"
    # gem "sidekiq-cron"
    ```

八、性能与扩展性分析

1. 性能基准

    | 指标 | Redis + Sidekiq | SolidQueue |
    |-----|----------------|-----------|
    任务吞吐量 | ~1000s 任务/秒 | ~200-300 任务/秒 |
    任务延迟 | <1ms | ~100ms |

| 适用范围 | 99.9% 的应用 | 95% 的应用 |

2. 扩展性评估公式

    Nate Berkopec 的扩展性公式:
    **所需应用实例数 = 请求率(请求/秒)× 平均响应时间(秒)**

    示例计算:
    - 应用每分钟处理 100 个请求
    - 平均响应时间 200ms
    - 即约 1.67 请求/秒
    - 所需实例:1.67 × 0.2 = 0.083 个实例

    实际案例:37signals 每天处理 2000 万个任务,约每秒 230 个任务,全部运行在 PostgreSQL 上,无需 Redis。

3. 使用场景判断

    **适合使用 SolidQueue**:
    - 任务处理少于 100 任务/秒
    - 任务延迟容忍度大于 100ms
    - 希望简化基础设施

    **需要保留 Redis**:
    - 持续处理数千任务/秒
    - 任务延迟低于 1ms 至关重要
    - 复杂的发布/订阅模式
    - 需要密集的速率限制或计数器

九、实际部署指南

1. 创建 Rails 8 应用

    ```bash
    $ rails new myapp --database=postgresql
    $ cd myapp
    ```

    Rails 8 自动配置 SolidQueue、SolidCache 和 SolidCable。

2. 配置队列数据库

    ```yaml
    # config/database.yml
    development:
      primary: &primary_development
        <<: *default
        database: myapp_development
      queue:
        <<: *primary_development
        database: myapp_queue_development
        migrations_paths: db/queue_migrate
    ```

    ```ruby
    # config/environments/development.rb
    config.active_job.queue_adapter = :solid_queue
    config.solid_queue.connects_to = { database: { writing: :queue } }
    ```

3. 完整部署架构

    ```mermaid
    graph TB
        subgraph 应用层
            Web[Web 服务器<br/>Puma]
            Jobs[SolidQueue 工作进程]
        end

        subgraph 数据层
            PG[(PostgreSQL)]
            DB1[(主数据库)]
            DB2[(队列数据库)]
        end

        subgraph 管理层
            MC[Mission Control<br/>/jobs]
        end

        Web --> DB1
        Jobs --> DB2
        MC --> DB2
        DB1 -.共享存储.-> PG
        DB2 -.共享存储.-> PG
    ```

    <img src="https://static.op123.ren/static/49/49ffcf3c04f4caa6.svg" alt="完整部署架构" width="700">

十、技术选型对比

1. Redis + Sidekiq vs SolidQueue

    | 方面 | Redis + Sidekiq | SolidQueue |
    |-----|----------------|-----------|
    | 设置复杂度 | 需要单独服务 + 配置 | 已内置 |
    | 查询语言 | Redis 命令 | SQL |
    | 监控 | 独立仪表板 | 与应用相同 |
    | 故障模式 | 6+ 种不同场景 | 2 种场景 |
    | 任务吞吐量 | ~1000s 任务/秒 | ~200-300 任务/秒 |
    | 适合应用比例 | 99.9% | 95% |
    | 成本 | 需付费企业版功能 | 全部免费 |

2. 运维复杂度对比

    ```mermaid
    graph LR
        subgraph Redis + Sidekiq
            R1[Redis 服务器]
            R2[Sidekiq 进程]
            R3[持久化配置]
            R4[高可用集群]
            R1 --> R2
            R1 --> R3
            R1 --> R4
        end

        subgraph SolidQueue
            S1[PostgreSQL]
            S2[SolidQueue 进程]
            S1 --> S2
        end
    ```

    <img src="https://static.op123.ren/static/56/56baa8563d17b756.svg" alt="运维复杂度对比" width="700">

十一、常见问题与最佳实践

1. 单数据库配置(备选方案)

    SolidQueue 推荐使用单独的数据库连接,但也可以运行在单一数据库中:
    1. 将 db/queue_schema.rb 内容复制到常规迁移
    2. 删除 db/queue_schema.rb
    3. 从环境配置中移除 config.solid_queue.connects_to
    4. 运行 rails db:migrate

2. 生产环境认证

    不要忘记在生产环境中为 Mission Control 添加认证:

    ```ruby
    # config/initializers/mission_control.rb
    config.mission_control.jobs.base_controller_class = "AdminController"
    ```

3. 轮询间隔

    默认轮询间隔:定时任务 1 秒,就绪任务 0.2 秒。从 Sidekiq 迁移时,如果感觉任务"变慢",需要调整期望。对于大多数应用,SolidQueue 的默认值工作良好。

十二、总结

Redis 和 Sidekiq 是经过精心设计的工程产品,十多年来 Rails 应用从中受益匪浅。但对于大多数 Rails 应用,Redis 和 Sidekiq 解决的是你并不存在的问题,代价是你无法承受的复杂性。

SolidQueue 的核心优势:
    - 简化基础设施
    - 减轻运维负担
    - 保持足够性能
    - 免费提供企业级功能

给 SolidQueue 一个尝试,让基础设施简化,运维负担减轻,专注于构建产品而不是维护技术栈。

参考资料

  1. I Love You, Redis, But I'm Leaving You for SolidQueue
最后修改:2026 年 01 月 14 日
如果觉得我的文章对你有用,请随意赞赏