導(dǎo)讀
微信的多維指標(biāo)監(jiān)控平臺(tái),具備自定義維度、指標(biāo)的監(jiān)控能力,主要服務(wù)于用戶自定義監(jiān)控。作為框架級(jí)監(jiān)控的補(bǔ)充,它承載著聚合前 45億/min、4萬(wàn)億/天的數(shù)據(jù)量。當(dāng)前,針對(duì)數(shù)據(jù)層的查詢(xún)請(qǐng)求也達(dá)到了峰值 40萬(wàn)/min,3億/天。較大的查詢(xún)請(qǐng)求使得數(shù)據(jù)查詢(xún)遇到了性能瓶頸:查詢(xún)平均耗時(shí) > 1000ms,失敗率居高不下。針對(duì)這些問(wèn)題,微信團(tuán)隊(duì)對(duì)數(shù)據(jù)層查詢(xún)接口進(jìn)行了針對(duì)性的優(yōu)化來(lái)滿足上述場(chǎng)景,將平均查詢(xún)速度從1000ms+優(yōu)化到了100ms級(jí)別。本文為各位分享優(yōu)化過(guò)程,希望對(duì)你有用!
目錄
1 背景介紹
2 優(yōu)化分析
2.1 用戶查詢(xún)行為分析
2.2 數(shù)據(jù)層架構(gòu)
2.3 為什么查詢(xún)會(huì)慢
3 優(yōu)化方案設(shè)計(jì)
3.1 拆分子查詢(xún)請(qǐng)求
3.2 拆分子查詢(xún)請(qǐng)求+redis Cache
3.3 更進(jìn)一步-子維度表
4 優(yōu)化成果
4.1 緩存命中率>85%
4.2 查詢(xún)耗時(shí)優(yōu)化至 100ms
5 結(jié)語(yǔ)
01
背景介紹
微信多維指標(biāo)監(jiān)控平臺(tái)(以下簡(jiǎn)稱(chēng)多維監(jiān)控),是具備靈活的數(shù)據(jù)上報(bào)方式、提供維度交叉分析的實(shí)時(shí)監(jiān)控平臺(tái)。
在這里,最核心的概念是“協(xié)議”、“維度”與“指標(biāo)”。例如,如果想要對(duì)某個(gè)【省份】、【城市】、【運(yùn)營(yíng)商】的接口【錯(cuò)誤碼】進(jìn)行監(jiān)控,監(jiān)控目標(biāo)是統(tǒng)計(jì)接口的【平均耗時(shí)】和【上報(bào)量】。在這里,省份、城市、運(yùn)營(yíng)商、錯(cuò)誤碼,這些描述監(jiān)控目標(biāo)屬性的可枚舉字段稱(chēng)之為“維度”,而【上報(bào)量】、【平均耗時(shí)】等依賴(lài)“聚合計(jì)算”結(jié)果的數(shù)據(jù)值,稱(chēng)之為“指標(biāo)”。而承載這些指標(biāo)和維度的數(shù)據(jù)表,叫做“協(xié)議”。
多維監(jiān)控對(duì)外提供 2 種 API:
- 維度枚舉查詢(xún):用于查詢(xún)某一段時(shí)間內(nèi),一個(gè)或多個(gè)維度的排列組合以及其對(duì)應(yīng)的指標(biāo)值。它反映的是各維度分布“總量”的概念,可以“聚合”,也可以“展開(kāi)”,或者固定維度對(duì)其它維度進(jìn)行“下鉆”。數(shù)據(jù)可以直接生成柱狀圖、餅圖等。
- 時(shí)間序列查詢(xún):用于查詢(xún)某些維度條件在某個(gè)時(shí)間范圍的指標(biāo)值序列。可以展示為一個(gè)時(shí)序曲線圖,橫坐標(biāo)為時(shí)間,縱坐標(biāo)為指標(biāo)值。
然而,不管是用戶還是團(tuán)隊(duì)自己使用多維監(jiān)控平臺(tái)的時(shí)候,都能感受到明顯的卡頓。主要表現(xiàn)在看監(jiān)控圖像或者是查看監(jiān)控曲線,都會(huì)經(jīng)過(guò)長(zhǎng)時(shí)間的數(shù)據(jù)加載。
團(tuán)隊(duì)意識(shí)到,這是數(shù)據(jù)量上升必然帶來(lái)的瓶頸。目前,多維監(jiān)控平臺(tái)已經(jīng)接入了數(shù)千張協(xié)議表,每張表的特點(diǎn)都不同。維度組合、指標(biāo)量、上報(bào)量也不同。針對(duì)大量數(shù)據(jù)的實(shí)時(shí)聚合以及 OLAP 分析,數(shù)據(jù)層的性能瓶頸越發(fā)明顯,嚴(yán)重影響了用戶體驗(yàn)。于是這讓團(tuán)隊(duì)人員不由得開(kāi)始思考:難道要一直放任它慢下去嗎?答案當(dāng)然是否定的。因此,微信團(tuán)隊(duì)針對(duì)數(shù)據(jù)層的查詢(xún)進(jìn)行了優(yōu)化。
02
優(yōu)化分析
2.1 用戶查詢(xún)行為分析
要優(yōu)化,首先需要了解用戶的查詢(xún)習(xí)慣,這里的用戶包含了頁(yè)面用戶和異常檢測(cè)服務(wù)。于是微信團(tuán)隊(duì)盡可能多地上報(bào)用戶使用多維監(jiān)控平臺(tái)的習(xí)慣,包括但不限于:常用的查詢(xún)類(lèi)型、每個(gè)協(xié)議表的查詢(xún)維度和查詢(xún)指標(biāo)、查詢(xún)量、失敗量、耗時(shí)數(shù)據(jù)等。
在分析了用戶的查詢(xún)習(xí)慣后,有了以下發(fā)現(xiàn):
- 【時(shí)間序列】查詢(xún)占比 99% 以上
出現(xiàn)如此懸殊的比例可能是因?yàn)椋赫{(diào)用一次維度枚舉,即可獲取所關(guān)心的各個(gè)維度。但是針對(duì)每個(gè)維度組合值,無(wú)論是頁(yè)面還是異常檢測(cè)都會(huì)在查詢(xún)維度對(duì)應(yīng)的多條時(shí)間序列曲線中,從而出現(xiàn)「時(shí)間序列查詢(xún)」比例遠(yuǎn)遠(yuǎn)高于「維度枚舉查詢(xún)」。
- 針對(duì)1天前的查詢(xún)占比約 90%
出現(xiàn)這個(gè)現(xiàn)象可能是因?yàn)槊總€(gè)頁(yè)面數(shù)據(jù)都會(huì)帶上幾天前的數(shù)據(jù)對(duì)比來(lái)展示。異常檢測(cè)模塊每次會(huì)對(duì)比大約 7 天數(shù)據(jù)的曲線,造成了對(duì)大量的非實(shí)時(shí)數(shù)據(jù)進(jìn)行查詢(xún)。
2.2 數(shù)據(jù)層架構(gòu)
分析完用戶習(xí)慣,再看下目前的數(shù)據(jù)層架構(gòu)。多維監(jiān)控底層的數(shù)據(jù)存儲(chǔ)/查詢(xún)引擎選擇了 Apache-Druid 作為數(shù)據(jù)聚合、存儲(chǔ)的引擎,Druid 是一個(gè)非常優(yōu)秀的分布式 OLAP 數(shù)據(jù)存儲(chǔ)引擎,它的特點(diǎn)主要在于出色的預(yù)聚合能力和高效的并發(fā)查詢(xún)能力,它的大致架構(gòu)如圖:
節(jié)點(diǎn) |
解析 |
Mater節(jié)點(diǎn) |
Overlord:實(shí)時(shí)數(shù)據(jù)攝入消費(fèi)控制器
Coordinator:協(xié)調(diào)集群上數(shù)據(jù)分片的發(fā)布和負(fù)載均衡 |
實(shí)時(shí)節(jié)點(diǎn) |
MiddleManager:實(shí)時(shí)數(shù)據(jù)寫(xiě)入中間管理者,創(chuàng)建 Peon 節(jié)點(diǎn)進(jìn)行數(shù)據(jù)消費(fèi)任務(wù)并管理其生命周期
Peon:消費(fèi)實(shí)時(shí)數(shù)據(jù),打包并發(fā)布實(shí)時(shí)數(shù)據(jù)分片 |
存儲(chǔ)節(jié)點(diǎn) |
Historical:存儲(chǔ)數(shù)據(jù)分片 |
2.3 為什么查詢(xún)會(huì)慢
查詢(xún)慢的核心原因,經(jīng)微信團(tuán)隊(duì)分析如下:
- 協(xié)議數(shù)據(jù)分片存儲(chǔ)的數(shù)據(jù)片段為 2-4h 的數(shù)據(jù),每個(gè) Peon 節(jié)點(diǎn)消費(fèi)回來(lái)的數(shù)據(jù)會(huì)存儲(chǔ)在一個(gè)獨(dú)立分片。
- 假設(shè)異常檢測(cè)獲取 7 * 24h 的數(shù)據(jù),協(xié)議一共有 3 個(gè) Peon 節(jié)點(diǎn)負(fù)責(zé)消費(fèi),數(shù)據(jù)分片量級(jí)為 12*3*7 = 252,意味著將會(huì)產(chǎn)生 252次 數(shù)據(jù)分片 I/O。
- 在時(shí)間跨度較大時(shí)、MiddleManager、Historical 處理查詢(xún)?nèi)菀壮瑫r(shí),Broker 內(nèi)存消耗較高。
- 部分協(xié)議維度字段非常復(fù)雜,維度排列組合極大 (>100w),在處理此類(lèi)協(xié)議的查詢(xún)時(shí),性能就會(huì)很差。
03
優(yōu)化方案設(shè)計(jì)
根據(jù)上面的分析,團(tuán)隊(duì)確定了初步的優(yōu)化方向:
- 減少單 Broker 的大跨度時(shí)間查詢(xún)。
- 減少 Druid 的 Segments I/O 次數(shù)。
- 減少 Segments 的大小。
3.1 拆分子查詢(xún)請(qǐng)求
在這個(gè)方案中,每個(gè)查詢(xún)都會(huì)被拆解為更細(xì)粒度的“子查詢(xún)”請(qǐng)求。例如連續(xù)查詢(xún) 7 天的時(shí)間序列,會(huì)被自動(dòng)拆解為 7 個(gè) 1天的時(shí)間序列查詢(xún),分發(fā)到多個(gè) Broker,此時(shí)可以利用多個(gè) Broker 來(lái)進(jìn)行并發(fā)查詢(xún),減少單個(gè) Broker 的查詢(xún)負(fù)載,提升整體性能。
但是這個(gè)方案并沒(méi)有解決 Segments I/O 過(guò)多的問(wèn)題,所以需要在這里引入一層緩存。
3.2 拆分子查詢(xún)請(qǐng)求+Redis Cache
這個(gè)方案相較于 v1,增加了為每個(gè)子查詢(xún)請(qǐng)求維護(hù)了一個(gè)結(jié)果緩存,存儲(chǔ)在 Redis 中:
假設(shè)獲取 7*24h 的數(shù)據(jù),Peon 節(jié)點(diǎn)個(gè)數(shù)為 3,如果命中緩存,只會(huì)產(chǎn)生 3 次 Druid 的 Segments I/O (最近的 30min)數(shù)據(jù),相較幾百次 Segments I/O 會(huì)大幅減少。
接下來(lái)看下具體方法:
3.2.1 時(shí)間序列子查詢(xún)?cè)O(shè)計(jì)
針對(duì)時(shí)間序列的子查詢(xún),子查詢(xún)按照「天」來(lái)分解,整個(gè)子查詢(xún)的緩存也是按照天來(lái)聚合的。以一個(gè)查詢(xún)?yōu)槔?/p>
{
"biz_id": 1, // 查詢(xún)協(xié)議表ID:1
"formula": "avg_cost_time", // 查詢(xún)公式:求平均
"keys": [
// 查詢(xún)條件:維度xxx_id=3
{"field": "xxx_id", "relation": "eq", "value": "3"}
],
"start_time": "2020-04-15 13:23", // 查詢(xún)起始時(shí)間
"end_time": "2020-04-17 12:00" // 查詢(xún)結(jié)束時(shí)間
}
其中 biz_id、 formula,、keys 了每個(gè)查詢(xún)的基本條件。但每個(gè)查詢(xún)各不相同,不是這次討論的重點(diǎn)。
本次優(yōu)化的重點(diǎn)是基于查詢(xún)時(shí)間范圍的子查詢(xún)分解,而對(duì)于時(shí)間序列子查詢(xún)分解的方案則是按照「天」來(lái)分解,每個(gè)查詢(xún)都會(huì)得到當(dāng)天的全部數(shù)據(jù),由業(yè)務(wù)邏輯層來(lái)進(jìn)行合并。
舉個(gè)例子,04-15 13:23 ~ 04-17 08:20 的查詢(xún),會(huì)被分解為 04-15、04-16、04-17 三個(gè)子查詢(xún),每個(gè)查詢(xún)都會(huì)得到當(dāng)天的全部數(shù)據(jù),在業(yè)務(wù)邏輯層找到基于用戶查詢(xún)時(shí)間的偏移量,處理結(jié)果并返回給用戶。
每個(gè)子查詢(xún)都會(huì)先嘗試獲取緩存中的數(shù)據(jù),此時(shí)有兩種結(jié)果:
結(jié)果 |
解析 |
緩存未命中 |
如果子查詢(xún)結(jié)果在緩存中不存在,即 cache miss。只需要將調(diào)用 DruidBorker 獲取數(shù)據(jù),異步寫(xiě)入緩存中,同時(shí)該子查詢(xún)緩存的修改的時(shí)間即可。 |
緩存命中 |
在談?wù)撁兄埃紫纫胍粋€(gè)概念「閾值時(shí)間(threshold_time)」。它表示緩存更新前的一段時(shí)間(一般為10min)。我們默認(rèn)緩存中的數(shù)據(jù)是不被信任的,因?yàn)榭赡芤驗(yàn)閿?shù)據(jù)積壓等情況導(dǎo)致一部分?jǐn)?shù)據(jù)延遲入庫(kù)。
如果子查詢(xún)命中了緩存,則存在兩種情況:「緩存部分命中」和「緩存完全命中」。其中部分命中如下圖所示。
end_time > cache_update_time - threshold_time:這種情況說(shuō)明了「緩存部分被命中」,從 cache_update_time-thresold_time 到 end_time 這段時(shí)間都不可信,這段不可信的數(shù)據(jù)需要從 DruidBroker 中查詢(xún),并且在獲取到數(shù)據(jù)后異步回寫(xiě)緩存,更新 update 時(shí)間。
|
經(jīng)過(guò)上述分析不難看出:對(duì)于距離現(xiàn)在超過(guò)一天的查詢(xún),只需要查詢(xún)一次,之后就無(wú)需訪問(wèn) DruidBroker 了,可以直接從緩存中獲取。
而對(duì)于一些實(shí)時(shí)熱數(shù)據(jù),其實(shí)只是查詢(xún)了
cache_update_time-threshold_time 到 end_time 這一小段的時(shí)間。在實(shí)際應(yīng)用里,這段查詢(xún)時(shí)間的跨度基本上在 20min 內(nèi),而 15min 內(nèi)的數(shù)據(jù)由 Druid 實(shí)時(shí)節(jié)點(diǎn)提供。
3.2.2 維度組合子查詢(xún)?cè)O(shè)計(jì)
維度枚舉查詢(xún)和時(shí)間序列查詢(xún)不一樣的是:每一分鐘,每個(gè)維度的量都不一樣。而維度枚舉拿到的是各個(gè)維度組合在任意時(shí)間的總量,因此基于上述時(shí)間序列的緩存方法無(wú)法使用。在這里,核心思路依然是打散查詢(xún)和緩存。對(duì)此,微信團(tuán)隊(duì)使用了如下方案:
緩存的設(shè)計(jì)采用了多級(jí)冗余模式,即每天的數(shù)據(jù)會(huì)根據(jù)不同時(shí)間粒度:天級(jí)、4小時(shí)級(jí)、1 小時(shí)級(jí)存多份,從而適應(yīng)各種粒度的查詢(xún),也同時(shí)盡量減少和 Redis 的 IO 次數(shù)。
每個(gè)查詢(xún)都會(huì)被分解為 N 個(gè)子查詢(xún),跨度不同時(shí)間,這個(gè)過(guò)程的粗略示意圖如下:
舉個(gè)例子:例如 04-15 13:23 ~ 04-17 08:20 的查詢(xún),會(huì)被分解為以下 10 個(gè)子查詢(xún):
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 |
這里可以發(fā)現(xiàn),查詢(xún) 1 和查詢(xún) 10,絕對(duì)不可能出現(xiàn)在緩存中。因此這兩個(gè)查詢(xún)一定會(huì)被轉(zhuǎn)發(fā)到 Druid 去進(jìn)行。2~9 查詢(xún),則是先嘗試訪問(wèn)緩存。如果緩存中不存在,才會(huì)訪問(wèn) DruidBroker,在完成一次訪問(wèn)后將數(shù)據(jù)異步回寫(xiě)到 Redis 中。
維度枚舉查詢(xún)和時(shí)間序列一樣,同時(shí)也用了 update_time 作為數(shù)據(jù)可信度的保障。因?yàn)樽罴?xì)粒度為小時(shí),在理想狀況下一個(gè)時(shí)間跨越很長(zhǎng)的請(qǐng)求,實(shí)際上訪問(wèn) Druid 的最多只有跨越 2h 內(nèi)的兩個(gè)首尾部查詢(xún)而已。
3.3 更進(jìn)一步-子維度表
通過(guò)子查詢(xún)緩存方案,我們已經(jīng)限制了 I/O 次數(shù),并且保障 90% 的請(qǐng)求都來(lái)自于緩存。但是維度組合復(fù)雜的協(xié)議,即 Segments 過(guò)大的協(xié)議,仍然會(huì)消耗大量時(shí)間用于檢索數(shù)據(jù)。
所以核心問(wèn)題在于:能否進(jìn)一步降低 Segments 大小?
維度爆炸問(wèn)題在業(yè)界都沒(méi)有很好的解決方案,大家要做的也只能是盡可能規(guī)避它,因此這里,團(tuán)隊(duì)在查詢(xún)層實(shí)現(xiàn)了子維度表的拆分以盡可能解決這個(gè)問(wèn)題,用空間換時(shí)間,具體做法為:
● 對(duì)于維度復(fù)雜的協(xié)議,抽離命中率高的低基數(shù)維度,建立子維度表,實(shí)時(shí)消費(fèi)并入庫(kù)數(shù)據(jù)。 ● 查詢(xún)層支持按照用戶請(qǐng)求中的查詢(xún)維度,匹配最小的子維度表。 |
04
優(yōu)化成果
4.1 緩存命中率>85%
在做完所有改造后,最重要的一點(diǎn)便是緩存命中率。因?yàn)榇蟛糠值恼?qǐng)求來(lái)自于1天前的歷史數(shù)據(jù),這為緩存命中率提供了保障:
- 子查詢(xún)緩存完全命中率(無(wú)需查詢(xún)Druid):86%
- 子查詢(xún)緩存部分命中率(秩序查詢(xún)?cè)隽繑?shù)據(jù)):98.8%
最明顯的效果就是,查詢(xún)?cè)L問(wèn) Druid 的請(qǐng)求,下降到了原來(lái)的 10% 左右。
4.2 查詢(xún)耗時(shí)優(yōu)化至 100ms
在整體優(yōu)化過(guò)后,查詢(xún)性能指標(biāo)有了很大的提升:
平均耗時(shí) 1000+ms -> 140ms;P95:5000+ms -> 220ms。
05
結(jié)語(yǔ)
微信多維指標(biāo)監(jiān)控平臺(tái) ,是微信監(jiān)控平臺(tái)的重要組成部分。在分析了用戶數(shù)據(jù)查詢(xún)行為之后,我們找到了數(shù)據(jù)查詢(xún)慢的主要原因,通過(guò)減少單 Broker 的大跨度時(shí)間查詢(xún)、減少 Druid 的 Segments I/O 次數(shù)、減少 Segments 的大小。我們實(shí)現(xiàn)了緩存命中率>85%、查詢(xún)耗時(shí)優(yōu)化至 100ms。當(dāng)然,系統(tǒng)功能目前也或多或少尚有不足,在未來(lái)團(tuán)隊(duì)會(huì)繼續(xù)探索前行,力求使其覆蓋更多的場(chǎng)景,提供更好的服務(wù)。
以上是本次分享全部?jī)?nèi)容,歡迎大家在評(píng)論區(qū)分享交流。如果覺(jué)得內(nèi)容有用,歡迎轉(zhuǎn)發(fā)~
作者:仇弈彬
來(lái)源:微信公眾號(hào):騰訊云開(kāi)發(fā)者
出處
:https://mp.weixin.qq.com/s/_hqYCY-ySKxSkYNC5WXH2g