关于性能优化:微信海量数据查询如何从1000ms降到100ms

1次阅读

共计 5225 个字符,预计需要花费 14 分钟才能阅读完成。

腾小云导读

微信的多维指标监控平台,具备自定义维度、指标的监控能力,次要服务于用户自定义监控。作为框架级监控的补充,它承载着聚合前 45 亿 /min、4 万亿 / 天的数据量。以后,针对数据层的查问申请也达到了峰值 40 万 /min,3 亿 / 天。较大的查问申请使得数据查问遇到了性能瓶颈:查问均匀耗时 > 1000ms,失败率居高不下。针对这些问题,微信团队对数据层查问接口进行了针对性的优化来满足上述场景,将均匀查问速度从 1000ms+ 优化到了 100ms 级别。本文为各位分享优化过程,心愿对你有用!

目录

1 背景介绍

2 优化剖析

2.1 用户查问行为剖析

2.2 数据层架构

2.3 为什么查问会慢

3 优化方案设计

3.1 拆分子查问申请

3.2 拆分子查问申请 +Redis Cache

3.3 更进一步 - 子维度表

4 优化成绩

4.1 缓存命中率 >85%

4.2 查问耗时优化至 100ms

5 结语

01、背景介绍

微信多维指标监控平台(以下简称多维监控),是具备灵便的数据上报形式、提供维度穿插剖析的实时监控平台。

在这里,最外围的概念是“协定”、“维度”与“指标”。例如,如果想要对某个【省份】、【城市】、【运营商】的接口【错误码】进行监控,监控指标是统计接口的【均匀耗时】和【上报量】。在这里,省份、城市、运营商、错误码,这些形容监控指标属性的可枚举字段称之为“维度”,而【上报量】、【均匀耗时】等依赖“聚合计算”后果的数据值,称之为“指标”。而承载这些指标和维度的数据表,叫做“协定”。

多维监控对外提供 2 种 API:

  • 维度枚举查问 :用于查问某一段时间内,一个或多个维度的排列组合以及其对应的指标值。它反映的是各维度散布“总量”的概念,能够“聚合”,也能够“开展”,或者固定维度对其它维度进行“下钻”。数据能够间接生成柱状图、饼图等。
  • 工夫序列查问 :用于查问某些维度条件在某个工夫范畴的指标值序列。能够展现为一个时序曲线图,横坐标为工夫,纵坐标为指标值。

然而,不论是用户还是团队本人应用多维监控平台的时候,都能感触到显著的卡顿。次要体现在看监控图像或者是查看监控曲线,都会通过长时间的数据加载。

团队意识到,这是数据量回升必然带来的瓶颈。目前,多维监控平台曾经接入了数千张协定表,每张表的特点都不同。维度组合、指标量、上报量也不同。针对大量数据的实时聚合以及 OLAP 剖析,数据层的性能瓶颈越发显著,重大影响了用户体验。于是这让团队人员不由得开始思考:难道要始终放任它慢下去吗?答案当然是否定的。因而,微信团队针对数据层的查问进行了优化。

02、优化剖析

2.1 用户查问行为剖析

要优化,首先须要理解用户的查问习惯,这里的用户蕴含了页面用户和异样检测服务。于是微信团队尽可能多地上报用户应用多维监控平台的习惯,包含但不限于: 罕用的查问类型、每个协定表的查问维度和查问指标、查问量、失败量、耗时数据等。

在剖析了用户的查问习惯后,有了以下发现:

  • 【工夫序列】查问占比 99% 以上

呈现如此迥异的比例可能是因为:调用一次维度枚举,即可获取所关怀的各个维度。然而针对每个维度组合值,无论是页面还是异样检测都会在查问维度对应的多条工夫序列曲线中,从而呈现「工夫序列查问」比例远远高于「维度枚举查问」。

  • 针对 1 天前的查问占比约 90%

呈现这个景象可能是因为每个页面数据都会带上几天前的数据比照来展现。异样检测模块每次会比照大概 7 天数据的曲线,造成了对大量的非实时数据进行查问。

2.2 数据层架构

剖析完用户习惯,再看下目前的数据层架构。多维监控底层的数据存储 / 查问引擎抉择了 Apache-Druid 作为数据聚合、存储的引擎,Druid 是一个十分优良的分布式 OLAP 数据存储引擎,它的特点次要在于杰出的预聚合能力和高效的并发查问能力,它的大抵架构如图:

