无线推送引擎架构演进

 

“您乘坐的G123将于16:17发车,请您提前前往苏州北站候车”,手机响了一声,拿起手机看到通知栏上...



“您乘坐的G123将于16:17发车,请您提前前往苏州北站候车”,手机响了一声,拿起手机看到通知栏上的提醒之后,你想起来今晚需要前往上海以便参加明天一早的会议,于是收拾收拾行李准备出发。合理的使用推送服务,通过精心编排的文案,加上恰到时机的提醒,可以使用户及时的收到对自己有价值的信息,进而可以提升产品的购买率,也能让用户感受到关怀,从而提升产品的复购率。

同程旅游APP是从2014年开始大规模使用推送服务的,到现在已经持续发展到了第四年。从一天二三十万的推送量,到现在最多一天8400万记录的推送量;从两分钟最多推送一万条的速度,到现在每分钟可达300多万条的速度;从过去仅支持省市级别批量推送的维度,到现在可以精确到具体某个会员、某个设备的推送……我们的推送服务架构在四年间进行了6次大版本的迭代。

作者 | 技术委员会-孙博

一.系统的初始形态

    推送系统的全部功能在一开始的时候仅是由两个JOB交替执行完成的。一个JOB负责初始化消息,它会从数据库中把未完成的推送任务,根据任务所关联的地理位置,从一张一天清洗一次的设备表中把相关的iOS设备取出来,组装成推送所需的数据格式后插入另外一张推送信息表中,而Android由于采用的是标签推送的方式,无需对每个设备都创建推送消息,直接在推送信息表中插入一条推向某个地区的推送信息数据即可。而另一个JOB就负责每分钟执行一次,每次从推送信息表中取出未推送的任务,调用第三方的API进行推送。整个架构非常简洁,对于当时一天二三十万,峰值70万左右的量来说,是能够完全满足需要的。

图 1            
直到2014年7月的一天,时值旧系统第一次创建全国推送任务,可是由于初始化JOB的一个隐蔽的BUG,导致在中午11点40分大家都去吃饭没人留意的时候,系统静悄悄的重复初始化了18次,而到了设定的推送时间13点之时,隐患正式爆发。在大家都在午休没有什么声音的办公室,几乎所有Android手机都在疯狂弹出推送,并且是非常有规律的1分钟弹一次,一直持续到我们紧急清空了推送消息表之后才算是暂停了这次事件。多亏iOS的推送机制为逐条发送而未受此次事件影响,但由于Android是使用的地理标签推送,发送一条就会发送到所有具有该标签的用户,由于在系统关停前共推送了13次,所以全国所有未关闭推送的Android用户都在此时收到了13条一模一样的“60元游马代”。13个数字确实不太吉利。上面既然提到的是事件“暂停”,那么事件一定未完结。谁都没想到的是,由于推送次数过多,标题也比较煽动,很多用户收到后几乎在同一时刻打开了APP,接踵而来的如洪水一般的流量摧枯拉朽般迅速冲垮了我们的各个系统,当时甚至让大家以为是被别人攻击了!

二.推翻旧架构的重大改版

    痛定思痛,我们在各方压力之下开始筹划了第一次的改造。我们首先改造了Android的发送机制,使其不再使用标签方式推送,而是改为像iOS一样逐条进行推送,并改用队列存储消息而不再通过数据库的表。同时出于性能及安全方面的考虑,我们尝试使用redis来提供队列功能,利用其lpop/rpop操作会删除List中元数据的特性从根本上杜绝了重复推送的风险。为了把精细化营销做到最好,推送数据的生产者改由数据中心担当。除此之外,我们为了能够支持改版时制定的扩容十倍推送量的目标,彻底改造了推送架构,不再使用一分钟执行一次的JOB来消费队列,而是设计了一套高性能、高可靠、高扩展性的线程池托管框架,利用该框架动态托管消费者单元,当队列中消息多时自动创建更多的消费单元来消费以增快推送速度、当队列中消息少时自动减少消费单元以降低机器损耗,同时我们也支持分布式部署,自2014年8月上线以后一直稳定运行,国庆节仅使用4台机器就轻松顶住了千万级的日推送量。

图 2   

三.推送全链路支持的改造

    这一次改版的成功,给予了我们很大的信心,我们随后开始大胆地推进第三版的研发规划。在第二版上线时,我们仅迁移了大批次的营销类推送的功能,但机票行程助手这类提醒类推送仍然在使用旧的推送JOB来实现。虽然当这种消息量当时并不大,但旧系统的扩展性、可靠性、实时性都难以提升,而且在提倡短信转推送的氛围下,迟早会面临推送量暴增的那天。未雨绸缪,我们提前做好准备。为了方便项目同事做接入,尽可能的少改代码就能从短信迁移到推送,我们联合公共研发同事设计了新的推送接口,项目方通过简单的变更原短信模板组件的参数,就能轻松切换到推送方式发送,而我们也只需在收到推送接口的请求后,组装成推送系统所需的格式写入redis队列即可复用我们自己的推送服务。尔后正如我们所预料的一样,我们第三版推送系统在2014年11月底上线没多久,就有项目同事开始联系我们接入推送事宜。

