全量SQL(所有訪問資料庫的SQL)可以有效地幫助安全進行資料庫審計,幫助業務快速排查效能問題。一般可通過開啟genlog日誌或者啟動MySQL審計外掛方式來進行獲取,而美團選用了一種非侵入式的旁路抓包方案,使用Go語言實現。無論採用哪種方案,都需要重點關注它對資料庫的效能損耗。本文介紹了美團基礎研發平臺抓包方案在資料庫審計實踐中遇到的效能問題以及優化實踐,希望能對大家有所幫助或啟發。
1 背景
資料庫安全一直是美團資訊保安團隊和資料庫團隊非常注重的領域,但由於歷史原因,對資料庫的訪問只具備取樣審計能力,導致對於一些攻擊事件無法快速地發現、定損和優化。安全團隊根據歷史經驗,發現攻擊訪問資料庫基本上都存在著某些特徵,經常會使用一些特定SQL,我們希望通過對MySQL訪問流量進行全量分析,識別出慣用SQL,在資料庫安全性上做到有的放矢。
2 現狀及挑戰
下圖是取樣MySQL審計系統的架構圖,資料採集端基於pcap抓包方式實現,資料處理端選用美團大資料中心的日誌接入方案。所有MySQL例項都部署了用於採集MySQL相關資料的rds-agent、日誌收集的log-agent。rds-agent抓取到MySQL訪問資料,通過log-agent上報到日誌接收端,為了減少延時,上報端與接收端間做了同機房排程優化。日誌接收端把資料寫入到約定的Kafka中,安全團隊通過Storm實時消費Kafka分析出攻擊事件,並定期拉資料持久化到Hive中。
我們發現,通常被攻擊的都是一些核心MySQL叢集。經統計發現,這些叢集單機最大QPS的9995線約5萬次左右。rds-agent作為MySQL機器上的一個寄生程式,為了宿主穩定性,資源控制也極為重要。為了評估rds-agent在高QPS下的表現,我們用Sysbench對MySQL進行壓測,觀察在不同QPS下rds-agent抓取的資料丟失率和CPU消耗情況,從下面的壓測資料來看結果比較糟糕:
QPS | 丟失率 | CPU利用率 |
---|---|---|
10368.72 | 1.03% | 307.35% |
17172.61 | 7.23% | 599.90% |
29005.51 | 28.75% | 662.39% |
42697.05 | 51.73% | 622.34% |
50833.50 | 63.95% | 601.39% |
如何在高QPS下保證較低的丟失率與CPU消耗?已經成為當前系統的一個亟待解決的難題與挑戰。
3 分析及優化
下面主要介紹圍繞丟失率與CPU消耗這一問題,我們對資料採集端在流程、排程、垃圾回收和協議方面做的分析與改進。
3.1 資料採集端介紹
首先,簡要介紹一下資料採集端rds-agent,它是一個MySQL例項上的程式,採用Go語言編寫,基於開源的MysqlProbe的Agent改造。通過監聽網路卡上MySQL埠的流量,分析出客戶端的訪問時間、來源IP、使用者名稱、SQL、目標資料庫和目標IP等審計資訊。下面是其架構圖,主要分為5大功能模組:
1. probe
probe意為探針,採用了gopacket作為抓包方案,它是谷歌開源的一個Go抓包庫,封裝了pcap。probe把抓取到原始的資料鏈路層幀封裝成TCP層的資料包。通過變種的Fowler-Noll-Vo演算法雜湊源和目的IP port欄位,快速實現把資料庫連線打散到不同的worker中,該演算法保證了同一連線的來包與回包的雜湊值一樣。
2. watcher
登入使用者名稱對於審計來說極其重要,客戶端往往都是通過長連線訪問MySQL,而登入資訊僅出現在MySQL通訊協議的認證握手階段,僅通過抓包容易錯過。
watcher通過定時執行show processlist獲取當前資料庫的所有連線資料,通過對比Host欄位與當前包的客戶端ip port,補償錯過的使用者名稱資訊。
3. worker
不同的worker負責管理不同資料庫連線的生命週期,一個worker管理多個連線。通過定期比對worker的當前連線列表與watcher中的連線列表,及時發現過期的連線,關閉並釋放相關資源,防止記憶體洩漏。
4. connStream
整個資料採集端的核心邏輯,負責根據MySQL協議解析TCP資料包並識別出特定SQL,一個連線對應一個connStream Goroutine。因為SQL中可能包含敏感資料,connStream還負責對SQL進行脫敏,具體的特定SQL識別策略,由於安全方面原因,這裡不再進行展開。
5. sender
負責資料上報邏輯,通過thrift協議將connStream解析出的審計資料上報給log-agent。
3.2 基礎效能測試
抓包庫gopacket的效能直接決定了系統效能上限,為了探究問題是否出在gopacket上,我們編寫了簡易的tcp-client和tcp-server,單獨對gopacket在資料流向圖中涉及到的前三個步驟(如下圖所示)進行了效能測試,從下面的測試結果資料上看,效能瓶頸點不在gopacket。
QPS | pcap緩衝區 | 丟失率 | CPU利用率 |
---|---|---|---|
100000 | 100MB | 0% | 144.9% |
3.3 CPU畫像分析
丟失率與CPU消耗二者密不可分,為了探究如此高CPU消耗的原因,我們用Go自帶的pprof工具對程式的CPU消耗進行了畫像分析,從下面火焰圖的呼叫函式可以歸納出幾個大頭:SQL脫敏、解包、GC和Goroutine排程。下面主要介紹一下圍繞它們做的優化工作。
3.4 脫敏分析及改進
因為SQL中可能包含敏感資訊,出於安全考慮,rds-agent會對每一條SQL進行脫敏處理。
脫敏操作使用了pingcap的SQL解析器對SQL進行模板化:即把SQL中的值全部替換成“?”來達到目的,該操作需要解析出SQL的抽象語法樹,代價較高。當前只有取樣和抓取特定SQL的需求,沒有必要在解析階段對每條SQL進行脫敏。這裡在流程上進行了優化,把脫敏下沉到上報模組,只對最終傳送出去的樣本脫敏。
這個優化取得的效果如下:
對比項 | QPS | 丟失率 | CPU利用率 |
---|---|---|---|
改進前 | 50833.50 | 63.95% | 601.39% |
改進後 | 51246.47 | <font color=##FF0000>31.95%</font> | <font color=##FF0000>259.59%</font> |
3.5 排程分析及改進
從下面的資料流向圖可以看出整個鏈路比較長,容易出現效能瓶頸點。同時存在眾多高頻執行的Goroutine(紅色部分),由於數量多,Go需要經常在這些Goroutine間進行排程切換,切換對於我們這種CPU密集型的程式來說無疑是一種負擔。
針對該問題,我們做了如下優化:
- 縮短鏈路:分流、worker、解析SQL等模組合併成一個Goroutine解析器。
- 降低切換頻率:解析器每5ms從網路協議包的佇列中取一次,相當於手動觸發切換。(5ms也是一個多次測試後的折中資料,太小會消耗更多的CPU,太大會引起資料丟失)
這個優化取得的效果如下:
對比項 | QPS | 丟失率 | CPU利用率 |
---|---|---|---|
改進前 | 51246.47 | 31.95% | 259.59% |
改進後 | 51229.54 | <font color=##FF0000>0%</font> | <font color=##FF0000>206.87%</font> |
3.6 垃圾回收壓力分析及改進
下圖為rds-agent抓包30秒,已分配指標物件的火焰圖。可以看出已經分配了4千多萬個物件,GC壓力可想而知。關於GC,我們瞭解到如下兩種優化方案:
- 池化:Go的標準庫中提供了一個sync.Pool物件池,可通過複用物件來減少物件分配,從而降低GC壓力。
- 手動管理記憶體:通過系統呼叫mmap直接向OS申請記憶體,繞過GC,實現記憶體的手動管理。
但是,方案2容易出現記憶體洩漏。從穩定性的角度考慮,我們最終選擇了方案1來管理高頻呼叫函式裡建立的指標物件,這個優化取得的效果如下:
對比項 | QPS | 丟失率 | CPU利用率 |
---|---|---|---|
改進前 | 51229.54 | 0% | 206.87% |
改進後 | 51275.11 | 0% | <font color=##FF0000>153.32%</font> |
3.7 解包分析及改進
MySQL是基於TCP協議之上的,在功能除錯過程中,我們發現了很多空包。從下面的MySQL客戶端-服務端資料的互動圖可以看出:當客戶端傳送一條SQL命令,服務端響應結果,由於TCP的訊息確認機制,客戶端會傳送一個空的ack包來確認訊息,而且空包在整個流程中的比例較大,它們會穿透到解析環節,在高QPS下對於Goroutine排程和GC來說無疑是一個負擔。
下圖是MySQL資料包的唯一格式,通過分析,我們觀察到以下特點:
- 一個完整的MySQL資料包長度>=4Byte
- 客戶端新傳送命令的sequence id都是為0或者1
而pcap支援設定過濾規則,讓我們可以在核心層將空包排除掉,下面是上述特點對應的兩條過濾規則:
特點1: ip[2:2] - ((ip[0] & 0x0f) << 2) - ((tcp[12:1] & 0xf0) >> 2) >= 4
特點2: (dst host {localIP} and dst port 3306 and (tcp[(((tcp[12:1] & 0xf0) >> 2) + 3)] <= 0x01))
這個優化取得的效果如下:
對比項 | QPS | 丟失率 | CPU利用率 |
---|---|---|---|
改進前 | 51275.11 | 0% | 153.32% |
改進後 | 51246.02 | 0% | <font color=##FF0000>142.58%</font> |
基於上述經驗,我們對資料採集端進行功能程式碼重構,同時還進行一些其它優化。
4 最終成果
下面是優化前後的資料對比,丟失率從最高60%下降到了0%, CPU消耗從最高佔用6個核下降到了1個核。
為了探究抓包功能對MySQL效能損耗,我們用Sysbench做了一個效能對比測試。從下面的結果資料可以看出功能對MySQL的TPS、QPS和響應時間99線指標最高大約有6%的損耗。
5 未來規劃
雖然我們對抓包方案進行了各種優化,但對於一些延遲敏感的業務來說效能損耗還是偏大,而且該方案對一些特殊場景支援較差:如TCP協議層發生丟包、重傳、亂序時,MySQL協議層使用壓縮、傳輸大SQL時。而業界普遍採用了直接改造MySQL核心的方式來輸出全量SQL,同時也支援輸出更多的指標資料。目前,資料庫核心團隊也完成了該方案開發,正線上上灰度替換抓包方案中。另外,對於線上全量SQL端到端丟失率指標的缺失,我們也將陸續進行補齊。
本文作者
粟含,來自於美團基礎研發平臺/基礎技術部/資料庫技術中心。
招聘資訊
美團基礎技術部-資料庫技術中心誠招高階、資深技術專家,Base上海、北京。美團關聯式資料庫規模大,每年快速的增長,每天承載數千億的訪問流量。在這裡可以體驗高併發、高可用、高可擴充套件性的業務挑戰,可以緊跟並開拓業界前沿技術,體會到技術進步帶來的生產力提升,歡迎投遞簡歷至:suhan03@meituan.com。
閱讀美團技術團隊更多技術文章合集
前端 | 演算法 | 後端 | 資料 | 安全 | 運維 | iOS | Android | 測試
| 在公眾號選單欄對話方塊回覆【2021年貨】、【2020年貨】、【2019年貨】、【2018年貨】、【2017年貨】等關鍵詞,可檢視美團技術團隊歷年技術文章合集。
| 本文系美團技術團隊出品,著作權歸屬美團。歡迎出於分享和交流等非商業目的轉載或使用本文內容,敬請註明“內容轉載自美團技術團隊”。本文未經許可,不得進行商業性轉載或者使用。任何商用行為,請傳送郵件至tech@meituan.com申請授權。