本文由【友盟+】技术专家马巍源、张永峰共同撰稿
随着移动互联网和大数据技术的发展,智能手机的普及,几乎所有工作、学习、生活中的所有场景都离不开手机,手机 APP 已经取代了传统的生活方式,让人们可以体验便捷高效的服务,当然它也承载着大量丰富的信息,收集这些 APP 的数据,集中对数据进行清洗和分析,就能将这些海量数据变为有价值的数据能源。数据采集是开发数据能源的第一步,如何采集数据,什么样的技术架构能够支持海量数据的采集、甄别和传输,这是本文需要讨论的问题。
移动数据采集特点
与 PC 端不同,对于手机、iPad、智能手表、电视盒子等移动设备,我们触达它们的载体就是 APP。原生 SDK 在多语言上支持上需要投入很多的开发资源,跨平台应用开发渐成趋势,但 JS SDK 在各框架上的实现也各有差异,因此,目前移动采集 SDK 在对多平台、多语言的支持上难度较大。
难度更大的是对 Android 设备的机型适配问题。由于 Android 系统的开源特性,各厂商为了在各家机型上有更好的用户体验,都有针对性的做了 ROM 改良,尤其近些年 Android 在虚拟机、编译器上的改动较大,这就给机型适配带来更大的难度。为了不给 APP 带来卡顿、闪退、黑屏、崩溃、加载速度慢等差的体验,还需要支持开发者各种异常方式的接口调用,需要有极强的容错性。
移动端的流量在持续的增长,【友盟+】在移动端覆盖的 APP 超过 135 万款,覆盖全球移动设备日活跃数超过 14 亿个,每天处理的数据量达 280 亿,庞大的数据每天都在考验着我们的采集 SDK 和服务端的承载能力,【友盟+】在移动端采集技术上不断更新迭代,持续多年保持市场覆盖领先的地位。
SDK 与服务器通信协议的演进
我们最初 SDK 的设计思想是简单高效,因此在 SDK 端没有任何对数据预处理的逻辑,甚至缓存策略也非常简单,所有实时产生的数据都会实时上报服务器。但随着移动端流量的暴涨,这种高并发的请求对服务器带来很大的压力。下图是 1.0 版本的通信协议。
于是考虑通过控制发送频率来减小并发,开发者可以根据业务需要采用不同的发送策略:启动、间隔、退出发送,并且在【友盟+】平台可随时变更。虽然有效减小了服务端的压力,但又带来了另一个问题,单条数据的包体大小有可能超过 request-body 的上限,导致请求超时。并且流量压力同样是需要亟待解决的问题,于是,在 2.0 版本上我们对数据进行了压缩,并增加了安全机制。服务端增加了数据预处理的逻辑,完善了对数据的校验。
只能单向通信的协议是不灵活的,有很多时候我们需要 SDK 的行为进行一些控制,比如发送策略的修改、屏蔽错误埋点数据,或者发现数据被污染决定抛弃,这些操作服务器需要通知到 SDK,并且在没有长连接的情况下该怎么做。在 3.0 版本上我们把 http 请求的 response 的信息包体设计控制语义,SDK 除了从 response 获得服务器的接收状态,同时可以获得服务器的控制指令,从而实现服务器想要得到的效果。
如果每一条 Log 都必须等待并解析服务器返回的控制信息,显然服务器对数据处理的时效性和并发处理能力会大大折损,并且有些业务数据其实无需解析并执行这些控制信息。因此,我们对业务数据进行了精细的分解,一些业务数据使用双向通信协议,能够解析并执行控制指令,其余的业务数据属于状态无关数据,仍然使用单向通信协议。
那么未来其实还可以将控制协议与业务传输协议分离,各自使用不同的发送频率,但又能保证所有业务数据是受服务器指令控制的。
SDK 技术架构解析
移动数据采集 SDK 架构主要由三部分组成:用户接口、业务模块和控制模块。
我们可以从几个场景的时序图来解析这几个模块的工作原理。
APP 启动
用户启动 App 的时候,其实是触发了开发者调用的初始化接口,Service Moudle 和 Control Moudle 会异步的进行一些初始化的操作:创建 Session、加载设备信息等。
APP 在前台运行中
当用户在 APP 中有点击、滑动屏幕的行为,会触发开发者在 APP 中预设置埋点事件。
Servie Moudle 会生成相应的事件数据,调用 Control Moudle 的接口检查发送策略和安全策略,之后 Servie Moudle 会将事件数据放到缓存队里中待发送。
APP 退出
无论用户退出 APP 后,SDK 还会在短暂的瞬间完成很多操作:结束 Session、持久化保存数据,在 iOS 中还会直接完成数据封装、打包、上报的工作。
SDK 组件化架构
我们提供的产品功能越来越多,业务场景越来越复杂,为了满足各种各样的解决方案的需求,SDK 需要为各个业务场景维护多个分支、多个版本,开发资源浪费、版本迭代周期拉长,为了解决这个问题,我们必须要设计一个灵活的架构,使每个产品功能变成可自由组合、拆卸的组件。
组件化将统一约定 package 和 public API 的文件规范。针对当前【友盟+】业务的需求,建立标准的 SDK 产品公共库(如:network,serialize,configure,cache 等),组件化结构分为两部分,Common 将作为一个独立的 library package,而 Component 中每个产品作为独立 library。
其结构如下:
业务组合灵活,适用更多场景
组件的划分的颗粒度,可以根据业务需求,我们的设计是根据产品,或者业务来划分组件。一个产品可能包含很多功能,比如统计产品包含事件数据采集、错误数据采集、A/B Test 等功能,Push 产品包含消息推送和应用内消息,在某些场景下,可能有些开发者会只使用部分功能,比如,只用错误分析功能和 Push 的消息推送,那么组件颗粒细化到功能层,就会更加灵活,可满足更多场景的需求,并且体积的减小是对开发者来说是非常有吸引力的。
业务逻辑解耦,代码更加健壮
组件化的架构改变了以前业务逻辑与基础功能深度耦合的状况,业务开发人员可以专注于业务逻辑的实现,而不需要考虑如网络通信、消息队列管理、设备信息采集等基础功能的实现。业务逻辑代码的任何改动,不会影响基础功能逻辑,加强了代码的健壮性,同时在回归测试周期上也大大缩短。
【友盟+】数据采集技术将持续的适应业务场景的变化,未来我们的目标是让我们的 SDK 更加智能,更加安全,让企业及开发者集成更加简单、数据更加精准。