Rails 8 默认捆绑的后台任务库 Solid Queue 发了 v1.6.0,release 标题上就写着一句话:加了 fiber execution mode。配置文件里只要把 threads: 5 换成 fibers: 100,一个 worker 就能同时挂着上百个任务,靠的不是开更多线程,而是 Ruby 的协程调度。这行代码改动不大,但它把 Solid Queue 从"线程池是唯一选择"拉进了"你自己挑并发模型"的阶段。
发生了什么,几句话说清
- 是什么.Solid Queue 新增可选的 fiber 执行模式,依赖 Async 这个 gem,配置里写
fibers数量代替threads数量。 - 前提条件.Rails 得开启
config.active_support.isolation_level = :fiber,否则用不了。 - 官方说法.这对 I/O 密集型任务特别有用,官方原话点名了"LLM 调用"这类场景——等接口返回的时间远比算的时间长。
- 谁写的.这个 PR 由社区贡献者 crmne 提交,是他在这个项目的第一次贡献。
信息就这么多。release 页面没有基准数据,没写默认是否开启,也没说明依赖哪个版本的 Async。想知道细节,得翻对应的 PR,而不是看这页 changelog——这也是这次更新最容易被忽略的一点:官方给的是标题级的信号,不是使用说明书。
线程池和协程调度器,到底差在哪
Ruby 长期靠多线程解决 I/O 并发:一个任务在等数据库或网络返回时,线程被挂起,OS 调度器换另一个线程跑。代价是每个线程都要占内存、都要上下文切换,线程开多了,机器先喘不过气。
Fiber 走的是另一条路:所有任务挤在一个线程里,由 Async 这样的调度器手动决定谁在等、谁该跑,等待网络返回的时候主动让出控制权。这套机制建立在 Ruby 3.0 引入的 Fiber Scheduler API 上,Samuel Williams 写的 Falcon 应用服务器早就是这条路线的先行者。Solid Queue 这次算是把同一套逻辑搬进了任务队列。
说漏的那部分,恰恰是风险所在
协作式调度有个硬条件:所有可能阻塞的调用都得是"协程友好"的。数据库驱动、HTTP 客户端,只要有一处还在用老式阻塞 I/O,这一次调用就会把整个调度器卡死——不是只卡这一个任务,是卡住这个 reactor 线程上所有排队的任务。
线程池模式下,一个任务卡住只影响它自己那个线程,别的线程照跑不误。Fiber 模式把鸡蛋放进一个篮子,篮子摔了全砸。
- 风险.mysql2、pg、sqlite3 这些常见数据库驱动是否已经完全适配 Fiber Scheduler,Solid Queue 官方并未在 release 里说明;一个未适配的阻塞调用,代价是整条调度线的吞吐归零。
谁该现在切,谁该再等等
真正适合 Fiber 模式的,是那些等待远多于计算的任务——调用大模型接口、抓第三方 API、发邮件通知。这类任务里,线程池大部分时间都在"空等",Fiber 能把这份浪费省下来。
如果任务里混着图片处理、大量数据计算这类吃 CPU 的活,Fiber 模式帮不上忙,甚至可能因为一处阻塞调用拖累一整条队列。这也是为什么官方在说明里特意点名"LLM 调用"——这几乎是当下最典型的"等待型"负载,不难猜到这个功能是冲着谁去的。
协程调度不是免费的午餐,它把并发风险从"线程数"转移到了"每一行代码是否阻塞"。
对 Rails 生态而言,这次更新更像一个信号:去 Redis 化的 Solid 系列,不满足于只在"要不要依赖外部服务"上做减法,现在开始在并发模型上也给开发者留了选择权。Sidekiq 长期靠成熟的多线程模型立足,Solid Queue 这次没有正面对标性能,而是先把协程这条路铺出来——这条路谁先踩实,谁就先拿到 I/O 密集场景里的下一个优势。眼下能看清的只有方向,基准数据和默认行为,还得等对应 PR 和后续版本把话说完。
