架构师

DockerCon 上重点发布的“Moby”,在国内技术圈引起了不小的震动。厂商们普遍为 Docker 公司释放 Moby 项目拍手称赞,一线技术圈(GitHub,HN)却掀起了一阵 “口诛笔伐” 的小波澜。容器圈内一位多年的 Docker 项目贡献者甚至直接感慨:“从此社区再无 Docker !”这件事,究竟该如何解读与看待?
展开查看详情

1.1

2.InfoQ 架构师 2017 年 6 月 卷首语 If only HP knew what HP knows 杨旸 AI 无疑是 2017 年最火的热词之一。 2015 年 ILSVR 比赛里,微软用 30 多层的神经网络将图像分类的错误 率降至 4.94%,首次击败人类(5.1%),从某种程度上证明了机器不仅是“决 策支持系统”, 它们本身就可以提供更可靠的答案。2017 年 Kaggle 的肺 癌检测进一步展示了人类经验(医生)和软件(开发者)之间崭新的合作 方式:两者分别通过历史记录和模型,将各自的修为注入机器大脑,用多 种数据加快其进化,让一个集人类专家多年智慧的绝世高手在数日内诞生。 这些成功,将人们对 AI 和数据的关注提升到前所未有的高度。 惠普的一位前 CEO 曾说 :“If only HP knew what HP knows,it would be three times more profitable" ( 如果惠普真的掌握它实际拥 有的信息 , 盈利将比现在高三倍)。AI 正在带来这样的可能性。今天它 还只能做 X->Y 的映射,在不远的未来,也许真的能被快速训练成集万众 之长,晓天文地理,服务于普罗众生,能传续万代的超级大脑,而且绝对 不会出现“不给冰淇淋就不上补习班”的抵触情绪。 人类早已习惯了被自己的创新所取代:蒸汽机取代了拉车夫,机械取 代了手工工人,互联网取代了不少唱片公司、零售、纸媒。当 BOSS“资本” 每次面对这些机遇时,它的选择自始至终毫无悬念。客观上也解放着人类: 2

3. 卷首语 多少人愿意回到这些技术革命之前的时代呢? 回到 If-HP-Knew-What-HP-Knows,我们仍然面临着同样的课题。数 据仍然没有被充分利用,而且数据湖面临变成数据沼泽的危险。数据工程 师们分属不同项目组,服务于不同部门,做着类似的事:数据加载、清 洗、映射、打标签、聚合和计算出新的数据集,再用于各种推荐、挖掘或 AI。湖越来越满,集群越来越忙。而数据用户们不知道有哪些可靠的数据, 对陌生的不敢用,只好让自己的工程师加工,最终形成越来越多的数据孤 岛。 因此,在利用 AI 的同时,通过数据治理,让更多用户更全面地利用 数据,也是数据工作重点之一。比如梳理所有数据用户的需求,共同定义 数据标准和 CDE,将不同等级的数据商品化,供所有用户超市般地搜索、 选用和分享等等。 AI 大牛 Andrew Ng 做过一个比喻:如果机器学习是一艘火箭,那么 神经网络是引擎,数据是燃料,缺一不可。所以,在追 AI 的同时,记得 先搞定数据。 3

4. CONTENTS / 目录 热点 | Hot “从此社区再无 Docker ?” 那 “Moby” 又是什么? Kotlin 成为正式的 Android 编程语言 推荐文章 | Article 如何构建一套高可用的移动消息推送平台? 兼顾稳定和性能,58 大数据平台的技术演进与实践 阿里巴巴为什么要选择星际争霸作为 AI 算法研究环境? 观点 | Opinion 人工智能与软件架构 架构师 2017 年 6 月刊 本期主编 杜小芳 提供反馈 feedback@cn.infoq.com 流程编辑 丁晓昀 商务合作 sales@cn.infoq.com 发行人 霍泰稳 内容合作 editors@cn.infoq.com 4

5. 专题 热点| Topic | Hot “从此社区再无 Docker ?”那 “Moby” 又是什么? 作者 张磊 DockerCon 上重点发布的“Moby”,在国内技术圈引起了不小的震 动。厂商们普遍为 Docker 公司释放 Moby 项目拍手称赞,一线技术圈 (GitHub,HN)却掀起了一阵 “口诛笔伐” 的小波澜。容器圈内一位多 年的 Docker 项目贡献者甚至直接感慨:“从此社区再无 Docker !”这 件事,究竟该如何解读与看待? 谁也没料到的是,这次 DockerCon 上重点发布的 “Moby”,却在国 内这个以往不太跟国际接轨的圈子里引起了不小的震动。有趣的是,在 厂商们普遍为 Docker 公司释放 Moby 项目拍手称赞的同时,一线技术圈 (GitHub,HN)里却掀起了一阵 “口诛笔伐” 的小波澜。知乎该话题下 的一篇高赞匿名回答 ( 倒不如说是吐槽): https://www.zhihu.com/question/58805021/answer/159490433 5