节点 解析
Mater 节点 Overlord:实时数据摄入生产控制器 Coordinator:协调集群上数据分片的公布和负载平衡
实时节点 MiddleManager:实时数据写入两头管理者,创立 Peon 节点进行数据生产工作并治理其生命周期 Peon:生产实时数据,打包并公布实时数据分片
存储节点 Historical:存储数据分片 DeepStorage:分片直达存储,不对外查问 MetaDataStorage:元信息,如表构造 Zookeeper:存储实时工作和状态信息

2.3 为什么查问会慢

查问慢的外围起因,经微信团队剖析如下:

  • 协定数据分片存储的数据片段为 2-4h 的数据,每个 Peon 节点生产回来的数据会存储在一个独立分片。
  • 假如异样检测获取 7 24h 的数据,协定一共有 3 个 Peon 节点负责生产,数据分片量级为 123*7 = 252,意味着将会产生 252 次 数据分片 I/O。
  • 在时间跨度较大时、MiddleManager、Historical 解决查问容易超时,Broker 内存耗费较高。
  • 局部协定维度字段非常复杂,维度排列组合极大 (>100w),在解决此类协定的查问时,性能就会很差。

03、优化方案设计

依据下面的剖析,团队确定了初步的优化方向:

  • 缩小单 Broker 的大跨度工夫查问。
  • 缩小 Druid 的 Segments I/O 次数。
  • 缩小 Segments 的大小。

3.1 拆分子查问申请

在这个计划中,每个查问都会被拆解为更细粒度的“子查问”申请。例如间断查问 7 天的工夫序列,会被主动拆解为 7 个 1 天的工夫序列查问,散发到多个 Broker,此时能够利用多个 Broker 来进行并发查问,缩小单个 Broker 的查问负载,晋升整体性能。

然而这个计划并没有解决 Segments I/O 过多的问题,所以须要在这里引入一层缓存。

3.2 拆分子查问申请 +Redis Cache

这个计划相较于 v1,减少了为每个子查问申请保护了一个后果缓存,存储在 Redis 中:

假如获取 7*24h 的数据,Peon 节点个数为 3,如果命中缓存,只会产生 3 次 Druid 的 Segments I/O(最近的 30min)数据,相较几百次 Segments I/O 会大幅缩小。

接下来看下具体方法:

3.2.1 工夫序列子查问设计

针对工夫序列的子查问,子查问依照「天」来合成,整个子查问的缓存也是依照天来聚合的。以一个查问为例:

