打开网易新闻 查看精彩图片

2023年Stack Overflow调研显示,Ruby在Web框架使用率仍排前三,但提到消息队列,67%的开发者直接跳过了AWS原生方案。不是SQS不好用,是文档散得像拼图——生产者配置、消费者配置、Worker配置,三个场景三套逻辑,新手看完直接劝退。

这篇从巴西开发者社区挖来的实战笔记,把三种配置路径一次性摊平。如果你正在Rails项目里纠结要不要为了队列引入Redis,这些数据能帮你省下一台服务器钱。

纯生产者模式:5行代码搞定接入

纯生产者模式:5行代码搞定接入

很多团队用SQS只是当个"快递中转站"——业务系统只管丢消息,处理交给下游服务。这种场景下配置极简,核心就两步:装SDK、填环境变量。

Gemfile里加一行:

gem 'aws-sdk-sqs'

bundle install后,在.env或Rails credentials里塞四个字段:

AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_REGION=us-east-1
AWS_SQS_QUEUE_NAME=
AWS_QUEUE_URL=

这里有个坑:QUEUE_NAME和QUEUE_URL都要填。前者是给SDK做队列定位用的,后者是实际API调用地址。只填一个会在某些Region报错,报错信息还模棱两可——"Queue not found"——其实队列存在,只是URL拼错了。

发送消息的代码短到离谱:

result = Aws::SQS::Client.new.send_message(
queue_url: ENV['AWS_QUEUE_URL'],
message_body: { order_id: "123" }.to_json
)

返回的result对象里藏着MessageId,建议落库。后续排查消息丢失时,这是唯一能跟AWS CloudWatch对账的凭证。我见过有团队因为没存这个ID,消息被SQS删了都查不到原因——SQS默认保留消息4天,过期自动清理,CloudWatch日志也跟着没了。

Worker模式:Sidekiq用户迁移指南

Worker模式:Sidekiq用户迁移指南

如果你想让Rails自己消费队列,得换个工具。aws-sdk-sqs只管发不管收,持续轮询需要额外写循环代码,还容易踩并发坑。

社区成熟的方案是Shoryuken——名字取自日语"手里剑",作者是个巴西全栈工程师。定位很明确:SQS版的Sidekiq。配置逻辑对Rubyist极度友好,迁移成本比换到RabbitMQ低一个数量级。

同样Gemfile加一行:

gem 'shoryuken'

配置文件config/shoryuken.yml里,concurrency建议先设5。SQS的计费模型是按请求次数算,不是按消息数。concurrency太高会空轮询烧钱,太低又吞吐不够。5是个保守起点,后续根据CloudWatch的EmptyReceives指标调。

队列优先级用数组表示:

queues:
- [<%= ENV.fetch('AWS_SQS_QUEUE_NAME') %>, 1]

后面这个数字是权重。如果配成[queue_a, 2], [queue_b, 1],Worker会花2/3时间监听queue_a。这个设计比Sidekiq的严格队列更灵活,但也更容易配错——我见过有团队把权重写成10和1,结果次要队列的消息延迟了40分钟。

开发环境常遇的坑:Worker文件加载失败。Rails 6+的Zeitwerk对非标准目录很敏感,app/workers默认不在autoload路径里。解法是在config/application.rb里加两行:

config.eager_load_paths.delete("#{Rails.root}/app/workers")
config.eager_load_paths.unshift("#{Rails.root}/app")

第一行先删掉可能重复的路径,第二行把workers目录塞到加载队列最前面。顺序不能反,否则Rails会报"uninitialized constant"。

Worker代码:三个细节决定稳定性

Worker代码:三个细节决定稳定性

Shoryuken的Worker写法跟Sidekiq几乎一样,但SQS的语义差异藏在细节里。

基础结构:

class ExampleWorker
include Shoryuken::Worker

shoryuken_options queue: ENV.fetch('AWS_SQS_QUEUE_NAME'), auto_delete: true

def perform(sqs_msg, body)
payload = JSON.parse(body)
::ExampleService.new(payload).call
end
end

注意perform方法的两个参数。Sidekiq只有一个args,Shoryuken把原始SQS消息对象sqs_msg也传进来了。这个设计是为了让你能手动控制消息生命周期——比如处理到一半发现依赖服务挂了,可以调sqs_msg.change_visibility把消息塞回队列,避免被其他Worker抢走。

auto_delete: true是双刃剑。消息进perform方法就被标记为删除,如果代码抛异常,消息已经没了。SQS的Dead Letter Queue(死信队列)能兜底,但默认不开启。生产环境建议先关auto_delete,处理完手动调sqs_msg.delete,配合begin-rescue确保异常消息能进DLQ。

JSON.parse(body)也有坑。SQS消息体是字符串,但AWS Console测试发送时,如果直接贴JSON,底层会再包一层引号。解析失败时先打日志看原始body长什么样,别急着改代码。

成本对比:为什么有人宁愿用Redis

成本对比:为什么有人宁愿用Redis

说个反直觉的数据:月消息量低于500万时,SQS成本可能比自建Redis高。

SQS标准队列定价是每百万请求0.40美元,但"请求"的定义很宽——SendMessage、ReceiveMessage、DeleteMessage、ChangeMessageVisibility都算。一个消息从发送到删除,至少3次请求。如果concurrency配高了空轮询,ReceiveMessage次数会爆炸。

Shoryuken默认用长轮询(WaitTimeSeconds=20)降低空请求,但配置不当还是会踩坑。对比Sidekiq+Redis:Redis没有按请求计费,内存成本固定。消息量小、延迟要求不高的场景,Redis更便宜。

但SQS的托管优势在规模上来后显现。自动扩容、跨区域复制、CloudWatch集成,这些自己搭Redis集群得雇专人维护。2022年AWS re:Invent有个案例:某电商大促期间SQS自动处理了每秒12万条消息,同等流量Redis集群需要提前两周做分片预热。

选型建议:消息量<100万/月、团队没DevOps人手,用Sidekiq+Redis;消息量>1000万/月、需要跨区域容灾,SQS+Shoryuken更省头发。

巴西这篇笔记的最后一段挺有意思——作者说他在生产环境跑了3年Shoryuken,唯一一次故障是因为把AWS_REGION写成了us-east-1,而队列实际在sa-east-1。SQS的报错是"Queue does not exist",他排查了4小时才发现是Region错配。

这种错在本地开发很难测出来,因为大多数开发者只有一个AWS账号,Region配置往往复制粘贴。你的项目里,环境变量有没有类似的"隐形炸弹"?