关于即时通讯:喜马拉雅亿级用户量的离线消息推送系统架构设计实践

61次阅读

共计 6653 个字符,预计需要花费 17 分钟才能阅读完成。

本文由喜马拉雅技术团队李乾坤原创,原题《推送零碎实际》,感激作者的自私分享。

1、引言

1.1 什么是离线音讯推送

对于 IM 的开发者来说,离线音讯推送是再相熟不过的需要了,比方下图就是典型的 IM 离线音讯告诉成果。

1.2 Andriod 端离线推送真心不易

挪动端离线音讯推送波及的端无非就是两个——iOS 端和 Andriod 端,iOS 端没什么好说的,APNs 是惟一选项。

Andriod 端比拟奇葩(次要指国内的手机),为了实现离线推送,各种保活黑科技层出不穷,随着保活难度的一直降级,能够应用的保活伎俩也是越来越少,有趣味能够读一读我整顿的上面这些文章,感受一下(文章是按工夫程序,随着 Andriod 零碎保活难度的晋升,一直进阶的)。

《利用保活终极总结(一):Android6.0 以下的双过程守护保活实际》
《利用保活终极总结(二):Android6.0 及以上的保活实际(过程防杀篇)》
《利用保活终极总结(三):Android6.0 及以上的保活实际(被杀复活篇)》
《Android P 正式版行将到来:后盾利用保活、音讯推送的真正噩梦》
《全面盘点以后 Android 后盾保活计划的实在运行成果(截止 2019 年前)》
《2020 年了,Android 后盾保活还有戏吗?看我如何优雅的实现!》
《史上最强 Android 保活思路:深刻分析腾讯 TIM 的过程永生技术》
《Android 过程永生技术终极揭密:过程被杀底层原理、APP 应答被杀技巧》
《Android 保活从入门到放弃:乖乖疏导用户加白名单吧(附 7 大机型加白示例)》

下面这几篇只是我整顿的这方面的文章中的一部分,特地留神这最初一篇《Android 保活从入门到放弃:乖乖疏导用户加白名单吧(附 7 大机型加白示例)》。是的,以后 Andriod 系统对 APP 自已保活的容忍度简直为 0,所以那些曾今的保活伎俩在新版本零碎里,简直通通都生效了。

自已做保活曾经没戏了,保离线音讯推送总归是还得做。怎么办?依照现时的最佳实际,那就是对接种手机厂商的 ROOM 级推送通道。具体我就不在这里开展,有趣味的地能够详读《Android P 正式版行将到来:后盾利用保活、音讯推送的真正噩梦》。

自已做保活、自建推送通道的时代(这里当然指的是 Andriod 端啦),离线音讯推送这种零碎的架构设计绝对简略,无非就是每台终端计算出一个 deviceID,服务端通过自建通道进行音讯透传,就这么点事。

而在自建通道死翘翘,只能依赖厂商推送通道的现在,小米、华为、魅族、OPPO、vivo(这只是支流的几家)等等,手机型号太多,各家的推送 API、设计规范各不相同(别跟我提什么对立推送联盟,那玩意儿我等他 3 年了——详见《万众瞩目的“对立推送联盟”上场了》),这也间接导致先前的离线音讯推送零碎架构设计必须从新设计,以适应新时代的推送技术要求。

1.3 怎么设计正当呢

那么,针对不同厂商的 ROOM 级推送通道,咱们的后盾推送架构到底该怎么设计正当呢?

本文分享的离线音讯推送零碎设计并非专门针对 IM 产品,但无论业务层的差异有多少,大抵的技术思路上都是相通的,心愿借喜马拉雅的这篇分享能给正在设计大用户量的离线音讯推送的你带来些许启发。

  • 举荐浏览:喜马拉雅技术团队分享的另一篇《长连贯网关技术专题(五):喜马拉雅自研亿级 API 网关技术实际》,有趣味也能够一并浏览。

学习交换:

即时通讯 / 推送技术开发交换 5 群:215477170 [举荐]
挪动端 IM 开发入门文章:《新手入门一篇就够:从零开发挪动端 IM》
开源 IM 框架源码:https://github.com/JackJiang2…

