360 政企安全集團基於 Flink 的 PB 級數據即席查詢實踐

簡介: Threat Hunting 平臺的架構與設計,及以降低 IO 爲目標的優化與探索。爲什麼以及如何使用塊索引。

本文整理自 360 政企安全集團的大數據工程師蘇軍以及劉佳在 Flink Forward Asia 2020 分享的議題《基於 Flink 的 PB 級數據即席查詢實踐》,文章內容爲:

  1. Threat Hunting 平臺的架構與設計(蘇軍)
  2. 以降低 IO 爲目標的優化與探索(劉佳)
  3. 未來規劃

 

img

首先做一個簡單的個人以及團隊介紹。我們來自 360 政企安全集團,目前主要從事 360 安全大腦的 “威脅狩獵“ 項目的開發工作。我們團隊接觸 Flink 的時間比較早,在此期間,我們基於 Flink 開發出了多款產品,並在 2017 年和 2019 年參加了於柏林舉辦的 Flink Forward 大會,分別介紹了我們的 “UEBA” 以及 “AutoML” 兩款產品。

img

本次分享主要分爲兩塊內容:

  • 第一部分 “Threat Hunting 平臺的架構與設計” 將由蘇軍來爲大家分享;
  • 第二部分 “以降低 IO 爲目標的優化與探索” 將由劉佳來爲大家分享。

一、Threat Hunting 平臺的架構與設計 (蘇軍)

第一部分內容大致分爲三個部分,分別是:

  • 平臺的演進
  • 架構設計
  • 深入探索索引結構

1. 平臺的演進

img

我們認爲所有技術的演化和革新都需要具體的商業問題來驅動,以下是我們團隊近幾年基於 Flink 開發的幾款產品:

  • 2017 年我們基於 Flink DataStream 開發了用戶行爲分析系統 UEBA,它是通過接入企業 IT 拓撲的各類行爲數據,比如身份認證數據、應用系統訪問數據、終端安全數據、網絡流量解析數據等等,以用戶 / 資產爲核心來進行威脅行爲的實時檢測,最後構建出用戶威脅等級和畫像的系統;
  • 2018 年基於 UEBA 的實施經驗,我們發現安全分析人員往往需要一種手段來獲取安全事件對應的原始日誌,去進一步確認安全威脅的源頭和解決方式。於是我們基於 Spark 開發了 HQL 來解決在離線模式下的數據檢索問題,其中 HQL 可以認爲是表達能力比 SQL 更加豐富的查詢語言,大致可以看作是在 SQL 能力的基礎上增加了算法類算;
  • 2019 年隨着離線 HQL 在客戶那邊的使用,我們發現其本身就能夠快速定義安全規則,構建威脅模型,如果在離線模式下寫完語句後直接發佈成在線任務,會大大縮短開發週期,加上 Flink SQL 能力相對完善,於是我們基於 Flink SQL + CEP 來升級了 HQL 的能力,產生了 HQL RealTime 版本;
  • 2020 年隨着客戶數據量的增大,很多已經達到了 PB 級,過往的解決方案導致離線的數據檢索性能遠遠低於預期,安全分析人員習慣使用 like 和全文檢索等模糊匹配操作,造成查詢延時非常大。於是從今年開始,我們着重優化 HQL 的離線檢索能力,並推出了全新的 Threat Hunting 平臺。

img

通過調查發現,擁有 PB 級數據規模的客戶往往有以下幾個商業需求:

  • 第一是低成本的雲原生架構。我們知道目前大部分的大數據架構都是基於 hadoop 的,其特點是數據就在計算節點上,能夠減少大量網絡開銷,加速計算性能。但是整個集羣爲了做到資源均衡,往往需要相同的資源配置,且爲了能夠存儲儘量多的數據,集羣規模會很大, 所以這類架構在前期需要投入大量硬件成本。

    而存算分離和彈性計算則能夠解決這一問題,因爲磁盤的價格是遠低於內存和 CPU 的,所以用廉價的磁盤存儲搭配低配 CPU 和內存來存儲數據,用少量高配機器來做計算,可以在很大程度上降低成本。

  • 第二是低延時的查詢響應。安全分析人員在做威脅檢測時,大部分時間是即席查詢,即通過過濾、join 來做數據的檢索和關聯。爲了能夠儘快的獲取查詢結果,對應的技術方案是:列存/索引/緩存。

    • 列存不用多說了,是大數據領域常見的存儲方案;
    • 在列存的基礎上,高效的索引方案能夠大量降低 io,提高查詢性能;
    • 而存算分析帶來的網絡延時可以由分佈式緩存來彌補。
  • 第三是需要豐富的查詢能力,其中包括單行的 fields/filter/udf 等,多行的聚合 /join,甚至算法類的分析能力,這部分我們主要依賴於自己開發的分析語言 HQL 來提供。

