在当下互联网行业,监控概念与重要性已经不须要再进行阐述,然而监控分为多种,对物理层(机房,云主机)的监控,对传输链路的监控,对已部署服务的监控等等,然后端的代码一般直接运行在服务器并处于24小时实时的监控状态之下,一旦服务的可用性出现问题,SRE和DEV每每在第一时间就会收到告警,并根据告警信息在第一时间解决故障。相比之下,前端代码则运行在客户端上,为了让前端可以和后端同样,须要将客户端的前端代码监控起来,当客户端出现故障时,能第一时间通知到前端负责人,定位故障,及时止损。javascript
那前端监控系统都须要监控什么?在前端应用日渐复杂的今天,我认为对于前端的监控主要分为三个方面:html
性能监控前端
为何要监控性能呢?由于对于任何一家互联网公司,性能每每与利益直接相关。有数据调查显示:当Google 延迟 400ms时,搜索量降低 0.59%、Bing 延迟 2s,收入降低 4.3%、Yahoo 延迟 400ms,流量降低 5-9%,因此,不少公司在作用户体验分析时,第一个看的就是性能监控指标,在前端领域,性能无非是如下参考指标:java
并且很重要的一点,也是你们每每最容易忽视的:性能会伴随产品的迭代而有所衰减。特别在移动端,网络条件十分不稳定的状况下。性能优化不存在“黄金法则”,咱们须要一套性能监控系统持续监控、评估、预警页面性能情况、发现瓶颈,更有针对性的指导优化工做的进行。
webpack
异常监控
web
除了性能以外,咱们还要监控客户端脚本发生的报错,前端报错受网络,机型,业务逻辑影响并且大部分错误难以还原现场,好比咱们团队时时收到用户的反馈和投诉:ajax
面对用户的反馈,开发常常感到困惑:到底有多卡,哪一个步骤卡?是个别现象仍是大面积都受到了影响?白屏时页面请求的返回码是多少?是被运行商劫持仍是CDN出了问题?能让用户用Charles配合抓个包么?如何作有针对性的优化?优化的结果怎么去衡量?算法
为了解决这些痛点,咱们须要对客户端服务进行基于用户行为的监控。chrome
数据监控数据库
互联网公司的产品,每个决策,每个迭代都须要分析各类数据,数据中每每会有咱们须要的答案:
这部分监控数据主要供PM/PD使用,业务数据能够驱动业务自身的增加,有人曾说:“要下降创业失败的可能性只有两种方法:一是未卜先知,另外一个是作精益的数据分析”,因而可知数据分析的重要性。
目前已经存在了一些针对前端监控解决方案:Sentry,Badjs,jsTracker,GrowingIo等等,在公司内部也有自研的监控系统。它们都从不一样维度试着解决前端在监控方面的问题,你们的实现思路都很相似、要实现监控,首先要采集指标:
这里要针对的主要是白屏时间、首屏时间、用户可操做、总下载时间。
这里以首屏时间为例:高版本chrome浏览器中能够直接经过 firstPaintTime 接口来获取load time,但大部分浏览器并不支持,必须想其余办法来监测。谨记一点,在作时间相关测量时,千万不要使用setTimeout和setInterval方法,由于在单线程执行引擎中,异步队列的执行是不能确保执行时间的。这边给出一种可行的测量方案,准确率在99成以上。
<doctype html>
<html>
<head>
<script type="text/javascript">
var timerStart = Date.now();
</script>
<!-- 加载其余资源,执行代码blabla -->
</head>
<body>
<!-- 路由框架挂载节点 -->
<script type="text/javascript">
$(document).ready(function() {
console.log("DOMready 时间 ", Date.now()-timerStart);
});
$(window).load(function() {
console.log("全部资源加载完成 时间: ", Date.now()-timerStart);
});
</script>
</body>
</html>
复制代码
另外一种优雅的解决方案是直接使用window.performance接口:
connectEnd Time when server connection is finished.
connectStart Time just before server connection begins.
domComplete Time just before document readiness completes.
domContentLoadedEventEnd Time after DOMContentLoaded event completes.
domContentLoadedEventStart Time just before DOMContentLoaded starts.
domInteractive Time just before readiness set to interactive.
domLoading Time just before readiness set to loading.
domainLookupEnd Time after domain name lookup.
domainLookupStart Time just before domain name lookup.
fetchStart Time when the resource starts being fetched.
loadEventEnd Time when the load event is complete.
loadEventStart Time just before the load event is fired.
navigationStart Time after the previous document begins unload.
redirectCount Number of redirects since the last non-redirect.
redirectEnd Time after last redirect response ends.
redirectStart Time of fetch that initiated a redirect.
requestStart Time just before a server request.
responseEnd Time after the end of a response or connection.
responseStart Time just before the start of a response.
timing Reference to a performance timing object.
navigation Reference to performance navigation object.
performance Reference to performance object for a window.
type Type of the last non-redirect navigation event.
unloadEventEnd Time after the previous document is unloaded.
unloadEventStart Time just before the unload event is fired.复制代码
接口兼容性:
异常指标
主动捕获异常方案主要是 onError 和 addEventListener,onError 在 IE6 开始就支持了,因此 大部分系统的主动采集是使用的 onError。这里注意浏览器的同源性策略(CORS),在高级浏览器中若是浏览器捕获到了错误信息,若是 JS 文件所在的域名(如:meituan.com)和当前的页面地址(如:dianping.com)是跨域的,那么引擎会自动把onError 中的参数 替换为 script error,此时没法获取行列数以及报错详细信息。解决方案是在标签引入时加上crossorigin字段。
虽然传统方法可以自动catch大部分错误,可是也伴随着如下缺陷:
数据指标
传统监控方案采用的都是手动埋点上报,可是缺点十分明显:手动埋点每每会出现埋点混乱,甚至埋错、漏埋的问题,埋点沟经过程中,数据团队和业务工程团队配合困难,新功能的开发每每伴随着新埋点的增长,并且数据团队的需求优先级每每靠后,致使不少新上线功能得不到数据的验证。
而有些基于关系型数据库的系统实时性差,数据要隔天才能查看,查询命令执行一次动辄耗费几十分钟乃至上小时,已经没有效率可言。
伴随着上面讨论的问题,咱们寻求新的解决方案,一种高可用的监控方案。它应该具备以下特征:
根据这些需求,咱们团队打造了一套全新的监控体系,新系统采用了无埋点SDK(小程序),ELK作本地化日志存储,并使用了基于动态阈值的告警策略。下面是系统架构图:
开发人员在本地经过代码接入SDK后,便可使用监控体系的所有功能:数据采集,上报,聚合分析,智能告警等功能,并且全部数据均是实时上报,秒级查询。
在最开始探索过程当中,咱们使用webpack插件+npm包下载方式,可是因为两部分上报逻辑在网络极差的状况下,会出现写缓存冲突的问题,致使重复上报或错误上报,现已将架构调整为单一script标签引入的方式,部分保留下来的主动上报接口,开发能够根据本身须要在业务代码中再次封装:
...
moduleClick(options) {
const { name, ...otherOptions } = options;
M.moduleClick(name, otherOptions);
},
/**
* 曝光事件
* @param options
*/
moduleView(options) {
const { name, ...otherOptions } = options;
M.moduleView(name, otherOptions);
},
/**
* 编辑事件
* @param options
*/
moduleEdit(options) {
const { name, ...otherOptions } = options;
M.moduleEdit(name, otherOptions);
},
...
复制代码
接入后,新版系统和以前相比有哪些变化?
1.由于采集是无埋点全量的,关键方法都会进行参数上报,而后能够经过分类聚合创建用户的操做时序,经过故障上下文准肯定位问题。
2. 对resource和ajax请求指标作采集,能够筛选出故障用户当时的场景信息:
3.告警采用动态阈值,对于周期性强的数据,经过机器学习的算法进行环比告警,大大下降了误报和漏报:
在作日志存储的时候,数据量是一个挑战,咱们采用的是集群架构,可是一个用户量很大的站点,日志的上报量是很是高的,高峰期时,一个1万日活APP可能会突破3000的qps,这对日志系统并发能力和稳定性是很大的挑战。咱们选择了全量master+data节点的方式,对数据副本分片设置为1,任意一台节点挂掉,会由副本选举出新的主分片,不会形成日志丢失。在写入方面,咱们选择了bulk方式批量写入,经过反复试验,批量写入线程大小在5MB-15MB之间。因为日志系统是主要面对写入的,因此关闭了_all查询,写入性能优化1倍,同时gc新老分配为1:4,保证了批量写入的稳定。
经过对新监控解决方案的探索,咱们积累了比较宝贵的数据分析经验,对于终端哪些数据对于故障处理,性能优化起到重要做用有了新的认识,不过目前系统仍处于迭代过程当中,距离预约的目标还有比较大的优化空间。
在将来,咱们将重点攻克如下几个问题:
减小上报量:合并数据结构,释放更多上行带
优化SDK性能:减小缓存写入频率,作到业务对监控模块无感
识别周高峰和节假日,同时加强数据清洗能力,提升数据的可用性
3.优化数据分析体验
开放埋点配置平台,让产品自主配置业务埋点,经过配置文件转化成埋点,省时高效。