专栏首页腾讯云监控专栏前端性能监控 (RUM) 接入层服务高并发优化实践—缓存模型

前端性能监控 (RUM) 接入层服务高并发优化实践—缓存模型

作者:张翔,腾讯云监控高级工程师

听说上次 RUM 重构,还是上次了!

这次 RUM 重构的目的是?

RUM 要提升用户体验!!!

用户体验好了,用户就用的更好了!

真是听君一席话如听一席话

下面我们正式切入主题!

背景

为了更流畅的用户体验,RUM 的采集上报服务接入层正在经历着 2.0 版本的重构与升级。接入层的重构使用了性能更好的 Golang。而在重构升级的过程中也重新对以前的缓存模型进行设计与实现。

RUM 目前整体接入的项目超过数万个,每个项目都对应着不同的配置,如抽样率,域名校验信息,欠费数据与项目状态等等。这些数据在处理上报请求的时,都会被用到,这大量数据会持久化存在 RUM 控制台。为了能够快速地获取到对应数据,我们急需设计数据缓存机制。 

中心化缓存+本地缓存机制

结构图:

我们将接入层服务部署在 serverless 上,再将数据放置腾讯云数据库 Redis 中,中心化处理缓存数据。但是如果每一个请求都请求一次 Redis,这样不仅会影响请求的响应时间,增加一次网络 io,并且在面对监控上报如此大的请求量的时候,Redis 的读写能力也会受到极大的挑战。

虽然 Redis 的读写性能极高,Redis 的读性能在每秒 110000 次,但是面对服务端平均几十万 QPS 的压力还是会有点吃不消,如果 Redis 的 CPU 满载就会频繁出现 OOM 的错误。

[点击查看大图]

因此我们需要在本地建立缓存机制,缓解 Redis 的压力与降低服务端请求耗时。服务端会先去查找内存中的项目数据,避免了直接对 Redis 的读取 io,进而优化对于 Redis 的 io 压力,降低 Redis 的 CPU 压力,避免 OOM 错误。

避免缓存惊群效应

除了上面所说的本地缓存优化,我们还需要注意一个常见的缓存陷阱问题:缓存的惊群效应。

所谓惊群效应,即在多并发线程的场景下,爆发式地向缓存服务拿取数据,导致缓存服务无法处理瞬时大量请求导致服务崩溃的现象。

而在以前 Node 服务的场景下,我们使用 pm2 来进行进程守护并且使用 cluster 模式,单个 pod 起了 8 个进程,由于在 Node 里共享内存开发成本比较大,所以需要每个进程自行维护进程内的内存数据集,这样除了内存利用率不高以外,还有惊群效应的问题。接入层的 TKE 上的 pod 数量一般都在 300 个左右,我们设置的内存缓存过期为 2 分钟,这样的话其实每两分钟就会有 300*8N = 2400 * N 次以上的 Redis io 读操作(N 是需要执行的命令的个数)。

这种情况下,可以尽可能优化 N 的个数,在 RUM 接入层里,我们将这个数量优化成 1,所有项目的数据都放在 Redis 的 hash 表数据结构里面,可以通过 HGETALL 命令一次性将所有数据取出,而不是通过一个个的 cache key 重复执行拿到对应的项目数据。

除此之外,惊群效应还需要注意一点,就是对于内存中没有的数据,我们应该谨慎甚至是禁止服务直接调取缓存服务取用数据,看下以下代码例子:

// 简单的代码例子
const projectHash = {}; // 存储项目数据,这里是异步并且服务启动前去 redis 中取数据的
app.use(async ctx => {    if (!Object.keys(projectHash) == 0 && projectHash[ctx.query.id]) {        projectHash[ctx.query.id] = await redis.hget(key, ctx.query.id);    }        return projectHash[ctx.query.id];});

这样的一个即时获取数据的机制,在服务进程启动缓存数据还没有准备好的时候,服务突然收到大量的请求,会将瞬间的请求量直接打到缓存服务上,这样会发生缓存践踏,容易破防。

