首页 > 代码库 > 说说apns和http2

说说apns和http2

相信以苹果爸爸一贯的行事风格,必然在某一天宣布不再支持旧协议,所以各公司都会开始逐渐转用新推送API,我很难想到会有那么多人私信我一些关于nghttp2的问题,我也是萌新啊,虽然过了很久,想写一些遇到的问题,记录一下备忘。
 
人在做一件事的时候反倒脑子不清楚,过后回看,有些做法不合适,有些应该做的没做。
 
 
仅考虑对接APNs做推送业务,用最不严谨的方式,粗略的分几类:
1.token量百万以下,推到时间不要求假实时(几秒内),单appkey
这种是很多小型app的自用要求,一句话,只让我能推,就ok。
方案:搞一个推单条的工具,定时跑一下就行了。
方案2:对接任意一个云推送sdk(推荐:极光),省心,说不定这个app活不了几个月。
 
2.token量千万级别,有一定实时性要求,单appkey,还要有一些常规性质统计数据支撑,比如用户送达率,token失效率(token失效意味着应用卸载,这个数据对产品狗们很关键)
 
这种是典型的一个成熟期app产品的推送场景,当希望产品能在自己把控下演进的公司,会想不开,选择自研
我们之后主要考虑这种场景,做一个最通用的东西,先起个名,比如说这个东西可以叫apnspx ,px是proxy的意思。它的形态从外面看,是一个单进程http服务,使用jsonapi跟外界通信。
最好不要搞特殊化,做后端6,7年了,唯一一个小经验就是:
所有同步接口,都应该用http json api,所有异步接口,都要走MQ,如果团队成熟想上rpc,都应该用grpc,不然的话,事情就会一团糟。
 
3.普适性推送平台,要处理成千上万个不同的appkey,十亿以上海量token,秒级甚至毫秒级送达,还要有详尽的业务报表和漂亮曲线展示给你老板,用来说明你的app非常牛逼或者快要死了。
 
这是类似极光,云吧,个推等那几家做云推送公司的核心需求。
然而,在这个层面上,apns只是这个复杂系统中的一个小服务,更多制约系统性能的,反倒不是apns本身了。常见的几个关键问题,都是需要在系统层面去解决的。
- 数据总线,系统的吞吐全部需要用消息队列来异步化
- token失效淘汰,需要有一套常规缓存系统加合理的淘汰算法
- 长连接轮转,需要有一套分析app活跃性和推送特征的分析系统,反哺后端,指导资源分配
- 亿级app客户的处理,高峰时期的系统伸缩,同上
- 平台是开放的,当然还要有防攻击防刷的那一整套东西
- 到APNs的连通性,甚至跟代码无关,快让你的老大,去跟运维组的老大刚一波正面,让他们把机房部署在美国或者香港,你看戏就好,保证推送速度飞快。
 
简化场景3,这些东西想全做在apnspx,是想不开的行为,我假设系统里核心部分是部署在容器化的平台上,只负责推送,和输出原始统计日志,别的策略由更高层的调度来处理,比如某app出现大推送的动态扩容这种问题。
 
从头开始作一个小设计,并时刻谨记,只做场景2,不然会陷入无尽选择恐惧的泥潭,其中为什么这么做我写一下个人的理解
 
参数传入时机的权衡,
程序启动了,带有几个初始参数:appkey,证书(现在生产开发也是一个证书了)
评论:之前曾经妄图运行中动态拉取证书,这样可以支持多appkey,后来发现,证书的过期,失效与更新是又一个问题,反倒复杂化了。事实上这些逻辑应该做在另一个层面。
细化至少要有如下可配置项:
连接数,连接最大重试数,token失败最大重试数
 
输入未动,统计先行。功能本身甚至不如统计重要,服务本身的统计要能从一个/status接口输出,
最要紧的:
当前批次队列长度,就绪数,等待数,平均连接重试数,这些可以验证是否网络通畅
启动到目前,推送批次量,推送总量,成功,失败,重试,各自总量
还有一组维度:每秒,每5分钟内以上各指标。
这些信息是评判健康的最主要标准,到这个粒度暂时够了。
 
输入,一个json,起码要支持两种格式,一个payload+tokenlist,一个tokenlist,并且每个token有各自的payload
第一种是推广告和新闻最常用的,有必要单独
第二种是每个用户会有所不同,类似出验证码,或者精细化人群推送
也就是说:输入进来要分好给每个token推什么,细分人群不是apnspx的功能
 
核心的推送引擎,根据实现的选择有两种模式,多路复用的无阻塞风格,语言级协程
无论是哪一种,我都 **非常不建议** 使用http2的多路stream特性,除非google给我写了一个库,让我用起来感受不到stream。
一条连接,一路stream,失败关闭,成功继续。保持简单
多路stream,不好用,不适用
就像nghttp2提供的六万多个回调(其实大概是十几个,难道我会去数吗?),作为库的使用者,应用层开发者,要明白一个事实:这些不是给你用来写那些挣工资的代码的,除非你对有更高要求
顺带说一下nghttp2,提供了协议中定义的每一个阶段前后的钩子,目的是给高性能服务器h2o提供最精细的控制能力和手段,同理stream特性也是,是用来给web浏览器内核这种级别的项目来用的。
 
http2的本质复杂性,是由于它违反了网络分层模型,当然是由于迫不得已。
http2在应用层协议上重新定义了一个连接策略层,然后再在上面定义应用层。
如果使用stream,相当于在处理一个微型的tcp协议栈。
 

第一步:域名解析的问题,在3那个极端场景里,阻塞的域名解析也不可接受,但要认识到,这个问题是another problem,使用单独的方案,getaddrinfo调用它就是阻塞的我能怎么办?我也很绝望啊!系统级的有skydns,小规模的亦可以自己维护一个表,或者无视阻塞,这一步之后在系统中,只考虑ip。

 
第二步:socket连接,ssl连接,这一步出错会有很多信息,好好的返回详细,说明苹果爸爸认为你的证书有问题了
 
维护两个线程安全队列
主线程将数据不断的push数据进引擎,引擎将结果push进处理完毕队列,失败但未达到重试次数的,重新进输入队列
最后结果集队列全部完毕,将结果输出,结果就是apns返回的那一坨,可以加一些自定义的,比如推送完成时间等。
 
当认为因为偶发服务挂掉等原因导致的状态丢失不可接受,就使用一种快速的持久化队列,替代内存队列,比如redis,或者ssdb。这样服务本身是stateless的,更好一些。
 
这一批推送完毕,断开连接。或者延迟断开,等待下一批,建议最好是主动做断链重连。
推送中断链,重连重试,直到超过自定义次数上限。
 
结果做json序列化,返回。
 
使用时候控制每一批的数量,分批多次。一次推得太多,APNs会生气,认为你在攻击它,把链接关了。
APNs到国内网络不稳定,可能就断了。
APNs自己有bug,就断了
APNs不高兴了,就断了
不知道为什么就断了,比如海底光缆断裂,奥特曼击毁地球等。
不要相信长连接的可用性,在可能失败的地方都加入重试逻辑。
任何重试都要设定上限,并给出统计结果。
 
以前做的是场景3,可惜种种原因,自己也很不尽满意,根据上述,我会缓慢的写一个满足场景2需求的小项目,也算是一段工作的总结吧,有空就写写
以前每次一想到,代码量很大一块在统计、异常处理哪里,一点都不clean,就泄气,我连苹果手机都没有,我写个卵
 
无奈写代码就是跟现实世界做斗争,人生不是一个纯函数 
网络编程更是特别如此。
 
 

说说apns和http2