6.InfoQ 架构师 2017 年 6 月 更是一时间取代了 DockerCon 本身成为了技术圈里争相讨论热点。 如此矛盾的表现,为这个本来就不太明朗的商业事件更增添了难以理解的 色彩。 到底发生了什么? “Docker,还是Moby?” 一向善于创造 fancy 的新名词的 Docker 公司,这还是头一次因为 一个新名字陷入尴尬的局面。而这次“危机”的源头,则是 Docker 公司 在并未对外释放明显信号的情况下,直接将 Github 上原隶属于 Docker 组织的 Docker 项目,直接 transfer 到了一个新的、名叫 Moby 的组织 下,并将其重命名为 Moby 项目。这此并无征兆的突然“改名”和“移交”, 正是本次 DockerCon 2017 上宣布的唯一一个大新闻。对此,容器圈内一 位多年的 Docker 项目贡献者直接感慨:“从此社区再无 Docker !” 说的一点没错。 究竟如何解读? 实际上,知乎回答的总结部分已经一针见血了: “Docker 公司直接把原 Docker 项目改名成了 Moby,是为了将之前 数年里构建出来的庞大的粉丝团体和 Google 搜索内容(Google search footprint)全部转移到 Docker 公司的商业产品上。” 在开源项目的运营是一个系统工程,但是整个过程中确有这么三个入 口是维护者需要苦心经营的重中之重: • Google 搜索结果; • Stackoverflow 上的问答内容; • Google Group 的讨论话题。 一个开源项目要想成功,一个重要的手段是在目标受众群体中赢得广 泛的用户基础,然后以此为基础构建外围生态。说的更现实一点,就是必 须要有热度和知名度。只有不断被受众群体提及并且关注的开源项目才有 6

7. 专题 热点| Topic | Hot 机会生存下去。这个现实也是开源圈子里向来”只认第一,不认第二“的 一个重要原因。 Docker 项目可以说是近几年最成功的后端开源项目之一,更难能可 贵的是,Docker 项目的成功并非像 Tensorflow、Kubernetes 这种含着 金钥匙出生的名门之秀,也非像 Etcd 这种定位于核心依赖并且容易被纳 入已有体系的独立模块。Docker 项目本身是带着颠覆性的,是要求你推 倒重来的(这不同于 Etcd)。但它本身又没有什么黑科技成分,没有太 高的进入门槛(这又不同于 TF,k8s),它全部构建于现有的操作系统技 术之上,却几乎单靠着 Idea 本身就取得了巨大的成功。这种项目,短时 间内不可能再出现下一个了,因为它的出现,几乎就意味着一个新产业的 诞生。 我们一般能够从 star 数估计出一个开源项目背后的生态,但是 Docker 项目背后的生态和群体规模,恐怕要比 4 万个 star 所能代表的 意义要大得多。 成千上万的用户,支撑起国内外无数家创业公司的庞大产业,数十亿 美元的风险资本,庞大的关联产业群体,甚至说它重塑了云计算市场也不 为过。 “Docker” 这个 Google footprint 背后的价值,委实难以估量。 对于 “Docker 到 Moby” 的转变,业界可谓众说纷纭,但最为困扰 用户和开发者的,无外乎两个问题: 如果是为了更好的模块化 Docker 项目(所谓的“乐高”式的架构), 那么为什么不能保留 Docker 项目的名字,然后再拆分? 如果是为了更好的区分 “project 和 product” ,那么为什么不能 给 Docker 公司的商业产品取个更好听的企业版名字(比如 Moby),而 保留 Docker 作为一个独立的开源项目? 最后的结果是令人遗憾的,甚至都没在社区进行公开讨论的余地。 所 以, 无 论 Solomon 如 何 辩 解, 无 论 是 手 绘 架 构 图 还 是 连 发 Hacknews,这一次 “Docker 到 Moby” 的转变过程,其背后透露出来的 用意无比清晰而坚定: 7

8.InfoQ 架构师 2017 年 6 月 “从今往后,Docker 关键字的价值只属于 Docker 公司。” 这种略显霸道的做法,正是一线开发者层面矛盾被激发的源头。 但这冰冻三尺,又非一日之寒。 早在 Docker 项目刚受到广泛关注不久,就已经有很多意见指出 Docker 这个名字既是公司名字,又是开源项目的名字,而且很快又成了 公司商业产品的名字,这本身就是很大的隐患。但 Docker 公司并没有采 取什么措施,反而更加关注和遏制任何第三方试图滥用 Docker 关键字的 苗头。 Docker 公司有意无意之间制造的这个模糊地带,在 Docker 项目高 速发展的两年里,将开源社区用心经营出来的庞大受众和用户群体,同公 司未来商业产品的影响力和目标客户通过 “Docker” 关键字成功的统 一起来。然后,在 “Docker 到 Moby” 这个原本可以用来修正这个错 误的时间点上,Docker 公司又毫无征兆地、以迅雷不及掩耳之势完成了 Docker 项目的重命名。至此,“社区再无 Docker”,就成了这个无比成 功的项目最后的感慨。这也是 Github 上和 HN 上的开发者感觉被冒犯的 主要原因。 我们无法判断这是不是 Solomon 一个人的决定(笔者倾向于这应该 是董事会共同完成的战略决断,甚至有可能有传说中 VC 方面的压力), 但这件事情本身的发展过程,却同“善意的独裁者”这一形象不谋而合。 没有投票,没有征询,没有公开的文档和章程来遵守,Docker 这个开源 项目从诞生到“消失”,遵循着的都是不断颠覆人们预设的决定。我们甚 至很难被说服 Docker 是一个真正的开源项目。有时候,我们觉得它更像 一个商业产品的特定阶段,甚至,还带有几分传奇色彩,故意让我们看不 清,摸不透: “它崛起,它辉煌,它使命完成,它消逝不见”。 我们还有 Moby 困惑和焦虑,成为了 DockerCon 之后容器圈里的普遍情绪,尤其我 8