第三版上线后面临的不仅是提醒类消息的接入量增加,更是由于系统性能的提升给了营销同事更大的发挥空间,我们的营销类消息的量日与俱增,在2014年圣诞节创下了6400万的日推送量记录,虽然远超我们设计规划的量,但系统还是顽强的抗住了,只不过系统已经不堪重负,由于消息存储量的暴增,即便做了逻辑分表,数据库仍然难以支持在单表已经破亿的前提下再实时插入海量数据,还得支持快速查询……

图 3   

四.业务量暴增下的存储升级

         2015年春节前我们启动了亿级推送系统的改造计划,主要目的是对存储进行大幅度的升级。我们在推送后立刻向redis写入每个设备最新消息的缓存,并将消息写入一个redis队列,系统持续自动的拉取队列中的消息数据,每15秒或每5000条时批量写入mongodb一次。而当用户拉取消息列表时,我们再将mongodb中的数据与redis中缓存的最新消息数据做merge操作,拼装成用户的消息列表做响应。在2015年3月上线后,我们欣喜的发现mongodb的性能比我们想象的还要高,高峰期用户拉取消息的平均耗时是10ms,这也标志着我们第四版的改版又是成功的!

第四版的改造由于大幅度的提升了存储的效率,即便是推送高峰期DB也不会再发出报警,我们的系统从此进入了可无人值守状态。上线后长达半年的时间里都没有过重新发布,并且即便CPU被高峰期的推送量压成了红线机器也能正常运行。不过在这个期间,由于一到高峰期CPU就会暴增,也确实给我们的运维小伙伴带来了不少麻烦。

图 4   

五.减能增效对系统瘦身优化

    每一台推送系统的负载中都会包含400-1200个活跃的消费单元用来消费redis队列,并组装成推送指定的数据格式利用Agent向API发送请求,而在改版前的设计里,每一个消费单元都会创建独占的Agent,而在从发起请求到收到响应的整个过程中,Agent的调用链都是线程安全的,同时Agent对象又较重,创建时还需要加载证书文件等各种各样的配置,尤其是iOS的推送更是由于采用Socket通信的缘故使得这个过程甚至要长达3s。因此,我们在新的规划中合并了消费单元的Agent,使消费单元在组装完数据后不再直接使用Agent发送消息,而是将数据送入Agent的内部队列,用更少的Agent单元完成了原来同样的任务量,我们还重新设计了iOS推送的Agent,为其设计了类似于连接池的功能,使得重建推送通道的速度大幅提升。在性能得到优化的同时,我们为了降低系统的成本,又将原来用于写入mongodb的缓冲队列由redis更换为了进程中的缓冲池。我们使用相似的机制——每15秒或缓冲池满5000条时自动将缓冲池中的数据批量写入mongodb,同样的达到了我们异步写入的目的,但却节省了30G的redis成本。

图 5   
第五版推送系统于15年10月上线后,由于CPU开销更小,系统运行相比前作更加稳定,我们一直到16年6月也仅有两次因切换redis实例才重启过两次。但随着系统愈加稳定,我们又产生了新的想法——我们希望能以更轻量的方式实现推送系统,并且能够支持更多的APP通过我们的平台进行推送。

六.更轻更快的新挑战

    经过技术选型,我们使用了go语言重新开发,并进一步改造了消费单元的模型。我们在新的设计中弱化了同程旅游APP专有的数据格式,强化了推送API固有的格式,使推送的Agent不再局限于为同程旅游APP提供服务。我们在消费redis的单元及Agent单元间增加了Adapter单元来提供数据格式转换的功能,当我们需要添加对其他APP的推送支持时,只需为其简单的设计一个Adapter,就可以轻松接入我们的推送服务进行推送,极大的简化了改造的过程。

经过两个月的设计与开发,我们在16年9月将第六版初版的推送系统部署在了生产环境,与.NET版的推送并行运行,让两套环境共同消费提醒类推送消息的redis队列来提供推送服务。在16年国庆节期间,新的go版的推送系统表现出了比.NET版更强劲的实力,不仅资源占用更少,推送的性能也更高。因此我们又紧锣密鼓的开始对营销类批量推送的逻辑进行迁移,并在17年春节后正式将具备全功能的新版推送系统部署到了生产环境,一直稳定运行至今。在推送的高峰期,通过趋势系统的埋点监控,现在的推送系统的消费速度峰值可以达到每分钟300万以上!

图 6   

七.不能停下的脚步

    目前,推送系统正在将消息的推送与存储做解耦,在2017年3月,我们已将营销类消息的存储从推送系统中拆除,进一步降低了系统的开销。利用kafka队列在推送任务创建时对消息做前置写入,巧妙的错开了mongodb的写入高峰及读取高峰,极大的提升了消息接口性能。后续等我们将为数不多的提醒类消息的写入也从推送系统中拆除后,所有的信息存储将全部独立成新的子系统,而对于推送系统来说,将如其名称一样只留下“推送”的核心功能,不再需要分担额外的运算能力用于存储,届时推送系统将正式完整新一轮的改造。

整体架构图如下:

图 7   
三年多的持续迭代,只为达到更高的要求。

铭记初心,只为让更多人享受旅游的乐趣。

--- 孙博


    关注 同程研发中心


微信扫一扫关注公众号

0 个评论

要回复文章请先登录注册