高性能推送服务架构设计

高性能推送服务架构设计

在上一篇文章『聊一聊APP消息推送那些事』中,我讲了手机APP消息推送的基本原理,这篇文章主要是从技术的角度,介绍一个高性能推送平台的架构设计。目的是分享一些我自己在推送上的经验,以及做推送平台中遇到的一些问题和解决办法,为那些遇到类似问题的朋友提供一些参考。


# 01 背景

由于本人目前就职的公司对于推送有很强的依赖,尤其是每天都会有很多时效性很强的内容需要通知到用户。和竞争对手比拼的就是速度,相同的消息到达用户手机上的时间决定了产品的竞争力,也体现了一个公司的技术实力。因此,公司非常重视推送的性能。

细节决定成败,有时候一些小小的提升,可能带来很大的蝴蝶效应。比如,在其他功能相差不多,两款APP都可以满足用户需求的前提下;我们的APP通知每次都比另一款先通知用户,哪怕每次只早到3秒,给用户的感受就会很不同。也更能征服用户,获得用户的支持。

对于一个中小型创业公司来说,技术储备必然不能和大厂相提并论。所以,公司对应技术的投入一直有限,也没有固定的推送团队,依赖的也都是第三方公司。由于业务重心调整,在我负责推送业务期间,公司领导决定对推送进行大规模重构,目的是逐渐减少对第三方公司的依赖,提升推送性能,实现更灵活更多功能的推送平台。不过,话说回来,团队成员也只有我一个人。

我个人对项目热情比较高,原因是这个项目可以充分扩充我的技术储备,提升大型项目经验与平台设计经验,而且我一直热衷于打有显著成果的硬仗。


# 02 技术选型

敌人确定了,仗也一定要打,那么就要确定如何才能打赢这场仗。战略上要藐视敌人,战术上要重视敌人。作为有多年开发经验的老程序员,我以前有过大型平台设计和开发经验,对于项目规划设计有一定心得。高性能服务设计虽然经验不足,但是并没有害怕。

在技术选型上我面临几个选择:

    1. 在旧的推送服务上做重构。

    2. 基于PHP的Swoole扩展开发全新的系统。

    3. 基于公司另一个团队的实践,以Golang语言实现全新系统。

高性能推送服务架构设计

关于这几个方案我和同事做了几次深入讨论,最终选择第三种,原因有几点:

    1. 旧项目上打补丁开发周期虽然短,但是长远来看不能达到重构的目的,而且作为程序员都知道与其维护,不如重新开发。

    2. 尽管公司技术栈是以PHP为主,但是PHP在做高性能服务上并不擅长。而且,Swoole虽然以高性能著称,但是目前社区活跃度与影响力并不大,且开发难度有些大。

    3. Golang以高性能著称,以前的项目经验已经证明了这一点。而且Golang开发难度不大,且有兄弟项目组经验可以借鉴。

高性能推送服务架构设计


# 03 架构设计

技术选型完成后,我对未来的推送平台做了规划与设计。平台化主要考虑几方面核心因素:

    1. 完善的功能,满足运营的多样性需求,灵活实现业务需求。

    2. 优秀的性能,在推送的速度上做到极致,并且最大可能节省机器成本。

    3. 完善的日志系统,日志系统可以帮助定位推送到达用户设备上的任何问题。

    4. 完善的数据统计,对于推送后的到达,点击等统计尤为重要,可以帮助了解用户的使用情况,预计评估推送效果。

    5. 安全性考虑,推送对于APP来说是一把双刃剑,错误或者恶意操作会存在政治上以及法律上的风险,所以一定要有安全鉴权机制。

    6. 高鲁棒性,整个平台要有完善的容错机制,能够避免一些误操作带来的严重后果。

    7. 高可用性,由于推送是有状态服务,所以避免不了服务宕机或者重启,要保证此时数据不丢失。

    8. 易用性,方便快速接入,降低使用成本。

高性能推送服务架构设计

上图是我设计并实现的高性能推送平台架构图,图中红色模块是核心模块,分别负责接入的安全校验,常规接口,常驻内存程序,核心计算模块。结合了其他的存储,缓存队列以及第三方库组成了推送平台。


# 04 实现

## 功能