9. 专题 热点| Topic | Hot 们国内的 Docker 圈子比国外热度更高,产业也大,随之而来的失望也更 大。像往日熙熙攘攘的容器技术讨论群里,冷不丁有人问一句“Docker 改名了,我们怎么办?”,实在难有几个靠谱的回应。事实上,我们自己 的开源项目里也 vendor 了“github.com/docker/docker”。但是当讨论 到某个需要修改的 Docker 代码时,我们也很困惑:谁知道我们要改的部 分将来会拆到哪里去呢?是 Moby 下面,还是 Docker 下面?没人知道。 不过,至少有一点是可以肯定的,现在的 Moby 项目,依然会扮演着 Docker 容器圈里的重要角色。拆分之后,Moby 项目的独立性和健壮性也 能有更好的保证,社区里的开发者和维护者还可以专注于自己熟悉的模块, 而不是像现在一样拥挤在一个 Docker 项目上不可开交。 更为重要的是,厂商和一线开发者,对 “Docker 到 Moby 的转换” 可以说是完全不同的感受。对于 Google,CoreOS,RedHat,Mesos 等玩 家来说,一个拆分后的、不隶属于 Docker 公司的开源项目,明显增加了 更多合作而非竞争的机会。就拿 Google 来说,作为以往很容易被当靶子 的一方,动不动就 “碾压 Kubernetes” 的拙劣对比估计很难再现了, 一个真正开放的社区,在公开场合对同类项目的宽容、友善和协作才是主 流基调。 事 实 上,Moby 开 源 项 目 今 后 的 路 线, 在 工 业 界 已 有 范 例, 比 如 Cloud Foundry 项目。Pivotal Cloud Foundry(PCF)作为一款 PaaS 商 业产品,在全球范围内已经取得了巨大的成功,世界 500 强三分之二 已经成为了 P 家的优质客户。但如果好奇的话,你会发现 GitHub 上 并没有一个叫 CloudFoundry 的项目,而是以多个功能模块的方式散布 在 cloudfoundry 组织下面,比如 UAA,GoRouter 等等。然后,开源社 区用户可以通过一个叫 cf-release 的项目编译出来一套完整的社区版 CloudFoundry 供自己部署和使用。 Docker 公司目前的处理方法也是类似的。Docker 公司旗下两款产品 分别是 Docker-CE(社区免费版)和 Docker-EE(企业收费版),它们 都必须从 docker.com 官网下载下来使用(这是区分项目和产品的一个重 9

10.InfoQ 架构师 2017 年 6 月 要方式)。你并不会找到有一个开源项目叫 Docker-CE,但是你可以使用 Moby 组织下面的各个项目自己编译一个跟 Docker-CE 功能一样的软件出 来。不过,估计少有人会这么做吧。大多数人应该还是图个方便会下载使 用 Docker-CE,这也可以说是 Docker 公司的一个小手段:你都用了 CE 了,试一下 EE 又何妨呢。 LinuxKit 在 Moby 体系中,还有一个项目也值得关注,那就是 LinuxKit。 细心的人可能已经发现了,LinuxKit 的前生是 https://github.com/ docker/moby 项目(Docker 公司这玩儿名字是有多溜)。实际上,你如 果用过 Docker for Win 或者 for Mac 的话,就知道在这种非 Linux OS 场景下,Docker 其实是启动了一个叫 MobyLinuxVM 的虚拟机来跑 Docker 容器的,为了能够让这个额外的 VM 层足够轻量级,内核裁剪和 定制的工作就必不可少,这正是 LinuxKit 项目的来源。 比较有意思的是,DockerCon 上对 LinuxKit 的宣传都是在“安全” 两个字上。这难免有抓眼球的嫌疑,定制宿主操作系统确实能够降低宿主 机的被攻击面,但这并不改变容器本身在多租户环境中隔离性和共享内核 的问题本质。这种推广方式也确实一度引起了国内一些用户的误解。不过, 在使用了精简 OS 之后,用户在云上启动宿主机的时间就会大大减少,这 的确是实实在在的好处。 那么究竟应该怎么解读 LinuxKit 呢? 首先需要明确的是:LinuxKit 本身并不是一个精简操作系统,它是 用来编译出可运行的精简操作系统镜像(包括 kernel,disk.img,BIOS. iso 等)的工具,这一点区别,国内有些介绍文章也不经意混淆了。 其次是它的使用方式:用户首先需要使用 LinuxKit 工具来制作一个 精简操作系统,然后在 IaaS 上或者裸机上使用 KVM 等来使用这个系统 启动一个虚拟机来把这个操作系统运行起来。 需要注意的是,在制作的过程中,用户希望启动的 Docker 镜像也是 10

