Loading... # 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" style=""> 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" style=""> 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" style=""> 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" style=""> 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" style=""> 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" style=""> 十、技术选型对比 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" style=""> 十一、常见问题与最佳实践 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](https://www.simplethread.com/redis-solidqueue/) 最后修改:2026 年 01 月 14 日 © 允许规范转载 赞 如果觉得我的文章对你有用,请随意赞赏