因此在新的接入层重构的时候,我们去掉了这样的即时获取数据机制,以防缓存服务的压力过大而造成服务雪崩。

那我们怎么更新内存数据呢?我们采取 refresh-ahead caching 策略。

refresh-ahead caching

即提前刷新缓存,这样的策略在微服务架构中非常常见,牺牲了一点数据的实时性而大幅度提升了服务的性能。缓存由微服务异步重新加载更新,客户端请求只访问内存缓存以达到快速响应的诉求,微服务中运行一个调度程序刷新缓存。

内存缓存性能优化

Golang 并发编程

Node 单进程模型在编程中,对于数据的修改是以函数为单位作为一个原子操作的,但是这样共享内存会相对麻烦,而在 Go 中,异步编程是使用了更为轻量的协程来达到更好的异步性能,而协程是会有并发操作的概念的,即会同时有多个协程对于某一个变量进行值修改操作。

而我们对于配置数据,项目数据等等都是放置在内存的一个 Map 里的,为了避免多协程下对于同一个变量读写出现错误,因此使用 Golang 重构的过程中使用了锁的机制。

为什么 node 中不需要锁而 golang 中需要锁呢?—— Memory Model

这里关于异步编程的概念特别有意思,一定要圈起来!

为什么 Node 中不需要锁而 Golang 中需要,原因可以深究到 Golang 中的 Memory Model。

所谓 Memory Model 即内存模型,也就是它指定了一个协程(goroutinue)在什么条件下可以保证读取某个变量的时候观察到其他协程(goroutinue)写入的值。

那就会有一个很有意思的问题出现:为什么会出现某个协程看不到某个变量被写进来的值呢?这个涉及 CPU 的结构与内存重排机制。

内存重排

所谓内存重排,也就是所写的高级语言转化为汇编即对内存的读写指令时,CPU 设计者为了提高性能,会使用类似分支预测等手段优化对于内存的读写性能,来看一个伪代码例子:

// code1x := 0for i := 0; i < 100; i++ {    x = 1    fmt.Println(x)}
// code2x := 1for i := 0; i < 100; i++ {    fmt.Println(x)}

编译器重排下,有可能将代码逻辑从 code1 变成 code2 的形式,code1 和 code2 的结果在同一个协程中是一致的,若如果同时有另外一个线程对于 x 这个变量进行 x=0 赋值操作,code1 的打印结果可能就是 1100011 而 code2 的打印结果可能就是 1111000000,在多核情况下是很难断定这两个程序是等价的。这就是编译器重排,即编译器会改变变量的赋值顺序。

除此之外,由于 CPU 为了提高运算性能与读写性能,抚平内核,内存,硬盘之间的速度差异,建立了三级缓存,比如让如下图的 (2) 行代码的执行无需等待 (1) 行代码的执行结果”可见”,所以将 (1) 的结果存到 store buffer 中:

store buffer 这样的机制在单线程是完美的,但是在多线程里面,比如先执行了 (1) 和 (3) 的指令,写入了 store buffer,再执行 (2) 和 (4),此时由于每次执行顺序不同,打印的结果不尽相同,最坏情况是 A/B 两个变量打印出来都是 0:

所以,对于多线程程序,CPU 都会支持锁机制,也就是内存屏障(memory barrier),barrier 指令要求所有对内存的操作必须扩散到内存之后才能执行其他对于内存的操作,这也就是锁的本质。因此 Golang 里面需要使用到锁机制。

明确了需要使用锁之后,我们第一版的代码很快就出来了,我们使用了 sync.Map 来在内存中保存项目数据。

// 伪代码
type ProjectRepo struct {    data sync.Map}
var projectRepo ProjectRepo
func (p *ProjectRepo) LoadData() {    rawData := redis.HGetAll(key)    p.data.Store('data', rawData)}
func (p *ProjectRepo) Get(projectId string) {    rawData, _ := p.data.Load('data')return rawData[projectId]}