2. 架構設計

img

首先,數據是來自於已經存儲在 ES 中的歷史數據和 kafka 裏的實時數據,其中 ES 裏的歷史數據我們通過自己開發的同步工具來同步,kafka 裏的實時數據我們則通過 Streaming File Sink 寫 orc 文件到存儲集羣。在數據同步的同時,我們會將這批數據的索引信息更新到數據庫中。

安全分析人員會從前端頁面通過寫交互式分析語言 HQL 發起數據檢索的請求,此時請求會進入調度系統,一旦開始執行作業,首先會將分析語句解析成算子列表,算子緩存算法會判斷該次查詢是否可以命中緩存系統中已有的緩存數據。

  • 如果分析語句的輸入是已經算好並且 cache 好了的中間結果,那麼直接讀取緩存來繼續計算;
  • 如果不能命中,證明我們必須從 orc 文件開始重新計算。

我們會先提取出查詢語言的過濾條件或者是 Join 條件來做謂詞下推,進入索引數據庫中獲得目前符合該查詢的文件列表,隨後將文件列表交給計算引擎來進行計算。計算引擎我們採用雙引擎模式,其中複雜度高的語句我們通過 Flink 引擎來完成,其它較爲簡單的任務我們交給平臺內部的 “蜂鳥引擎”。“蜂鳥引擎” 基於 Apache arrow 做向量化執行,加上 LLVM 編譯,查詢延遲會非常小。

由於整個系統的存算分離,爲了加速數據讀取,我們在計算集羣節點上增加了 alluxio 來提供數據緩存服務,其中不僅緩存 remote cluster 上的數據,同時會緩存部分歷史作業結果,通過算子緩存的算法來加速下次計算任務。

這裏還需要強調兩點:

  • 第一點是索引數據庫會返回一批符合該條件的文件列表,如果文件列表非常大的話,當前的 Flink 版本在構建 job graph 時,在獲取 Filelist Statistics 邏輯這裏在遍歷大量文件的時候,會造成長時間無法構建出 job graph 的問題。目前我們對其進行了修復,後期會貢獻給社區。
  • 第二點是數據緩存那一塊,我們的 HQL 之前是通過 Spark 來實現的。用過 Spark 的人可能知道,Spark 會把一個 table 來做 cache 或 persist。我們在遷移到 Flink 的時候,也沿用了這個算子。Flink 這邊我們自己實現了一套,就是用戶在 cache table 時,我們會把它註冊成一個全新的 table source,後面在重新讀取的時候只會用這個新的 table source 來打通整個流程。

3. 深入探索索引結構

img

數據庫爲了加速數據檢索,我們往往會事先爲數據創建索引,再在掃描數據之前通過索引定位到數據的起始位置,從而加速數據檢索。而傳統數據庫常見的是行索引,通過一個或若干字段創建索引,索引結果以樹形結構存儲,此類索引能夠精確到行級別,索引效率最高。

某些大數據項目也支持了行索引,而它所帶來的弊端就是大量的索引數據會造成寫入和檢索的延時。而我們平臺處理的是機器數據,例如終端/網絡這類數據,它的特點是重複度非常高,而安全分析的結果往往非常少,極少數的威脅行爲會隱藏在海量數據裏,佔比往往會是 1/1000 甚至更少。

所以我們選擇性價比更高的塊索引方案,已經能夠支撐目前的應用場景。目前通過客戶數據來看, 索引能夠爲 85% 的語句提供 90% 以上的裁剪率,基本滿足延時要求。

img

