关键决策
这一页整理通知系统设计过程中遇到的关键决策点,每个决策都说明了当时面临的问题、对比过的方案和最终的选择理由。
决策 1:消息队列选型
问题
通知发送需要异步化,避免阻塞业务链路,还要支持延迟投递和消息确认。
方案对比
| 方案 | 延迟队列 | 消息确认 | 优先级 | 运维成本 |
|---|---|---|---|---|
| RabbitMQ | 支持 | 支持 | 支持 | 中 |
| Kafka | 需额外实现 | 支持 | 弱 | 高 |
| Redis Stream | 弱 | 支持 | 弱 | 低 |
决策
选择 RabbitMQ。团队对它最熟悉,延迟队列、消息确认、优先级都开箱即用,量级也完全够用。Kafka 更适合大数据量的日志管道,对通知场景属于过度设计。
决策 2:统一推送平台 vs 自建推送
问题
安卓在国内没有统一推送通道,各家厂商都有自己的服务,要不要自己对接?
方案对比
- 统一推送平台(极光):一套 API 接入,维护成本低,但到达率受平台影响、无法精细控制、按量收费。
- 自建推送系统:完全控制,可优化到达率,但开发维护成本高,要对接多家厂商 API。
决策
先用统一推送平台,再逐步自建。 起步阶段用极光快速上线,等用户量和投诉量上来、对到达率有明确要求时,再自建核心通道。这是”渐进式演进”原则的典型应用。
决策 3:站内信分表策略
问题
站内信表会无限增长,单表达到几百万条后,查询和写入都会变慢。
方案对比
- 单表 + 索引:实现简单,但表大了以后性能下降,且无法扩展。
- 按用户 ID 分表:把数据分散到 16 张表,单表数据量可控,热点用户分散。
决策
选择按用户 ID 分表(16 张表)。站内信的访问模式天然按用户隔离(每个用户只看自己的消息),按用户分表是最自然的切分方式。未读计数单独建表,避免每次打开 App 都扫全表。
决策 4:多端同步方式
问题
用户同时登录手机、平板、电脑,在一台设备上标记已读,其他设备要同步。
方案对比
| 方案 | 实时性 | 资源消耗 | 实现复杂度 |
|---|---|---|---|
| 轮询同步 | 最长 30 秒 | 高(无更新也查询) | 低 |
| 推送同步 | 实时 | 低 | 中 |
| WebSocket 长连接 | 实时 | 低 | 高 |
决策
选择推送同步为主,WebSocket 为辅。系统已经有推送通道,静默推送可以实现实时同步且资源高效;App 前台打开消息中心时再用 WebSocket 实时更新。轮询作为兜底,App 打开时主动同步一次。
决策 5:未读计数方案
问题
未读数量是每次打开 App 的高频查询,直接 COUNT(*) 扫表会很慢。
方案对比
- Redis 缓存计数:查询快,但可能丢失或过期,且不便于按类型统计。
- 独立计数表:实时更新,准确,可按类型统计(评论未读 3 条、点赞未读 5 条)。
决策
选择独立计数表。虽然多一次写入,但计数准确、不依赖缓存过期,而且能支持按类型展示未读数。这个取舍在”准确性”和”查询性能”之间选择了准确性。
决策 6:限流策略
问题
热门动态会产生几百上千条通知,如何在不漏重要消息的前提下避免轰炸?
方案对比
- 固定阈值限流:简单,但一刀切,无法区分重要通知。
- 按类型限流:灵活,但要手动设置每种类型阈值。
- 聚合 + 限流:相似通知合并,再按类型限流。
决策
选择聚合 + 限流。聚合把”张三等 15 人点赞”合成一条,限流保证同一动态每小时最多发 1 条点赞通知。私信等重要类型不聚合、不限流。既减少打扰,又保留关键信息。
决策 7:偏好存储
问题
用户偏好要支撑高频检查(每次发通知都查),如何保证查询快且一致?
方案对比
- 只存 MySQL:数据准确,但每次发通知都查库,延迟高。
- MySQL + Redis 缓存:读走缓存,写回数据库,兼顾性能和一致。
决策
选择 MySQL 持久化 + Redis 缓存。偏好是低频修改、高频读取的数据,非常适合缓存。修改偏好时先写 MySQL,再更新或失效 Redis 缓存,保证最终一致。
决策 8:数据库与推送的降级
问题
推送服务或消息队列不可用时,通知系统如何保证核心能力?
决策
定义降级顺序:
P0(核心):站内信写入 —— 消息绝不能丢
P1(重要):推送投递 —— 推送失败降级为只发站内信
P2(次要):实时多端同步 —— 退化为打开 App 时主动同步
P3(可选):效果统计 —— 可延迟上报
降级的核心思路是:保证”消息不丢”永远优先于”实时送达”。用户错过一次推送还能在站内信看到,但如果消息丢了,就永远无法挽回。