11. 专题 热点| Topic | Hot 要打包进去的。这样虚拟机启动后就会使用事先打包进的 Docker 镜像来 启动容器进程,这个过程由内置 containerd 和 runc 来完成。 所以,这个通过 LinuxKit 打包出来的 OS 镜像确实具备了“可移植 性”,但实际上还是要借助虚拟机来运行在 Mac 上或者 Windows 上等。 这其实跟 CoreOS 等各种各样的专门用来跑容器 OS distro 区别不大。 另外,通过 LinuxKit 生成的这个可运行 OS 镜像,同时打包了操作 系统和待运行的应用,这听起来是不是有点熟悉?没错。LinuxKit 正是 Unikernels 团队的作品,可以毫不隐晦的说,LinuxKit 实际上是一种 Unikernels 概念的更大众化的实现。毕竟,相比起徒手制作 Unikernels 镜像的过程,使用 LinuxKit 工具就要轻松多了。当然,这也意味着 LinuxKit 比 Unikernels 还是要重的,在实际测试中无论在资源使用上, 还是启动速度上两者都有明显差距。 总结成一句话:LinuxKit 所定义的是一种 Unikernels 风格的、专 门为运行容器任务而定制的 OS distro。 既然还是 OS distro,那么它的场景跟 CoreOS、RancherOS、RedHat Atomic 也是类似的。我也相信 Docker 用户应该没兴趣在裸机上大规 模使用 LinuxKit,虽然 “使用 KVM 启动一个虚拟机” 这项工作被 LinuxKit 接管了,但接下来 “徒手” 进行虚拟化管理的工作能胜任的 人恐怕就不多了。 但是在 LinuxKit 正在发力的公有云领域,OS distro 确实很有发挥 空间。在这里,用户并不需要关系下面的虚拟机部分,就比如已经支持的 GCE 吧,用户只需往 GCE 上传自己的 build 出来的 LinuxKit OS 镜像, 就可以在云上启动一个虚拟机来运行它,而打包进去的容器也随之启动了。 这就实现了宿主机层面的一致性,也是在解决了 “这份代码在我机器上 是可以跑” 的问题之后,“这个 Docker 镜像在我的机器上是可以跑” 这个新生问题的也终于得到了缓解。 当 然, 既 然 是 OS distro, 你 也 可 以 在 这 个 OS 镜 像 中 打 包 Kubernetes binary,或者 swarmd,这样就相当于直接拿这些机器当宿主 11

12.InfoQ 架构师 2017 年 6 月 机在 GCE 上来搭一个容器云了。 不 过 话 说 回 来, 任 何 一 种 OS distro 的 目 的 之 一, 都 是 要 制 造 Vendor Lock,LinuxKit 也不例外。对于公有云提供商来说,这不是个友 善的信号。想象一下,将来某一天 AWS 上的容器用户都不使用 AMI,而 是使用 LinuxKit 镜像来启动虚拟机来跑容器了,那就真尴尬了。所以, 如果真说 “降维打击”(事实上,我认为这个词开始被滥用了,只有它 的原创者 Frank Yu 的精彩演讲里形容 Docker 和 PaaS 的关系才是最形 象的一次比喻),LinuxKit “打击” 的是所有在操作系统层面试图针对 容器做文章(包括 distro,安全补丁,精简内核等等)的玩家,这个覆 盖面可不小。各家公有云,RedHat,CoreOS,Rancher 等大小公司恐怕都 在其中。从这个角度来看,LinuxKit 的推广难度,可想而知。 最后提一句,LinuxKit 是自己一个单独的 org,不属于 Moby,也不 属于 Docker,不绑定 SwarmKit,不内置 Docker Engine,其雄心壮志确 实略见一斑。正如一位 Kubernetes 项目贡献者的评论所说的那样:这家 伙不做 Unikernel 了,改做 Universal Kernel 了。 写在最后 当我们静下来重新审视 DockerCon 的这几项内容,不难发现,大多 数容器用户实际上并无需过分纠结 Docker 或者 Moby。免费的 Docker- CE 会一直存在,Moby 开源社区依然会活跃,而且模块化后,要 hack 还 变容易了,何乐而不为? 更何况,绝大多数用户都应该是在使用诸如 Kubernetes,DC/OS, SwarmKit 等上层编排工具的,在 containerd 都已经归属 CNCF 的今天, 下层 runtime 的风吹草动实在不应该浪费大家时间去做过多思考。我相 信,无论是原 Docker 项目的开发者,还是是心有不甘的匿名用户,最终 还是希望能安安静静的写代码,“Docker 还是 Moby” 的讨论其实并没 有太大意义。 唯一值得我们铭记的是,无论是过去还是现在,开源社区都并非 “世 12

13. 专题 热点| Topic | Hot 外桃源” 般的净土,它是现实商业市场在技术领域的延伸,它正在迅速 地吞噬着现代软件世界,改变着我们的竞争规则。这里既有激烈的厂商斗 争,也有友善的跨国协作,更有 Docker 公司这样的模式创新者来不断颠 覆我们的思想。 作为身处其中的技术人员,唯有时刻保持对技术心存敬畏,对客观事 实冷静思考,不盲从,不热捧,方才能有更坚实的立足之地。就如我们一 度难以抑制的 Docker 热潮,此刻确实需要降温。 作者介绍 张 磊,Hyper 项 目 成 员,Kubernetes 项 目 Project Manager 和 Feature Maintainer,CNCF 成员。 13

14.InfoQ 架构师 2017 年 6 月 Kotlin 成为正式的 Android 编程语言 作者 Abel Avram ,译者 薛命灯 在 Google I/O 2017 大会上,Google 正式宣布 Android Studio 3.0 完全支持 Kotlin,Kotlin 也将从 JetBrain 移交给独立的非盈利组织来进 行后续的开发。 Google 正式将 Kotlin 加入到 Android 的开发语言行列,其他为数不 多的语言还包括 Java 和 C++。关于为什么选择 Kotlin,Google 说“Kotlin 简洁、表现力强,而且具有类型安全和空值安全(null-safe)的特点, 很多 Android 开发者发现 Kotlin 可以让开发变得更快也更有趣”。另 一个很重要的原因是 Kotlin 可以与 Java 进行完整的互操作,并且也是 运行在 JVM 上。Kotlin 还能调用 C++ 和 Android 代码,因为它可以通过 external 标识符来支持 JNI。Kotlin 的源代码可以被编译成 JVM 的字节码, 也可以生成 JavaScript 代码。 14

15. 专题 热点| Topic | Hot 在过去,开发人员需要通过 Android Studio 的插件来使用 Kotlin, 而现在,他们可以在 Android Stuido 3.0 里直接使用 Kotlin,包括重构、 自动完成、lint、调试和其他操作。Android Studio 3.0 也是在 Google I/O 2017 大会上发布的,不过现在只有处于 canary 阶段的预览版本,可 能还需要几个月的时间才能发布正式版。 Kotlin 与 Java 之 间 的 互 操 作 性 是 它 被 广 泛 采 用 的 特 性 之 一。 Android 的 Java 代码可以被 Kotlin 调用,Kotlin 的代码也可以被 Java 调用,一个 Android 项目里可以包含 Java 文件和 Kotlin 文件,Java 文 件还可以被转成 Kotlin 文件。开发人员不一定要全面地学习 Kotlin,他 们可以继续使用 Java 开发,然后试试水,看看会不会喜欢上 Kotlin。对 于喜欢 Kotlin 简洁性的开发者来说,他们或许会更多地使用 Kotlin。 Kotlin 兼容 JDK 6 及以上版本,它可以运行在大多数 Android 版本上, 包括一些比较旧的版本。Android 同时也支持 Kotlin 的 coroutine,不过 因为这是一个实验性质的特性,所以 Google 并没有针对这个特性做出任 何承若,它完全取决于这门语言自身的发展。 Google 透露,他们正在与 JetBrain 展开合作,将 Kotlin 移交给第 三方的非盈利组织进行后续的开发。虽然说 Kotlin 是开源的,不过交给 这样的一个组织来看管,可以在一定程度上保证语言的未来不会落入某一 家的公司手里。这一举动也意味着 Google 将会介入 Kotlin 的推广工作, 这对于 Kotlin 来说是一个利好消息。 15

16.InfoQ 架构师 2017 年 6 月 如何构建一套高可用的移动消息推送平台? 作者 李晓清 董泽光 消息推送作为移动 APP 运营中的一项关键技术,已经被越来越广泛 的运用。本文追溯了推送技术的发展历史,剖析了其核心原理,并对推送 服务的关键技术进行深入剖析,围绕消息推送时产生的服务不稳定性,消 息丢失、延迟,接入复杂性,统计缺失等问题,提供了一整套平台级的高 可用消息推送解决方案。实践中,借助于该平台,不仅能提能显著提高消 息到达率,还能提高研发效率,并道出了移动开发基础设施的平台化架构 思路。 推送基础 移动互联网蓬勃发展的今天,大部分手机 APP 都提供了消息推送功 16

17. 推荐文章 | Article 能,如新闻客户端的热点新闻推荐,IM 工具的聊天消息提醒,电商产品 促销信息,企业应用的通知和审批流程等等。推送对于提高产品活跃度、 提高功能模块使用率、提升用户粘性、提升用户留存率起到了重要作用, 作为 APP 运营中一个关键的免费渠道,对消息推送的合理运用能有效促 进目标的实现。 推送最早诞生于 Email 中,用于提醒新的消息,而移动互联网时代 则更多的运用在了移动客户端程序。要获取服务器的数据,通常有两种方 式:第一种是客户端 PULL(拉)方式,即每隔一段时间去服务器获取是 否有数据;第二种是服务端 PUSH(推)方式,服务器在有数据的时候主 动发给客户端。 很显然,PULL 方案优点是简单但是实时性较差,我们也可以通过 提高查询频率来提高实时性,但这又会造电量、流量的消耗过高,反之 PUSH 方案基于 TCP 长连接方式实现,消息实时性好,但是由于要保持 APP 客户端和服务端的长连接心跳,也会带来额外的电量和流量消耗。因 此在整体架构设计中需要折中平衡,目前主流的推送实现方式都是基于 PUSH 的方案。 移动推送的三种实现方式 目前移动推送技术实现方式主要有以下三种。 轮询方式(PULL) 客户端和服务器定期的建立连接,通过消息队列等方式来查询是否有 新的消息,需要控制连接和查询的频率,频率不能过慢或过快,过慢会导 致部分消息更新不及时,过快会消耗更多的资源(流量、电量等),对用 户体验有较大伤害。 短信推送方式(SMS PUSH) 通过短信发送推送消息,并在客户端植入短信拦截模块(主要针对 Android 平台),可以实现对短信进行拦截并提取其中的内容转发给 App 17

