引言
埋点对于移动应用来说至关重要,无论是赋能业务增长,还是优化技术实现,埋点数据和技术日志都为决策和优化提供了关键依据。转转App也有着一套自研的日志采集系统(Lego),从2015年转转App上线第一个版本到现在,Lego逐步从一个单一功能架构演变为支持自动化采集、实时上报、业务与技术日志隔离的复合架构。
Lego演进历程
总结起来,可以分为四个阶段:
- 从零到一建设能力;
- 业务埋点与技术日志拆分;
- 提高核心埋点实时性;
- 架构升级性能提升。
本文将为大家介绍客户端在Lego升级的四个阶段中的设计思路和解决方案。
Lego:从零到一
背景
- 移动开发早期(2015 年)阶段,移动设备资源(CPU,内存,电量)有限,网络环境不稳定(网速慢,连接质量差);
- 业界技术实现普遍注重低功耗,低开销实现数据的采集上报;
- 埋点相对较少,业务规模相对较小;
架构设计
在上面的背景下,首先面对不稳定的网络环境,采用合并多个埋点的数据,将数据写入本地文件的方式,尽量减少接口请求的频次,防止网络传输不稳定导致数据丢失,较低的上报频次,同时也减轻服务端的压力;并且将埋点数据文件压缩处理后再上报以节省网络传输的数据流量开销。其次由于应用主进程运行内存有限,又涉及数据格式化,文件写入,文件压缩,网络接口上报等操作,采用独立子进程的方式处理可以避免挤占主进程的资源,同时隔离影响,由此有了下面的架构设计:
Lego初始架构
图中可见,应用主进程通过启动Service(后台服务)实现与子进程数据通信,发送配置更新,写日志和上传日志的指令和数据到 Lego 子进程。然后在子进程中进行初始化配置,将日志数据格式化写入文件,在动态配置的时间间隔(默认两分钟)或者接收到主动触发的上传指令时,将日志文件压缩后通过接口上传,成功后则删除压缩文件,否则在下次发送时机触发时,再次尝试上传。
架构特点
- 独立内存空间:采用子进程做日志处理上报,子进程拥有独立的内存空间,避免挤占应用主进程的内存等资源;
- 进程隔离,稳定性高:子进程独立与主进程运行,如果子进程在执行日志写入或上传时崩溃,不会影响App主进程的正常运行。这样大大提高应用的稳定性与用户体验;
- 性能开销低:子进程中将日志数据写入文件,每两分钟合并发送一次,可以减少频繁的接口请求,减轻服务端压力的同时减少应用的性能开销。
- 节省流量:客户端经过格式化数据,写入本地文件后,每两分钟将日志文件压缩后上传到数据服务端,服务端接收数据后再进行文件解压缩,落盘,清洗,落表。
Lego4APM:业务埋点与技术日志拆分
背景
- 用户规模增长,精细化和自动化决策运营的内在需求,迫切需要强实时性的数据采集,上报,处理;
- 业务埋点与APM 日志混合上报,未做区分,给大数据部门的数据处理造成时效性压力;
- 客户端本身也需要将性能相关的埋点统一化,规范化,需要有新的聚合维度;
实现方案
上报的实现还是采用 Lego 的原有架构,复制了新的组件 Lego4APM,将上报的数据服务接口换成技术埋点的专用接口,将技术日志与业务埋点分流。按照日志级别划分,聚合原有的性能日志埋点,再将原有的日志采集接口的底层上报逻辑直接迁移到新组件。
拆分后结构图
Lego业务与技术埋点拆分结构图
图中ZPM为基于转转位置模型自研的自动化埋点采集框架,采集完的数据通过 Lego 上报,APM则是性能监控框架采集技术日志,通过Lego4APM组件上报,其中 Lego 与 Lego4APM 是相同实现的两套独立的组件。
LegoRealtime:提高实时性
背景
将用户行为埋点与 APM 日志通过 Lego 和 Lego4Apm 分流后,做到数据隔离,但是Lego 原有设计是多埋点合并上报,每2 分钟压缩文件后上报一次,为实现自动化运营的整体方案,客户端要尽可能保证在 200ms 时延内完成核心用户行为的采集上报,提高实时性。
方案设计
方案设计阶段我们也调研前端的方案,是直接采用的接口上报,但是作为移动端,需要发挥移动端的特性,下面是实时版本设计思路。
LegoRealtime 架构图
方案实现关键
- 数据备份容灾:考虑移动端的环境多样问题,容易出现了异常,弱网,断网的情况,数据备份很必要,同时也要区分日志是实时的,还是异常备份数据,涉及日志记录的状态修改,数据库备份比文件备份更合适。
- 异常数据重传机制:在网络连接恢复,应用重新启动,以及固定时间间隔,对异常数据进行批量重传;
- 简单格式化,高效处理:实时上传只利用了https本身支持的内容压缩,未做额外的压缩等处理,直接以json格式上传数据,数据服务端在处理数据时相比压缩格式上报,不需要额外的解压缩处理,更高效。
- 稳定可控:方案设计之初就考虑上线后如何控制稳定迁移以及数据验证的问题,增加了灰度控制开关和数据验证接口。
迁移方案
- 无侵入迁移:为了避免大量更改原有业务埋点的接入代码,我们选择通过拦截原有 Lego 上报的接口,通过白名单控制核心埋点走实时上报,对原有接入业务无侵入,降低影响范围。
- 上线稳定控制:配置 ABTest灰度开关,方案验证阶段,先按照10%的灰度比例切换到实时上报,观察数据完整性,后端服务的稳定性,验证通过后逐步增大比例到 50%,稳定运行一周后全量切换。
- 数据质量验证:
- 时效性方面,通过 APM 后台的监控数据,确认Lego 实时上报接口的往返时延是否达到低时延设计目标。
- 完整性方面,采用了实时埋点通过实时方式和文件压缩合并方式同时上报的方案来验证数据完整性,其中文件压缩合并方式使用的是Lego4APM,因为技术日志落盘数据与业务数据是隔离的,将这部分数据上报到技术埋点不会污染业务数据,用以验证完整性非常合适。
验证结论
- 性能平台监控到,实时上报接口的平均往返时延为 160ms,发送时延按一半估算为80ms,远远低于设计目标的200ms,为大数据端数据处理预留了操作空间;
- 由于实现的机制不同,Lego实时上报的数据比 Lego4APM 上报的数据多 1%,在合理范围内;
Lego 新架构:性能提升
背景
- 业务方面:
- 某些场景存在后台启动服务,存在隐私合规问题;
- 数据组反馈数据重复上报问题,以及埋点缺失问题;
- 技术方面:
- 应用市场反馈 Lego 相关的卡顿问题(当埋点量较多时,频繁从主进程发送埋点数据和配置到子进程,每次数据交互都是 IPC,这会挤占系统的 binder 通信的资源,Android系统的各种服务包括页面的创建,点击事件的交互等都是使用 binder 通信实现,容易引发卡顿问题)
- 多进程配置管理复杂,相同的配置主进程配置后需要再同步至子进程,这给组件的升级维护带来了额外的成本。
- Lego 和 Lego4APM相同功能的多套代码,修改出现分叉,维护成本增高;
- Lego 项目由来已久,实现方案不符合于移动端内存相对充足,性能过剩的现状;
新方案架构设计
基于业务和技术多方面因素考虑,我们对 Lego进行了重新设计,新架构如下图:
Lego 新架构设计
图中可以看到,新架构采用的是子线程方案,在主进程创建Lego 实例,通过实例进行配置管理,写日志和上传触发, 同时实例通过创建HandlerThread 子线程,在子线程中进行日志文件写入和压缩上传。
新方案优点
- 无需IPC,开销低:老架构的子进程方案需要频繁的 IPC,且更改配置时,主进程和子进程都要维护,而新架构在同一进程中,线程间内存共享,无需要 IPC的额外性能消耗,卡顿问题自然解决,配置只需要保证线程安全即可。
- 无隐私合规问题:老架构某些场景触发后台启动服务实现IPC,而上报数据包含位置信息,用户设备 id等隐私数据,引发隐私不合规问题,新架构中无需IPC,也就不需要从后台启动服务了。
- 支持多实例,易维护:非核心用户行为埋点和 APM 埋点的上报实现基本一致,老架构需要多份复制,额外占用系统进程,同时维护成本大,新架构支持多个实例后,两种上报只需要两个实例,两套配置即可,性能和维护成本上没有额外的消耗。
- 上报稳定:老架构存在重复上报,埋点缺失的问题,新架构中通过 HandlerThread 的事件循环队列实现写日志与上传日志串行执行。防止边上传边写入容易造成重复上报和缺失的问题。
版本迁移方案
Lego、Lego4APM作为客户端的基础能力,承载着客户端所有业务及技术埋点,一旦出现问题,对于客户端来说将是灾难。对如此重要的组件进行重构,面临巨大的风险,需要制定一个稳妥的、渐进的、对上层业务无感知的迁移方案。计划将版本迁移拆分为两个阶段:
- 阶段一:对照验证,通过选定指定埋点,对比新老版本埋点数量及参数是否在合理范围内,验证时间全量后两天,验证通过进入第二阶段,验证不通过则停止版本迁移,定位问题后下个版本继续进入阶段一。
- 阶段二:版本切换,按用户比例逐步切换到新版Lego,用户比分为三个阶梯:10%、50%、100%,每个阶梯灰度一周时间,期间观察核心业务埋点指标(如登录PV/UV、详情页PV/UV、订单PV/UV)是否正常。
迁移后埋点数据对比结果
验证结论:新版本埋点数据的内容完整没有缺失,并且比旧版本的数据量多1%
总结与展望
Lego系统结构
上图是目前转转客户端的Lego日志系统结构图,其中包含了数据采集和数据上报两个部分。业务核心埋点数据由ZPM(转转位置模型自动化采集框架)采集,通过LegoRealtime组件实时上报;业务其他埋点则是手动埋点采集,通过基于Lego新架构的Lego4Buz实例上报;技术日志用APM日志框架(性能监控)采集,由基于Lego新架构的Lego4APM实例上报。
通过这些优化和升级,不仅增加数据上报的可靠性和实时性,还显著提升了稳定性,降低了维护成本。在这个过程中也收获了宝贵的经验,在实现日志上报功能时,我们不仅需要考虑如何减少不必要的性能开销,发挥客户端的优势,合并上报以减轻服务器的压力,同时我们还需要确保新旧版本的平滑过渡和安全迁移,制定周全的迁移方案,以便在出现问题时能够实时回退,做到安全可控。
关于埋点上报的优化,我们还有其他的演进方向,如LegoRealtime实时上报,频繁的https接口请求,可以替换成其他低功耗传输方式;而Lego新架构的异常处理方面,在服务异常时会频繁重试,可能会让系统异常问题雪上加霜。此外,大日志文件上传的策略优化等方面,还有演进升级的空间,如采取抛弃策略,避免影响整体运行,或者拆分成小文件缩短上报时间,提高成功率。
总之,升级之路是不断优化和改进现有方案的过程,我们将继续寻求更加高效、稳定和安全的实现方式,推动Lego系统的不断演进和升级。