某些大數據平臺是將索引數據以文件的形式存儲在磁盤上,外加一些 cache 機制來加速數據訪問,而我們是將索引數據直接存在了數據庫中。主要有以下兩個方面的考慮:

  • 第一是 transaction。我們知道列存文件往往是無法 update 的,而我們在定期優化文件分佈時會做 Merge File 操作,爲了保證查詢一致性,需要數據庫提供 transaction 能力。
  • 第二是性能。數據庫擁有較強的讀寫和檢索能力,甚至可以將謂詞下推到數據庫來完成,數據庫的高壓縮比也能進一步節省存儲。

img

上圖爲塊索引的設計。在我們的索引數據庫中,我們把這些數據分爲不同類別數據源,比如終端數據爲一類數據源,網絡數據爲一類數據源,我們分類數據源的邏輯是他們是否擁有統一的 Schema。就單個數據源來說,它以日期作爲 Partition,Partition 內部是大量的 ORC 小文件,具體到索引結構,我們會爲每一個字段建 min/max 索引,基數小於 0.001 的字段我們建 Bloom 索引。

上文提到過,安全人員比較喜歡用 like 和全文檢索。對於 like 這一塊,我們也做了一些優化。全文檢索方面,我們會爲數據來做分詞,來構建倒排索引,同時也會對於單個分詞過後的單個 item 來做文件分佈層面的位圖索引。

img

上圖是一個索引大小的大致的比例假設,JSON 格式的原始日誌大有 50PB,轉化成 ORC 大概是 1PB 左右。我們的 Index 數據是 508GB, 其中 8GB 爲 Min/Max 索引,500GB 爲 Bloom。加上上文提到的位圖以及倒排,這個索引數據的佔比會進一步加大。基於此,我們採用的是分佈式的索引方案。

img

我們知道日誌是在不斷的進行變化的,對於有的數據員來說,他有時會增加字段或者減少字段,甚至有時字段類型也會發生變化。

那麼我們採取這種 Merge Schema 模式方案,在文件增量寫入的過程中,也就是在更新這批數據的索引信息的同時來做 Schema Merge 的操作。如圖所示,在 block123 中,文件 3 是最後一個寫入的。隨着文件的不斷寫入,會組成一個全新的 Merge Schema。可以看到 B 字段和 C 字段其實是歷史字段,而 A_V 字段是 A 字段的歷史版本字段,我們用這種方式來儘量多的讓客戶看到比較全的數據。最後基於自己開發的 Input format 加 Merge Schema 來構建一個新的 table source ,從而打通整個流程。

二、以降低 IO 爲目標的優化與探索 (劉佳)

上文介紹了爲什麼要選擇塊索引,那麼接下來將具體介紹如何使用塊索引。塊索引的核心可以落在兩個字上:“裁剪”。裁剪就是在查詢語句被真正執行前就將無關的文件給過濾掉,儘可能減少進入計算引擎的數據量,從數據源端進行節流。

img

這張圖展示了整個系統使用 IndexDB 來做裁剪流程:

  • 第一步是解析查詢語句。獲取到相關的 filter
,可以看到最左邊的 SQL 語句中有兩個過濾條件, 分別是 src_address = 某個 ip,occur_time > 某個時間戳。
  • 第二步將查詢條件帶入 Index DB 對應數據源的 meta 表中去進行文件篩選
。src_address 是字符串類型字段,它會聯合使用 min/max 和 bloom 索引進行裁剪。occur_time 是數值類型字段並且是時間字段,我們會優先查找 min/max 索引來進行文件裁剪。需要強調的是, 這裏我們是將用戶寫的 filter 封裝成了 index db 的查詢條件,直接將 filter pushdown 到數據庫中完成。
  • 第三步在獲取到文件列表後,這些文件加上前面提到的 merged schema 會共同構造成一個 TableSource 來交給 Flink 進行後續計算。

同時,構建 source 的時候,我們在細節上做了一些優化。比如在將 filter 傳給 ORC reader 的時候,清除掉已經 pushdown 了的 filter, 避免在引擎側進行二次過濾。當然, 這裏並不是將所有 filter 都清除掉了,我們保留了 like 表達式,關於 like 的 filter pushdown 會在後文介紹。

img

