微信自用的跨平台移动端IM网络层封装库Mars详解,自己可以写IM app了

1、前言

关于微信内部正在使用的网络层封装库Mars(参见《微信Mars:微信内部正在使用的网络层封装库,即将开源》),于2016年12月28日正式公开源码(源码地址:https://github.com/Tencent/mars,也可从本文文末的附件下载之,Android版演示程序可以从文末的附件中下载)。

之前无论是微信团队还是手机QQ团队,都以腾讯公司的名义在Github开源了数个工程,但这些工程所受的关注度远不及Mars。之所以Mars广受关注的原因,其实搞移动端IM或推送技术的开发者同行都明白,因为移动网络实在太不可靠、太复杂,以至于写出一个能用于大规模用户环境的稳定、省流量、省电、数据传输流畅、弱网络健壮、后台自动保活等技术指标的IM或推送是相当困难的。

更为重要的原因是毕竟微信Mars经过微信团队多年积累并经过海量用户的测试和使用,是经受的住各种复杂移动端网络环境、各种乱七八糟型号智能手机的真实考验的。若Mars开源,必将为IM及相关技术应用领域的同行带来很多有价值的实践成果,毕竟微信的体量和应用规模决定了技术的高度,确实是值得同行学习和关注。

之前的文章,比如《微信移动端应对弱网络情况的探索和实践PPT》、《微信Mars:微信内部正在使用的网络层封装库,即将开源》,也都或多或少对Mars进行了初步介绍,但微信Mars到底是个啥玩意,它能解决啥问题?

我们简要的概括一下,微信Mars解决了如下问题:

[1] 提供长连、短连两种网络通道;

[2] 常规的网络能力,例如 DNS 防劫持、动态 IP 下发、就近接入、容灾恢复等;

[3] 贴合移动互联网的网络层解决方案;

[4] 贴合移动终端的平台特性:前后台、活跃态、休眠、省电、省流量等。

以上特点,还不尽于概括微信Mars的技术特征,建议对C++熟悉的IM或推送技术同行可以直接去看看Mars源码。

那么微信Mars到底有什么用呢?毫无疑问,微信Mars存在的前提就是为了更好的服务微信这个超级IM而存在,最适合干的活就是开发移动端IM了,当然由于提炼的很好,相信移动端推送技术等都是可以使用微信Mars作为网络层lib来使用,从而以微信的成果为起点开发出拥有更加优秀网络体验的移动端富网络应用。

好了,言归正传,本文正文内容引用了微信开发团队的资料,请继续往下阅读。(本文同步发布于:http://www.52im.net/thread-684-1-1.html)

2、关联文章

《移动端IM实践:实现Android版微信的智能心跳机制》

《微信Mars:微信内部正在使用的网络层封装库,即将开源》

《微信移动端应对弱网络情况的探索和实践PPT [附件下载]》

《微信异步化改造实践:8亿月活、单机千万连接背后的后台解决方案》

3、微信Mars起源

2012 年中,微信支持包括 Android、iOS、Symbian 三个平台。但在各个平台上,微信客户端没有任何统一的基础模块。2012 年的微信正处于高速发展时期,各平台的迭代速度不一、使用的编程语言各异,后台架构也处在不断探索的过程中。多种因素使得各个平台基础模块的实现出现了差异,导致出现多次需要服务器做兼容的善后工作。网络作为微信的基础,重要性不言而喻。任何网络实现的 bug 都可能导致重大事故。例如微信的容灾实现,如果因为版本的实现差异,导致某些版本上无法进行容灾恢复,将会严重的影响用户体验,甚至造成用户的流失。我们亟需一套统一的网络基础库,为微信的高速发展保驾护航。

恰好,这个时候塞班渐入日暮,微信对塞班的支持也逐渐减弱。老大从塞班组抽调人力,组成一个三人小 team 的初始团队,开始着手做通用的基础组件。这个基础组件最初就定位为:跨平台、跨业务的基础组件。现在看,这个组件除了解决了已有问题,还给微信的高速发展带来了很多优势,例如:

基础组件方便了开展专项的网络基础研究与优化。

基础组件为多平台的快速实现提供了有力的支持。

经过四年多的发展,跨平台的基础组件已经包含了网络组件、日志组件在内的多个组件。回头看,这是一条开荒路。

4、微信Mars设计原则

在基础模块的开发中,设计尤为重要。在设计上,微信基础组件以跨平台、跨业务为前提,遵从高可用,高性能,负载均衡的设计原则。

可用是一个即时通讯类 App 的立身之本。高可用又体现在多个层面上:网络的可用性、 App 的可用性、系统的可用性等。

网络的可用性:

移动互联网有着丢包率高、带宽受限、延迟波动、第三方影响等特点,使得网络的可用性,尤其是弱网络下的可用性变得尤为关键。Mars 的 STN 组件作为基于 socket 层的网络解决方案,在很多细节设计上会充分考虑弱网络下的可用性。

App 的可用性:

App 的可用性包含稳定性、运行性能等多个方面。文章高性能日志模块 xlog 描述了 xlog 在不影响 App 运行性能的前提下进行的大量设计思考。

系统的可用性:

除了考虑正常的使用场景,APP的设计还需要从整个系统的角度进行设计思考。例如在容灾设计上,Mars 不仅使用了服务器容灾方案,也设计了客户端的本地容灾。当部分服务器出灾时,目前微信可以做到,15min 内把95%以上的用户转移到可用服务器上。

保障高可用并不代表可以牺牲性能,对于一个用户使用最频繁的应用,反而更要对使用的资源精打细算。例如在 Mars 信令传输超时设计中,多级超时的设计充分的考虑了可用性与高性能之间的平衡取舍。

微信自用的跨平台移动端IM网络层封装库Mars详解,自己可以写IM app了

在这四年,我学到最多的就是简单和平衡。 把方案做的尽可能简单,这样才不容易出错。设计方案时大多数时候都不可能满足所有想达到的条件,这个时候就需要去平衡各个因素。在组件中一个很好的例子就是长连接的连接频率(具体实现见longlink_connect_monitor.cc),这个连接频率就是综合耗电量,流量,网络高可用,用户行为等因素进行综合考虑的。

5、Mars 的发展历程

5.1 阶段1:让微信跑起来

微信自用的跨平台移动端IM网络层封装库Mars详解,自己可以写IM app了

voidStartTask(...);

intOnTaskEnd(...);

voidOnPush(...);

boolReq2Buf(...);

intBuf2Resp(...);

boolMakeSureAuthed();

在线程模型的选择上,最早使用的是多线程模型。当需要异步做一个工作,就起一个线程。多线程势必少不了锁。但当灰度几次之后发现,想要规避死锁的四个必要条件并没有想象中的那么容易。用户使用场景复杂,客户端的时序、状态的影响因素多,例如网络切换事件、前后台事件、定时器事件、网络事件、任务事件等,导致了不少的死锁现象和对象析构时序错乱导致的内存非法访问问题。

这时,我们开始思考,多线程确实有它的优点:可以并发甚至并行提高运行速度。但是对于网络模块来说,性能瓶颈主要是在网络耗时上,并不在于本地程序执行速度上。那为何不把大部分程序执行改成串行的,这样就不会存在多线程临界区的问题,无锁自然就不会死锁。

微信自用的跨平台移动端IM网络层封装库Mars详解,自己可以写IM app了

5.2 阶段2:修炼内功

在多次的灰度验证、数据比对下,微信各平台的网络逻辑顺利的过渡到了统一基础组件。为了有效的验证组件的效果,我们开发了 smc 的统计监控组件,开始关注网络的各项指标,进行网络基础研究与优化,尤其是关注移动网络的特征。

微信自用的跨平台移动端IM网络层封装库Mars详解,自己可以写IM app了

微信自用的跨平台移动端IM网络层封装库Mars详解,自己可以写IM app了

移动特性优化:微信的使用场景大部分是在手机端进行使用,在组件的设计过程中,我们也会研究移动设备的特性,并进行结合优化。例如,结合移动设备的无线电资源控制器(RRC)的状态切换,对一些性能要求特别特别敏感的请求,进行提前**的优化处理等。

5.3 阶段3:“抓妖记”

基础组件全量上线微信后,以微信的用户量,当然也会遇到各种各样的“妖”。例如,写网络程序躲不开运营商。印象比较深刻的某地的用户反馈连接 WiFi 时,微信不可用,后来 tcpdump 发现,当包的大小超过一定大小后就发不出去。解决方案:在 WiFi 网络下强制把 MSS 改为1400(代码见unix_socket.cc)。

做移动客户端更避不开手机厂商。一次遇到了一个百思不得其解的 crash,堆栈如下:

微信自用的跨平台移动端IM网络层封装库Mars详解,自己可以写IM app了


看堆栈结合程序 xlog 分析,非阻塞 socket 卡在了 connect 函数里超过了6 min, 被我们自带的 anr 检测(代码见anr.cc)发现然后自杀。最后实在束手无策,联系厂商一起排查,最终查明原因:为了省电,当手机锁屏时连的不是 WiFi 且又没有下行网络数据时,芯片 gate 会关闭,block 住所有网络请求,直到有下行数据或者超过 20min 才会放开。当手机有网络即使是手机网络的情况下,很难没有下行数据,所以基本不会触发组件自带的 anr 检测,但当手机没连接任何网络时,就很容易触发。解决方案:厂商修改代码逻辑,当没有任何网络时不 block 网络请求。

运营商和手机厂商对我们来说已经是一个黑盒,但其实也遇到过更黑的黑盒。当手机长时间不重启,有极小概率不能继续使用微信,重启手机会恢复。但因为一直找不到一个愿意配合我们又满足条件的用户,导致这个问题很长一段时间内都没有任何进展,最终偶然一个机会,在一台测试机器上重现了该问题,tcpdump 发现在三步握手阶段,服务器带回的客户端带过去的 tsval 字段被篡改,导致三步握手直接失败,而且这个篡改发生在离开服务器之后到达客户端之前。

这个问题是微信网络模块中排查时间最长也是花费精力最多的一个问题,不仅因为很长一段时间内无案例可分析,也因为在重现后,联系了大量的同事和外部有关人的帮忙,想排查出罪魁祸首。但因为中间涉及的环节和运营商相关部门过多,无法继续排查下去,最终也没找到根本原因。 解决办法:服务器更改 net.ipv4.tcp_timestamps = 0。

这段时间是痛并快乐着,见识到了各种极差的网络,才切肤感受到移动网络环境的恶劣程度,但看着我们的网络性能数据在稳步提升又有种满足感。截止到今天,已经很少有真正的网络问题需要跟进了。这也是我们能有时间开始把这些代码开源出去的很大的一个原因。

6、为了更好地开源Mars,微信团队做了这些前期工作

大概一年前(大约2015年10月),我们开始有想法把基础组件开源出去,当时大家都在纠结叫什么名字好呢?此时恰逢《火星救援》正在热映,一位同事说干脆叫 Mars 吧,于是就定下来叫了 Mars。看了看代码,发现想要开源出去可能还是需要做一些其他工作的。

6.1 代码重构

微信自用的跨平台移动端IM网络层封装库Mars详解,自己可以写IM app了

mars-open 也就是要开源出去的代码,独立 git repo。

mars-private 是可能开源出去的代码,依赖 mars-open。

mars-wechat 是微信业务性相关的代码,依赖 mars-open 和 mars-private。

最后,为了接口更易用,对调用接口以及回调接口的参数也进行了反复思考与修改。

6.2 编译优化

微信自用的跨平台移动端IM网络层封装库Mars详解,自己可以写IM app了

为了让使用者可定制代码,对于编译 Android 平台我们提供了两种选择:

1. 动态库。有些可能需要定制的代码都提供了默认实现。

2. 先编译静态库,再编译动态库。

编译出来静态库后,实现自己需要定制的代码后,执行 ndk-build 后即可编译出来动态库。 对于 Apple 系平台,把头文件全部收拢为 Mars 维护,直接编译出 Framework。

为了能让开发者快速的入门,我们提供了 Android、iOS、OS X 平台的 demo(微信开源Mars的Demo源码点此进入),其他平台的编译和 demo 会在不久就加上支持。

微信自用的跨平台移动端IM网络层封装库Mars详解,自己可以写IM app了

▲ 成型的 Mars 结构图如上图所示

6.3 同类技术横向对比

我们做的一直都不是满足所有需求的组件,只是做了一个更适合我们使用的组件,这里也列了下和同类型的开源代码的对比。

微信自用的跨平台移动端IM网络层封装库Mars详解,自己可以写IM app了

可以看出:

Mars 中包括一个完整的高性能的日志组件 xlog;

Mars 中 STN 是一个跨平台的 socket 层解决方案,并不支持完整的 HTTP 协议;

Mars 中 STN 模块是更加贴合“移动互联网”、“移动平台”特性的网络解决方案,尤其针对弱网络、平台特性等有很多的相关优化策略。

总的来说,Mars 是一个结合移动 App 所设计的基于 socket 层的解决方案,在网络调优方面有更好的可控性,对于 HTTP 完整协议的支持,已经考虑后续版本会加入。

6.4 Mars开源源码结构

微信自用的跨平台移动端IM网络层封装库Mars详解,自己可以写IM app了

7、获取Mars的演示Demo(Sample)

7.1 下载和安装

试用 Android Demo 请从文末的附件下载之,iOS sample 请通过Mars的Github仓库编译获得(或文末的附件下载源码)。

7.2 Mars Android版演示程序截图


微信自用的跨平台移动端IM网络层封装库Mars详解,自己可以写IM app了

微信自用的跨平台移动端IM网络层封装库Mars详解,自己可以写IM app了

7.3 Mars iOS版演示程序源码截图

微信自用的跨平台移动端IM网络层封装库Mars详解,自己可以写IM app了

8、本文小结

微信自用的跨平台移动端IM网络层封装库Mars详解,自己可以写IM app了