浅谈基于HTTP2推送消息到APNs
您目前处于:编程  2017年01月31日

简介

介绍基于HTTP2实现消息推送苹果APNs的设计思路和架构实现,并讲解采用Netty4 构建 HTTP2 长连接客户端,推送消息到苹果APNs的技术实现。

简单介绍从 Http1、Http1.1 到 Http2 语义的发展变化,以及 APNs 的相关知识。

正文

#1 HTTP2

HTTP1.0最早在网页中使用是在1996年,那个时候只是使用一些较为简单的网页上和网络请求上,而HTTP1.1则在1999年才开始广泛应用于现在的各大浏览器网络请求中,同时HTTP1.1也是当前使用最为广泛的HTTP协议。

在 HTTP/1.0 的时候,client每请求一项资源,都必须先建立一次 TCP 连线,而在 client 收到 server 的 response后,便会断开TCP连线。而在 HTTP/1.1 的时代,允许同域名下的资源 request and response 后,才断开 TCP 连线。


HTTP2 是 HTTP/1.1 后的一次重大的改进,在协议层面改善了以上问题,减少资源占用,来,直接感受一下差异:

HTTP/2 is the future of the Web, and it is here!

这是 Akamai 公司建立的一个官方的演示,用以说明 HTTP/2 相比于之前的 HTTP/1.1 在性能上的大幅度提升。 同时请求 379 张图片,从Load time 的对比可以看出 HTTP/2 在速度上的优势。

HTTP/2 源自 SPDY/2。SPDY 系列协议由谷歌开发,于 2009 年公开。它的设计目标是降低 50% 的页面加载时间。当下很多著名的互联网公司都在自己的网站或 APP 中采用了 SPDY 系列协议(当前最新版本是 SPDY/3.1),因为它对性能的提升是显而易见的。主流的浏览器(谷歌、火狐、Opera)也都早已经支持 SPDY,它已经成为了工业标准,HTTP Working-Group 最终决定以 SPDY/2 为基础,开发 HTTP/2。HTTP/2 标准于2015年5月以 RFC 7540 正式发表。

HTTP/2 的5个特色:

Binary Framing Layer

HTTP/2 采用二进制格式传输数据,而非 HTTP/1.x 的文本格式。

在 HTTP/1.X 由 OSI model 中的 Application Layer,存在着 Binary Framing Layer,记录着 HTTP 的内容像 HEADER 中的 method、content、message 等内容,以及 Data 部分。

Frame 的基本格式如下:

一个基础的 Stream 由 HEADER frame 与 Data frame 组成。(共有10种 frame)

而每次的 connection 可以乘载着任意数量的 stream。