接下來着重介紹一下四大優化點:

  • 第一點,數據在未排序的情況下,裁剪率是有理論上限的,我們通過在數據寫入的時候使用 hilbert 曲線排序原始數據來提升裁剪率;
  • 第二點,因爲安全領域的特殊性,做威脅檢測嚴重依賴 like 語法,所以我們對 orc api 進行了增強,使其支持了 like 語法的下推;
  • 第三點,同樣是因爲使用場景嚴重依賴 join,所以我們對 join 操作也做了相應的優化;
  • 第四點,我們的系統底層支持多種文件系統,所以我們選取 Alluxio 這一成熟的雲原生數據編排系統來做數據緩存,提高數據的訪問局部性。

1. 裁剪率的理論上限及 Hilbert 空間填充曲線

img

裁剪可以抽象成 N 個球扔進 M 個桶的概率問題,在這裏我們直接說結論。假設行在塊中隨機均勻分佈,所有塊的總行數固定,查詢條件命中的總行數也固定,則塊命中率直接與 “命中的總行數 / 總塊數” 正相關。

結論有兩個:

  • 第一點,如果命中總行數 = 總塊數,即 X 軸值爲 1 的時候,命中率爲 2/3, 也就是 2/3 的塊,都包含命中的行,對應的塊修剪率的上限是 1/ 3。1/3 是一個很低數值,但是由於它的前提是數據隨機均勻分佈,所以爲了讓數據分佈更好,我們需要在數據寫入時對原始數據進行排序。
  • 第二點,假設命中總行數固定,那麼大幅度減少每塊中的行數來增加總塊數,也能提升塊修剪率。所以我們縮小了塊大小。根據測試結果,我們設定每個文件的大小爲:16M。縮小文件大小是很簡單的。針對排序,我們引入了 hilbert 空間填充曲線。

img

爲什麼使用 hilbert 曲線?主要是基於兩點:

  • 首先是,以什麼路徑遍歷 2 維空間,使路徑的地址序列對其中任一維度都基本有序?爲什麼要對每一列或者說子集都有序?因爲系統在使用的過程中,查詢條件是不固定的。數據寫入時排序用到了 5 個字段,查詢的時候可能只用到了其中的一個或兩個字段。Hilbert 排序能讓多個字段做到既整體有序,又局部有序。
  • 另外,空間填充曲線有很多,還有 Z 形曲線、蛇形曲線等等,大家可以看看右邊這兩張對比圖。直觀的看,曲線路徑的長跨度跳躍越少越好,點的位置在迭代過程中越穩定越好。 而 hilbert 曲線在空間填充曲線裏面綜合表現最好。

hilbert 用法,就是實現一個 UDF,輸入列值,輸出座標值,然後根據座標值排序。

img

我們抽樣了客戶環境所使用的 1500 條 SQL 語句,過濾掉了其中裁剪率爲分之 100% 的相關語句,也就是沒有命中文件的無效語句。然後還剩下 1148 條,我們使用這些語句做了裁剪率排序後,對裁剪率進行了對比,裁剪率 95 百分位從之前的 68% 提升到了 87%,提升了 19%。可能大家會覺得 19% 這個數值不是特別高,但如果我們帶上一個基數,比如說 10 萬個文件,這樣看的話就會很可觀了。

2. 字典索引上 Like 的優化

img

之前也有講到安全行業的特殊性,我們做威脅檢測的時候會嚴重依賴 like 查詢。鑑於此,我們也對它做了優化。

  • 首先我們爲 ORC api 添加了 like 條件表達式,保證 SQL 中的 like 能下推到 orc record reader 中。
  • 其次,重構了 orc record reader 的 row group filter 邏輯,如果發現是 like 表達式,首先讀取該字段的 dict steam,判斷 dict stream 是否包含 like 目標字符串,如果字典中不存在該值,直接跳過該 row group,不用讀取 data stream 和 length steam,能大幅提高文件讀取速度。後期我們也考慮構建字典索引到索引數據庫中,直接將字典過濾 pushdown 到數據庫中完成。

例如圖上所示,最左邊的 SQL 中有三個表達式。前兩個在上文中已經提到了,是將 filter 直接 pushdown 到 index db 中完成,我們交給 orc reader 的 filter 只有最後一個 attachment_name like '%投標%',真正需要讀取的記錄只是 dict 包含 ”投標“ 的 row group,也就是做到了 row group 級別的過濾,進一步減少了需要進入計算引擎的數據量。

