1.背景
随着业务的发展,用户对于网络的依赖场景会越来越多,随之而来遇到的各种异常网络场景也越来越多。
- 1、为了保证网络的接口的持续健康、及时发现问题,为网络性能优化提供数据基础。
- 2、同时以报表的形式直观的去展现现有网络问题。
- 3、也为了更好的支撑后续业务的发展,就需要我们去搭建起网络监控体系。
2.目标
提供一套完整的网络采集、监控和预警的可视化机制,用于线上接口可用性和健康度观察,并提供一系列排查问题的辅助信息。
通过仪表盘将接口健康度可视化,包括但不限于以下能力:
- 总体请求成功率
- 过滤单个请求成功率、失败错误码
- 查看接口请求详情
- 接口访问平均耗时、时长分布
- 通过对接后端的链路追踪,实现从客户端请求的发起到最终具体服务器的处理返回全链路追踪。
3.整体架构概览
不管是 iOS 还是 Android ,两者最终要做的目标方案如上图。这里从下到上分别阐述每个部分的功能:
网络基础库:针对平台特性,这里有 iOS 的 AFnetworking、Alamofire 和 Android 的 okhttp3、okhttp4 ,其实现原理应该都差不多,都是针对底层的网络api进行进一步的封装,提高接口的易用性。
拦截器:主要是针对各个基础网络库进行接口拦截。根据平台不同,具体的拦截方案在下文中阐述~
网络封装库:一般开发过程都会针对基础的网络库再做二次封装,加入一些策略、缓存、安全校验等管理,使其更加贴合业务和快速接入使用。
插件/功能模块:以插件化的形式提供额外的网络功能
统计模块:将从业务开始调用到回调给业务方的各个环节的耗时及状态值,变成统计数据汇报到APM。
网络诊断模块:对关键业务进行诊断,包括dns解析、ping、弱网检测等,输出诊断报告并上报到APM。
重试模块:根据策略进行重试,包括 ip 重试、https 降级重试、原 url 重试等。
httpdns模块:提供 httpdns 能力,解决域名劫持问题。
上传模块:提供上传能力,包括断点续传、分片上传以及包体大小、上传耗时等信息监控。
下载模块:提供下载能力,包括大文件下载、断点续传以及包体大小、下载耗时等信息监控。
mock 模块:提供 mock 能力,主要用于测试和后台接口还没有准备好的情况下使用。
对外接口层:这一层直接对接上层业务。
4.数据采集
第一期方案主要是做数据采集,然后上报阿里云,
web 可视化界面和插件/功能模块由于人力问题暂时不纳入第一期,后续再继续做。
- 1数据采集重点落在架构概览图的“拦截器”这一部分,采集的数据主要有以下字段:
参数 key | 类型 | 参数意义 | 备注 | |
---|---|---|---|---|
请求数据 | traceId | string | 单次请求的唯一标识 id | |
id | string | 节点 | 用于以树状图的形式分析和呈现调用链路 | |
pid | string | 父级节点 | 用于以树状图的形式分析和呈现调用链路 | |
url | string | 请求 url | 取 host 后面那一段 url | |
scheme | string | 协议 | http or https | |
clientHost | string | host | 域名 | |
queryParam | string | 请求参数 | ||
method | string | 请求方式 | POST、GET、DELETE 等 | |
ttt | double | 请求总耗时,单位:毫秒 | responseEnd -fetchStart | |
dnst | double | dns 解析耗时,单位:毫秒 | domainLookupStart-domainLookupEnd | |
cnt | double | 连接耗时,单位:毫秒 | connectStart-connectEnd | |
tls | double | tls 耗时,单位:毫秒 | secureConnectionStart-secureConnectionEnd | |
reqt | double | request 耗时,单位:毫秒 | requestStart-requestEnd | |
rest | double | response 耗时,单位:毫秒 | responseStart-responseEnd | |
beginTime | double | 请求开始时间 | 取值 fetchStart | |
endTime | double | 请求结束时间 | 取值 responseEnd | |
handleTime | double | 请求处理时间,单位:毫秒 | responseEnd - fetchStart | |
responseCode | int | 响应状态码 | 这里取的是NSHTTPURLResponse的 error code,该 error code 只记录了 client-side 的 error,不包含服务端的,详细见苹果官方文档:Life Cycle of a URL Session with System-Provided Delegates | |
resd | string | 响应描述 | 成功为空,失败为 error.description | |
reqhsize | int64_t | 请求包header大小,单位:byte | ||
reqbsize | int64_t | 请求包body大小,单位:byte | ||
reshsize | int64_t | 响应包header大小,单位:byte | ||
resbsize | int64_t | 响应包body大小,单位:byte | ||
uhttpdns | bool | 是否有使用httpdns | ||
设备相关 | deviceId | string | 设备唯一标识 | 根据设备标识后面可以查询整个设备的网络请求情况 |
appVer | string | app 版本 | ||
appName | string | app bundle identifier | com.ad61v1.drawLiveStore | |
os | string | 系统版本 | 14.2 | |
platform | string | 平台类型 | iOS/Android | |
deviceType | string | 设备类型 | iPhone/华为/vivo | |
网络相关 | ope | string | 运营商 | 非wifi 下统计使用的是哪个运营商 |
ns | int | 网络类型 | 4G/5G/wifi,取值typedef NS_ENUM(NSUInteger, DLNetType) { DLNetTypeBreakdown = 0, DLNetTypeWiFi, DLNetType5G,DLNetType4G,NetType3G,DLNetType2G, DLNetTypeUnknow}; | |
地域相关 | latitude | double | 维度 | (可选)有开启位置权限下获取,不主动要求位置权限 |
longitude | double | 经度 | (可选)有开启位置权限下获取,不主动要求位置权限 | |
android 特有 | okHttpVer | string | OKHttp 版本号 | 不同版本对数据收集可能有影响 |
okHttpExceptionRetryCount | int | okHttp 内部异常重试次数 | okHttp 请求异常情况下,内部可能会进行重试,该重试不会经过 start |
- 2.针对平台特性不同,以下分别阐述拦截器这部分的实现方案:
-
iOS:NSProtocol + hook 方式
-
要 hook 的接口:
- Android:OkHttp+Retrofit+AOP+EventListener(Aspectj v2.0.10 方式)(推荐)
-
hook层 描述
hook 层主要包含两个东西:NetworkInterceptor & EventListener
1:利用AspectJ hook OKHttp的build 函数。
2:在build 函数执行之前,注入hook 内容,NetworkInterceptor & EventListener。
3:NetworkInterceptor 主要用于request & response的拦截处理,可以管理整条链条。
4:EventListener 主要收集网络各个阶段的耗时收集。
附:OkHttp3的架构图 & AOP 插桩原理 图
- 3.采集策略
对拦截到的所有网络请求,该如何抉择是否需要保留,是全部采集还是部分采集,这里需要跟进后台下发的配置进行采集,因为网络请求数据量比较大,所有必须进行抽样上报,抽样规则以设备为纬度(去除业务耦合),抽中的设备所有请求接口都需要上报,不在对设备的接口进行再采样的目的是为了能够从各种网络环境下收集单个设备的所有请求数据,才能完整地表现出该用户在网络请求健康度。罗列出来则为以下几点:
后台以设备为纬度进行采样,这里采用对采样率取模,然后下发配置
收到后台下发的采样配置的设备全量采集数据上报
app 启动的时候会上报deviceId,deviceId 的作用在于过滤特殊设备的特定请求。在获取到配置之前的网络请求又该如何处理?目前采用的方案是在SDK内置域名白名单,只有通过白名单检测的请求才记录上报,拿到配置之后更新本地配置。同时,该机制也可以过滤掉第三方的冗余数据,专注在我们想要收集的数据上~
设备绑定,这里除了deviceId外还需要以下这些信息
deviceId 设备唯一标识
platform 平台(iOS、Android、PC、web)
appCode 哪个项目的
appVersion app 版本,后续有可能会根据版本做兼容策略
配置信息
{
whiteList: { //针对域名
domain: ["xxxx","xxxx"]
},
blackList: { //针对接口
api:["host/v1/student/device/registerDevice","xxxx"]
},
batchUploadCount: 100 //批量上传条数
}
接口说明:
白名单是针对域名的,里面的 domain 字段是为了过滤第三方的网络请求。
黑名单是针对接口的,api 字段记录着完整的请求url,主要是为了防止特定用户的一些特定接口高频次失败影响了整体的数据。
batchUploadCount 为批量上传数目,达到设置的条数时再压缩上传。
第一次启动默认所有符合内置规则的请求都会上传,配置接口返回数据(为空这data为{})后更新本地默认规则,后续按照更新后的配置进行过滤。
接口文档地址:这里
如下:黑白名单配置---采集流程图:
获取配置时机
app启动时候,异步,延迟1.5秒再加载,防止影响启动速度和正常业务请求。失败则原url进行重试3次,根据域名白名单规则,该获取配置接口也同样会纳入监控。
- 4.对接通用平台链路追踪
客户端:
为了能够跟通用平台的链路追踪串联起来,实现请求从“客户端-路由器-服务器-具体响应服务器”一整条链路的完整监控,客户端这边需要 hook 每个请求,然后在请求头里面加上 requestId、linkWayId、linkWayPid。
requestId 用于标识唯一请求,生成规则:bunderIdentifier_uuid_threadId_timestamp 。
linkWayId、linkWayPid 用于以树状图的形式分析和呈现调用链路,linkWayId 取值为 1,linkWayPid 取值为 0。