在功能上,平台实现了多种推送方式。比如,按照全量设备推送,单设备推送,个性化payload(不同设备不同推送payload),多标签计算推送等等。这里的几种推送方式都是基于标签和设备的关系。这就需要我们有一个设备标签系统,保证根据业务需要,维护标签的增删改查。同时为了实现全量设备推送,我们还以虚拟标签的方式维护全部设备关系。此外,为了实现对性能的优化,以及降低资源浪费,还有受限与第三方接口现在,我们还是实现了按照设备活跃度的推送机制。

高性能推送服务架构设计

标签系统负责对接业务需求,维护标签与设备关系。推送服务则负责查询标签对应的设备,按照多分片,多个协程,高并发完成推送需求。

## 性能

推送系统实现高性能的方式主要有:

1. 使用Redis数据库存储标签与设备关系。而且通过业务分片把设备分成多份,有利于实现高并发系统。

2. 异步化,使用Redis队列把推送请求与推送服务分离。

3. 多协程,推送服务利用多个协程监视并消费队列中的推送请求。

除上述一些架构上的设计,还有在标签计算上也有一些细节上的注意。目前对于我们的线上业务而言,500万设备推送平均不足10秒钟,完全可以满足需求。

## Trace日志系统

作为程序员,想必最让大家困扰的就是不停的有用户报bug。每天不停的查bug,改bug,不仅让我们每天都忙忙碌碌却没有成长,而且严重降低效率导致经常加班,还会对我们的形象和自信心有很大打击。

作为一个健全的平台,trace系统必不可少。我为每一个请求生成一个全局唯一traceid(参考一些随机数算法),在整个推送生命周期内可以根据traceid记录请求完成全过程。

此外,为了最详细的定位问题,也可以方便统计。我通过异步离线方式,把每个设备的全部推送都记录到数据库。自从实现了trace功能,我的工作效率提高了很多,并且意外收获是发现了很多隐藏的bug,提升了系统的稳定性。在疫情发作的两个月时间内,推送系统没有出现过一次问题。

## 安全性

为了防止被一些恶人攻击,我在设计平台时,考虑了安全校验。通过随机数校验算法,以及屏蔽外部请求,最大限度的降低了风险。

此外,由于我们的特殊国情,必须考虑一些不可描述的风险。我在推送文案的检查上也接入了反作弊系统,防止一些不必要的麻烦。

## 鲁棒性

为了防止系统抖动,运营人员做一些重试操作,以及程序上的错误带来雪崩。在收到请求时,我做了必要的检查,短时间内去重。此外,还根据推送厂商提供的方案,在异常情况下也保证了收到相同的推送也仅展示一条。

 

## 稳定性

由于整个服务是常驻内存的Golang服务,是有状态的。如果系统发生异常,可能会导致数据丢失。还有由于系统升级也会有丢数据的可能,一旦丢失了,可能就会造成损失。另外,在系统第一期上线后,我的程序出现了OOM的情况。所以,我在原有程序上增加了优雅重启,并解决了bug。

此外,还存在由于网络或者第三方平台的异常,导致推送失败的情况。为了兼容这些情况,我还增加了失败重试机制。最大可能保证推送成功。

## 可用性

出于接入简单,还有开发成本,我使用http请求设计了系统,替代了原有的socket方案。这样做的好处是让我的平台快速的在公司内推展,最终在系统上线后一个月内,收敛了公司内全部的推送需求。而且,由于开发工作量降低,还有好用的功能,也增加了很多推送业务。


# 05 总结

在这个推送平台设计与实现过程中,我借鉴了公司内和业界的一些先进经验,同时也总结了一些问题,归纳出问题的症结所在,最后强化了系统。期间,我根据现有功能的不足,还有看到业务发展预测一些潜在的需求,提前开发了部分功能。还有根据产品特性以及用户使用习惯,也做了一些性能与成本等权衡策略;因为我们在实际工程开发中,不可能像在实验室做理论研究那样追求完美,既要考虑收益,又要考虑成本。

这个平台是我职业生涯到目前为止最满意的一个项目,因为这个系统我充分考虑了系统的各个方面,而且也足够完善,足够稳定。在平台上线后一段时间内得到了广泛应用,并且积累已久的推送需求都得到了满足,这让我颇有成就感。另外,由于疫情的缘故,没有新的需求。这两个月期间,整个系统都没有上线,没有发过一次代码。

我知道自己无论是在推送,还是在系统架构设计,以及在Golang开发上都是入门级别,仍有很多需要提高的地方。但是,相信本文也能为一些有需要的朋友提供小小帮助,仅此而已。

相关阅读:聊一聊APP消息推送那些事

 

高性能推送服务架构设计

欢迎关注公众号