有货前端 Web-APM 实践
有货前端 Web-APM 实践
0 背景
有货电商技术架构上采用的是前后端分离,前端是主要以业务展示和接口聚合为主,拥有自己的 BFF (Backend For Frontend),以 nodejs 为核心;后端以提供较小的业务数据接口,业务服务实现为主,以 java 技术体系为核心。在实际应用场景下,前后端应用对系统性能的关注点是不一样。因此,前端团队需要跟据自身的需求,来搭建自己的 APM 系统。
1 前端的需求
对前端团队来说,用户体验至关重要,而页面的打开速度就是用户能感知因素中最重要的一环。前端团队对 APM 的需求就是要尽可能地收集与页面打开速度有关的因素。经过对业务和技术的讨论,我们认为以下方面影响了页面的加载速度:
(1) 前端加载时间
在前端页面加载是用户感知的第一层。我们使用以下指标:
- 首屏加载时间:first-Screen
- 文档加载时间:DOMContentLoaded
- 页面加载时间:load
- 页面脚本错误:error
除了加载时间,我们还会把前端 js 运行过程中出现的错误上报。这样,我们就能够及时发现问题,快速修复上线,使公司损失最小化。
(2) 业务处理时间
- http 响应时间:req-res-time
- http 请求状态:http-status
这块我们主要关注页面请求到页面响应完成的时间:req-res-time,这个时间能够代表我们系统的响应速度,所以这个指标能衡量当时系统的性能。
此外,还会针对 http 状态码这个值也进行记录,这样就可以知道哪些路由有问题,这样就可以通过状态码的情况得到系统的健康程度。
(3) 接口调用时间
- api 调用时间:api-time
- api 调用状态:api-status
这块我们会对每个接口都监控其调用时间:api-time。同时我们还会针对每一次请求生成一个唯一 ID,对这个请求所调用的 api 进行标识,这样我们就能分析出,页面调用的接口数,每个接口调用的时间,接口的调用顺序等,这些数据对后端的压测和服务治理会非常有用。
同时,对 api 的响应状态码进行监控,方便及时了解后端接口的基本情况。
(4) 系统运行状态
- cpu 使用率: process-cpu
- memory 使用率: process-mem
这块包括系统 cpu 和 memory 的使用情况做了收集,方便我们知道机器的情况。
针对这四个方面,我们设定了这 10 项指标,通过这些指标,我们这能全方位对我们网站业务的速度和稳定性进行了解,方便以后优化。
2 整体架构
Web-APM 在整体架构设计上,分成了六个部分,如下图所示,包括 client,service , collector, storage, api ,ui。箭头代表数据的流向。其中,client 和 service 是收集指标并发送指标,而 collector 作用是汇集指标,过滤数据,存入 storage。 stoarge 的存在,我们是希望能保存一段时间数据情况,方便事后进行查找和分析。 而 api 则是对 storage 的数据对外提供一个接口,方便监控和分析;ui 是提供一个界面,方便使用者进行查看。
3 实现
从实现角度来看,我们还是比较功利的,即采用我们自己熟悉的技术,并没有上来就使用 ELK Stack,这其中是有原因的:
- 数据的体量上,百万级的已经够用了,用不上大数据这一套存储
- 前端组的技术能力上,与 ELK Stack 技术栈不匹配,不能吃透这套技术
- 当前也没有合适的人去做这块技术工作
于是我们跟据自己的需求和整体架构,在实现系统角度上,划分成了多个层,每个层有各自的选择,如下图所示:
注意:图中灰色的部分为未来规划。
从图中可以看出,Web-APM 系统实现上分成了 4 个层,分别是采集层,收集层,存储层和监控层,每一层我们都选择了合适的技术来实现。
(1) 采集层
采集层对应着我们的需求,也分成了四块,包括前端层(yas.js), 业务层(yohobuy-node),数据层(yohonode-lib),系统层(yohonode-lib)。不同的层采集不同的数据,最终数据发送到收集层。
收集数据时,解决以下问题:
首屏加载
我们参考了这篇文档,思路就是计算首屏基线高度之上的所有图片元素 onload 之后的时间。
错误处理
利用 window.error 接口来实现,self.writeError 里面就是我们自己的上报逻辑。
数据一致性
数据采集时,我们注意到数据不一致的问题。如路由。电商的页面很多,最多可能就商品详情页。从前端的角度来看商品详情页,每个商品详情页就是不同的 path,如 https://www.yohobuy.com/product/51768088.html
。但从后端来看就不过是一个参数 :id 而已,如 https://www.yohobuy.com/product/:id.html
。我们做监控的时候,不可能针对一个商品的链接进行监控,这样是没有什么用的,我们希望对商品详细页这一类的页面进行监控。 如果以前端的 path 来进行数据统计就只能统计到单个页面的问题,不能统计到商品详情页这一类的情况。当然可以在进行数据汇集的时候,进行正则匹配。这对我们来说不太现实,因为我们网站信息架构调整了多次,路由也已经调整多次,在另一个地方进行正则就意味着,要在另一个地方维护一张正则映射表。我们希望的是就一个地方维护路由。
这个问题我们进行了多次讨论,在技术的可行性下,选择了一个技术方案,在每个页面中,写入一个全局变量,把这个页面的后端路由的 md5 写入到页面中,这样只要这个页面路由不变,这个值就一直不变,发送监控数据就把这个路由值也带上,这样前后端的数据情况就能通过路 由对应起来,这样就能更方便的统计数据。
请求跟踪
有货前端项目都是以 nodejs 为核心建立的技术栈。在 nodejs 中,一直缺较好的技术手段对异步进行跟踪和标识。目前来看 async_hooks 技术应该是比较好的候选方案。但在对我们来说,该技术不是很合适。因为我们关注点会更高一点,只是针对业务流程进行跟踪,而不是对每一个异步进行细致的分析。在考虑到业务实际情况和技术实现,我们选择了基于 reqId 进行业务流程跟踪的方案。该方案时序图如下:
在页面请求过来时,我们针对这个请求生成 reqId,并且在调用后端 api 时,会带上 reqId 生成的上下文 ctx。最后返回页面时,把 reqId 写入用户的 cookie 中。同一个页面 ajax 发送请求时,就会去判断是否有这个 reqId,这样就能区分是页面上的 ajax 的请求,还是别的方式造成的请求,同时也能统计出页面上调用 ajax 请求的个数。
(2) 收集层
收集层,主要定义一下从采集端过来的数据的形式(influxdb),以什么协议传输(http)。
对打入的监控数据进行一个缓冲(buffer),依据条件过滤(filter)出我们需要的数据,把数据形式转化(tranform)成我们想要的形式,洗好的数据定时写入存储层中。
(3) 存储层
在存储层,我们分为在线存储和离线存储。在线存储是与监控有关需要实时交互的数据,使用 influxdb 时序数据库。在写入时,我们会把数据再精简,把最简单关键数据写入 influxdb 中,如 http-status, api-status, process, 方便下一层监控层使用。
influxdb 的使用中,我们也碰到了问题。例如:写入数据时 tag 过多,导致查询数据缓慢,我们就精简数据;为提高 influxdb 性能,会做一个队列,批量写入数据。
离线存储,我们选择是 mysql。在写入时,我们对数据进行过滤,主要保存错误,异常和慢路由。如前端和后端发生的错误(包括堆栈),还有哪些路由的请求时间长于 2000ms,这样方便我们进行离线地分析查看和统计。
还有,由于我们项目的特点,对离线储存不是要求一直保存,我们会定时对 mysql 进行整理和清除,当前我们就只保留最近 7 天的数据,这样我们离线储存的压力就比较小。
(4) 监控层
监控层,也是分成两部分,一个部分为 grafana ,利用 grafana 和 influxdb 配合的提醒功能,能对我们的线上环境通过短信和邮件实时进行提醒。
另一部分是我们的 ci 系统,包括一个监控面板,用于查看详细的错误情况和路由情况。特别对于前端,我们发布的代码都是经过 webpack 打包的代码,直接去找错误行列肯定是找不到的,因此我们生打包生产代码时,会生成 source-map 文件,ci 查看前端脚本错误,会去解析 source-map, 拿到出错代码的前后 20 行,这样能方便地定位前端的错误。下图为解析 source-map 的相关代码和前端的展示结果。
ci 每天都会去 mysql 查看路由的情况和异常的情况,生成统计报表,邮件给前端团队。
5 接入方式
当前 agent 代码,前端代码,为了满足功能需要,引入方式是将压缩后的 agent 代码直接插入到布局模板的 head 中。配置项目是在压缩时就写入定值,这样能减少项目接入的复杂度。
对于服务器端代码的接入,我们当前没有采用独立进程的方式进行部署,这是因为使用者和维护者都是同一个团队,还有监控这块对性能影响并不是很大。所以我们接入方式大部分直接引入一个独立的包就可以完成工作,少部分的代码直接传入 express 对象代理接口和监听事件,就能拿到数据,做到业务无感知。
收集层我们是独立部署的一台服务器,这样会更方便。而且我们设计成收集层挂了,也不会影响我们采集层的工作。
下图是系统的调用情况:
6 总结
当前,有货 Web-APM 基础的功能已经完成,已经在有货 pc 站和 h5 站项目进行部署,进行监控数据的上报。通过一段时间的使用,我们解决了多数由于字段未定义造成的系统问题,使我们的系统更加稳定;前端我们也针对情况有选择性地进行了页面的优化。详细的优化方案,请看这篇 有货移动WEB端性能优化探索实践2。
在未来,我们会对 Web-APM 进行以下方面优化:
- 增加浏览器端的数据采集深度,会从 performance, resource, navigator, screen 等方面进行信息采集
- 结合用户的 IP 和 useragent,对用户浏览器端进行分析
- 更详细的每日分析报告
- 告警机制更智能化
以上就是我们有货前端团队 Web-APM 的实践分享,欢迎大家批评指正。
有货前端 Web-APM 实践的更多相关文章
- 前端web worker实践与总结
参考链接:https://www.jianshu.com/p/97f6144dfddf
- web前端开发最佳实践笔记
一.文章开篇 由于最近也比较忙,一方面是忙着公司的事情,另外一方面也是忙着看书和学习,所以没有时间来和大家一起分享知识,现在好了,终于回归博客园的大家庭了,今天我打算来分享一下关于<web前端开 ...
- Web前端优化最佳实践及工具集锦
Web前端优化最佳实践及工具集锦 发表于2013-09-23 19:47| 21315次阅读| 来源Googe & Yahoo| 118 条评论| 作者王果 编译 Web优化Google雅虎P ...
- [转] Web 前端优化最佳实践之 Mobile(iPhone) 篇
原文链接:http://dbanotes.net/web/best_practices_for_speeding_up_your_web_site_server_mobile.html Web 前端优 ...
- 【社区公益】送《Web前端开发最佳实践》给需要的人
算起来至今,我进入软件开发行业已经有11年之久.从最初的研究人工智能,到后来的Web开发,控件开发,直到现在纯粹的Web前端开发.虽然没有大的作品问世,但也是勤勤恳恳,踏实做事,低调做人.从来不吹牛逼 ...
- Web前端开发最佳实践系列文章汇总
Web前端开发最佳实践(1):前端开发概述 Web前端开发最佳实践(2):前端代码重构 Web前端开发最佳实践(3):前端代码和资源的压缩与合并 Web前端开发最佳实践(4):在页面中添加必要的met ...
- Web前端开发最佳实践(9):CSS代码太太乱,重复代码太多?你需要精简CSS代码
前言 提高网站整体加载速度的一个重要手段就是提高代码文件的网络传输速度.之前提到过,所有的代码文件都应该是经过压缩了的,这可提高网络传输速度,提高性能.除了压缩代码之外,精简代码也是一种减小代码文件大 ...
- Web前端开发最佳实践(8):还没有给CSS样式排序?其实你可以更专业一些
前言 CSS样式排序是指按照一定的规则排列CSS样式属性的定义,排序并不会影响CSS样式的功能和性能,只是让代码看起来更加整洁.CSS代码的逻辑性并不强,一般的开发者写CSS样式也很随意,所以如果不借 ...
- Web前端开发最佳实践(7):使用合理的技术方案来构建小图标
大家都对网站上使用的小图标肯定都不陌生,这些小图标作为网站内容的点缀,增加了网站的美观度,提高了用户体验,可是你有没有看过在这些网站中使用的图标都是用什么技术实现的?虽然大部分网站还是使用普通的图片实 ...
随机推荐
- git本地项目关联远程仓库
应用场景: 当你在开发一个项目的时候,不想只在本地存储,想用git来管理代码时候的. 1.在你的项目根目录打开git命令窗口,通过 git init 命令把这个目录变成Git可以管理的仓库: git ...
- Mininet简介
在Coursera SDN开放课程中,编程作业要用Mininet来完成.这里对Mininet做一个简单的介绍. 什么是Mininet Mininet是由一些虚拟的终端节点(end-hosts).交换机 ...
- 学习笔记-C++ STL iterator与对指针的理解-20170618
vector的itrerator支持random access #include<iostream> #include<vector> using namespace std; ...
- curl post请求总是返回417错误
在进行post请求的时候, curl总是返回417错误 在使用curl做POST的时候, 当要POST的数据大于1024字节的时候, curl并不会直接就发起POST请求, 而是会分为俩步. 发送一个 ...
- 使用Angular CLI从蓝本生成代码
第一篇文章是: "使用angular cli生成angular5项目" : http://www.cnblogs.com/cgzl/p/8594571.html 这篇文章主要是讲生 ...
- nginx的自动化安装和启停脚本
个人原创,转载请注明出处和作者,否则依法追究法律责任 author: headsen chen date: 2018-03-07 14:39:11 nginx_install.sh #!/bin/ ...
- parted分区及挂载实战操作大全
个人原创博客,转载请注明,否则要负法律责任 2017-09-29-14:46:25[root@localhost ~]# df -hFilesystem Size Used Avail Use% Mo ...
- Spring OAuth2 GitHub 自定义登录信息
# 原因 最近在做一款管理金钱的网站进行自娱自乐,发现没有安全控制岂不是大家都知道我的工资了(一脸黑线)? 最近公司也在搞 Spring OAuth2,当时我没有时间(其实那时候不想搞)就没做,现在回 ...
- JDBC控制事务
概念 事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit).事务通常由高级数据库操纵语言或编程语言(如SQL,C++或Java)书写的用户程序的执行所引起,并 ...
- UML系列图
用例图: 时序图: