Presto 在阿里云實時日志分析中的實踐和優(yōu)化

一、業(yè)務(wù)背景
首先第一部分介紹一下我們的業(yè)務(wù)背景。阿里云 SLS 是一個云上一站式可觀測日志服務(wù)平臺。

SLS 提供了強大的數(shù)據(jù)采集、數(shù)據(jù)加工、消費投遞等能力,數(shù)據(jù)采集利器 ilogtail 目前也已經(jīng)完全開源。數(shù)據(jù)采集上來后提供數(shù)據(jù)的統(tǒng)一存儲,包括熱存、智能冷存等,幫助用戶盡可能節(jié)省成本。在存儲之上,提供了數(shù)據(jù)處理與分析能力,包括即席查詢分析、關(guān)聯(lián)分析等。這兩塊構(gòu)成了整個 SLS 產(chǎn)品的核心基礎(chǔ)能力。在這個基礎(chǔ)能力之上提供了豐富的工具和應(yīng)用,最終服務(wù)于各種不同的角色和用戶。
本文將聚焦在存儲和分析基礎(chǔ)能力上面的建設(shè),重點分享日志分析系統(tǒng),以及在面對核心問題時的一些架構(gòu)設(shè)計思路和經(jīng)驗。

這是具體日志分析業(yè)務(wù)覆蓋和服務(wù)的能力,主要是圍繞日志場景去進行數(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ù),包括比如實時監(jiān)控、實時大屏這一類基于日志數(shù)據(jù)分析去做的一些業(yè)務(wù),其刷新率非常高,所以用戶的并發(fā)查詢請求量非常大;還有一些比如像基于日志的數(shù)據(jù)去做實時的告警、鏈路分析、交互式分析、AI 異常檢測等,這一類業(yè)務(wù)主要是對于數(shù)據(jù)的時效性要求非常高,要求查詢和分析延時要能夠做到秒級實時。
還有一類業(yè)務(wù),比如像可視化工具、運營報表、schedule SQL 這一類的業(yè)務(wù),數(shù)據(jù)量是非常大的,面臨超大數(shù)據(jù)規(guī)模的問題。就整體業(yè)務(wù)覆蓋而言,SLS 除了在阿里云上對外提供日志服務(wù)外,在集團內(nèi)部也被眾多的 BU 所使用,同時也經(jīng)歷了多年雙十一的挑戰(zhàn)。
分析引擎的整體能力方面,我們目前每天大概有數(shù)十億次的查詢,每天的行掃描規(guī)模大概在千萬億級別,吞吐大概在數(shù)十 PB 規(guī)模。而我們平均的查詢延時小于 300ms,在業(yè)務(wù)高峰時刻的并發(fā)峰值能夠達到 7.2 萬,屆時系統(tǒng)會面臨數(shù)十萬的 QPS 壓力。以上就是整體業(yè)務(wù)的情況。
二、核心問題

面對上述業(yè)務(wù)場景和需求,我們面臨的最核心問題主要包括四個方面。
首先,區(qū)別于傳統(tǒng)的離線數(shù)倉,我們是一個在線的實時分析服務(wù),所以對于查詢的低延時要求非常高。我們要求秒級的查詢,并且數(shù)據(jù)要可見即可得、可得即可算。
第二,我們面對的數(shù)據(jù)處理規(guī)模是非常大的,數(shù)據(jù)的行掃描規(guī)模可能從百萬到千億級別不等,并且規(guī)模是彈性多變的。
第三,會面臨用戶高并發(fā)的查詢壓力,像雙十一這種業(yè)務(wù)高峰時刻能達到 7.2 萬的并發(fā)峰值,同時單點會有上千的并發(fā)查詢、數(shù)十萬的計算任務(wù),所以如何去解決系統(tǒng)在面臨這種高并發(fā)查詢下的負載壓力,是我們面臨的又一個核心問題。
最后還要去解決整個云服務(wù)的高可用以及租戶間的隔離,由于云服務(wù)多、租戶是共享云上資源的,所以不可避免會有各種各樣的熱點資源爭用。怎樣去解決服務(wù)的治理以及壓力的防控,保障云服務(wù)的高可用,也是我們面臨的核心問題之一。
三、關(guān)鍵設(shè)計

接下來主要圍繞這四個核心的問題,分享在系統(tǒng)架構(gòu)設(shè)計以及關(guān)鍵環(huán)節(jié)上面的思考和權(quán)衡。首先是 SLS 日志查詢分析范式,主要是由三部分因素組成:第一部分是查詢語句,類似于搜索引擎,可以根據(jù)相關(guān)的關(guān)鍵字或者是一些過濾查詢條件,將特征數(shù)據(jù)檢索出來。第二部分是分析語句,也就是標(biāo)準(zhǔn)的 SQL 語句,可以針對檢索出來的一些特征數(shù)據(jù),進行靈活的統(tǒng)計和分析。第三部分是時間范圍,可以指定任意的時間范圍,在這個范圍內(nèi)進行日志數(shù)據(jù)的分析。所以這三個要素構(gòu)成了 SLS 整個日志查詢分析的范式。

日志數(shù)據(jù)有它自己的一些特點。首先時間是日志數(shù)據(jù)的一個天然屬性。其次日志分析 99% 的場景是面向特征的,比如像上圖中的示例,服務(wù)訪問日志中包含時間、日志級別、地域、訪問域名、http status、延時等多個字段,我們可能就想分析來自 cn-shanghai 地域的訪問情況,那我們可以通過關(guān)鍵詞檢索過濾出需要分析的數(shù)據(jù)。第三,分析的數(shù)據(jù)往往具有局部性,比如對于上面的服務(wù)日志,我們可能就想分析 status 字段,那對于每一條檢索出來的日志,并不需要將整行日志的數(shù)據(jù)全部加載。這些日志數(shù)據(jù)的特點是實時、低延時查詢分析的關(guān)鍵所在。

實時計算、低延遲的關(guān)鍵,我認(rèn)為首先是快速定位數(shù)據(jù),其次是高效加載數(shù)據(jù),最后是如何執(zhí)行高效計算。在這里索引和列存是關(guān)鍵。首先介紹一下我們的存儲模型,這是一個三級結(jié)構(gòu),最外層是 project,實現(xiàn)了用戶級別的隔離;在 project 內(nèi)可以有多個 logstore,它是日志數(shù)據(jù)的存儲單元,實現(xiàn)了生命周期 TTL 的管理;在一個 logstore 內(nèi)部是由多個數(shù)據(jù)分片(我們叫它 Shard)組成。Shard 間是按照 Range 粒度進行切分,日志數(shù)據(jù)的寫入,是類似于一個隊列的形式進行追加,然后按照 hash 均衡負載到各個 Shard 分片上。最終是以 LSM-Tree(log structure merge Tree)的寫入模型將數(shù)據(jù)存儲下來。
前面我們剛剛提到了日志的一個天然屬性是時間,這里我們基于 LSM 追加寫入模型,其實日志數(shù)據(jù)在一個 Shard 內(nèi)都是按照時間進行分布的。所以第一個關(guān)鍵點是基于時間檢索模型,根據(jù) From 和 To 的時間范圍可以快速地定位到某一個 Shard 在某一段時間內(nèi)的數(shù)據(jù)。同時根據(jù)查詢分析范式,對于前面的查詢條件,我們可以利用索引倒排技術(shù),高效檢索出來我們需要的特征數(shù)據(jù)。同時,剛剛還提到分析數(shù)據(jù)可能是局部的,用戶可能只需要分析日志數(shù)據(jù)中的某些字段,所以我們實現(xiàn)了列存,對于索引字段進行列式存儲,分析時將指定列的列存數(shù)據(jù)加載上來進行分析即可。
所以,最終在 LSM 寫入之后,會進行異步的索引和列存構(gòu)建過程,最終統(tǒng)一存儲到我們的分布式存儲。這就構(gòu)成了我們整體的存儲模型。總體來說,通過索引和列存,以空間來換時間,減少了 IO 次數(shù)和無效的數(shù)據(jù)掃描,提升了數(shù)據(jù)讀取和計算效率。