3. 基於索引對 join 的優化

img

威脅情報的匹配中大量使用 join 操作,如果要加速 join 的性能,僅僅是 where 條件的 filter pushdown 是遠遠不夠的。

Flink 中已經內置了許多 join 算法,比如 broadcast join, hash join 和 sort merge join。其中,sort merge join 對預先排好序的表 join 非常友好,而上文有提到我們使用 Hilbert 曲線來對多字段進行聯合排序,所以 sort merge join 暫時不在我們的優化範圍之內。

另外,我們知道 join 的性能和左右表的大小正相關,而威脅情報 join 的稀疏度非常高,所以事先對左右表做裁剪,能夠大幅減少進入 join 階段的數據。

上文提到過我們已經爲常見字段建立了 bloom 索引。那麼利用這些已經創建好的 bloom,來進行文件預過濾,就變得順理成章,並且省掉了構建 bloom 的時間開銷。

對於 broadcast join,我們直接掃描小表,將小表記錄依次進入大表所屬文件的 bloom,判斷該數據塊是否需要, 對數據量大的表做預裁剪。

對於 hash join,正如我們看到的,我們可以預先對 join key 的文件級 bloom 做 “預 join” 操作,具體就是將左表所屬的某個文件的 bloom 依次與右表所屬文件的 bloom 做 “與” 操作,只保留左右表能 ”與後結果條數不爲 0“ 的文件,再讓各表剩餘的文件進入引擎做後續計算。

img

比如說圖上的這三張表,分別是 table1、 table2 和 table3 。我們可以從 index DB 中獲取到表的統計信息,也就是文件個數或者說是文件表的大小。圖上就直接列的是文件個數:table 1 是 1000 個, 然後 table 2 是 5 萬個文件, table 3 是 3 萬個文件。

我們就是參照上一張圖片裏面的邏輯進行預 join,然後預估 join 的成本。我們會讓成本低的預 join 先進行,這樣的話就能夠大幅度減少中間結果,提升 join 的效率。

4. Alluxio 作爲對象存儲的緩存

img

因爲底層文件存儲系統的多種多樣,所以我們選取了 Alluxio 數據編排系統,Alluxio 的優點是讓數據更靠近計算框架,利用內存或者 SSD 多級緩存機制加速文件訪問,如果在完全命中 cache 的情況下,能夠達到內存級 IO 的文件訪問速度,減少直接從底層文件系統讀文件的頻次,很大程度上緩解了底層文件系統的壓力。

對我們系統來說就是它帶來了更高的併發,而且對低裁剪率的查詢更友好,因爲低裁剪率的話就意味着需要讀取大量的文件。

如果這些文件在之前的查詢中已經被 load 到 cache 裏面,就能夠大幅度的提升查詢速度。

img

在做完這些優化以後,我們做了性能對比測試。我們選取了一個規模爲 249TB 的 es 集羣。它使用了 20 臺服務器,Flink 使用了兩臺服務器,爲了在圖標上看到更直觀的對比效果,我們選取了 16 條測試結果。

圖表上紅橙色的是 es,藍色的是 HQL 優化前,綠色的是 HQL 優化後。上面的數字標籤是與 es 相比,HQL 的性能差值。比如第一個標籤就意味着 HQL 的性能五倍於 es,其中 6 號和 7 號比 es 慢,主要是因爲 HQL 是塊索引,es 是行索引,全在內存裏面,所以可以做到超快的檢索速度。13 號是因爲 HQL 在使用 not equal 的情況下,裁剪率相對較差。

總體說,優化效果是很明顯的,大部分語句在與 es 查詢速度相比是持平甚至略優的。完全滿足客戶對長週期數據存儲和查詢的期望。

三、未來規劃

img

上圖是未來規劃。因爲客戶現場經常會涉及到很多的 BI Dashboard 運算和長週期運算報告的需求,所以我們下一步會考慮做 BI 預算,以及蘇軍提到的容器化和 JVM 預熱,當然還有對標 es,以及提升多用戶併發查詢的能力。

原文鏈接

本文爲阿里雲原創內容,未經允許不得轉載。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章