(本文同步公布于:http://www.52im.net/thread-36…)

2、技术背景

首先介绍下在喜马拉雅 APP 中推送零碎的作用,如下图就是一个新闻业务的推送 / 告诉。

离线推送次要就是在用户不关上 APP 的时候有一个伎俩触达用户,放弃 APP 的存在感,进步 APP 的日活。

咱们目前次要用推送的业务包含:

  • 1)主播开播:公司有直播业务,主播在开直播的时候会给这个主播的所有粉丝发一个推送开播揭示
  • 2)专辑更新:平台上有十分多的专辑,专辑上面是一系列具体的声音,比方一本儿小说是一个专辑,小说有很多章节,那么当小说更新章节的时候给所有订阅这个专辑的用户发一个更新的揭示:
  • 3)个性化、新闻业务等。
  • 既然想给一个用户发离线推送,零碎就要跟这个用户设施之间有一个分割的通道。

做过这个的都晓得:自建推送通道须要 App 常驻后盾(就是引言里提到的利用“保活”),而手机厂商因为省电等起因广泛采取“激进”的后盾过程管理策略,导致自建通道品质较差。目前通道个别是由“推送服务商”去保护,也就是说公司内的推送零碎并不间接给用户发推送(就是上节内容的这篇里提到的状况:《Android P 正式版行将到来:后盾利用保活、音讯推送的真正噩梦》)。

这种状况下的离线推送流转流程如下:

国内的几大厂商(小米、华为、魅族、OPPO、vivo 等)都有本人官网的推送通道,然而每一家接口都不一样,所以一些厂商比方小米、个推提供集成接口。发送时推送零碎发给集成商,而后集成商依据具体的设施,发给具体的厂商推送通道,最终发给用户。

给设施发推送的时候,必须说分明你要发的是什么内容:即 title、message/body,还要指定给哪个设施发推送。

咱们以 token 来标识一个设施,在不同的场景下 token 的含意是不一样的,公司外部个别用 uid 或者 deviceId 标识一个设施,对于集成商、不同的厂商也有本人对设施的惟一“编号”,所以公司外部的推送服务,要负责进行 uid、deviceId 到集成商 token 的转换。

3、整体架构设计

如上图所示,推送零碎整体上是一个基于队列的流式解决零碎。

上图右侧:是主链路,各个业务方通过推送接口给推送零碎发推送,推送接口会把数据发到一个队列,由转换和过滤服务生产。转换就是上文说的 uid/deviceId 到 token 的转换,过滤下文专门讲,转换过滤解决后发给发送模块,最终给到集成商接口。

App 启动时:会向服务端发送绑定申请,上报 uid/deviceId 与 token 的绑定关系。当卸载 / 重装 App 等导致 token 生效时,集成商通过 http 回调告知推送零碎。各个组件都会通过 kafka 发送流水到公司的 xstream 实时流解决集群,聚合数据并落盘到 mysql,最终由 grafana 提供各种报表展现。

4、业务过滤机制设计

各个业务方能够无脑给用户发推送,但推送零碎要有节制,因而要对业务音讯有抉择的过滤。

过滤机制的设计包含以下几点(按反对的先后顺序):

  • 1)用户开关:App 反对配置用户开关,若用户敞开了推送,则不向用户设施发推送;
  • 2)文案排重:一个用户不能收到反复的文案,用于避免上游业务方发送逻辑出错;
  • 3)频率管制:每一个业务对应一个 msg_type,设定 xx 工夫内最多发 xx 条推送;
  • 4)静默工夫:每天 xx 点到 xx 点不给用户发推送,免得打搅用户劳动。
  • 5)分级管理:从用户和音讯两维度进行分级管制。

针对第 5 点,具体来说就是:

  • 1)每一个 msg/msg_type 有一个 level,给重要 / 高 level 业务更多发送机会;
  • 2)当用户一天收到 xx 条推送时,不是重要的音讯就不再发给这些用户。

5、分库分表下的多维查问问题

很多时候,设计都是基于实践和教训,但实操时,总会遇到各种具体的问题。

喜马拉雅当初曾经有 6 亿 + 用户,对应的推送零碎的设施表(记录 uid/deviceId 到 token 的映射)也有相似的量级,所以对设施表进行了分库分表,以 deviceId 为分表列。

但实际上:常常有依据 uid/token 的查问需要,因而还须要建设以 uid/token 到 deviceId 的映射关系。因为 uid 查问的场景也很频繁,因而 uid 副表也领有和主表同样的字段。

因为每天会进行一两次全局推,且针对缄默用户(即不常应用 APP 的用户)也有专门的推送,存储方面实际上不存在“热点”,尽管应用了缓存,但作用很无限,且占用空间微小。