再來看計算和存儲架構(gòu),首先無論是存儲還是計算,都是分布式架構(gòu)。日志數(shù)據(jù)的寫入基于 LSM 模型,在寫入節(jié)點上面,一部分熱數(shù)據(jù)在 memory 里面,另一部分則已經(jīng) Dump 下去,最終寫到分布式存儲中,這部分是數(shù)據(jù)寫入。而查詢分析時需要加載數(shù)據(jù),我們希望能高效利用 LSM 模型特性,盡可能地從 memory 中加載數(shù)據(jù),減少不必要的網(wǎng)絡(luò)和磁盤 IO,因此在存儲和計算架構(gòu)上,我們進行了數(shù)據(jù)本地性的設(shè)計,將計算節(jié)點和存儲節(jié)點放在同一個機器上面,同時因為計算節(jié)點和存儲節(jié)點是跨進程的,所以涉及到數(shù)據(jù)的交互,這里是通過 domain socket 進行控制面的通信,通過 share memory 完成數(shù)據(jù)交接。
通過數(shù)據(jù)本地性的設(shè)計,我們利用了 LSM 里面本地的 mem cache,同時利用分布式存儲節(jié)點上面的 page cache,減少了不必要的磁盤 IO;同時也避免了節(jié)點間跨網(wǎng)絡(luò)的 IO 開銷,最終有效地提升了 IO 效率。

有了前面這兩點,要實現(xiàn)實時低延遲計算,仍然存在不少挑戰(zhàn)。這里引用計算機領(lǐng)域一個大佬的話“所有計算機領(lǐng)域的問題都可以通過另外一層抽象來解決”。我們其實也是借鑒了這一思想,在整個系統(tǒng)里面實現(xiàn)了一個分層緩存。
在數(shù)據(jù)層面,利用了分布式存儲節(jié)點上面的 page cache,利用寫入節(jié)點上面的 memory cache 這樣的一些緩存能力。
在索引層面,緩存了倒排數(shù)值、字典等等一些索引塊的信息,減少反復(fù)索引數(shù)據(jù)的加載以及解碼開銷。
在分析引擎層面,對元數(shù)據(jù)進行緩存,將索引字段信息、Shard 分片信息,還有數(shù)據(jù)分布等這些信息進行緩存,來加速 SQL 語義的解析以及物理執(zhí)行計劃的生成過程。同時,對于相同 SQL 的邏輯執(zhí)行計劃進行了緩存,來減少分析引擎核心節(jié)點 coordinator 上面的重復(fù) SQL 解析的開銷。
在調(diào)度層面,對數(shù)據(jù)的分片以及任務(wù)執(zhí)行的調(diào)度歷史進行緩存,這樣做的好處是可能有一些節(jié)點上面已經(jīng)加載過一部分的數(shù)據(jù),它已經(jīng)執(zhí)行過一些歷史任務(wù),對這些調(diào)度歷史進行緩存之后,可以基于親和力的調(diào)度,下次再計算的時候,可以再調(diào)度到這個節(jié)點上,最大化的利用數(shù)據(jù)的本地性以及下層緩存的一些收益。
在計算緩存層面,實現(xiàn)了一個 partial agg operator 的算子。它主要是緩存相同數(shù)據(jù)在相同算子上的部分聚合計算結(jié)果,來避免相同數(shù)據(jù)反復(fù)加載和計算的開銷。
最終在結(jié)果緩存層面,會緩存完全相同的查詢的最終計算結(jié)果,來減少無效的查詢開銷。基本上通過這三個層面,在查詢的實時性以及低延時上面,可以做到較好的表現(xiàn)。