锁机制的优化

写下了第一版代码完成了功能,回看上面的内存模型,其实可以轻松认识到锁是有巨大开销的。所以继续反思一下我们是否真的需要锁?

sync.Map 里面存在一个互斥锁,不管是读是写都共用了一把锁,显而易见这样的效率是非常低的。

但是如果不用锁又会存在并发问题,所以思考一下,能否进一步优化呢?

Golang 里面有 RWMutex 即读写锁机制,将读锁和写锁进行分离,读写互相不打扰,但是这个机制给了我们一个灵感,Local cache 这种数据属于读多写少的数据,并没有太多并发写的诉求,是否能直接连写锁这样的机制都省略掉呢?答案是可以的!

COW — Copy On Write 技术

copy-on-write 技术即写时复制,是微服务降级与微服务 local cache 中优化的常见思路。对于内存配置数据等等这些读多写少的数据,非常适合这种方式。

所谓写时复制,就是写操作时复制全量老数据到一个新对象中,并携带本次新的数据,最后利用原子替换,更新读取方的数据,以达到无锁访问共享数据。

可以看下面的 benchmark 数据,atomic 原子替换的读写性能比锁机制强出好几个数量级,操作时间和比原生的 Map 数据相差无几。

[点击查看大图]

实际 projectRepo benchmark 对比:

[点击查看大图]

使用 cow 之后:

[点击查看大图]

读性能比以前多出了至少 500 万次以上的次数。

并且 atomic 有一个优势就是它是 Go 并发同步原语之一,即使后面需要多协程去写,这里也能满足无缝切换。

因此最终我们优化项目数据的数据层内存中使用 atomic 原子替换来存储我们的内存数据已达到最佳性能。

总结

以上就是RUM 接入层 2.0 对于服务的缓存模型优化的全部内容,整个优化的过程我最大的收获就是:对于每一个细节都有优化的空间,我们需要尽可能地深挖个中原理,计算机的底层知识依然在需要性能优化的时候起到帮助,要明白语言的底层与操作的原理,才能从根本上发现并针对性地进行优化。

点击文末「阅读原文」即可了解前端性能监控(RUM)。

联系我们

扫码加云监控小助手,回复“Rum”

加入前端性能监控技术交流群

RUM 相关文章:


关注我们,了解腾讯云监控的最新动态

文章分享自微信公众号:
腾讯云监控

本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!

如有侵权,请联系 yunjia_community@tencent.com 删除。
登录 后参与评论
0 条评论