多分表以及缓存导致数据存在三四个正本,不同逻辑应用不同正本,经常出现不统一问题(谋求统一则影响性能),查问代码非常复杂且性能较低。

最终咱们抉择了将设施数据存储在 tidb 上,在性可能用的前提下,大大简化了代码。

6、非凡业务的时效性问题

6.1 基本概念
推送零碎是基于队列的,“先到先推”。大部分业务不要求很高的实时性,但直播业务要求半个小时送达,新闻业务更是“欲求不满”,越快越好。

若进行新闻推送时:队列中有巨量的“专辑更新”推送期待解决,则专辑更新业务会重大烦扰新闻业务的送达。

6.2 这是隔离问题?
一开始咱们认为这是一个隔离问题:比方 10 个生产节点,3 个专门负责高时效性业务、7 个节点负责个别业务。过后队列用的是 rabbitmq,为此革新了 spring-rabbit 反对依据 msytype 将音讯路由到特定节点。

该计划有以下毛病:

  • 1)总有一些机器很忙的时候,另一些机器在“隔岸观火”;
  • 2)新增业务时,须要额定配置 msgType 到生产节点的映射关系,保护老本较高;
  • 3)rabbitmq 基于内存实现,推送刹时顶峰时占用内存较大,进而引发 rabbitmq 不稳固。

6.3 其实是个优先级问题
起初咱们觉察到这是一个优先级问题:高优先级业务 / 音讯能够插队,于是封装 kafka 反对优先级,比拟好的解决了隔离性计划带来的问题。具体实现是建设多个 topic,一个 topic 代表一个优先级,封装 kafka 次要是封装生产端的逻辑(即结构一个 PriorityConsumer)。

备注:为形容简略,本文应用 consumer.poll(num) 来形容应用 consumer 拉取 num 个音讯,与实在 kafka api 不统一,请知悉。

PriorityConsumer 实现有三种计划,以下别离论述。

1)poll 到内存后从新排序:java 有现成的基于内存的优先级队列 PriorityQueue 或 PriorityBlockingQueue,kafka consumer 失常生产,并将 poll 到的数据从新 push 到优先级队列。

1.1)如果应用有界队列,队列打满后,前面的音讯优先级再高也 put 不进去,失去“插队”成果;
1.2)如果应用无界队列,原本应堆在 kafka 上的音讯都会堆到内存里,OOM 的危险很大。
2)先拉取高优先级 topic 的数据:只有有就始终生产,直到没有数据再生产低一级 topic。生产低一级 topic 的过程中,如果发现有高一级 topic 音讯到来,则转向生产高优先级音讯。

该计划实现较为简单,且在晚顶峰等推送密集的时间段,可能会导致低优先级业务齐全失去推送机会。

3)优先级从高到低,循环拉取数据:

一次循环的逻辑为:

consumer-1.poll(topic1-num);
cosumer-i.poll(topic-i-num);
consumer-max.priority.poll(topic-max.priority-num)

如果 topic1-num=topic-i-num=topic-max.priority-num,则该计划是没有优先级成果的。topic1-num 能够视为权重,咱们约定:topic- 高 -num=2 * topic- 低 -num,同一时刻所有 topic 都会被生产,通过一次生产数量的多少来变相实现“插队成果”。具体细节上还借鉴了“滑动窗口”策略来优化某个优先级的 topic 长期没有音讯时总的生产性能。

从中咱们能够看到,时效问题先是被了解为一个隔离问题,后被视为优先级问题,最终转化为了一个权重问题。

7、过滤机制的存储和性能问题

在咱们的架构中,影响推送发送速度的次要就是 tidb 查问和过滤逻辑,过滤机制又分为存储和性能两个问题。

这里咱们以 xx 业务频控限度“一个小时最多发送一条”为例来进行剖析。

第一版实现时:redis kv 构造为 <deviceId_msgtype, 已发送推送数量 >。

频控实现逻辑为:

  • 1)发送时,incr key,发送次数加 1;
  • 2)如果超限(incr 命令返回值 > 发送次数下限),则不推送;
  • 3)若未超限且返回值为 1,阐明在 msgtype 频控周期内第一次向该 deviceId 发消息,需 expire key 设置过期工夫(等于频控周期)。

