喜马拉雅亿级推送系统实践
《喜马拉雅亿级推送系统实践》共 0.3 万字,通篇围绕 Redis、Kafka、架构、需求 展开,归在「后端与系统架构」这一层。
适合给 AI 系统搭底座、要管得住的人
目录骨架(8 节)
- 背景介绍
- 整体设计
- 一些具体的技术问题
- 技术架构设计
- 业务过滤机制设计
- [分库分表下的多维查询问题](about:blank)
- 特殊业务的时效性问题
- 过滤机制的存储和性能问题
本次分享内容
- 业务背景和推送系统架构设计
- 业务过滤机制设计
- 分库分表下的多维查询问题
- 特殊业务的时效性问题
- 过滤机制的存储和性能问题
- 总结
背景介绍

首先介绍下推送系统的作用,如上图是一个新闻业务的推送 / 通知,推送主要就是在用户不打开 APP 的时候有一个手段触达用户,保持 APP 的存在感,提高 APP 的日活。公司目前主要用推送的业务包括
- 主播开播,公司有直播业务,主播在开直播的时候会给这个主播的所有粉丝发一个推送开播提醒
- 专辑更新,平台上有非常多的专辑,专辑下面是一系列具体的声音,比如一本儿小说是一个专辑,小说有很多章节,那么当小说更新章节的时候给所有订阅这个专辑的用户发一个更新的提醒。
- 个性化、新闻业务等。
既然想给一个用户发推送,系统要跟这个用户设备之间有一个联系的通道。自建通道需要 App 常驻后台,而手机厂商因为省电等原因普遍采取 “激进” 的后台进程管理策略,导致自建通道质量较差。目前通道一般是由 “推送服务商” 去维护,也就是说公司内的推送系统并不直接给用户发推送,推送流转流程如下:

国内的几大厂商 hmov 有自己官方的推送通道,但是每一家接口都不一样,所以一些厂商比如小米、个推提供集成接口。发送时推送系统发给集成商,然后集成商根据具体的设备,发给 hmov 这些具体的厂商,最终发给用户。
给设备发推送的时候,必须说清楚你要发的是什么内容,即 title、message/body,还要指定给哪个设备发推送。我们以 token 来标识一个设备, 在不同的场景下 token 的含义是不一样的,公司内部一般用 uid 或者 deviceId 标识一个设备,对于集成商、hmov 也有自己对设备的唯一 “编号”,所以公司内部的推送服务,要负责进行 uid、deviceId 到集成商 token 的转换。
整体设计
技术架构设计

推送整体上是一个基于队列的流式处理系统,上图右侧是主链路,各个业务方通过推送接口给推送系统发推送,推送接口会把数据发到一个队列,由转换和过滤服务消费。转换就是上文说的 uid/deviceId 到 token 的转换,过滤下文专门讲,转换过滤处理后发给发送模块,最终给到集成商接口。
App 启动时会向服务端发送绑定请求,上报 uid/deviceId 与 token 的绑定关系。当卸载 / 重装 App 等导致 token 失效时,集成商通过 http 回调告知推送系统。各个组件都会通过 kafka 发送流水到公司的 xstream 实时流处理集群,聚合数据并落盘到 mysql,最终由 grafana 提供各种报表展示。
业务过滤机制设计
各个业务方可以无脑给用户发推送,但推送系统要有节制,因此要对业务消息有选择的过滤,包括以下几点(按支持的先后顺序):
- 用户开关,App 支持配置用户开关,若用户关闭了推送,则不向用户设备发推送。
- 文案排重,一个用户不能收到重复的文案,用于防止上游业务方发送逻辑出错。
- 频率控制,每一个业务对应一个 msg_type,设定 xx 时间内最多发 xx 条推送。
- 静默时间,每天 xx 点到 xx 点不给用户发推送,以免打扰用户休息。
- 用户 / 消息分级
- 每一个 msg/msg_type 有一个 level,给重要 / 高 level 业务更多发送机会。
- 当用户一天收到 xx 条推送时,不是重要的消息就不再发给这些用户。
一些具体的技术问题
分库分表下的多维查询问题
喜马拉雅现在已经有 6 亿 + 用户,对应的推送系统的设备表(记录 uid/deviceId 到 token 的映射)也有类似的量级,所以对设备表进行了分库分表,以 deviceId 为分表列。

但实际上经常有根据 uid/token 的查询需求,因此还需要建立以 uid/token 到 deviceId 的映射关系。因为 uid 查询的场景也很频繁,因此 uid 副表也拥有和主表同样的字段。

因为每天会进行一两次全局推,且针对沉默用户(不常使用 app)也有专门的推送,存储方面实际上不存在 “热点”,虽然使用了缓存,但作用很有限,且占用空间巨大。多分表以及缓存导致数据存在三四个副本,不同逻辑使用不同副本,经常出现不一致问题(追求一致则影响性能), 查询代码非常复杂且性能较低。最终选择了将设备数据存储在 tidb 上,在性能够用的前提下,大大简化了代码。

特殊业务的时效性问题
推送系统是基于队列的,“先到先推”。大部分业务不要求很高的实时性,但直播业务要求半个小时送达,新闻业务更是 “欲求不满”,越快越好。若进行新闻推送时,队列中有巨量的“专辑更新” 推送等待处理,则专辑更新业务会严重干扰新闻业务的送达。
