# 通知 / 推送系统 架构模板 > **代表产品 / 原型**:Novu、Courier、各类「消息中心 / 推送平台」,底层依赖 FCM / APNs > **一句话定位**:把「一件事发生了」可靠地、按用户偏好、通过多种渠道(站内 / 推送 / 短信 / 邮件 / IM)送达对的人——不重复、不轰炸、失败能重试。 --- ## 1. 一句话定位 通知系统 = **一条「事件 → 触达」的统一管道**。 它把系统里发生的各种事(订单发货了、有人 @ 你了、登录异常),变成用户在合适渠道、合适时机收到的一条通知。它是 [事件驱动](../../tutorial/04-十大核心架构模式.md) + [消息队列](../../tutorial/04-十大核心架构模式.md) + **渠道适配器**的综合体——核心难点是**扇出(一件事通知海量人)、渠道抽象(各渠道天差地别)、和「不打扰」**。 ## 2. 业务本质:它在解决什么问题 通知是产品**拉回用户、传递关键信息**的命脉:交易状态、安全告警、社交互动、营销召回。一条及时的「你的快递到了」提升体验,一条「检测到异地登录」可能阻止盗号。 但它是把双刃剑:**通知过度 = 骚扰 = 关推送 = 卸载。** 所以这个系统的高级之处,不在「能发」,而在「**克制地发**」——发对的人、对的事、对的频率。 ## 3. 核心需求与约束 **功能性需求:** - [ ] 多渠道:站内信、App 推送、短信、邮件、IM(如企业微信 / Slack) - [ ] 模板与多语言渲染 - [ ] 用户偏好与订阅(收什么、不收什么、免打扰时段) - [ ] 去重、限频、聚合(把多条合并成一条摘要) - [ ] 重试、定时 / 批量发送、已读状态 **非功能性需求 / 质量属性:** | 质量属性 | 目标 | 为什么对这类系统重要 | |---|---|---| | **可靠投递** | 重要通知不丢 | 「交易 / 安全」类通知丢了是事故 | | **及时性** | 秒级~分钟级 | @ 提醒、验证码晚了就没用 | | **扇出能力** | 一事件→海量接收者 | 营销 / 公告可能推给上亿人 | | **不打扰** | 限频 / 聚合 | 轰炸用户 = 用户流失 | **关键约束(不可逾越的边界):** - 🔴 **各渠道协议 / 限制天差地别**:App 推送必须经 APNs(iOS)/ FCM(Android),短信经运营商网关,各有速率与格式限制。 - 🔴 **第三方渠道不可靠**:推送 / 短信网关会延迟、会失败,必须重试 + 状态追踪。 - 🔴 **营销洪峰**:大促一条公告瞬间扇出上亿条。 - 🔴 **合规**:必须支持退订、免打扰、反垃圾(否则违法且伤品牌)。 ## 4. 架构全景图 ``` 事件源(订单/社交/安全/营销…) │ 「发生了 X」 ▼ ┌──────────────────────────────────────────────────────────────┐ │ 通知服务 │ │ ① 查用户偏好 / 订阅(他想不想收?现在是免打扰时段吗?) │ │ ② 模板渲染(填充内容、多语言) │ │ ③ 去重 + 限频 + 聚合(别重复、别轰炸) │ │ ④ 决定走哪些渠道、按优先级 │ └───────────────────────────┬──────────────────────────────────┘ ▼ 入队(异步,削峰 + 重试) ┌──────────────────────┐ │ 消息队列(分优先级) │ 交易类 > 营销类 └──────────┬───────────┘ ▼ ┌────────────────────────────────────────────┐ │ 渠道适配器(把统一指令翻译成各渠道协议) │ └──┬─────────┬──────────┬──────────┬──────────┘ ▼ ▼ ▼ ▼ ┌─────┐ ┌──────┐ ┌───────┐ ┌──────┐ │站内 │ │ Push │ │ 短信 │ │ 邮件 │ → 用户 │ 信 │ │FCM/ │ │ 网关 │ │ │ │ │ │APNs │ │ │ │ │ └─────┘ └──────┘ └───────┘ └──────┘ 失败 ──▶ 重试;投递状态回执 ──▶ 状态追踪 ``` > 灵魂在于:**第 ①③ 步(偏好 + 去重限频)是「克制」,队列是「削峰 + 可靠」,渠道适配器是「屏蔽异构」。** 一个只会「来一条发一条」的通知系统,迟早把用户轰走。 ## 5. 组件职责 - **事件接入**:接收各业务系统抛来的事件。*为什么需要*:解耦——业务只管喊「发生了 X」,不关心怎么通知。 - **偏好 / 订阅管理**:记录每个用户想收什么、免打扰时段。*为什么需要*:尊重用户选择是体验和合规的底线。 - **模板引擎**:把事件 + 数据渲染成各渠道的文案。*为什么需要*:内容要按渠道(短信 70 字 vs 邮件富文本)和语言定制。 - **去重 / 限频 / 聚合**:防重复、防轰炸,把多条合并成摘要。*为什么需要*:这是「不打扰」的技术实现。 - **队列(分优先级)**:削峰 + 异步 + 重试缓冲。*为什么需要*:营销洪峰要削峰,且发通知绝不能阻塞业务(见决策 1)。 - **渠道适配器**:把统一指令翻译成 APNs / FCM / 短信 / 邮件协议。*为什么需要*:屏蔽各渠道差异,和 [AI 网关](../ai-gateway/README.md) 的适配器同理。 - **投递状态追踪**:记录每条通知发没发出、成没成功。*为什么需要*:可靠投递和可观测的基础。 ## 6. 关键数据流 **场景一:一条通知的旅程** ``` 1. 事件「订单已发货(用户 U)」 ──▶ 通知服务 2. 查 U 的偏好:他开了「物流通知」,且现在不是免打扰 ──▶ 继续 3. 渲染模板:「您的订单已发货,预计明天送达」 4. 去重检查:这条没发过 ──▶ 入队(优先级:交易类=高) 5. 渠道适配器:U 装了 App ──▶ 走 FCM 推送;同时落一条站内信 6. FCM 返回成功 ──▶ 记录投递状态;失败则重试 / 降级到短信 ``` **场景二:限频聚合(防轰炸)** ``` 某帖子 1 分钟内被 50 人点赞: ✗ 笨做法:推 50 条「XX 赞了你」──▶ 用户被轰炸,关推送 ✓ 聚合:攒一个时间窗,合并成「50 人赞了你的帖子」一条 ── 技术能力要服务于「不打扰」的产品智慧 ``` ## 7. 数据模型与存储选择 核心实体:`通知`;`用户偏好 / 订阅`;`模板`;`投递记录`;`站内消息`。 | 数据 | 存储类型 | 为什么 | |---|---|---| | 用户偏好 / 订阅 | 关系型 | 结构化、要一致、频繁查 | | 站内信箱 | 见 [社交信息流](../social-feed/README.md) 的收件箱模型 | 写扩散 / 读扩散的取舍 | | 投递记录 / 状态 | 时序 / 列存 | 海量、按时间聚合统计投递率 | | 待发队列 | 消息队列 | 削峰、重试、优先级 | ## 8. 关键架构决策与权衡 ⭐ **决策 1:同步发,还是异步发?⭐** - 同步:业务调通知接口,等它发完才继续。简单,但**通知渠道一慢 / 一挂,业务主流程就被拖死**。 - 异步:业务只把事件丢进队列,立刻返回;通知服务慢慢发。 - **取向**:必须异步。**发通知是非关键路径,绝不能阻塞业务**,且队列天然给了削峰和重试。 **决策 2:去重与限频(不打扰的核心)⭐** - 不做:同一事件可能触发多条、热点事件疯狂轰炸。 - 做:幂等去重(同一通知只发一次)+ 限频(单位时间上限)+ 聚合(合并成摘要)。 - **取向**:必做。**「不打扰」是产品生命线,技术要为它服务。** **决策 3:推送渠道——自建还是用平台?** - 自己连设备:不可能,iOS / Android 推送**必须**走 APNs / FCM。 - **取向**:App 推送一律经 APNs / FCM;系统只负责「调好这些平台」,不可能绕过。 **决策 4:投递保证——at-least-once + 幂等** - 重要通知宁可重发也别丢(at-least-once),但重发会导致用户收到重复。 - **取向**:服务端 at-least-once 重试,客户端 / 渠道侧做幂等去重。**和 [支付](../payment-system/README.md) 一样:宁重勿丢,靠幂等兜重复。** ## 9. 规模化与瓶颈 - **第一个瓶颈:营销扇出洪峰(亿级)。** → 破解:队列削峰 + 批量发送 + 按用户分片并行。 - **第二个瓶颈:渠道限流(短信 / 推送有速率上限)。** → 破解:按渠道配额排队、平滑发送。 - **第三个瓶颈:交易通知被营销洪峰挤压。** → 破解:**优先级队列**,交易 / 安全类走高优先级独立通道。 - **第四个瓶颈:投递状态数据海量。** → 破解:时序存储 + 聚合统计投递率。 ## 10. 安全与合规要点 - **退订与免打扰**:必须支持一键退订、免打扰时段,满足反垃圾 / 隐私法规(否则违法)。 - **防轰炸 / 防滥用**:限频既是体验也是安全(防止被用来骚扰)。 - **内容安全**:模板要防注入(用户数据填进模板别被利用);通知内容可能含敏感信息,要脱敏。 - **渠道凭证安全**:APNs 证书 / 短信网关密钥是敏感凭证。 ## 11. 常见误区 / 反模式 - ❌ **同步发通知,阻塞业务主流程** → ✅ 异步队列,通知是非关键路径。 - ❌ **不去重、不限频,疯狂轰炸** → ✅ 去重 + 限频 + 聚合摘要。 - ❌ **交易通知和营销混一个优先级** → ✅ 分优先级 / 分通道,重要的先到。 - ❌ **无视用户偏好,硬发** → ✅ 偏好 + 退订 + 免打扰。 - ❌ **一条一条发,不批量** → ✅ 大扇出要批量。 - ❌ **假设渠道一定成功** → ✅ 重试 + 投递状态追踪 + 降级。 ## 12. 演进路线:MVP → 成长期 → 成熟期(不同阶段怎么设置) | 阶段 | 规模量级 | 怎么设置(具体) | 此时该操心什么 | |---|---|---|---| | **MVP** | 起步 | 一两个渠道(邮件 / Push),直接接 FCM / 邮件服务,简单队列异步发 | 先把「该通知的事能通知到」跑通 | | **成长期** | 多渠道 / 上量 | 用 **Novu** 类或自建:多渠道 + 模板 + 偏好 + 去重限频 + 异步队列 + 重试 | 可靠投递、不打扰、扇出能力 | | **成熟期** | 亿级扇出 | 优先级队列、智能聚合(摘要)、跨渠道编排(先 Push 没读再短信)、A/B、投递率可观测 | 规模、优先级保障、转化、合规 | ## 13. 可复用要点 - 💡 **异步队列削峰 + 重试,是处理「非关键路径」的标准姿势。** 发通知、发邮件、写日志,都不该挡在主流程上。 - 💡 **渠道适配器屏蔽异构第三方**:对内一套指令,对外 N 个适配器——和 [AI 网关](../ai-gateway/README.md)「适配多供应商」是同一招。 - 💡 **at-least-once + 幂等去重**:宁可重发也别丢,靠去重兜住重复,呼应 [支付系统](../payment-system/README.md)。 - 💡 **「不打扰」是产品智慧高于技术能力的范例。** 能发不代表该发——好系统懂得克制。 ## 🎯 随堂检验 --- ## 参考原型与延伸阅读 > 本模板基于以下**真实开源项目**与**官方文档**整理。 **🔧 开源原型(可直接读代码):** - [novuhq/novu](https://github.com/novuhq/novu) — 开源通知基础设施,统一 API 覆盖站内 / 邮件 / 短信 / Push / Slack 多渠道,体现多渠道扇出、模板、工作流编排。 **📖 官方文档:** - [Firebase Cloud Messaging 架构总览](https://firebase.google.com/docs/cloud-messaging/fcm-architecture) — Google 官方文档,讲 FCM 后端的主题扇出、传输层路由与投递,推送系统的权威参考。 - [FCM 通过 APNs 投递 iOS 推送](https://firebase.google.com/docs/cloud-messaging/ios/receive-messages) — 说明 FCM 如何以 Apple APNs 作为传输层,体现「多平台传输层对接」。 --- > 📌 一句话记住通知系统:**它不是「一个能发短信的接口」,而是「一条把『发生了什么』克制地、可靠地、按偏好送达对的人的管道」——所有设计都在回答『怎么发对人、发对事、发对频率,还不丢、不重、不轰炸』。**