上述计划有以下毛病:

  • 1)目前公司有 60+ 推送业务,6 亿 + deviceId,一共 6 亿 *60 个 key,占用空间微小;
  • 2)很多时候,解决一个 deviceId 须要 2 条指令:incr+expire。

为此,咱们的解决办法是:

  • 1)应用 pika(基于磁盘的 redis)替换 redis,磁盘空间能够满足存储需要;
  • 2)委托零碎架构组裁减了 redis 协定,反对新构造 ehash。

ehash 基于 redis hash 批改,是一个两级 map <key,field,value>,除了 key 能够设置有效期外,field 也能够反对有效期,且反对有条件的设置有效期。

频控数据的存储构造由 <deviceId_msgtype,value> 变为 <deviceId,msgtype,value>,这样对于多个 msgtype,deviceId 只存一次,节俭了占用空间。

incr 和 expire 合并为 1 条指令:incr(key,filed,expire),缩小了一次网络通信:

  • 1)当 field 未设置有效期时,则为其设置有效期;
  • 2)当 field 还未过期时,则疏忽有效期参数。

因为推送零碎重度应用 incr 指令,能够视为一条写指令,大部分场景还用了 pipeline 来实现批量写的成果,咱们委托零碎架构组小伙伴专门优化了 pika 的写入性能,反对“写模式”(优化了写场景下的相干参数),qps 达到 10w 以上。

ehash 构造在流水记录时也施展了重要作用,比方 <deviceId,msgId,100001002>,其中 100001002 是咱们约定的一个数据格式示例值,前中后三个局部(每个局部占 3 位)别离示意了某个音讯(msgId)针对 deviceId 的发送、接管和点击详情,比方头 3 位“100”示意因发送时处于静默时间段所以发送失败。

附录:更多音讯推送技术文章

《iOS 的推送服务 APNs 详解:设计思路、技术原理及缺点等》
《信鸽团队原创:一起走过 iOS10 上音讯推送 (APNS) 的坑》
《Android 端音讯推送总结:实现原理、心跳保活、遇到的问题等》
《扫盲贴:意识 MQTT 通信协议》
《一个基于 MQTT 通信协议的残缺 Android 推送 Demo》
《IBM 技术经理访谈:MQTT 协定的制订历程、倒退现状等》
《求教 android 音讯推送:GCM、XMPP、MQTT 三种计划的优劣》
《挪动端实时音讯推送技术浅析》
《扫盲贴:浅谈 iOS 和 Android 后盾实时音讯推送的原理和区别》
《相对干货:基于 Netty 实现海量接入的推送服务技术要点》
《挪动端 IM 实际:谷歌音讯推送服务 (GCM) 钻研(来自微信)》
《为何微信、QQ 这样的 IM 工具不应用 GCM 服务推送音讯?》
《极光推送零碎大规模高并发架构的技术实际分享》
《从 HTTP 到 MQTT:一个基于位置服务的 APP 数据通信实际概述》
《魅族 2500 万长连贯的实时音讯推送架构的技术实际分享》
《专魅族架构师:海量长连贯的实时音讯推送零碎的心得体会》
《深刻的聊聊 Android 音讯推送这件小事》
《基于 WebSocket 实现 Hybrid 挪动利用的音讯推送实际(含代码示例)》
《一个基于长连贯的平安可扩大的订阅 / 推送服务实现思路》
《实际分享:如何构建一套高可用的挪动端音讯推送零碎?》
《Go 语言构建千万级在线的高并发音讯推送零碎实际(来自 360 公司)》
《腾讯信鸽技术分享:百亿级实时音讯推送的实战经验》
《百万在线的美拍直播弹幕零碎的实时推送技术实际之路》
《京东京麦商家开放平台的音讯推送架构演进之路》
《理解 iOS 音讯推送一文就够:史上最全 iOS Push 技术详解》
《基于 APNs 最新 HTTP/ 2 接口实现 iOS 的高性能音讯推送(服务端篇)》
《解密“达达 - 京东到家”的订单即时派发技术原理和实际》
《技术干货:从零开始,教你设计一个百万级的音讯推送零碎》
《长连贯网关技术专题(四):爱奇艺 WebSocket 实时推送网关技术实际》
《喜马拉雅亿级用户量的离线音讯推送零碎架构设计实际》

更多同类文章 ……

本文已同步公布于“即时通讯技术圈”公众号。

▲ 本文在公众号上的链接是:点此进入。同步公布链接是:http://www.52im.net/thread-36…

正文完
 0