消息队列与对象存储:异步与文件往哪放
用队列解耦慢任务,用对象存储存文件
- 理解消息队列解决什么问题
- 知道 RabbitMQ/Kafka/BullMQ 各自定位
- 理解对象存储 vs 本地磁盘
- 知道为什么大文件不该进数据库
消息队列像奶茶店:你点单后拿到小票排队做,不用站在柜台干等;做好了叫号取餐。接口也一样:发邮件、跑报表这种慢活,先丢进队列立刻返回「已收到」,后台慢慢做,接口就不会卡住用户。
为什么需要消息队列?
用户点「导出报表」,如果同步等报表跑完(几十秒),接口就卡几十秒,用户只能干转圈。消息队列把这类慢任务变成异步:接口收到请求后只往队列里塞一条消息就立即返回,后台 worker 慢慢消费、做完通知用户。
# 同步 vs 异步
print("""
同步(堵):
用户点导出 → 接口等报表跑完(30s) → 才返回
用户转圈 30 秒,超时还可能失败
异步(队列):
用户点导出 → 接口塞消息进队列(0.1s) → 立即返回"处理中"
后台 worker 慢慢跑 → 完成后发站内信/邮件通知
""")RabbitMQ:功能全、路由灵活,通用业务异步任务;Kafka:超高吞吐、日志/数据流场景;BullMQ:基于 Redis、Node 生态里做队列最简单。选型深挖见 s13-m3。
至少一次 vs 恰好一次:消息可能重复
队列不是绝对可靠的传送带。worker 处理完消息、还没来得及告诉队列「我搞定了」就崩溃了,队列会认为它失败,把这条消息再投给另一个 worker——于是同一条消息被处理了两次。绝大多数队列默认保证的是「至少一次投递」(消息不会丢,但可能重复),而不是昂贵的「恰好一次」。
既然可能重复,消费端就必须幂等:同一条消息处理一遍和处理多遍,结果一样。比如「给用户加 10 积分」这种,重复两次就加了 20,出事了;改成「把该订单的积分状态标记为已发放」(用订单 id 做去重,发过就跳过),重复消费也只会生效一次。幂等性是队列从玩具走向生产的必修课。
问:队列能保证每条消息恰好只被处理一次吗?答:一般不能,主流是至少一次,靠消费端幂等来兜底。别假设消息只会来一次,否则重试和崩溃时就会出现重复发邮件、重复加积分。
对象存储:文件往哪放
用户上传的图片、视频、附件,不该存在数据库里。数据库是用来高效查询结构化数据的,塞大文件会让表膨胀、备份变慢。正确做法:文件存对象存储(如 S3、MinIO、阿里云 OSS),数据库里只存一个文件的访问 URL。
# 数据库里存的不是文件本身,而是它的地址
print("""
post 表:
id = 1
title = "我的游记"
cover_url = "https://oss.example.com/covers/abc.jpg" ← 只存 URL
(图片二进制在对象存储里)
""")把图片二进制塞进 BLOB 列,会让查询、备份、迁移都变慢,还浪费昂贵的数据库连接。文件归对象存储,数据库只存元数据和 URL,这是后端的基本分工。
为什么成本上也不划算?数据库是昂贵的资源:它要保证事务、持久化、主从复制,存储和内存都贵,连接数还很有限。如果你把每张 2MB 的图片二进制塞进去,一次列表查询光是把这些字节读出来就占着数据库连接和内存,备份时连文件一起备份,库文件膨胀几十倍。对象存储则是为海量二进制文件廉价设计的:按存储量计费便宜、自带 CDN 分发、扩容几乎无限。数据库里只存一个几十字节的 URL,查询轻快、备份小。价格与计费以各家云厂商最新报价为准,这里给的是分工逻辑而非具体数字。
下列哪个场景最该用消息队列做异步?
关于消息队列的投递语义,下面哪个说法是对的?
资深工程师加餐
底层原理 · 大厂视角 · 工程经验,点卡片展开
网络侧:DNS→TCP/TLS→HTTP 请求与响应;浏览器侧:解析 HTML 建 DOM、解析 CSS 建 CSSOM、合并成渲染树,再经过布局(Layout)、绘制(Paint)、合成(Composite)上屏。前端性能优化几乎都围绕这条链路:减小资源体积、优化关键渲染路径、避免频繁触发重排重绘。
挑战任务
判断任务是否该异步
慢任务(发邮件、跑报表、处理图片)适合丢消息队列异步做,即时查询不该。实现 is_async(task):task 在慢任务清单里返回 True。
课后作业
场景分析:哪些该走队列
逐个判断任务该走消息队列异步还是同步处理。ASYNC 清单里的返回 "queue",SYNC 清单里的返回 "sync"。打印每个任务的路由结果。判断依据:用户需不需要立刻拿到结果、任务耗不耗时。