第二個核心問題就是超大數(shù)據(jù)規(guī)模的問題。我們剛剛所講的存儲模型,由于用戶的日志數(shù)據(jù)越寫越多,數(shù)據(jù)塊可能越來越多。按照我們前面數(shù)據(jù)本地性這樣的設(shè)計,所有的計算要在這樣的一個存儲節(jié)點上面去走,隨著單 Shard 上數(shù)據(jù)規(guī)模越來越大,單節(jié)點的數(shù)據(jù)讀取和計算能力可能是不夠的。所以整體來說,我們會將 LSM 落到分布式存儲里面的一些 block 的數(shù)據(jù)塊,把它散列到更多的存儲節(jié)點上面,分派給上層更多的計算節(jié)點,這樣整體再交給上面的計算匯聚層,去做相關(guān)的計算的匯聚。這樣一來,在存儲層面我們的 IO 壓力可以得到水平散列,在計算層面,我們的計算并行度能夠得到大幅的提升,在計算節(jié)點上面的內(nèi)存、CPU 這些資源也能夠得到水平擴展。這個是我們在整體架構(gòu)上面做的調(diào)整(即存儲計算分離)。
但是我們會面臨新的挑戰(zhàn)。由于剛剛所說的數(shù)據(jù)本地性的設(shè)計,就是為了避免網(wǎng)絡(luò)開銷來高效地利用數(shù)據(jù)的本地的緩存,這種存算分離的模式,可能會丟失一部分?jǐn)?shù)據(jù)的本地性,可能會導(dǎo)致延時的增高。另外,雖然我們?nèi)プ隽怂降臄U展,但是由于數(shù)據(jù)的一些熱點或者是一些傾斜,可能會造成一些局部的熱點的負載壓力。
針對數(shù)據(jù)本地性丟失問題,我們的應(yīng)對方式是基于親和力的調(diào)度,再去調(diào)度到這個節(jié)點上,利用這個節(jié)點上的數(shù)據(jù)的本地性,盡可能減少數(shù)據(jù)加載以及延時的開銷。另外一個就是去對負載進行實時的感知,通過均衡調(diào)度的一些策略,盡量去減少系統(tǒng)的負載的一些熱點。所以整體來說,我們是在速度和規(guī)模之間進行一個權(quán)衡。通過水平擴展,我們可以實現(xiàn) IO、內(nèi)存以及 CPU 等資源的橫向擴展能力。同時通過存算分離的架構(gòu),可以提升存算的并行度,解決超大數(shù)據(jù)規(guī)模的問題。并通過親和力的調(diào)度,以及負載均衡來應(yīng)對新的挑戰(zhàn)。

第三個核心問題,系統(tǒng)會面臨一些高并發(fā)的查詢壓力。整體來說,分析引擎的架構(gòu)是非常簡單的,前面會有一個 coordinator,也就是一個協(xié)調(diào)節(jié)點。具體工作的 worker 節(jié)點,統(tǒng)一由 coordinator 節(jié)點來負責(zé)整體任務(wù)的調(diào)度。所以當(dāng)用戶的并發(fā)查詢請求越來越高的時候,coordinator 上面的負載就會非常大,因為它既要承接前面用戶的查詢請求,同時還要負責(zé) SQL 的整體的解析任務(wù),同時還要負責(zé)整體的計算過程當(dāng)中的任務(wù)調(diào)度。我們在實際線上也進行了采樣分析,發(fā)現(xiàn) SQL 解析部分,包括詞法分析、語法分析,還有 planner 生成以及優(yōu)化改寫這些步驟,對于 CPU 的消耗開銷是非常大的,尤其是 plan 生成和優(yōu)化改寫這兩步。

另一方面,我們也分析了我們線上的一些業(yè)務(wù),發(fā)現(xiàn)很多業(yè)務(wù)來自于儀表盤、智能告警,還有 schedule SQL 這樣一些業(yè)務(wù)。這類業(yè)務(wù)查詢是固定不變的,只變動一些時間。所以這樣的查詢所對應(yīng)的邏輯執(zhí)行計劃是不變的,我們就在這個層面去做了查詢 plan 這樣的一個緩存,通過 plan 的 cache 來減少系統(tǒng)關(guān)鍵節(jié)點上面的關(guān)鍵負載的開銷。最終的效果是緩存命中率能夠達到 75%,同時關(guān)鍵節(jié)點上 CPU 的消耗能夠降低 20% 到 30%,而且我們的 JVM 的 GC 壓力和次數(shù)也有明顯的降低。
另外一個高并發(fā)的問題就是我們的 coordinator 節(jié)點上可能會存在這種網(wǎng)絡(luò)連接數(shù)爆炸式的增長。因為 coordinator 在整個分析系統(tǒng)中,是核心協(xié)調(diào)節(jié)點,它要和集群里面所有的 worker 節(jié)點進行通信,任務(wù)上面進行節(jié)點上面的調(diào)度交互。所以當(dāng)集群里面的節(jié)點規(guī)模越來越大,單個 coordinator 節(jié)點網(wǎng)絡(luò)通信的量是非常大的。面臨的挑戰(zhàn)是單秒就可能達到 10 萬以上的并發(fā)任務(wù)數(shù)。原來是 HTTP 短連接這種通信模式,單個 coordinator 作為一個客戶端,要去和所有的 worker 節(jié)點進行通信。我們的應(yīng)對方案就是復(fù)用信道,將 HTTP 短連接改造成 RPC 長連。通過復(fù)用信道來減少反復(fù)建連的開銷。同時可以有效控制連接的規(guī)模,在集群內(nèi)把連接數(shù)做到恒定可控。