18.InfoQ 架构师 2017 年 6 月 应用处理,这个方案借助于运营商的短消息,能够保证最好的实时性和到 达率,但此方案对于成本要求较高,开发者需要为每一条 SMS 支付费用。 长连接方式(PUSH) 移动 Push 推送基于 TCP 长连接实现, 客户端主动和服务器建立 TCP 长连接之后 , 客户端定期向服务器发送心跳包用于保持连接 , 有消 息的时候 , 服务器直接通过这个已经建立好的 TCP 连接通知客户端。尽 管长连接也会造成一定的开销,对于轮询和 SMS 方案的硬伤来说,目前 已经是最优的方式,而且通过良好的设计,可以将损耗降至最低。不过, 随着客户端数量和消息并发量的上升,对于消息服务器的性能和稳定性要 求提出了非常大的考验。因此,就难度而言,此方式代价最高。 推送解决方案 基于 TCP 长连接的方式是主流的推送方式,基于该推送方式逐步发 展出系统级、应用级一系列的推送解决方案。 系统级方案 iOS 平台(APNs) iOS 在系统层面与苹果 APNs(Apple Push Notification service) 服务器建立连接,应用通过观察者模式向 ioS 系统注册关注的消息,系 统收到 APNs Server 消息后转发到相应的应用程序,整个过程很清晰, 并且所有 APP 都共用同一个系统级的连接,减少了系统开销,虽然 APNs 能无障碍的访问,但实际使用过程中,发现延时和丢消息的情况偶有发生。 Android 平台(C2DM) Android 的 C2DM(Android Cloud to Device Messaging)采取与 iOS 类似的机制,都是由系统层面来支持消息推送,但是由于 Google 的 服务在国内不能稳定的访问,此方案对于中国用户来说基本是无法使用的。 除了 Google 官方提供的方案,中国众多的手机厂商在其定制的系统 中也内置了推送功能,如小米、华为等。 18

19. 推荐文章 | Article 应用级方案 1. 第三方推送服务 鉴于 Android 平台 C2DM 推送的不可用性,国内涌现出大量的第三 方推送服务提供商,采用第三方推送服务的系统流程如下图: 图 1:消息推送流程 目前应用最为广泛的第三方推送服务提供商包括个推、极光、友盟、 小米、华为、BAT 等,绝大部分 APP 都会优先考虑采用第三方推送服务。 2. 自建推送服务 第三方服务在开发成本和消息到达率上表现都不错,但所有信息会经 过第三方服务器,对于信息敏感类 APP 而言,有必要考虑自建一套消息 推送服务,能最大化保证安全,但对于自建推送服务,如果从零开始来做 需要解决几个难点: • 第一,移动推送服务器对 App 客户端海量长连接的维护管理。 • 第二,App 客户端如何保证 Push Service 常驻,对于 Android 我们可以通过发现 push service 不存在可以定时拉起的方式。 • 第三,通信协议的制定,我们可以采用开源的 XMPP 方式实现, 也可以自定义协议,不管哪种方式我们都要保证消息传送的到达 率的准确性。第四,在移动互联网网络环境下,经常出现弱网环 19

20.InfoQ 架构师 2017 年 6 月 境,特别是 2G、3G 等网络不稳定的情况下,如果保证消息在弱 网环境下不重、不丢也是一个挑战。 存在问题 无论是第三方推送服务,还是自建推送服务,在实际的使用过程中, 发现都存在以下问题: • 应用服务端与推送服务强耦合。当推送服务不可用时,造成整个 业务系统无法推送,甚无法正常工作。 • 缺乏 ACK 机制。推送的过程是异步的,从应用服务端发送到推 送服务时,可以得知发送是否成功,但是从第三方推送服务下发 到 APP 时,无法得知客户端是否接收到。iOS 平台中,从推送服 务发送到苹果 APNs 服务时,同样无法确定 APNs 是否收到。同 时,第三方推送服务通常使用共享的推送通道,受其他推送方的 影响,可能造成消息的延迟和丢失。 • 服务会被杀死。尤其在 Android 平台上,后台推送 service 会 被各种主动或者被动原因 kill 掉,导致消息丢失。 • 缺乏消息的持久化。对于推送服务而言,消息推送是来一条推一 条,无法追溯历史消息和消息状态。 • 缺乏重传机制。整个推送过程涉及多个环节,当其中某个环节出 现问题,造成客户端接收不到推送的消息时,就导致消息丢失, 再无法接收到。 • 客户端接入逻辑复杂。每接入一个新的 APP,都要进行重复的接 入工作,接入逻辑完全一致,代码无法复用,需要在不同项目中 拷贝。 • 客户端与推送服务的 SDK 强耦合。客户端使用推送服务的接口, 而各推送服务提供的接口不统一,如果需要替换推送服务,那么 接入部分代码需完全重写。 • 缺乏数据监控和统计。每个应用每天推送了多少消息,成功到达 20

21. 推荐文章 | Article app 多少,失败多少,目前均没有统计。 解决之道 为了解决以上问题,我们考虑基于第三方消息推送服务构建一套移动 消息推送中间件平台,该消息平台采用了低耦合的分层架构设计(如图 2 所示),分为三层:接入层、传输层和应用层。其中接入层是业务方调用 的入口,我们采用异步消息队列的方式提供了较高的业务系统发送消息的 速度,并且具备了消息缓冲功能,即使高峰期的海量消息推送对整个平台 冲击较少,保护了推送系统; 传输层会从接入层接收消息并进行解析,对推送消息进行合法性检查 校验,如果消息不合法直接丢弃,同时将合法的消息进行协议转换并发送 到对应的第三方推送平台;应用层主要是提供统一的 SDK 供业务使用, 封装适配第三方推送平台的 SDK 接口到统一的接口 SDK 中,这样业务 APP 使用方只关注统一封装的 SDK 即可实现业务消息的操作,而不需要 考虑各种滤重、校验等通用操作。主要功能包括: • 屏蔽推送接口,实现业务与推送服务解耦,提供一套通用的客户 端 SDK,简化客户端接入。 • 实现多点接入,可同时接入多套推送服务,根据历史推送成功率 动态选择最优推送路径,当一条路径失效可选择备用路径进行推 送,保证消息推送万无一失。 • 引入消息持久化机制,方便追溯和统计。 • 引入消息的 ACK 机制和重传机制,提高消息的到达率。 • 实现数据监控和统计机制,提供相关数据的统计分析,和报警预 警功能。 • 提供 web 管理后台,便于进行 APP 设置、推送设置、查看数据 报表,提高系统维护的工作效率。 整个系统设计由三部分组成:移动推送平台、客户端 SDK、应用管理 界面(第三方推送服务和自建推送服务统称为推送服务)。 21