{
    "biz_id": 1, // 查问协定表 ID:1
    "formula": "avg_cost_time", // 查问公式:求均匀
    "keys": [ 
        // 查问条件:维度 xxx_id=3
        {"field": "xxx_id", "relation": "eq", "value": "3"} 
    ], 
    "start_time": "2020-04-15 13:23", // 查问起始工夫
    "end_time": "2020-04-17 12:00" // 查问完结工夫 }

其中 biz_id、formula,、keys 了每个查问的根本条件。但每个查问各不相同,不是这次探讨的重点。

本次优化的重点是基于查问工夫范畴的子查问合成,而对于工夫序列子查问合成的计划则是依照「天」来合成,每个查问都会失去当天的全副数据,由业务逻辑层来进行合并。

举个例子,04-15 13:23 ~ 04-17 08:20 的查问,会被合成为 04-15、04-16、04-17 三个子查问,每个查问都会失去当天的全副数据,在业务逻辑层找到基于用户查问工夫的偏移量,处理结果并返回给用户。

每个子查问都会先尝试获取缓存中的数据,此时有两种后果:

后果 解析
缓存未命中 如果子查问后果在缓存中不存在,即 cache miss。只须要将调用 DruidBorker 获取数据,异步写入缓存中,同时该子查问缓存的批改的工夫即可。
缓存命中 在议论命中之前,首先引入一个概念「阈值工夫 (threshold_time)」。它示意缓存更新前的一段时间(个别为 10min)。咱们默认缓存中的数据是不被信赖的,因为可能因为数据积压等状况导致一部分数据提早入库。如果子查问命中了缓存,则存在两种状况:「缓存局部命中」和「缓存齐全命中」。其中局部命中如下图所示。缓存局部被命中:end_time > cache_update_time – threshold_time:这种状况阐明了「缓存局部被命中」,从 cache_update_time-thresold_time 到 end_time 这段时间都不可信,这段不可信的数据须要从 DruidBroker 中查问,并且在获取到数据后异步回写缓存,更新 update 工夫。缓存齐全命中:而缓存齐全命中则是种现实模式:end_time > cache_update_time – threshold_time。这种状况阐明了缓存被齐全命中,缓存中的数据都能够被置信,这种状况下间接拿进去就能够了。

通过上述剖析不难看出:对于间隔当初超过一天的查问,只须要查问一次,之后就无需拜访 DruidBroker 了,能够间接从缓存中获取。

而对于一些实时热数据,其实只是查问了 cache_update_time-threshold_time 到 end_time 这一小段的工夫。在理论利用里,这段查问工夫的跨度基本上在 20min 内,而 15min 内的数据由 Druid 实时节点提供。

3.2.2 维度组合子查问设计

维度枚举查问和工夫序列查问不一样的是:每一分钟,每个维度的量都不一样。而维度枚举拿到的是各个维度组合在任意工夫的总量,因而基于上述工夫序列的缓存办法无奈应用。在这里,外围思路仍然是打散查问和缓存。对此,微信团队应用了如下计划:

缓存的设计采纳了多级冗余模式,即每天的数据会依据不同工夫粒度:天级、4 小时级、1 小时级存多份,从而适应各种粒度的查问,也同时尽量减少和 Redis 的 IO 次数。

每个查问都会被合成为 N 个子查问,跨度不同工夫,这个过程的粗略示意图如下:

举个例子:例如 04-15 13:23 ~ 04-17 08:20 的查问,会被合成为以下 10 个子查问:

04-15 13:23 ~ 04-15 14:00 04-15 14:00 ~ 04-15 15:00 04-15 15:00 ~ 04-15 16:00 04-15 16:00 ~ 04-15 20:00 04-15 20:00 ~ 04-16 00:00 04-16 00:00 ~ 04-17 00:00 04-17 00:00 ~ 04-17 04:00 04-17 00:00 ~ 04-17 04:00 04-17 04:00 ~ 04-17 08:00 04-17 08:00 ~ 04-17 08:20

这里能够发现,查问 1 和查问 10,相对不可能呈现在缓存中。因而这两个查问肯定会被转发到 Druid 去进行。2~9 查问,则是先尝试拜访缓存。如果缓存中不存在,才会拜访 DruidBroker,在实现一次拜访后将数据异步回写到 Redis 中。

维度枚举查问和工夫序列一样,同时也用了 update_time 作为数据可信度的保障。因为最细粒度为小时,在现实情况下一个工夫逾越很长的申请,实际上拜访 Druid 的最多只有逾越 2h 内的两个首尾部查问而已。

3.3 更进一步 - 子维度表

通过子查问缓存计划,咱们曾经限度了 I/O 次数,并且保障 90% 的申请都来自于缓存。然而维度组合简单的协定,即 Segments 过大的协定,依然会耗费大量工夫用于检索数据。

所以外围问题在于: 是否进一步升高 Segments 大小?

维度爆炸问题在业界都没有很好的解决方案,大家要做的也只能是尽可能躲避它,因而这里,团队在查问层实现了子维度表的拆分以尽可能解决这个问题,用空间换工夫,具体做法为:

● 对于维度简单的协定,抽离命中率高的低基数维度,建设子维度表,实时生产并入库数据。●查问层反对依照用户申请中的查问维度,匹配最小的子维度表。

04、优化成绩

4.1 缓存命中率 >85%

在做完所有革新后,最重要的一点便是缓存命中率。因为大部分的申请来自于 1 天前的历史数据,这为缓存命中率提供了保障:

  • 子查问缓存齐全命中率(无需查问 Druid):86%
  • 子查问缓存局部命中率(秩序查问增量数据):98.8%

最显著的成果就是,查问拜访 Druid 的申请,降落到了原来的 10% 左右。

4.2 查问耗时优化至 100ms

在整体优化过后,查问性能指标有了很大的晋升:

均匀耗时 1000+ms -> 140ms;P95:5000+ms -> 220ms。

05、结语

微信多维指标监控平台,是微信监控平台的重要组成部分。在剖析了用户数据查问行为之后,咱们找到了数据查问慢的次要起因,通过缩小单 Broker 的大跨度工夫查问、缩小 Druid 的 Segments I/O 次数、缩小 Segments 的大小。咱们实现了缓存命中率 >85%、查问耗时优化至 100ms。当然,零碎性能目前也或多或少尚有有余,在将来团队会持续摸索前行,力求使其笼罩更多的场景,提供更好的服务。

以上是本次分享全部内容,欢送大家在评论区分享交换。如果感觉内容有用,欢送转发~

-End-

原创作者|仇弈彬

技术责编|仇弈彬

你有哪些「性能优化」经验或教训分享? 又或者想要理解更多微信其余的技术?都能够在腾讯云公众号的评论区中留言。咱们将选取 1 则最有创意的评论,送出腾讯云开发者 - 棒球帽 1 个(见下图)。6 月 7 日中午 12 点开奖。

正文完
 0