可以用 chrome 内部自带的工具(chrome://net-internals/)查看 HTTP2 流量,但这个包信息量比较少,结构不如我们熟悉的 Fiddler or Wireshark 清晰。

用 wireshark 抓包:

一个包内有多个不同的 Steam ID

Multiplexing

Multiplexing 允许单一的 tcp 有多重请求/回应,也就是说 client 和 server 可以将 http 请求/回应分解成不藕合的 frame,然后随机发送,最后在另一端根据 stream ID 将 freame 组合起来。

Request Prioritization

在 HTTP/2 中,stream 有著 priority 的属性,而藉由 Priorty frame,便可以建立起 priority tree。

Header Compression

在 client 和 server 个维护一个 HPACK,採用 hash 的方式来记录 HEADER 的内容。也就是说当 client 要请求资源前会先去 HPACK 查找缺失的资源,接著请求缺少的资源。


Server Push

在 Binary Framing Layer 提过,frame 共有10种。在这裡会利用 PUSH_PROMISE 的frame,它用于 server 主动发送资源给 client。

#2 APNs

APNs 是 Apple Push Notification service 的简称(注意 APNs 的大小写, s不需要大写)。

反人类的旧APNs协议设计

在介绍新版 APNs 前,让我们来吐槽下旧的基于二进制的 APNs 协议设计是多么反人类:

在理论上,推送分发的服务器要打开一个同 APNs 网关服务器的连接,并保持这个连接。但在旧的协议下,APNs 服务却不保证 socket 能维持这个连接。如果通道上没有消息往来,空闲下来到话,socket将被路由掐断。也就是说:APNs 连接说断就断,而你无能为力。有意思的是:在旧的协议下,如果服务器响应成功的话,你将不会收到任何回应,但是如果服务器响应失败(例如,使用了一个非法的 Push token),服务器将返回了一个错误编码,并关闭这个socket。最重要的是,你必须重新发送使用这个无效 token 以后发送的所有推送(详情见示意图)。因此,你可能一直不能确定你的推送是否成功的被 APNs 服务器接收。

成功了不响应,失败了才响应,这个是最大的反人类。于是许多开发者想到了一个很 tricky 的办法:利用这个“漏洞”,比如在每发送10条后故意发送一个错误的token,如果APNs有响应了,就可以确认 APNs 是处在可用状态的,进而确认这10条消息是发送成功的。如果没有响应就说明可能连接已经中断,那么这10条消息很可能是丢失的,然后做进一步的处理。但代价显而易见:将导致你们的推送系统性能低下。苹果有一个名为"feedback"的服务,我们可以定时调用这个服务来获取invalid tokens的列表。这个服务你只要调用一次就可以获得所有的invalid tokens 列表。invalid token越多,你们的推送系统性能将越低。而且 APNs 只要一发生错误就关闭这个连接,然后重新连接。也就是“重启” socket 连接。

示意图:

图中的 PN2 去哪里了?它被放到了 feedback 列表里,等待下次你调用 feedback 服务,然后重发。

为什么Apple要在旧APNs中设计出“重启”的策略?

为了效率。

就像PC机出问题,我们总说“重启能解决90%的问题”。

那么接下来就让我们看看Apple为解决这些问题而推出的基于 HTTP/2 的全新 APNs 协议。

基于 HTTP/2 的全新 APNs 协议

来看下新版的 APNs 的新特性:

# Request 和 Response 支持JSON网络协议

# APNs支持状态码和返回 error 信息

- APNs推送成功时 Response 将返回状态码200,远程通知是否发送成功再也不用靠猜了!

- APNs推送失败时,Response 将返回 JSON 格式的 Error 信息。

# 最大推送长度提升到4096字节(4Kb)

# 可以通过 "HTTP/2 PING" 心跳包功能检测当前 APNs 连接是否可用,并能维持当前长连接。

# 支持为不同的推送类型定义 “topic” 主题

#不同推送类型,只需要一种推送证书 Universal Push Notification Client SSL 证书。

示意图:

其中最大的变化就是基于了 HTTP/2 协议,采用了长连接设计,并提供 “HTTP/2 PING” 心跳包功能检测、维持当前 APNs 连接,解决了老 APNs 无法维持连接的问题。而且新增的状态码特性,也解决了这个问题:无法获知消息是否成功地从你们的推送系统投递到了 APNs 上。理论上,你们可以保证消息是100%投递到了APNs的,因为你可以准确的知道哪条消息到达了APNs,哪些没到。重发特定失败消息成为可能。

#3 APNs的开源项目

基于 Socket 的 APNs-PUSH

1. https://github.com/notnoop/java-apns

2. https://github.com/RamosLi/dbay-apns-for-java

基于 HTTP2 的 APNs-PUSH

1. https://github.com/CleverTap/apns-http2

A Java library for sending notifications via APNS using Apple's new HTTP/2 API. This library uses OkHttp. Previous versions included support for Jetty's client, however, we've removed that due to instability of the Jetty client.

Note: Ensure that you have Jetty's ALPN JAR (OkHttp requires it) in your boot classpath. See here for more information. This is required until Java 9 is released, as Java 8 does not have native support for HTTP/2.

2. https://github.com/linkedkeeper/apns-http2

A Java library for sending notifications via APNS using Apple's new HTTP/2 API. This library uses Netty4.

Note: This is required until Java 7 is released.

总结

从基于 Socket 的 notnoop 和 dbay 实现 APNs 推送,到基于 HTTP2 的 linkedkeeper 的 APNs 推送,不断深入了解 APNs 和 HTTP2 等,以及基于 Netty4 构建 APNs-HTTP2 客户端,已在京东 POP 实现业务化运行,从性能和即时性都有明显的提升。


Reference:

https://www.processon.com/view/link/5880a71fe4b049e795b442bc


转载请并标注: “本文转载自 linkedkeeper.com (文/张松然)”