22.InfoQ 架构师 2017 年 6 月 移动推送平台提供统一的服务,对于应用层屏蔽推送服务接口,且实 现推送服务可动态轮替。推送平台将接收到的消息持久化到数据库中,方 便进行消息推送失败后的重发,以及后续数据的统计分析。 客户端 SDK 对 App 提供统一的使用接口,屏蔽推送服务 SDK 使用 细节,且实现多种推送 SDK 可替换,隐藏 SDK 复杂的接入过程,方便使用。 图 2:系统架构 应用管理系统面向 App 开发人员,实现应用申请,推送服务配置, 消息查询与管理,数据统计与分析。 主要流程 消息推送涉及的主要模块是消息推送平台和客户端 SDK,主要流程如 下图所示: 正常情况下,消息推送过程如下: • 系统接收到业务方的推送请求后,首先进行权限的验证,这包括 22

23. 推荐文章 | Article 应用 appKey 的验证、接口参数的验证、黑名单验证等。 • 验证不通过,返回错误信息;验证通过后,为此条消息分配一个 唯一 id(uuid),将消息内容持久化到数据库中,此时消息的状 态为待发送。 • 消息进入推送队列中,将之后推送接口请求的响应返回给业务 方。 • 推送队列的消费者从队列中取出待发送的消息,标记该条消息的 状态为发送中,然后调用第三方推送服务接口进行发送。 • 如果调用成功,那么标记该消息的状态为发送成功客户端未收 到。 • 客户端 SDK 在收到推送后,回调服务端接口,发送收到推送的回 执;服务端收到客户端回执后,标记消息状态为发送成功客户端 已收到。 图 3:消息推送中间件核心流程 对于推送过程中可能出现的异常情况,总结如下: • 在调用第三方推送服务接口时,可能出现调用失败的情况;此时 23

24.InfoQ 架构师 2017 年 6 月 需要标记消息的状态为发送失败,留待重发。 • 在调用第三方推送服务接口成功后、第三方推送服务在下发至客 户端的过程中,可能由于某种原因,造成客户端无法收到消息; 此时消息的状态为发送成功客户端未收到,对于这种状态,需要 重发。 • 客户端在收到推送的消息后、向服务端发送 ACK 回执时,可能由 于网络环境的问题,造成服务端没有收到客户端发送的回执,此 时消息的状态为发送成功客户端未收到,对于这种状态,需要重 发。 • 消息在重发 N 次(N 次可配置)、仍然没有进入发送成功客户端 已收到的状态,那么将不再进行自动重发;管理界面将提供手动 重发消息的操作入口,如有需要,可以手动再进行重发。监控平 台对于一直重复不成功的消息会报警通知操作人员,这样操作人 员可以及时通过手动方式处理。 根据消息发送流程,可以得到消息在生命周期中状态的变迁如下图: 图 4:消息状态机 24

25. 推荐文章 | Article 重发机制 消息重发主要存在三种场景:系统启动时,查询所有的发送失败或发 送成功未收到客户端回执的消息,加载到推送队列重发;系统运行时,后 台线程定时查询需要重发的消息,进入推送队列;手动触发时,直接将消 息加入推送队列。 由于消息推送中间件服务通常要求高可用,为分布式部署,消息重发 必须保证在单一节点执行,且保证只发送一次。需采用分布式锁的方式, 保证重发只发一次,主流实现方式有三种: • ZooKeeper:通过竞争创建临时节点的方式获取锁。 • Redis:Redlock 是 Redis 作者的提出了一种分布式锁的算法, 基于 Redis 实现,该算法实现了一种更安全、可靠的分布式锁管 理。 • 数据库:如使用 MySQL 的 GET_LOCK 函数。 对于每种锁机制的特点本文不详细介绍,根据实际应用需要任选一种 即可。 由于 iOS 平台和 Android 平台的差异,消息重发需要考虑平台差异 性。 使用第三方推送时,如果 iOS 应用在前台运行,那么将通过第三方 推送维护的长连接,以透传的方式直接下发到 APP,称为应用内消息;而 当 APP 在后台时,则第三方推送将消息推送到 APNs,由 APNs 推送到 APP,称为 APNs 通知。当通过 APNs 推送时,手机在收到消息后将在顶 部的通知栏出现相关推送内容,这一行为是系统级别的,APP 无法控制。 可能会出现这一问题:当 APP 在后台或者手机锁屏的情况下,如果服务 端重发了消息,手机的通知栏将出现多条通知。 因此,考虑当 APP 在后台时,针对 iOS 平台的消息不再进行重发; 只有当 APP 进入前台,才重新进行重发。APP 的活动状态通过第三方推 送服务的 api 可以获取到。 25

