3.1.4. IO管理
基于一个快速和高效管理的链路之上,做好IO调度和控制,也是提升效能和改善用户体验的重要环节。要探讨的内容包括:
① 异步IO
异步化IO的目的就是避免资源的集中竞争,导致关键任务响应缓慢。我们在后面差异服务个大的分类中会重点探讨。这里特别先提出来,是建议在程序架构顶层设计时,要在整体机制上支持异步化,设计必要的异步总线来联系各个层级模块,总线可能会涉及包括队列管理(优先级、超时、CRUD等)、事件驱动、任务调度等。
异步IO除了网络方面外,对移动设备,我们还特别要考虑一下磁盘IO的异步。因为频繁、大吞吐量的磁盘IO会造成APP的UI卡顿,从用户体验上看就是交互响应迟钝或者滑动帧率下降。一般来说,磁盘IO异步会选用空间换时间的方案,即缓存数据批量定时写入磁盘。
② 并发控制
有了异步IO,并发控制就显得尤为重要。把异步机制当作银弹任意使用,就如同我们给移动APP设计了一个叫“发现”的地方一样,很可能各种膨胀的需求、不知道如何归类的需求就纷至沓来,期待有朝一日被“发现”。
异步IO提供了一个很好的发射后不用管的机制,这就会造成使用者的膨胀,无论是否必要、无论轻重缓急,把请求一股脑的丢给异步队列,自己潇洒的转身就走。这样不但会带来效率和交互响应性能的下降,也会造成资源的无谓消耗。
在后面多异步这个大分类的讨论中会涉及到轻重缓急的话题,在前述异步IO的磁盘IO的时空效率转换话题中,还应该包括IO并发的控制,我们即不能因为并发过多的链路造成网络带宽的独占消耗影响其它APP的使用,也不可因快速、大量的异步数据造成缓写机制形同虚设或是占用过大的内存资源。
③ 推拉结合
PUSH机制应该是苹果公司在移动设备上取得辉煌成就的最重要两个机制之一,另外一个是移动支付体系。我们这里的讨论不包括iOS和APPLE移动设备的拟人化交互体验,只侧重根基性的机制能力。APNS解决了信息找人的问题,在过去,只有运营商的短信有这个能力,推送和拉取使得我们具备了实时获取重要信息的能力。
为何要推拉结合。因为系统级的推送体系也必须维持一个自己的链路,而这个链路上要承载五花八门的APP推送数据,如果太重,一方面会在设计上陷入个性化需求的繁琐细节中,另外一方面也会造成这条链路的拥堵和性能延迟。因此,通过PUSH通知APP,再由APP通过自己的链路去PULL数据,即有效的利用了PUSH机制,又能使得APP能按需使用网络,不但简化了链路管理,而且节省了电量和流量。
④ 断点续传
一方面,在讨论链路管理时,我们建议了优质网络下的并发链路来完成同一个重数据拉取任务。这就会涉及到任务的拆分和并行执行,基础是后台能支持断点续传。
另外一方面,从客户端的角度而言,移动网络的不稳定特点,可能会造成某个重数据拉取任务突然失败,无论是自动重试还是用户驱动的重试,如果能从上次失效的上下文继续任务,会有省时间、省电量和省流量的效果,想想也会觉得十分美好。
3.2. 轻往复
“技”止此尔。强调网络交互的“少”,更应强调网络交互的“简”。 我们在一条高时延易抖动的通道上取得效率优势的关键因素就是减少在其上的往复交互,最好是老死不相往来(过激),并且这些往复中交换的数据要尽量的简洁、轻巧,轻车简从。这个概念是不是有点像多干多错,少干少错,不干没错。
把我们实践过的主要手段提出来探讨:
① 协议二进制化
二进制比较紧凑,但是可读性差,也因此形成可维护性和可扩展性差、调测不便的不良印象。这也造成了大量可见字符集协议的出现。计算机是0和1的世界,她们是程序猿的水和电,任何一个整不明白,就没法愉快的生活了。
② 高效协议
高效的协议可以从两个层面去理解,一是应用层标准协议框架,二是基于其上封装的业务层协议框架,有时候也可以根据需要直接在TCP之上把这两个层面合并,形成纯粹的业务层私有协议框架。不过,为了简化网络模块的通讯机制和一些通用性、兼容性考虑,目前大多数情况下,我们都会选择基于HTTP这个应用层标准协议框架之上承载业务层协议框架。下面我们针对上述两个层面展开探讨。
首先是应用层的标准协议优化,比如HTTP/1.1的Pipeline、WebSocket(在HTML5中增加)、SPDY(由Google提出)、HTTP/2等,其中特别需要关注的是处在试验阶段的SPDY和草案阶段的HTTP/2。
SPDY是Google为了规避HTTP/1.1暨以前版本的局限性开展的试验性研究,主要包括以下四点:
1) 链路复用能力,HTTP协议最早设计时,选择了一问一答一连接的简单模式,这样对于有很多并发请求资源或连续交互的场景,链路建立的数量和时间成本就都增加了;
2) 异步并发请求的能力,HTTP协议最早的设计中,在拉取多个资源时,会对应并发多个HTTP链路(HTTP/1.1的Pipeline类似)时,服务端无法区分客户端请求的优先级,会按照先入先出(FIFO)的模式对外提供服务,这样可能会阻塞客户端一些重要优先资源的加载,而在链路复用的通道上,则提供了异步并发多个资源获取请求指令的能力,并且可以指定资源加载的优先级,比如CSS这样的关键资源可以比站点ICON之类次要资源优先加载,从而提升速度体验;
3) HTTP包头字段压缩(注:特指字段的合并删减,并非压缩算法之意)精简,HTTP协议中HEAD中字段多,冗余大,每次请求响应都会带上,在不少业务场景中,传递的有效数据尺寸远远小于HEAD的尺寸,带宽和时间成本都比较大,而且很浪费;
4) 服务器端具备PUSH能力,服务器可以主动向客户端发起通信向客户端推送数据;
HTTP/2由标准化组织来制定,是基于SPDY的试验成果开展的HTTP协议升级标准化工作,有兴趣了解详细情况可以参考HTTP/2的DRAFT文档。
其次是业务层的协议框架优化,它可以从三个方面考察,一是协议处理性能和稳定性好,包括诸如协议紧凑占用空间小,编码和解码时内存占用少CPU消耗小计算快等等,并且bad casae非常少;二是可扩展性好,向下兼容自不必说,向上兼容也并非不能;三是可维护性强,在协议定义、接口定义上,做到可读性强,把二进制协议以可读字符的形式展示,再通过预处理转化为源码级文件参与工程编译。可能会有同学强调协议调测时的可阅读、可理解,既然读懂01世界应该是程序员的基本修养,这一项可能就没那么重要了。
高效的业务层协议框架从分布式系统早期代表Corba的年代就有很多不错的实践项目,目前最流行的开源组件应属ProtoBuf,可以学习借鉴。
正所谓殊途同归、心有灵犀、不谋而合,英雄所见略同......,说来说去,高效协议的优化思路也都在链路复用、推拉结合、协议精简、包压缩等等奇技淫巧的范畴之内。
③ 协议精简
协议精简的目的就是减少无谓的数据传输,提升网络效能。俗话说“千里不捎针”,古人诚不我欺也。我们实践总结以下三点供参考:
1) 能不传的就不传。把需要的和希望有的数据都列出来,按照对待产品需求的态 度,先砍掉一半,再精简一半,估计就差不多了。另外,高效协议提供了比较好的扩展性,预留字段越少越好,移动互联网演化非常快,经常会发现前瞻的预留总是赶不上实际的需求;
2) 抽象公共数据。把各协议共性的属性数据抽象出来,封装在公共数据结构中, 即所谓包头一次就传一份,这个想法不新鲜,TCP/IP的设计者们早就身体力行了。除了带来数据冗余的降低外,还降低了维护和扩展的复杂度,一石二鸟,且抽且行;
3) 多用整数少用字符,数字比文字单纯,即简洁又清晰,还不需要担心英文不好被后继者BS;
4) 采用增量技术,通知变化的数据,让接收方处理差异,这是个很好的设计思想,实践中需要注意数据一致性的校验和保障机制,后面会有专门的细节讨论;
④ 协议合并
协议合并的目标是通过将多条交互指令归并在一个网络请求中,减少链路创建和数据往复,提升网络效能。把实战总结的六点提出来供参考:
1) 协议合并结合协议精简,效率翻番;
2) 协议合并的基础是业务模型的分析,在分类的基础上去做聚合。首先得区分出来缓急,把实时和异步的协议分类出来分别去合并;其次得区分出来轻重,协议请求或协议响应的数据规模(指压缩后),尽量确保在一个数据报文中可完成推拉;
3) 协议合并在包的封装上至少有两种选择,一是明文协议合并后统一打包(即压缩和解密);二是明文协议分别打包,最后汇总;前者效率高一些,在实战中用的也较普遍;后者为流式处理提供可能;
4) 协议合并对服务器的异步处理架构和处理性能提出了更高的要求,特别需要权衡网络交互效率和用户对后台处理返回响应期待之间的取舍;
5) 协议间有逻辑顺序关系时,要认真考虑设计是否合理或能否合并;
6) 重数据协议不要合并;
⑤ 增量技术
增量技术准确分类应该算是协议精简的一个部分,它与业务特点结合的非常紧密,值得单独讨论一下。增量技术在CS数据流交互比较大的时候有充分发挥的空间,因为这个技术会带来客户端和服务器计算、存储的架构复杂度,增加资源消耗,并且带来许多保障数据一致性的挑战,当然,我们可以设计的更轻巧,容许一些不一致。
我们用一个案例来看看增量技术的运用。
在应用分发市场产品中,都有一个重要功能,叫更新提醒。它的实现原理很简单,以Android设备为例,客户端把用户移动设备上安装的APP包名、APP名称、APP签名、APP版本号等信息发送到服务器,服务器根据这些信息在APP库中查找相应APP是否有更新并推送到客户端。这个过程非常简单,但如果用户手机上装了50个APP,网络上交互的数据流就非常客观了,即浪费流量和电量,又造成用户体验的缓慢,显得很笨重。
这个时候,增量技术就可以派上用场了,比如下面的方案:
1) 每个自然日24小时内,客户端选择一个时间(优先选择驻留在后台的时候)上报一次全量数据;
2) 在该自然日24小时的其它时间,客户端可以定时或在用户使用时发送增量数据,包括卸载、安装、更新升级等带来的变化;
3) 作为弱一致性的保障手段,客户端在收到更新提示信息后,根据提醒的APP列表对移动设备上实际安装和版本情况做一次核对;
4) 上述择机或定时的时间都可以由云端通过下发配置做到精细化控制;
⑥ 包压缩
前面精打细算完毕,终于轮到压缩算法上场了。选择什么算法,中间有哪些实战的总结,下面提出来一起探讨:
1) 压缩算法的选择,我们比较熟悉的压缩算法deflate、gzip、bzip2、LZO、Snappy、FastLZ等等,选择时需要综合考虑压缩率、内存和CPU的资源消耗、压缩速率、解压速率等多个纬度的指标,对于移动网络和移动设备而言,建议考虑使用gzip。另外需要注意的是,轻数据与重数据的压缩算法取舍有较大差异,不可一概而论;
2) 压缩和加密的先后秩序,一般而言,加密后的二进制数据流压缩率会低一些,建议先压缩再加密;
3) 注意一些协议组件、网络组件或数据本身是否已经做过压缩处理,要避免重复工作,不要造成性能和效率的下降。比如一些图片格式、视频或APK文件都有自己的压缩算法。说到这,问题又来了,如果应用层标准协议框架做了压缩,那么基于其上封装的业务层协议框架还需要压缩吗,压缩技术到底哪家强?这个问题真不好回答,考虑到HTTP/2这样的应用层标准协议框架定稿和普及尚需时日,建议在业务层协议框架中做压缩机制。或者追求完美,根据后端应用层标准协议框架响应是否支持压缩及在支持时的压缩算法如何等信息,动态安排,总的原则就是一个字:只选对的,不选贵的;
3.3. 强监控
可监方可控,我们在端云之间,要形成良好的关键运营数据的采集、汇总和分析机制,更需要设计云端可控的配置和指令下发机制。本篇重点讨论与主题网络方面相关关键指标的“监”和“控”。
以就快接入为例来探讨一下强监控能力的构建和使用。
1) 接入质量监控,客户端汇总接入调度FSM执行过程元信息以及业务请求响应结果的元信息,并由此根据网络类型不同、运营商不同、网络接入国家和省市不同分析接入成功率、业务请求成功率(还可细化按业务类型分类统计)、前述二者失败的原因归类、接入302重定向次数分布暨原因、接入和业务请求测速等;
2) 建设云端可控的日志染色机制,便于快速有针对性的定点排查问题;
3) 终端硬件、网络状态的相关参数采集汇总;
4) 建设云端可控的接入调度(比如接入IP列表等)和网络参数(比如连接超时、IO超时、任务超时、并发链接数、重试间隔、重试次数等)配置下发能力;
5) 服务器根据汇总数据,通过数据分析,结合服务器自身的监控机制,可以做到:
a. 支持细粒度的接入调度和网络参数的优化云控;
b. 支持服务器的部署策略优化;
c. 发现移动运营商存在的一些差异化问题比如URL劫持、网络设备超时配置不当等问题便于推动解决;
d. 发现分省市服务器服务质量的异常情况,可以动态云端调度用户访问或者降级服务,严重时可以及时提示客户端发出异常安抚通告,避免加剧服务器的负载导致雪崩。安民告示的快速呈现能力,考验了一个团队对可“控”理解的深度,我们在实践中,提供了三级措施来保障:第一级是服务器端通过协议或跳转URL下发的动态通告,这在非IDC公网故障且业务接入服务器正常可用时适用;第二级是预埋静态URL(可以是域名或IP形式,优先IP)拉取动态通告,适用其它故障,静态URL部署的IP地址最好同本业务系统隔离,避免因为业务服务所在IDC公网故障不可用时无法访问;第三级是客户端本地预埋的静态通告文案,内容会比较模糊和陈旧,仅作不时之需;
e. 支持异步任务的云端可配可控,比如下载类APP的下载时间、下载标的和下载条件约束(磁盘空间、移动设备电量、网络类型等)的差异化配置,通过错峰调度,达到削峰平谷并提升用户体验的效果;
特别需要注意的是,客户端数据报告一定要有数据筛选控制和信息过滤机制,涉及用户隐私的敏感信息和使用记录必须杜绝采样上报。在我们的日志染色机制中要特别注意,为了排查问题极可能把关键、敏感信息记录报告到后端,引入安全风险。