作者:李振,腾讯云前端性能监控负责人
什么是前端性能监控(RUM)
腾讯云前端性能监控 (RUM) 是一站式前端监控解决方案,用户只须要装置 SDK 到本人的我的项目中,通过简略配置化,即可实现对用户页面品质的全方位守护,真正做到了低成本应用和无侵入监控。前端性能监控专一于 Web,小程序等大前端畛域,次要关注用户页面性能(页面测速,接口测速,CDN 测速等)、品质(JS 谬误,Ajax 谬误等),并且通过联动腾讯云利用性能监控实现对前后端监控一体化的买通。
前端性能监控技术架构历史
前端性能监控次要做日志和指标的综合解决,次要开发性能是 SDK + 接入层 + API 层 + 可视化。这看似简略的性能,会有什么难点呢?前端性能监控是如何实现的?
前端性能监控技术难点:高维数据的解决和高并发申请的解决。
高并发想必大家曾经耳熟能详了,咱们面对的是有数 C 端用户的数据收集和上报,业务数据的保障也会影响咱们服务的稳定性。
高维数据 可能大家都比拟生疏,那么什么是高维数据呢?
比方你要计算某个页面在某个地区,某个机型,在某个运营商下的均匀耗时,如果采纳离线计算的形式,你须要提前把每个维度下的值计算出来,这样轻松就是几十亿上百亿的维度散布!
前端性能监控最后架构
最早抉择了前端同学最相熟的 Node + MongoDB + MySQL 的模式进行开发,底层是参考 BadJS(在线收集 JavaScript error 信息的插件)的实现,并对其进行了无状态解决和容器化托管。
这套架构最次要的问题是性能瓶颈显著,而且没有方法进行指标计算。性能瓶颈次要在 MongoDB,在写并发超过一定量级后,MongoDB 就无奈接受数据写入了,即便做了读写拆散。计算瓶颈次要存在于架构设计,次要实现流程如下:
这套架构强撑了咱们很长一段时间,两头也做了有数优化,比方通过“Node 分布式定时工作”来减少计算能力,然而面对前端监控这种动辄几十亿上百亿的维度计算,基本杯水车薪。
穷则思变,既然现有架构没方法满足咱们的诉求,就寻求新的技术计划。
剖析日志存储问题
前端性能监控收集了很多用户的品质日志,包含:JS 谬误,Promise 谬误,AJAX 异样等,这些日志能够帮助开发者疾速定位问题,并且剖析页面品质。将日志存储在 MongoDB 中的,益处是读写简略,搜寻速度较快。然而日志上报量大了,应用 MongoDB 也成了性能瓶颈。
解决 MongoDB 应用问题
应用 MongoDB 咱们最早遇到的是满容的问题,用户日志上报量大了,就把日志写满了。解决方案:把每个我的项目的数据独自存一个 Collection,把 Collection 设置为“capped”即为固定汇合,每个汇合的数量固定,只容许用户保留数千万条日志,这样就能够解决了满容的问题,而且能够保障 MongoDB 的存储增长不会有突增和异样的状况,然而毕竟对于大我的项目来说,会面临查不到历史日志的状况。
除此之外还是存在很多运维问题,除了满容以外,还遇到过连接池占满,session 数异样,CPU 异样等等一系列问题。而且随着业务数逐步增多,MongoDB 这种数据库存储的模式曾经不能满足前端性能监控的业务诉求了。
引进腾讯云日志服务(CLS)
通过了周密的剖析,咱们引入了腾讯云日志服务。在应用一段时间后,果决把日志全量迁徙到了腾讯云日志服务。
切换到腾讯云日志服务后也是遇到了一系列问题,比方最常见的“CLS 查不到日志了”,然而 CLS 背地有团队撑持,而且有稳固的迭代和运维,问题嗖的一声就解决了。
优化指标计算
解决了日志一大难题,指标计算问题就显得有些“孤独只影”了。在前端性能监控中,咱们会帮用户计算各种维度值的平均值和分位数。指标该如何做优化呢?
指标计算和日志不同,除了读和写,还波及到一些对业务的了解。咱们始终陷入了自建的困扰,尝试了多种形式,也没有成果。次要起因是无奈解脱古老的技术框架。一次次闭门造车,却一次次被战胜。
偶然间据说外部团队,有成熟的指标解决计划。应用腾讯云流计算 Oceanus+ 腾讯云时序数据库 CTSDB,从此为前端性能监控指标解决开拓了一条新的路线。
改良后的前端性能监控整体架构
整体来看,SDK 做数据采集后,通过腾讯云 API 网关 + 腾讯云负载平衡的形式,把数据负载平衡发送到已部署在 K8S 集群上的日志接管层和测速计算层。这两个模块对用户数据进行限流和抽样,再调用微服务补齐字段,比方城市,运营商等,而后把日志数据通过 API 接口写入到腾讯云日志服务 CLS,再把性能数据通过 Telegraf(收集和报告指标和数据的代理)形式上报到腾讯云监控中台,最终存储在 Clickhouse(一个开源、高性能的列式 OLAP 数据库管理系统)里。
其中咱们次要精力在开发 SDK 还有日志接管层、测速解决层和查问数据的 API 层以及数据可视化局部。测速的逻辑稍显简单,上面我将具体的解说其中的设计和实现。
测速架构介绍
首先咱们收集数据是通过 StatsD SDK,把用户上报的数据汇聚成咱们想要的指标数据,其中次要是:
- count:累计值,包含 PV,自定义事件访问量。
- set:去重值,包含 UV,自定事件拜访用户数。
- histogram : 直方图,用来计算性能指标数据。
- summary:统计数据,用户计算性能和指标数据(与 histogram 相似,尽管也上报了,然而咱们次要应用 histogram)
StatsD 收集完指标数据后,通过部署在 Sidecar 上的 Telegraf agent 把数据上报给云监控中台。
Telegraf:数据收集 agent,可编写插件对接监控中台协定上报鉴权,流量限度等。
应用 Sidecar 模式的劣势:
- 通过形象出与性能相干的独特基础设施到一个不同层升高了微服务代码的复杂度
- 可能升高微服务架构中的代码反复度和代码耦合度
- 节约 Node 多过程模型对 telegraf 的重复使用
微服务抽离
从下面的架构图中还能够看到,咱们应用 Golang 做了一些微服务,其中包含
- Ipcity:把用户 ip 信息解析为城市信息和运营商信息的服务。
- Restful:提供独立的剪枝微服务。
- kafka:把数据旁路到用户提供的 Kafka 中的服务。
当然这样的微服务架构也不是欲速不达的,也是咱们通过长时间的摸索和失败得进去的教训。
拿 ipcity 举例。有一段时间发现服务器内存总是十分高,而且服务时不时解体,通过 heapdump 抓取了服务解体时候的内存 dump。
发现内存中存在一个超大的 Object,来自 ipcity。咱们把 ipcity 的过程独自跑起来,发现就曾经占有了 1.38G 的内存,而且这个内存会随着数据源进化一直变大。
这对于 Node 多过程模型来说,简直是致命的,因为每个过程都会启用一个 ipcity 的服务。
咱们用 pm2 在 8 核 pod 上启动了 8 个 Node 过程,这样一算,仅是 ipcity 就占有了 12G 的内存,内存的增长也随之带来 CPU 的增长。
于是思考到咱们应该把 ipcity 服务独自抽离进去,接入层通过 rpc 的形式进行调用,而不是每个过程中引入 ipcity 模块。
最初把 ipcity 的服务放在 trpc-go 里,接入层通过 rpc 通信实现 ip 转化为地区和运营商的工作。
这样做的成果十分显著:
- 数据接入层性能更纯正,数据转发,Node 服务做本人善于的事件;
- 彻底解决 mode 多过程模型反复消耗内存的问题;
- 把一些密集型 CPU 计算放在 trpc-go 中实现;
- Ipcity 独立降级,独立保护,不影响整个接入层。
因为 Golang 语言自身性能就远远好于 Node,并且微服务革新也缩小了接入层 CPU 抢占的一些问题。所以这个优化给咱们老本带来了微小的晋升。广州区集群 pod 数降落了 65.75%。
欣慰之余,把之前困扰咱们的 Kafka 连接池的问题也用雷同的形式革新了。业务上咱们容许用户在平台上录入一个 Kafka topic,能够把原始数据旁路给用户。
因为整个服务是无状态的,并且会随着业务上报量平行扩容,用户上报的数据会随机调配到任意一个 Node 节点上,因而须要咱们每个 Node 节点都跟用户提供的 Kafka 放弃连贯。不言而喻,这样对用户 Kafka 的连接数是一个挑战。解决办法也是通过微服务革新,把全副数据集中在几个节点上,再旁路给用户。完满解决,后续又陆续革新了告警服务,剪枝服务。
RUM 倒退遇到的问题以及解决思路
流量激增
随着业务的倒退,咱们首先面临的挑战是用户上报数据流量激增。
业务申请一段时间内突增 650%。想必这种状况对于大多数企业来说都是十分致命的。鉴于之前的教训,咱们应用单机限流 + 按我的项目令牌桶抽样解决流量突增问题。
单机限流:精确来说,是 Node 单过程内存限流,这个次要为了避免一些十分突发和异样的流量进入咱们的数据后盾。
令牌桶抽样:次要针对测速数据,履行按我的项目和接口级别的抽样管制,能够保障每个我的项目,每种类型的接口最大入库不超过某个预设值。
流量整型后的成果也十分显著,无论是部署咱们服务的 stke 节点,还是用来限流的 Redis,资源都大幅降落。Stke 广州节点数降落 40%,Redis CPU 从 57% 降落到 17%。
UV 计算
晚期应用 Redis hash-set 计算 uv,依据规定对用户惟一值(aid)取模后散布在 60 个不同的 Redis key 中,随着业务增长,尤其是“小程序垂搜”退出,UV 数据显著上涨,hash-set 占有内存大,计算耗费大的问题凸显。旧版本过后宽口径 UV 约几十亿每天,用这种形式 UV 数据应用的 hash-set 最大占有较大的内存。
这种计划显然是行不通的,重新整理需要,UV 计算是基于一天用户去重的,实质上是 DAU,咱们须要一个能够实现去重算法的计划。过后思考的有布隆过滤器和 HyperLogLog 算法,还有业界比拟风行的离线计算的计划。
HyperLogLog 是什么?
- HLL 是一种近似的去重算法
- HLL 应用极少存储空间计算不反复元素的个数
- 多个应用 HLL 统计出的维度基数能够重聚合
HyperLogLog 比照 Redis hash 还有 HLL 算法以及离线计算的形式。
比照来说,离线计算和 hash 的形式,能够提供一个高精度的 UV 计算形式,然而 hash 的形式带来极大的内存占用,离线计算的形式须要咱们引入额定的计算框架,老本较高。所以最初抉择了 HLL 算法,HLL 算法劣势是能够高效计算去重元素个数,占有空间极少,毛病是存在大量误差。上面是咱们用 1 亿 条不重合 aid 存储应用 hash 和 HLL 中内存占用的区别。
应用 hash 的形式,占用了 4.37G 的内存,应用 HLL 的形式,只占用了 12k 的内存,数据比照惊人。
当然 HLL 算法的误差也不是永远这么大的,随着其内局部桶个数的不同,占用内存大小和误差也在变动。
不过 Redis 中 HLL 默认抉择了 14 分桶,因而显示误差是 0.81%。那有没有方法缩小误差呢?惟一的方法就是本人实现 HLL 算法,目前咱们的计划是通过 Clickhouse 实现了 HLL 算法,并且应用 16 分桶,这样精度更高,而且存储空间的增长也在可承受范畴内。
页面主动剪枝
页面剪枝次要面对的是具备 Restful 格调页面和 API 的剖析和统计性能,以下图为例。
从截图中能够看进去,单个页面地址和单条 API 测速无意义,也无奈聚合。即便服务端有短缺的算力,Web 侧页面展现也是一个问题。那么 Restful 格调 的页面地址和 API 如何聚合呢?
咱们给出的计划是在服务端主动帮用户把数据做汇聚,把下面 URL 外面的变量汇聚成为一个 *。
首先依据 / 把整个 URL 地址绘制成为一个树,这样的话,变量节点就会变成一个有超级多兄弟节点的叶子结点。那咱们只须要判断某个节点的子节点个数超过肯定的阈值就能够得出,这个 URL 可能是属于 Restful 格调的发散节点。
所以这个问题就简化成了,当某个节点的子节点数大于 N,子节点可能就是一个变量,咱们就能够把其子节点汇聚成为 * 以满足咱们的诉求。惟一的变量就变成了 N,咱们只须要确定 N 的值就能够了。这是一个典型的数据分析模型转化为数学公式的问题。最终依据教训和实在用户数据,咱们给了一个 N 的大略值用来解决这个问题。
当然这个算法并不是完满的,比方构建剪枝树须要耗费比拟多内存,剪枝数有新的节点退出的时候须要对各个节点做比照,因为剪枝树须要放在 Redis 中,所以还有 Redis 高并发读写的问题。
然而随着咱们计算能力的加强,尤其是引入 ClickHouse 作为性能数据的存储引擎后,根本没有再遇到高维度的问题。
### 应用动静网关解决流量霎时突增
应用动静网关
后面咱们讲到通过流量整型来解决突发流量的问题,这个是建设在流量的增长是在一段时间内实现的。
而真实情况咱们遇到了一些流量会在一瞬间增长到某个值的状况,比方新年的红包流动,游戏的抽奖,购物的抢购,热门视频的开播等(这些都是血泪的教训)。这种状况若不及时扩容,服务将会解体。
对于这种案例,解决方案只有一种,就是在网关层限度,可是咱们应用的网关却又无奈满足这种自定义的限度。
面对突发流量,一般性主动扩所容的架构无论如何都无奈齐全满足需要,惟一的解决方案就是在平时就做冗余。比方咱们接入层平时 CPU 占用大略在 30 – 50% 左右,当 CPU 达到 70% 的时候,接口就会有一些异样。那可想而知,如果某个工夫点有超过原并发 2 倍申请量时候,服务器在扩容之前就曾经大量报错了。
然而将整个接入层做冗余老本太高了,于是咱们还是须要一层网关,于是抉择自研一套业务网关。
在这个背景下,动静网关我的项目应运而生,心愿把所有跟流量相干的申请都放在网关中进行,以缩小业务的压力。
目前动静网关根底性能曾经上线,仔细的用户可能也发现了,前端性能监控的上报接口更稳固更高效了,根本能够在 30-50ms 内实现响应。
动静网关的架构如下
动静网关的外围性能在于拿到用户申请后,间接返回了 204 给用户,而后再异步申请真正的接入层,把用户数据转发给接入层。所以无论后盾服务部署在哪里,并发如何,都能够保障用户上报侧不会受到影响。只有网关服务异地多活,就能够保障用户上报接口的性能,随后也能够充分利用边缘计算节点来部署网关服务,成果更加显著。
总结
随着流量一直递增,整体架构面临着流量和维度爆炸的双重压力。下列为解决计划总结:
- 服务无状态化解决后,整个服务的瓶颈就变成了依赖的第三方服务;
- 通过流量整型解决突发流量和异样流量的问题;
- 通过把局部服务微服务化升高整个服务负载,并且解决连接池占有的问题;
- 对数据上报形式进行架构优化,来解决高维的问题;
- 通过应用 Redis Hyperloglog 优化 UV 计算;
- 通过剪枝算法来升高页面地址和接口地址的维度;
- 引入 openrestry 做业务测网关,把用户申请异步解决,放慢服务器响应速度,也解决突发流量的问题。
点击理解腾讯云前端性能监控(RUM)