导读
背景
北斗前端监控系统
北斗前端监控系统,是由58同城前端通道发起,用户增长前端技术部主导,多个事业部成员协同研发的基础设施;立项至今北斗经历了多轮迭代,系统能力逐步得到完善,目前支持web和react-native两种技术栈的页面流量、性能、JS运行异常、接口调用异常、资源加载异常的实时数据收集、分析和查询,以及多维度分钟级的告警服务,保障了前端团队对于线上状态的快速感知,除了基本的监控环节外,还落地了用户行为轨迹日志和前后端数据全链路的高阶能力。截止2021年4月份,已经有几十个独立团队、170+的项目接入使用。
以下为北斗前端监控系统的架构全貌:
SDK层(SDK Layer):分为JSSDK和RNSDK,部分数据由native补充收集。
数据接收服务层(Data Collect Service Layer):接收SDK上报的流量、错误、接口、资源、性能数据,并执行数据清洗和动态采样策略。
数据存储层(Storage Layer):北斗的数据存储仓库同时使用了Druid和Elasticsearch,Druid主要用于存储预聚合数据,方便获取复杂场景下的指标聚合数据,而Elasticsearch用于存储明细日志,方便明细查询。
核心服务层(Core Service Layer):主要包含告警分析、采样率计算等定时任务、第三方日志平台数据接收、可视化平台业务;北斗团队实现了一套node定时服务模块来处理复杂而灵活的数据计算,并支持集群部署;
可视化平台(Web DashBoard):功能包括权限管理、项目管理、日志查询以及各维度、各层次的日志可视化图表展示;
北斗前端监控系统的目标和未来的发展方向是打造前端全领域的线上质量监控系统,并对前端各种线上质量数据做整合,最终形成一个前端线上数据平台,对外提供数据服务。在北斗团队不断迭代和完善系统的过程中,我们遇到了很多挑战,这些挑战来源于越来越广泛的业务场景,不断提升的流量负载,以及系统规模不断扩大而引发的维护成本提升。在下面的文章内容中,会详细介绍挑战和解决方案。
SDK碎片化管理
在前端web工程中,如果你想引入一个JSSDK一般有两种方式:
在页面的<head>中引入一个<script>标签,<script>的src属性设置JSSDK的地址,而JSSDK的地址会附带版本号。
另一种方式是在package.json中配置dependencies依赖项,通过npm 或者 yarn 集成进工程中。
从业务方的角度看,这两种引入方式都很常见,没什么问题;但是从SDK维护方的角度来看,就有所不同;SDK会不断迭代,定期、不定期增加新功能,修复一些问题,维护团队有尽快发布版本,让业务方快速完成更新的诉求。但是当北斗支持的业务不断增加的时候,引入SDK的工程数量级会变的越来越大,这意味着新版本的落地会存在长尾问题,进而衍生出版本碎片化问题,这将大幅提升北斗监控系统在SDK和服务方面的维护成本。
为了解决快速更新的问题,北斗团队考虑了三个方案:
方案一:
<script type="text/javascript" crossorigin="anonymous" src="https://j1.58cdn.com.cn/beidou-
sdk/browser/bundle.lazyload.js"></script>
在<script>标签中引入的是SDK的动态加载器地址,动态获取SDK最新的版本号,然后拼接最新的SDK地址,动态创建<script>标签,实现SDK的加载;
这种方式解决了动态升级的问题,但是有比较严重的缺陷。首先,SDK注入环境的时机较晚,需要执行两次串行的网络请求,这会导致在SDK注入环境前产生的部分日志数据获取不到,比如接口信息需要SDK对XHR或者Fetch做hook,如果接口在SDK注入前调用完成,意味着就获取不到request和response数据;其次,如果版本通过接口来获取,每天亿级的请求会增加版本号服务的负载压力。
方案二:
通过npm 接入SDK,指导业务方配置package.json的dependencies时使用^。方案的优势在于可以使用npm、yarn这种包管理工具;劣势是SDK的较大版本升级依然依赖于业务方上线。
方案二还有一个改造方案,就是也集成一个动态加载器,但是缺陷其实和方案一相同。
方案三:
<script type="text/javascript" crossorigin="anonymous" src="https://j1.58cdn.com.cn/beidou-
sdk/browser/bundle.min.js"></script>
在<script>标签中设置SDK源码地址,不设置版本号,通过强制更新cdn节点资源的方式进行升级,同时针对这一个JS,定制较短超时时间,解决缓存问题。优势在于这样既解决了方案一的注入时机比较晚的问题,也减少了额外的接口服务成本;劣势在于cdn节点请求量会大幅增加。
最终,我们选择了方案三,并实现了一整套部署流程。cdn节点的请求量增加的问题,由于只有一个js资源,所以对cdn节点压力增加并不会很高。
除此之外,58同城内部还有TMS的机制,这种机制通过前端上线流程和Java工程相关联,<script>是通过一个Java服务动态引入,每次JSSDK新版本上线,都会触发Java服务生成带着新版本号的JSSDK地址。这种方式相比方案三更为合理,但是前后端分离的工程数量逐渐增加,由前端自行维护模板,顾TMS是方案三的补充。
强制全量版本升级,风险比较大,对于质量的要求非常高。所以我们还会做如下工作:
行为轨迹日志
北斗的行为轨迹日志收集支持两种环境:web端和App端,可以收集包括性能、JS错误、自定义日志、交互、接口、资源、hybrid调用7种类型的日志,其中接口、资源会收集所有正常和异常的请求,hybrid调用为App独有。多端多类型的日志数据来源也较为多样,如何整合日志成为北斗团队面临的挑战。
行为轨迹日志的交互流程如下:
行为轨迹日志能力的架构设计过程中,北斗团队调研了上文7种日志的采集技术,也收集了集团现有中台基础设施对相关日志类型的采集能力,发现不同端都有各自的日志上报渠道,有些类型的日志在其他中台基础设施已经采集。本着北斗前端监控系统在未来整合前端线上质量数据的目标,我们制定了两个策略:「连接」和「借力」。
在浏览器端和微信端,北斗JSSDK负责收集7种行为轨迹日志类型的5种,包括性能、JS错误、自定义日志、接口、资源,并且通过用户特殊事件触发直接上报到北斗平台。
集团内的WMDA系统收集了北斗需要的交互日志。WMDA的SDK会把用户唯一标识注入根域的cookie中,为了连接WMDA,北斗JSSDK读取了WMDA的唯一标识,合并进北斗日志一并上报,在北斗平台展示某个用户近期的行为轨迹日志日志时,根据WMDA的唯一标识按需从WMDA拉取交互日志,实现交互日志的整合。
在App端,58同城的APP工厂有自己的用户日志上报机制,是由Wlog组件实现的,北斗的行为轨迹日志在APP端与APP工厂合作,「借力」了这个通道。北斗的JSSDK采集的日志会通过工厂的Hybrid组件的接口存储到APP,同时Hybrid组件也会采集Hybrid的交互日志及核心实现日志。Wlog组件会根据时序和类别对H5日志和Native日志进行整合,并响应用户的操作,将日志上报到Wlog平台,再实时同步到北斗行为轨迹日志服务。
北斗行为轨迹日志服务会执行两个环节,一个是日志文件解密,一个是日志结构的转换,最终转换为北斗的日志结构,存储到Elasticsearch中。
前后端链路打通
前后端链路打通的好处主要有两点,一方面可以收集全链路的性能,包括Api响应时间和外网耗时,另一方面可以真实还原某个异常请求的全部执行过程。
58同城内部的后端链路追踪系统是基于Skywalking开发的WTrace,北斗作为前端监控系统和WTrace连接的初步方案如下:
1、由北斗JSSDK针对每个Api请求生成sw8键值对,并注入header,sw8的值是一组数据,其中包含链接前后端日志的唯一标识。
2、北斗JSSDK通过拦截的方式收集Api的request和response数据,并上报;
3、WTrace在后端服务中除了收集原有请求日志之外,还收集了请求header头中的sw8。
4、北斗增加sw8及WTrace跳转链接的展示,WTrace支持通过sw8展示后端服务链路日志。
从前端视角来看,以上策略实现起来并不难,可是忽略了一个事实,每天集团Web页面的Api接口调用达千亿级别,北斗和WTrace全量采集都需要非常大的集群规模,这显然不能接受。那么需要解决的问题有几个方面:
1、保证服务集群小规模扩张的情况下,服务器负载可以维持正常状态;
2、保证北斗和WTrace可以收集相同Api调用的日志,实现打通;
3、保证服务接收到的日志可以支撑异常排查和全链路性能分析;
经过考量,我们制定了最终的技术策略:
1、Api请求分而治之,异常Api请求量级较小,全量收集;正常Api调用量级较大,采样收集;
2、在采样方面,由于web页面是接口的请求发起端,故由北斗JSSDK来设置基准采样率,WTrace基于基准采样率做处理;
3、在兜底方面,如果存在某个业务突然业务量剧增,导致接收服务负载过高,可以通过JSSDK实时更新来解决。同时WTrace内部也设计了兜底机制,在采样日志量级超过阈值之后,也会做二次采样。双层采样机制,可以保证以最小的成本实现目标。
除了上报流程,北斗在内部也与WTrace有交互,北斗内部调用WTrace的提供的服务,按需读取sw8在WTrace的采样标记,实现真正数据打通。
整体的方案架构如下:
提升聚合分析页面的加载性能
北斗在上线三周之后,集团内部的前端团队踊跃接入,日均支持的PV很快达到了5000w的级别。此时,暴露了一个问题,就是平台分析页面的加载性能比较差,主要原因是接口耗时比较长。
分析页面主要展示了不同质量指标在不同环境条件下的区间变化趋势,接口的数据主要来自Druid,由于Druid集群的机制是在服务器资源固定的情况下,单日存储数据量越大查询速度越慢,所以北斗在调用Druid时,会耗时较长。
针对Druid调用耗时长的问题,我们在两个环节尝试解决问题:
1、尝试在不影响平台使用的条件下降低存储量;
2、优化Druid的调用细节。
虽然存储量可以通过扩大Druid集群来解决,但是降本提效还是技术团队的必修课。为了减少存储量,北斗把优化的目标放在了减少性能数据上面,因为异常数据分析看的是总数,可是性能指标比如页面完全加载时间、最大内容绘制时间主要取平均值,总数一个都不能少,平均值并不需要。
首先,我们设定了一个阈值:100w,采样规则是日性能数据100w以上的项目只存储100w条,100w以下的项目全量存储,这个阈值是动态可变的。
其次,我们设计了小时级动态采样率,动态采样率的计算公式为:
每小时动态采样率 = 100w /项目性能数据上报总量(过去24小时)
如果动态采样率 < 1,将执行采样存储,采样率 为 动态采样率,如果动态采样率 >= 1,意味着上报量小于等于100w,则不执行采样存储。
第三,采样处理没有部署在JSSDK中,而是部署到了北斗日志数据接收服务中,每小时读取一次Redis的采样率数据,而Redis的采样率数据来自另一个定时服务每小时的计算。
整体数据操作流程如下:
上线动态采样处理后,Druid的日存储数据量从5.4亿下降到了1.2亿,接口响应时间也有大幅降低。
Druid提供了两种查询方式,一种是DSL查询,一种是SQL查询。SQL和MySQL的SQL非常类似,学习成本较低,成为了我们的方案选择。
早期我们在研发的时候,为了后续研发简单,把SQL的查询粒度锁定为每一个指标,比如我想查询错误pv比,需要调用两次,先查询pv,再查询错误数,最后在通过node组合:
# pv
SELECT TIME_FLOOR(__time,PT1M,'Asia/Shanghai') timestamp, projectId , COUNT(DISTINCT pid) pv
from hdp_ubu_tech_wei_beidou_data where __time>='2020-11-12 00:00:00+08:00'
and __time<='2020-11-12 23:59:59+08:00' and projectId = '100' group by 1, projectId
# 错误数
SELECT TIME_FLOOR(__time,PT1M,'Asia/Shanghai') timestamp,projectId , COUNT( content ) AS exceptionCount
from hdp_ubu_tech_wei_beidou_data where __time>='2020-11-12 00:00:00+08:00'
and __time<='2020-11-12 23:59:59+08:00' and projectId = '100' group by 1, projectId
这样的方式虽然组合很清晰,但是耗时很长。后来我们做了优化,如下:
SELECT TIME_FLOOR(__time,PT24H,'Asia/Shanghai') timestamp ,COUNT(DISTINCT pid) as pv, COUNT( content )/COUNT(DISTINCT pid) as exceptionAvg ,COUNT(resourceUrl)/COUNT(DISTINCT pid) as resAvg,COUNT( apiUrl )/COUNT(DISTINCT pid) as apiAvg from hdp_ubu_tech_wei_beidou_data where __time>='2020-11-12 00:00:00+08:00' and __time<='2020-11-12 23:59:59+08:00' and projectId = '100' group by 1
通过这样的方式,我们将pv、JS错误pv比、资源异常pv比、接口异常pv比合并为1个SQL,4次SQL查询可以变为1次,同时还去掉了node的拼接处理,大大减少了请求次数,显著提升性能。
经过两个环节的优化,北斗的聚合分析页面的单node接口平均耗时从 1573.75ms下降到了780.97ms,整体下降了50.3%。
分钟级告警
在告警的整体选型和核心逻辑处理上面,我们经历了几次的抉择和优化。
早期在设计告警架构的时候,为了实现分钟级的告警体验,团队考虑过开源流处理框架,58集团内部的Wstream就是基于Flink的实时数仓基础设施,通过调研,我们发现虽然Flink支持SQL处理,但是针对一些更复杂的需求时,比如自定义函数、微服务调用等,SQL还是显得力不从心,需要其他开发语言支持,而且并不支持对于前端同学更友好的JavaScript,这会让后期的维护成本较高。
我们把目光投向了另一种比较成熟的方案:定时任务。定时任务包括三个环节:
1、监控数据读取。指定时获取前一个时间段的监控数据。
2、告警阈值配置读取。指获取被监控项目设置的告警规则,包括指标和阈值。
3、阈值判断。指用前一个时间段的数据和阈值做比较,决定要不要发出告警
后两个环节一个数据量小,来自MySQL,一个执行速度由CPU决定,显然,告警的「分钟级」,取决于第一个环节是否可以高效率的获取数据。
告警需要的监控数据和分析页面的数据非常类似,都是基于时段的聚合数据,比如一分钟内的JS错误总数,是不是我们像分析数据一样,直接读取Druid就可以解决呢?
答案是肯定的。Druid是一个高性能的实时分析型数据库,对于海量数据的聚合查询速度比较快,我们有88%的复杂聚合查询请求的响应时间都在1000ms以下,所以是可以满足分钟级的要求的。
以项目维度去查询,还是以指标维度去查询,是摆在我们面前的第二个问题。两个方案的差别在于对Druid的请求次数。
项目维度的执行过程:
指标维度的执行过程:
很显然,项目数会不断增加。如果当前项目数为N个,想实现分钟级的处理,必须要在很短时间内请求N次,N越大,意味着并发次数越高,可是Druid单节点是有并发次数限制的,这一点不能支持N无限增长。如果控制并发,串行执行,意味着N越大,约不能保证实时性。
相反,指标维度却不存在这些问题,即使串行执行,由于指标数较少,也会在1分钟内执行完成。
为了进一步提升实时性和服务器利用率,北斗实现了redlock为基础的分布式定时任务。每个指标的告警独立作为一个任务,每分钟启动一次,由集群的所有机器来争夺制定权。整体设计如下:
权限管理本身不是监控技术的范畴,是产品架构的范畴,但是设计的是否合理,会影响监控平台使用体验和后续的维护效率。
北斗的权限系统针对「静」和「动」两个方面。「静」指的是组织架构现状,而「动」指组织架构的不断调整。
目前最为普及的权限系统设计模型是「基于角色的访问控制」(RBAC:Role-Based Access Control),简单来描述,就是一个用户绑定一个或者多个角色,一个角色关联多个权限。
一般来讲,监控系统中的项目是权限操作的对象,但是从组织架构的角度来讲,项目不是唯一的权限操作对象。举个例子,如果一个部门新招入了一个员工,如果项目是权限操作对象的话,意味着需要把所有的项目挨个加上这个员工的权限。而如果把组织作为操作对象的话,只需要给员工赋予组织权限,就可以把组织之下全部项目加上权限了。
所以,北斗最终的权限设计模型,是把组织融合进了RBAC中,「静」的架构如下:
管理员和成员,均被组织隔离。这样有几个优势:
1、组织划分由集团BSP来支持,不需要将组织成员在北斗中注册,在北斗基于BSP关联一个组织,组织下的全部成员全部获得成员权限;
2、组织架构的形态有多样性的特点,叶子节点的组织和父节点的组织,都可以被关联为北斗的组织。
3、组织和组织之间的项目权限隔离,在项目管理上不会相互影响。
4、后期可以实现针对组织的监控数据报告,如果只针对项目,在一个团队中缺乏对比性。
在大公司中,组织架构的调整非常频繁,同时调整的方式也很多样,调整围绕三个维度:
1、业务。业务其实就是「项目」,业务在整合的过程中,有可能被划转给其他团队来支撑。
2、团队。团队负责的业务方向可能会发生变化,团队会在组织架构中被重建,生成一个新的部门。
3、团队成员。团队成员一般在架构调整中会流入,也可能流出。
以「静」的基础上,如何实现「动」呢?答案就在于业务、团队和团队成员三者的关系。项目会在团队间流转,团队成员也会在团队间流转,团队成员可能跟着项目流转,也可能脱离项目流转。团队成员和项目可分离的特点意味着项目无需和成员关联,这样印证了「静」中没有「项目管理员」角色的原因。由于团队成员的流转,其实和BSP相关联,所以北斗平台不需要关心,北斗最终实现了两个功能:
1、组织架构半自动同步
2、项目转移给其他组织
组织架构的半自动同步,为什么不是实时同步,原因是组织架构调整,一般会进行工作交接,一般场景是项目的原维护方,交接给新的维护方,所以项目转移是有原维护方做的,所以,如果组织架构突然调整,原维护方已经没有权限了,无法执行交接,所以先进行项目转移,再进行组织架构同步,是更合理的顺序。
最重要的是,「项目转移」权限,会下放给了组织成员角色,类似于Gitlab的「Transfer project to another namespace」,这样可以降低北斗平台维护团队的维护成本,减少了客服压力。
总结
我们在应对北斗带给我们的挑战时,深深的感觉到了前端监控领域还有很多场景我们没有实现,平台还有很多细节没有优化,所以北斗未来的路还比较长,我们会继续优化细节,完善功能,后面会推出更多对场景细化的专题文章,也欢迎各界同学可以一起交流监控项目的研发经验。
最后,给我们的团队打个广告,58同城依旧处于飞速发展的阶段,我们团队也依然坚持着对技术的渴望和追逐,奋力前行,58集团用户增长技术部持续招聘,各种职位人才都需要,欢迎各界大牛进入团队,联系方式 liyi06@58.com
有奖互动
Perseverance Prevails
欢迎在评论区为作者大佬打call,将抽取点赞前十名送出58技术周边奖品一份。
开奖时间:6月15日 18:00