相关文章

  • 微信小程序首屏耗时优化

    前言 关于耗时: 1. 首屏耗时与用户设备、所在网络环境、程序代码有很大关系。线上环境有2/3的运气成分。 2. 理论上相同地域访问 https 比访问 ip ...

    腾讯云监控团队
  • 【技术分享】Go 工程化-前端性能监控接入层 Layout 设计实践

     作者:黎志航&张翔,腾讯监控高级工程师 前言 本文主要介绍 腾讯云前端性能监控(RUM)在全新接入层上的 Go 工程化实践,介绍 Go 项目布局(下文称 Pr...

    腾讯云监控团队
  • 健康码如何通过监控提升小程序的用户体验?

    从2020年疫情爆发以来,全国上下均处在疫情防控常态化期间,“健康码”已经成为各地大量人员流动场所进出的重要凭证。

    腾讯云监控团队
  • 1+1>2|加快应用访问速度的两大利器

    作者简介:胥耀,腾讯云监控产品经理,具有六年云产品工作经验,目前主要负责腾讯云前端性能监控和云监控相关的产品策划工作,对监控和运维领域具有深刻理解。

    腾讯云监控团队
  • 云原生时代下的端到端一体化监控解决方案

    作者:张加浪,腾讯云云监控高级工程师 前言 某电商客户的网站加速 30% ,调用成功率上升3%,实现了分钟级定位故障...... 某银行实现端到端的全链路覆盖和...

    腾讯云监控团队
  • 基于RUM的前端优化理论与实践-性能篇

    前言 对于前端来说,最重要的是体验,而在前端体验中,最为核心的就是性能。  相信大多数用户接入前端性能监控(RUM)都是为了通过RUM质量评价体系来验证前端性...

    腾小云
  • 百万 QPS 前端性能监控系统设计与实现

    作者:李振,腾讯云前端性能监控负责人 什么是前端性能监控(RUM) 腾讯云前端性能监控 (RUM) 是一站式前端监控解决方案,用户只需要安装 SDK 到自己...

    腾讯云监控团队
  • 基于 RUM 的前端优化理论与实践 - 性能篇(一)

    作者:李振,腾讯云前端性能监控负责人 前言 对于前端来说,最重要的是体验,而在前端体验中,最为核心的就是性能。 相信大多数用户接入前端性能监控(RUM)都...

    腾讯云监控团队
  • 【腾讯云前端性能优化大赛】微信小程序首屏耗时优化,减少等待降低耗能

    RUM 是腾讯提供的一款前端监控方案,只需根据赛事指引在控制台上创建业务系统和应用,获取上报ID;通过安装 npm 依赖配置 JSON 就可以实现测速和日志的收...

    小白伪全栈
  • 边缘计算将推动 CDN 进入新时代

    CDN(内容分发网络)属于边缘应用程序,后者则是CDN 服务的一个超集。我们正生活在一个超级连接的世界当中,所有的东西都可以被推至云端。将内容放在一个地方,站在...

    CloudBest
  • Elastic APM详解之APM技术分类和实现方式

    在2021年4月份,Elastic刚刚入围了Gartner的APM魔力象限。如Elastic同时新晋入围的,还有阿里云:

    点火三周
  • 监控产品常见问题(第1期)

    Prometheus 监控服务(TMP) 1. TMP 和自建有什么区别吗 TMP 完全兼容开源生态,并与腾讯云监控数据打通,帮助用户快速搭建监控体系(自定义监...

    腾讯云监控团队
  • 监控产品上新月报【10月】

    云监控产品中心 10月功能发布总览: 应用性能观测 APM 1.支持 PHP 和 Python 语言探针部署,具体接入步骤可查看官网接入指南:  https:...

    腾讯云监控团队
  • 【案例分享】腾讯游戏说:从 Web 性能评估探寻前端优化策略

    刘馨忆 腾讯 IEG 公共数据平台部前端开发工程师,硕士毕业于英国曼彻斯特大学。主要负责内容生态相关toB 业务系统的开发,对内容审核链路、数据可视化看板有丰富...

    腾讯云监控团队
  • 鹅厂前端大佬教你如何优化首屏耗时【直播预约】

    在用户访问页面的过程中,首屏耗时是最重要的体验之一,访问页面的快慢直接影响了用户体验、用户留存等。如何衡量首屏耗时、优化首屏耗时对提升产品质量、提升用户体验起着...

    腾讯云监控团队
  • 不敢相信,技术栈,居然被P站秒了

    PornHub的FE,分享了P站前端一些实践,英文比较晦涩难懂,故翻译整理了一下,很多同学对前端技术不是很熟悉,故加入了简单解释,希望对大家理解相关技术有帮助。...

    架构师之路
  • 【直播预约】业务链路全监控最佳实践

    随着用户和业务量的日益增长,互联网应用的服务架构也在不断升级。从逻辑复杂的大型单体服务到简单模块化的微服务,每个后台应用搭载的业务逻辑逐步简化,但整个分布式后...

    腾讯云监控团队
  • 应用性能前端监控,字节跳动这些年经验都在这了

    随着用户数量的不断增长,对于站点体验衡量的的需求也日益紧迫,用户会将产品和他们每天使用的体验最好的 Web 站点进行比较。想着手优化,则必须先有相关的监控数据,...

    ssh_晨曦时梦见兮

扫码关注云+社区

领取腾讯云代金券