Presto 在阿里云實(shí)時(shí)日志分析中的實(shí)踐和優(yōu)化
一、業(yè)務(wù)背景
首先第一部分介紹一下我們的業(yè)務(wù)背景。阿里云 SLS 是一個(gè)云上一站式可觀測日志服務(wù)平臺。
SLS 提供了強(qiáng)大的數(shù)據(jù)采集、數(shù)據(jù)加工、消費(fèi)投遞等能力,數(shù)據(jù)采集利器 ilogtail 目前也已經(jīng)完全開源。數(shù)據(jù)采集上來后提供數(shù)據(jù)的統(tǒng)一存儲,包括熱存、智能冷存等,幫助用戶盡可能節(jié)省成本。在存儲之上,提供了數(shù)據(jù)處理與分析能力,包括即席查詢分析、關(guān)聯(lián)分析等。這兩塊構(gòu)成了整個(gè) SLS 產(chǎn)品的核心基礎(chǔ)能力。在這個(gè)基礎(chǔ)能力之上提供了豐富的工具和應(yīng)用,最終服務(wù)于各種不同的角色和用戶。
本文將聚焦在存儲和分析基礎(chǔ)能力上面的建設(shè),重點(diǎn)分享日志分析系統(tǒng),以及在面對核心問題時(shí)的一些架構(gòu)設(shè)計(jì)思路和經(jīng)驗(yàn)。
這是具體日志分析業(yè)務(wù)覆蓋和服務(wù)的能力,主要是圍繞日志場景去進(jìn)行數(shù)據(jù)分析。日志數(shù)據(jù)的形態(tài)是多種多樣的,包括無結(jié)構(gòu)的、半結(jié)構(gòu)的以及結(jié)構(gòu)化的。我們在數(shù)據(jù)源層面統(tǒng)一收集、存儲到存儲引擎當(dāng)中,再通過 SQL 的分析引擎向上層提供數(shù)據(jù)分析服務(wù)。
具體業(yè)務(wù),包括比如實(shí)時(shí)監(jiān)控、實(shí)時(shí)大屏這一類基于日志數(shù)據(jù)分析去做的一些業(yè)務(wù),其刷新率非常高,所以用戶的并發(fā)查詢請求量非常大;還有一些比如像基于日志的數(shù)據(jù)去做實(shí)時(shí)的告警、鏈路分析、交互式分析、AI 異常檢測等,這一類業(yè)務(wù)主要是對于數(shù)據(jù)的時(shí)效性要求非常高,要求查詢和分析延時(shí)要能夠做到秒級實(shí)時(shí)。
還有一類業(yè)務(wù),比如像可視化工具、運(yùn)營報(bào)表、schedule SQL 這一類的業(yè)務(wù),數(shù)據(jù)量是非常大的,面臨超大數(shù)據(jù)規(guī)模的問題。就整體業(yè)務(wù)覆蓋而言,SLS 除了在阿里云上對外提供日志服務(wù)外,在集團(tuán)內(nèi)部也被眾多的 BU 所使用,同時(shí)也經(jīng)歷了多年雙十一的挑戰(zhàn)。
分析引擎的整體能力方面,我們目前每天大概有數(shù)十億次的查詢,每天的行掃描規(guī)模大概在千萬億級別,吞吐大概在數(shù)十 PB 規(guī)模。而我們平均的查詢延時(shí)小于 300ms,在業(yè)務(wù)高峰時(shí)刻的并發(fā)峰值能夠達(dá)到 7.2 萬,屆時(shí)系統(tǒng)會面臨數(shù)十萬的 QPS 壓力。以上就是整體業(yè)務(wù)的情況。
二、核心問題
面對上述業(yè)務(wù)場景和需求,我們面臨的最核心問題主要包括四個(gè)方面。
首先,區(qū)別于傳統(tǒng)的離線數(shù)倉,我們是一個(gè)在線的實(shí)時(shí)分析服務(wù),所以對于查詢的低延時(shí)要求非常高。我們要求秒級的查詢,并且數(shù)據(jù)要可見即可得、可得即可算。
第二,我們面對的數(shù)據(jù)處理規(guī)模是非常大的,數(shù)據(jù)的行掃描規(guī)??赡軓陌偃f到千億級別不等,并且規(guī)模是彈性多變的。
第三,會面臨用戶高并發(fā)的查詢壓力,像雙十一這種業(yè)務(wù)高峰時(shí)刻能達(dá)到 7.2 萬的并發(fā)峰值,同時(shí)單點(diǎn)會有上千的并發(fā)查詢、數(shù)十萬的計(jì)算任務(wù),所以如何去解決系統(tǒng)在面臨這種高并發(fā)查詢下的負(fù)載壓力,是我們面臨的又一個(gè)核心問題。
最后還要去解決整個(gè)云服務(wù)的高可用以及租戶間的隔離,由于云服務(wù)多、租戶是共享云上資源的,所以不可避免會有各種各樣的熱點(diǎn)資源爭用。怎樣去解決服務(wù)的治理以及壓力的防控,保障云服務(wù)的高可用,也是我們面臨的核心問題之一。
三、關(guān)鍵設(shè)計(jì)
接下來主要圍繞這四個(gè)核心的問題,分享在系統(tǒng)架構(gòu)設(shè)計(jì)以及關(guān)鍵環(huán)節(jié)上面的思考和權(quán)衡。首先是 SLS 日志查詢分析范式,主要是由三部分因素組成:第一部分是查詢語句,類似于搜索引擎,可以根據(jù)相關(guān)的關(guān)鍵字或者是一些過濾查詢條件,將特征數(shù)據(jù)檢索出來。第二部分是分析語句,也就是標(biāo)準(zhǔn)的 SQL 語句,可以針對檢索出來的一些特征數(shù)據(jù),進(jìn)行靈活的統(tǒng)計(jì)和分析。第三部分是時(shí)間范圍,可以指定任意的時(shí)間范圍,在這個(gè)范圍內(nèi)進(jìn)行日志數(shù)據(jù)的分析。所以這三個(gè)要素構(gòu)成了 SLS 整個(gè)日志查詢分析的范式。
日志數(shù)據(jù)有它自己的一些特點(diǎn)。首先時(shí)間是日志數(shù)據(jù)的一個(gè)天然屬性。其次日志分析 99% 的場景是面向特征的,比如像上圖中的示例,服務(wù)訪問日志中包含時(shí)間、日志級別、地域、訪問域名、http status、延時(shí)等多個(gè)字段,我們可能就想分析來自 cn-shanghai 地域的訪問情況,那我們可以通過關(guān)鍵詞檢索過濾出需要分析的數(shù)據(jù)。第三,分析的數(shù)據(jù)往往具有局部性,比如對于上面的服務(wù)日志,我們可能就想分析 status 字段,那對于每一條檢索出來的日志,并不需要將整行日志的數(shù)據(jù)全部加載。這些日志數(shù)據(jù)的特點(diǎn)是實(shí)時(shí)、低延時(shí)查詢分析的關(guān)鍵所在。
實(shí)時(shí)計(jì)算、低延遲的關(guān)鍵,我認(rèn)為首先是快速定位數(shù)據(jù),其次是高效加載數(shù)據(jù),最后是如何執(zhí)行高效計(jì)算。在這里索引和列存是關(guān)鍵。首先介紹一下我們的存儲模型,這是一個(gè)三級結(jié)構(gòu),最外層是 project,實(shí)現(xiàn)了用戶級別的隔離;在 project 內(nèi)可以有多個(gè) logstore,它是日志數(shù)據(jù)的存儲單元,實(shí)現(xiàn)了生命周期 TTL 的管理;在一個(gè) logstore 內(nèi)部是由多個(gè)數(shù)據(jù)分片(我們叫它 Shard)組成。Shard 間是按照 Range 粒度進(jìn)行切分,日志數(shù)據(jù)的寫入,是類似于一個(gè)隊(duì)列的形式進(jìn)行追加,然后按照 hash 均衡負(fù)載到各個(gè) Shard 分片上。最終是以 LSM-Tree(log structure merge Tree)的寫入模型將數(shù)據(jù)存儲下來。
前面我們剛剛提到了日志的一個(gè)天然屬性是時(shí)間,這里我們基于 LSM 追加寫入模型,其實(shí)日志數(shù)據(jù)在一個(gè) Shard 內(nèi)都是按照時(shí)間進(jìn)行分布的。所以第一個(gè)關(guān)鍵點(diǎn)是基于時(shí)間檢索模型,根據(jù) From 和 To 的時(shí)間范圍可以快速地定位到某一個(gè) Shard 在某一段時(shí)間內(nèi)的數(shù)據(jù)。同時(shí)根據(jù)查詢分析范式,對于前面的查詢條件,我們可以利用索引倒排技術(shù),高效檢索出來我們需要的特征數(shù)據(jù)。同時(shí),剛剛還提到分析數(shù)據(jù)可能是局部的,用戶可能只需要分析日志數(shù)據(jù)中的某些字段,所以我們實(shí)現(xiàn)了列存,對于索引字段進(jìn)行列式存儲,分析時(shí)將指定列的列存數(shù)據(jù)加載上來進(jìn)行分析即可。
所以,最終在 LSM 寫入之后,會進(jìn)行異步的索引和列存構(gòu)建過程,最終統(tǒng)一存儲到我們的分布式存儲。這就構(gòu)成了我們整體的存儲模型??傮w來說,通過索引和列存,以空間來換時(shí)間,減少了 IO 次數(shù)和無效的數(shù)據(jù)掃描,提升了數(shù)據(jù)讀取和計(jì)算效率。
再來看計(jì)算和存儲架構(gòu),首先無論是存儲還是計(jì)算,都是分布式架構(gòu)。日志數(shù)據(jù)的寫入基于 LSM 模型,在寫入節(jié)點(diǎn)上面,一部分熱數(shù)據(jù)在 memory 里面,另一部分則已經(jīng) Dump 下去,最終寫到分布式存儲中,這部分是數(shù)據(jù)寫入。而查詢分析時(shí)需要加載數(shù)據(jù),我們希望能高效利用 LSM 模型特性,盡可能地從 memory 中加載數(shù)據(jù),減少不必要的網(wǎng)絡(luò)和磁盤 IO,因此在存儲和計(jì)算架構(gòu)上,我們進(jìn)行了數(shù)據(jù)本地性的設(shè)計(jì),將計(jì)算節(jié)點(diǎn)和存儲節(jié)點(diǎn)放在同一個(gè)機(jī)器上面,同時(shí)因?yàn)橛?jì)算節(jié)點(diǎn)和存儲節(jié)點(diǎn)是跨進(jìn)程的,所以涉及到數(shù)據(jù)的交互,這里是通過 domain socket 進(jìn)行控制面的通信,通過 share memory 完成數(shù)據(jù)交接。
通過數(shù)據(jù)本地性的設(shè)計(jì),我們利用了 LSM 里面本地的 mem cache,同時(shí)利用分布式存儲節(jié)點(diǎn)上面的 page cache,減少了不必要的磁盤 IO;同時(shí)也避免了節(jié)點(diǎn)間跨網(wǎng)絡(luò)的 IO 開銷,最終有效地提升了 IO 效率。
有了前面這兩點(diǎn),要實(shí)現(xiàn)實(shí)時(shí)低延遲計(jì)算,仍然存在不少挑戰(zhàn)。這里引用計(jì)算機(jī)領(lǐng)域一個(gè)大佬的話“所有計(jì)算機(jī)領(lǐng)域的問題都可以通過另外一層抽象來解決”。我們其實(shí)也是借鑒了這一思想,在整個(gè)系統(tǒng)里面實(shí)現(xiàn)了一個(gè)分層緩存。
在數(shù)據(jù)層面,利用了分布式存儲節(jié)點(diǎn)上面的 page cache,利用寫入節(jié)點(diǎn)上面的 memory cache 這樣的一些緩存能力。
在索引層面,緩存了倒排數(shù)值、字典等等一些索引塊的信息,減少反復(fù)索引數(shù)據(jù)的加載以及解碼開銷。
在分析引擎層面,對元數(shù)據(jù)進(jìn)行緩存,將索引字段信息、Shard 分片信息,還有數(shù)據(jù)分布等這些信息進(jìn)行緩存,來加速 SQL 語義的解析以及物理執(zhí)行計(jì)劃的生成過程。同時(shí),對于相同 SQL 的邏輯執(zhí)行計(jì)劃進(jìn)行了緩存,來減少分析引擎核心節(jié)點(diǎn) coordinator 上面的重復(fù) SQL 解析的開銷。
在調(diào)度層面,對數(shù)據(jù)的分片以及任務(wù)執(zhí)行的調(diào)度歷史進(jìn)行緩存,這樣做的好處是可能有一些節(jié)點(diǎn)上面已經(jīng)加載過一部分的數(shù)據(jù),它已經(jīng)執(zhí)行過一些歷史任務(wù),對這些調(diào)度歷史進(jìn)行緩存之后,可以基于親和力的調(diào)度,下次再計(jì)算的時(shí)候,可以再調(diào)度到這個(gè)節(jié)點(diǎn)上,最大化的利用數(shù)據(jù)的本地性以及下層緩存的一些收益。
在計(jì)算緩存層面,實(shí)現(xiàn)了一個(gè) partial agg operator 的算子。它主要是緩存相同數(shù)據(jù)在相同算子上的部分聚合計(jì)算結(jié)果,來避免相同數(shù)據(jù)反復(fù)加載和計(jì)算的開銷。
最終在結(jié)果緩存層面,會緩存完全相同的查詢的最終計(jì)算結(jié)果,來減少無效的查詢開銷?;旧贤ㄟ^這三個(gè)層面,在查詢的實(shí)時(shí)性以及低延時(shí)上面,可以做到較好的表現(xiàn)。
第二個(gè)核心問題就是超大數(shù)據(jù)規(guī)模的問題。我們剛剛所講的存儲模型,由于用戶的日志數(shù)據(jù)越寫越多,數(shù)據(jù)塊可能越來越多。按照我們前面數(shù)據(jù)本地性這樣的設(shè)計(jì),所有的計(jì)算要在這樣的一個(gè)存儲節(jié)點(diǎn)上面去走,隨著單 Shard 上數(shù)據(jù)規(guī)模越來越大,單節(jié)點(diǎn)的數(shù)據(jù)讀取和計(jì)算能力可能是不夠的。所以整體來說,我們會將 LSM 落到分布式存儲里面的一些 block 的數(shù)據(jù)塊,把它散列到更多的存儲節(jié)點(diǎn)上面,分派給上層更多的計(jì)算節(jié)點(diǎn),這樣整體再交給上面的計(jì)算匯聚層,去做相關(guān)的計(jì)算的匯聚。這樣一來,在存儲層面我們的 IO 壓力可以得到水平散列,在計(jì)算層面,我們的計(jì)算并行度能夠得到大幅的提升,在計(jì)算節(jié)點(diǎn)上面的內(nèi)存、CPU 這些資源也能夠得到水平擴(kuò)展。這個(gè)是我們在整體架構(gòu)上面做的調(diào)整(即存儲計(jì)算分離)。
但是我們會面臨新的挑戰(zhàn)。由于剛剛所說的數(shù)據(jù)本地性的設(shè)計(jì),就是為了避免網(wǎng)絡(luò)開銷來高效地利用數(shù)據(jù)的本地的緩存,這種存算分離的模式,可能會丟失一部分?jǐn)?shù)據(jù)的本地性,可能會導(dǎo)致延時(shí)的增高。另外,雖然我們?nèi)プ隽怂降臄U(kuò)展,但是由于數(shù)據(jù)的一些熱點(diǎn)或者是一些傾斜,可能會造成一些局部的熱點(diǎn)的負(fù)載壓力。
針對數(shù)據(jù)本地性丟失問題,我們的應(yīng)對方式是基于親和力的調(diào)度,再去調(diào)度到這個(gè)節(jié)點(diǎn)上,利用這個(gè)節(jié)點(diǎn)上的數(shù)據(jù)的本地性,盡可能減少數(shù)據(jù)加載以及延時(shí)的開銷。另外一個(gè)就是去對負(fù)載進(jìn)行實(shí)時(shí)的感知,通過均衡調(diào)度的一些策略,盡量去減少系統(tǒng)的負(fù)載的一些熱點(diǎn)。所以整體來說,我們是在速度和規(guī)模之間進(jìn)行一個(gè)權(quán)衡。通過水平擴(kuò)展,我們可以實(shí)現(xiàn) IO、內(nèi)存以及 CPU 等資源的橫向擴(kuò)展能力。同時(shí)通過存算分離的架構(gòu),可以提升存算的并行度,解決超大數(shù)據(jù)規(guī)模的問題。并通過親和力的調(diào)度,以及負(fù)載均衡來應(yīng)對新的挑戰(zhàn)。
第三個(gè)核心問題,系統(tǒng)會面臨一些高并發(fā)的查詢壓力。整體來說,分析引擎的架構(gòu)是非常簡單的,前面會有一個(gè) coordinator,也就是一個(gè)協(xié)調(diào)節(jié)點(diǎn)。具體工作的 worker 節(jié)點(diǎn),統(tǒng)一由 coordinator 節(jié)點(diǎn)來負(fù)責(zé)整體任務(wù)的調(diào)度。所以當(dāng)用戶的并發(fā)查詢請求越來越高的時(shí)候,coordinator 上面的負(fù)載就會非常大,因?yàn)樗纫薪忧懊嬗脩舻牟樵冋埱?,同時(shí)還要負(fù)責(zé) SQL 的整體的解析任務(wù),同時(shí)還要負(fù)責(zé)整體的計(jì)算過程當(dāng)中的任務(wù)調(diào)度。我們在實(shí)際線上也進(jìn)行了采樣分析,發(fā)現(xiàn) SQL 解析部分,包括詞法分析、語法分析,還有 planner 生成以及優(yōu)化改寫這些步驟,對于 CPU 的消耗開銷是非常大的,尤其是 plan 生成和優(yōu)化改寫這兩步。
另一方面,我們也分析了我們線上的一些業(yè)務(wù),發(fā)現(xiàn)很多業(yè)務(wù)來自于儀表盤、智能告警,還有 schedule SQL 這樣一些業(yè)務(wù)。這類業(yè)務(wù)查詢是固定不變的,只變動一些時(shí)間。所以這樣的查詢所對應(yīng)的邏輯執(zhí)行計(jì)劃是不變的,我們就在這個(gè)層面去做了查詢 plan 這樣的一個(gè)緩存,通過 plan 的 cache 來減少系統(tǒng)關(guān)鍵節(jié)點(diǎn)上面的關(guān)鍵負(fù)載的開銷。最終的效果是緩存命中率能夠達(dá)到 75%,同時(shí)關(guān)鍵節(jié)點(diǎn)上 CPU 的消耗能夠降低 20% 到 30%,而且我們的 JVM 的 GC 壓力和次數(shù)也有明顯的降低。
另外一個(gè)高并發(fā)的問題就是我們的 coordinator 節(jié)點(diǎn)上可能會存在這種網(wǎng)絡(luò)連接數(shù)爆炸式的增長。因?yàn)?coordinator 在整個(gè)分析系統(tǒng)中,是核心協(xié)調(diào)節(jié)點(diǎn),它要和集群里面所有的 worker 節(jié)點(diǎn)進(jìn)行通信,任務(wù)上面進(jìn)行節(jié)點(diǎn)上面的調(diào)度交互。所以當(dāng)集群里面的節(jié)點(diǎn)規(guī)模越來越大,單個(gè) coordinator 節(jié)點(diǎn)網(wǎng)絡(luò)通信的量是非常大的。面臨的挑戰(zhàn)是單秒就可能達(dá)到 10 萬以上的并發(fā)任務(wù)數(shù)。原來是 HTTP 短連接這種通信模式,單個(gè) coordinator 作為一個(gè)客戶端,要去和所有的 worker 節(jié)點(diǎn)進(jìn)行通信。我們的應(yīng)對方案就是復(fù)用信道,將 HTTP 短連接改造成 RPC 長連。通過復(fù)用信道來減少反復(fù)建連的開銷。同時(shí)可以有效控制連接的規(guī)模,在集群內(nèi)把連接數(shù)做到恒定可控。
第四個(gè)核心問題是服務(wù)的高可用以及租戶之間的隔離,這也是我們作為云服務(wù)不得不解決的一個(gè)核心問題。云上多租戶的一個(gè)核心挑戰(zhàn)在于如何在共享資源的前提下去做好租戶之間的隔離,做好服務(wù)的可用性。我們的思路跟 Linux 的多租戶分時(shí)復(fù)用的思路是相似的,分成若干的時(shí)間片去給用戶使用相關(guān)的資源。重點(diǎn)在于我們怎么去做隔離,以及怎么保證系統(tǒng)的可用性,我們通過限流的方式來做自我的保護(hù),限制用戶的使用。首先我們實(shí)現(xiàn)了分布式的用戶查詢隊(duì)列,基于一致性哈??梢詫⒕唧w的用戶落到具體的 coordinator 節(jié)點(diǎn)上,在 coordinator 節(jié)點(diǎn)上來統(tǒng)一管控用戶的資源使用情況,控制用戶的并發(fā)查詢數(shù)。同時(shí)在執(zhí)行過程當(dāng)中,去監(jiān)控用戶的內(nèi)存以及查詢時(shí)間的情況來限定其使用。
在具體的執(zhí)行層面,我們會對 task 的時(shí)間片進(jìn)行有效的限定,這里面包括計(jì)算層面的,還有查詢檢索層面的,以及 IO 層面的各種任務(wù)時(shí)間片。最后,在存儲層面,我們會對整體的數(shù)據(jù)掃描量進(jìn)行一個(gè)限定,避免一下打爆我們的網(wǎng)絡(luò)帶寬。整體來說,通過這樣的一個(gè)分層的限流措施,我們可以比較好地做到在共享資源情況下的租戶隔離,也做到一個(gè)比較好的系統(tǒng)的自我防護(hù),保證服務(wù)的高可用。
這里還帶來另外一個(gè)問題,由于我們做了各種限定,可能用戶的數(shù)據(jù)在計(jì)算的過程當(dāng)中沒有加載完整,這就會導(dǎo)致查詢不精確。針對這種情況,我們的解決思路是并沒有直接去返回,查詢失敗了會把本次查詢的一個(gè)已經(jīng)計(jì)算出來的結(jié)果返回,并且會標(biāo)記這個(gè)結(jié)果是不精確的。同時(shí)由于我們分層緩存的設(shè)計(jì),通過讓用戶進(jìn)一步地去查詢,可以漸進(jìn)式地去逼近一個(gè)精確的結(jié)果。整體來說,我們是通過分層的保護(hù)和限流,來實(shí)現(xiàn)租戶資源之間的隔離和服務(wù)的穩(wěn)定可用。同時(shí)我們要在速度、規(guī)模還有穩(wěn)定性上面去做一些權(quán)衡和取舍。
總結(jié)一下前面所介紹的實(shí)踐經(jīng)驗(yàn)。
首先,通過索引列存、數(shù)據(jù)本地性,以及分級的緩存,解決了第一個(gè)核心問題——查詢的實(shí)時(shí)性以及低延時(shí)問題;
第二,通過水平的擴(kuò)展、存算分離等架構(gòu)上的改造,解決了第二個(gè)核心問題;
第三,通過一些關(guān)鍵節(jié)點(diǎn)上面的性能提升,以及網(wǎng)絡(luò)上的優(yōu)化,解決了系統(tǒng)高并發(fā)上的壓力。我們目前能夠支持云上的海量用戶的在線并發(fā)查詢。同時(shí)我們經(jīng)受住了多年雙 11 大促業(yè)務(wù)高峰并發(fā)峰值的考驗(yàn)。
最后,通過分層的限流以及調(diào)度隔離,實(shí)現(xiàn)了整體的服務(wù)的高可用以及多租戶的隔離,可以穩(wěn)定支撐阿里集團(tuán)數(shù)十個(gè) BU,數(shù)千條業(yè)務(wù)線的日志分析需求。