第四個核心問題是服務(wù)的高可用以及租戶之間的隔離,這也是我們作為云服務(wù)不得不解決的一個核心問題。云上多租戶的一個核心挑戰(zhàn)在于如何在共享資源的前提下去做好租戶之間的隔離,做好服務(wù)的可用性。我們的思路跟 Linux 的多租戶分時復(fù)用的思路是相似的,分成若干的時間片去給用戶使用相關(guān)的資源。重點在于我們怎么去做隔離,以及怎么保證系統(tǒng)的可用性,我們通過限流的方式來做自我的保護,限制用戶的使用。首先我們實現(xiàn)了分布式的用戶查詢隊列,基于一致性哈希可以將具體的用戶落到具體的 coordinator 節(jié)點上,在 coordinator 節(jié)點上來統(tǒng)一管控用戶的資源使用情況,控制用戶的并發(fā)查詢數(shù)。同時在執(zhí)行過程當(dāng)中,去監(jiān)控用戶的內(nèi)存以及查詢時間的情況來限定其使用。

在具體的執(zhí)行層面,我們會對 task 的時間片進行有效的限定,這里面包括計算層面的,還有查詢檢索層面的,以及 IO 層面的各種任務(wù)時間片。最后,在存儲層面,我們會對整體的數(shù)據(jù)掃描量進行一個限定,避免一下打爆我們的網(wǎng)絡(luò)帶寬。整體來說,通過這樣的一個分層的限流措施,我們可以比較好地做到在共享資源情況下的租戶隔離,也做到一個比較好的系統(tǒng)的自我防護,保證服務(wù)的高可用。
這里還帶來另外一個問題,由于我們做了各種限定,可能用戶的數(shù)據(jù)在計算的過程當(dāng)中沒有加載完整,這就會導(dǎo)致查詢不精確。針對這種情況,我們的解決思路是并沒有直接去返回,查詢失敗了會把本次查詢的一個已經(jīng)計算出來的結(jié)果返回,并且會標(biāo)記這個結(jié)果是不精確的。同時由于我們分層緩存的設(shè)計,通過讓用戶進一步地去查詢,可以漸進式地去逼近一個精確的結(jié)果。整體來說,我們是通過分層的保護和限流,來實現(xiàn)租戶資源之間的隔離和服務(wù)的穩(wěn)定可用。同時我們要在速度、規(guī)模還有穩(wěn)定性上面去做一些權(quán)衡和取舍。

總結(jié)一下前面所介紹的實踐經(jīng)驗。
首先,通過索引列存、數(shù)據(jù)本地性,以及分級的緩存,解決了第一個核心問題——查詢的實時性以及低延時問題;
第二,通過水平的擴展、存算分離等架構(gòu)上的改造,解決了第二個核心問題;
第三,通過一些關(guān)鍵節(jié)點上面的性能提升,以及網(wǎng)絡(luò)上的優(yōu)化,解決了系統(tǒng)高并發(fā)上的壓力。我們目前能夠支持云上的海量用戶的在線并發(fā)查詢。同時我們經(jīng)受住了多年雙 11 大促業(yè)務(wù)高峰并發(fā)峰值的考驗。
最后,通過分層的限流以及調(diào)度隔離,實現(xiàn)了整體的服務(wù)的高可用以及多租戶的隔離,可以穩(wěn)定支撐阿里集團數(shù)十個 BU,數(shù)千條業(yè)務(wù)線的日志分析需求。

