26.InfoQ 架构师 2017 年 6 月 Android 平台不存在该问题。 由于消息重发可能会造成客户端收到重复消息,需要在客户端进行消 息去重。服务端为每一条消息分配了一个唯一 id,重发时唯一 id 不变。 客户端需要保存收到的每一条消息,在接收到新消息时首先根据唯一 id 判断是否已经收到了这条消息,如收到则不响应。客户端保存消息可以采 用 sqlite 数据库。 安全和控制 客户端 SDK 与服务端的通信过程使用 appKey 和 appSecret 进行权 限控制。appKey 是服务端为每个 app 分配的唯一标识,appSecret 是服 务端为每个 app 分配的秘钥。 客户端 SDK 在请求服务端 HTTP 接口时,会将 appKey+appSecret 做一次签名,将签名值作为签名 sign 参数,与其他请求参数(业务 参 数 +appKey) 一 同 传 到 服 务 端; 服 务 端 拿 到 请 求 参 数 后, 也 先 用 appKey+appSecret 做一次签名,比较和客户端传来的 sign 参数是否一 致,从而完成权限验证过程。为了能够实现灵活控制推送与否,可实现黑 名单管理的功能。处于黑名单内的 app 客户端不再进行消息的推送。黑 名单控制的粒度到账号级别,也可以根据实际业务需要进行分组管理。 在某些业务场景中,需要对消息进行过滤,分析,做出相应的处理甚 至预警,借助于消息推送平台,都能方便的实现。 SDK 设计 客户端 SDK 是基于推送服务的 SDK 封装实现,对外提供统一的使用 接口。SDK 的使用者不再关注具体使用了哪些第三方推送、推送服务的接 入细节。实现与推送服务的充分解耦,降低开发和使用成本。 由于 iOS 和 Android 平台的差异性,在客户端 SDK 的封装上存在 差异,下面分别介绍两个平台的 SDK 封装方式。 iOS 平台 26

27. 推荐文章 | Article SDK 提供启动和停止的方法;同时定义一个 protocol,包含 SDK 提 供的接口。SDK 在收到消息或出现错误时将会回调 protocol 中的接口。 @protocol HAPushSDKDelegate <NSObject> @optional - (void)ha_didReceiveMessage:(NSString *)message; - (void)ha_occurError:(NSError *)error; @end @interface HAPushSDK : NSObject + (void)ha_startSDKWithAppKey:(NSString *)appKey appSecret:(NSString *)appSecret appDelegate:(id)appDelegate; + (void)ha_stopSDK; @end 由于推送的接入涉及 AppDelegate 的生命周期方法,为避免 SDK 使 用者关注这些繁琐的细节,SDK 使用 Aspects 的方式,将推送时相应的 处理函数 hook 到 AppDelegate 的生命周期方法上。 + (void)ha_startSDKWithAppId:(NSString *)appId appSecret:(NSString *)appSecret appDelegate:(id)appDelegate { …… __weak __typeof(self)weakSelf = self; [appDelegate aspect_hookSelector:@selector(applica tion:didRegisterForRemoteNotificationsWithDeviceToken:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo){ NSLog(@"\n>>> application:didRegisterForRemoteNotificat ionsWithDeviceToken: <<< aspect invoke\n"); NSArray *arguments = aspectInfo.arguments; NSData *deviceToken = arguments[1]; NSString *token = [[deviceToken description] stringByT rimmingCharactersInSet:[NSCharacterSet characterSetWithCharact 27

28.InfoQ 架构师 2017 年 6 月 ersInString:@"<>"]]; token = [token stringByReplacingOccurrencesOfString:@" " withString:@""]; NSLog(@"\n>>>[DeviceToken Success]:%@\n\n", token); deviceToken = token; [GeTuiSdk registerDeviceToken:token]; //以个推为例 } error:nil]; …… } Android 平台 在 Android 中使用 Receiver 组件来接收收到的消息。一个基本的 配置如下所示: <receiver android:name="com.xxx.message.receiver.HAPushReceiver" android:exported="false" > <intent-filter> <action android:name="com.***.sdk.action.[PUSH_APPID] " /> </intent-filter> </receiver> 流程如下:当推送服务的 SDK 在接收到推送过来的消息后,将发送 广播,这个广播的用 intent-filter 标识,当应用中的 Receiver 代码 注册了这个 intent-filter,就可以接收到广播,并进行后续处理。 系统管理 消息后台管理系统提供应用申请、应用服务配置、推送服务配置、消 息查询与管理等功能。 1. 应用申请 填写应用名、应用描述等信息后,生成该应用唯一的 appKey 和 appSecret。 28

29. 推荐文章 | Article 2. 应用服务配置 为应用选择要使用的移动端通用服务,可供选择的有推送、反馈、版 本发布。 图 5:后台管理示意图 3. 推送服务配置 为应用配置推送服务,可供选择个推、极光等;以及推送时使用的优 先级顺序。 4. 消息查询与管理 查看应用所发出的消息,包括消息所属应用、所属账号、消息的状态、 最终发送成功的第三方渠道、消息的来源、发送者 ip 等信息 5. 数据统计 通过分析 message 表中的各消息的状态,可统计各应用消息的发送 成功率和到达率,以及哪个第三方推送的更优,方便选择。同时,提供每 日、每周、每月推送消息量的统计,并提供统计图表。 高可用、高性能、高稳定性 消息推送平台通过无状态设计、统一存储、冗余部署方式保证了高可 用,对应的状态数据统一存储到 MySQL、Redis 中保证各个无状态实例共 29