作者: 粟含
全量SQL(所有訪問數(shù)據(jù)庫的SQL)可以有效地幫助安全進行數(shù)據(jù)庫審計,幫助業(yè)務(wù)快速排查性能問題。一般可通過開啟genlog日志或者啟動MySQL審計插件方式來進行獲取,而美團選用了一種非侵入式的旁路抓包方案,使用Go語言實現(xiàn)。無論采用哪種方案,都需要重點關(guān)注它對數(shù)據(jù)庫的性能損耗。
本文介紹了美團基礎(chǔ)研發(fā)平臺抓包方案在數(shù)據(jù)庫審計實踐中遇到的性能問題以及優(yōu)化實踐,希望能對大家有所幫助或啟發(fā)。
1 背景
數(shù)據(jù)庫安全一直是美團信息安全團隊和數(shù)據(jù)庫團隊非常注重的領(lǐng)域,但由于歷史原因,對數(shù)據(jù)庫的訪問只具備采樣審計能力,導致對于一些攻擊事件無法快速地發(fā)現(xiàn)、定損和優(yōu)化。安全團隊根據(jù)歷史經(jīng)驗,發(fā)現(xiàn)攻擊訪問數(shù)據(jù)庫基本上都存在著某些特征,經(jīng)常會使用一些特定SQL,我們希望通過對MySQL訪問流量進行全量分析,識別出慣用SQL,在數(shù)據(jù)庫安全性上做到有的放矢。
2 現(xiàn)狀及挑戰(zhàn)
下圖是采樣MySQL審計系統(tǒng)的架構(gòu)圖,數(shù)據(jù)采集端基于pcap抓包方式實現(xiàn),數(shù)據(jù)處理端選用美團大數(shù)據(jù)中心的日志接入方案。所有MySQL實例都部署了用于采集MySQL相關(guān)數(shù)據(jù)的rds-agent、日志收集的log-agent。rds-agent抓取到MySQL訪問數(shù)據(jù),通過log-agent上報到日志接收端,為了減少延時,上報端與接收端間做了同機房調(diào)度優(yōu)化。日志接收端把數(shù)據(jù)寫入到約定的Kafka中,安全團隊通過Storm實時消費Kafka分析出攻擊事件,并定期拉數(shù)據(jù)持久化到Hive中。
我們發(fā)現(xiàn),通常被攻擊的都是一些核心MySQL集群。經(jīng)統(tǒng)計發(fā)現(xiàn),這些集群單機最大QPS的9995線約5萬次左右。rds-agent作為MySQL機器上的一個寄生進程,為了宿主穩(wěn)定性,資源控制也極為重要。為了評估rds-agent在高QPS下的表現(xiàn),我們用Sysbench對MySQL進行壓測,觀察在不同QPS下rds-agent抓取的數(shù)據(jù)丟失率和CPU消耗情況,從下面的壓測數(shù)據(jù)來看結(jié)果比較糟糕:
如何在高QPS下保證較低的丟失率與CPU消耗?已經(jīng)成為當前系統(tǒng)的一個亟待解決的難題與挑戰(zhàn)。
3 分析及優(yōu)化
下面主要介紹圍繞丟失率與CPU消耗這一問題,我們對數(shù)據(jù)采集端在流程、調(diào)度、垃圾回收和協(xié)議方面做的分析與改進。
3.1 數(shù)據(jù)采集端介紹
首先,簡要介紹一下數(shù)據(jù)采集端rds-agent,它是一個MySQL實例上的進程,采用Go語言編寫,基于開源的MysqlProbe的Agent改造。通過監(jiān)聽網(wǎng)卡上MySQL端口的流量,分析出客戶端的訪問時間、來源IP、用戶名、SQL、目標數(shù)據(jù)庫和目標IP等審計信息。下面是其架構(gòu)圖,主要分為5大功能模塊:
1. probe
probe意為探針,采用了gopacket作為抓包方案,它是谷歌開源的一個Go抓包庫,封裝了pcap。probe把抓取到原始的數(shù)據(jù)鏈路層幀封裝成TCP層的數(shù)據(jù)包。通過變種的Fowler-Noll-Vo算法哈希源和目的IP port字段,快速實現(xiàn)把數(shù)據(jù)庫連接打散到不同的worker中,該算法保證了同一連接的來包與回包的哈希值一樣。
2. watcher
登錄用戶名對于審計來說極其重要,客戶端往往都是通過長連接訪問MySQL,而登錄信息僅出現(xiàn)在MySQL通信協(xié)議的認證握手階段,僅通過抓包容易錯過。
watcher通過定時執(zhí)行show processlist獲取當前數(shù)據(jù)庫的所有連接數(shù)據(jù),通過對比Host字段與當前包的客戶端ip port,補償錯過的用戶名信息。
3. worker
不同的worker負責管理不同數(shù)據(jù)庫連接的生命周期,一個worker管理多個連接。通過定期比對worker的當前連接列表與watcher中的連接列表,及時發(fā)現(xiàn)過期的連接,關(guān)閉并釋放相關(guān)資源,防止內(nèi)存泄漏。
4. connStream
整個數(shù)據(jù)采集端的核心邏輯,負責根據(jù)MySQL協(xié)議解析TCP數(shù)據(jù)包并識別出特定SQL,一個連接對應(yīng)一個connStream Goroutine。因為SQL中可能包含敏感數(shù)據(jù),connStream還負責對SQL進行脫敏,具體的特定SQL識別策略,由于安全方面原因,這里不再進行展開。
5. sender
負責數(shù)據(jù)上報邏輯,通過thrift協(xié)議將connStream解析出的審計數(shù)據(jù)上報給log-agent。
3.2 基礎(chǔ)性能測試
抓包庫gopacket的性能直接決定了系統(tǒng)性能上限,為了探究問題是否出在gopacket上,我們編寫了簡易的tcp-client和tcp-server,單獨對gopacket在數(shù)據(jù)流向圖中涉及到的前三個步驟(如下圖所示)進行了性能測試,從下面的測試結(jié)果數(shù)據(jù)上看,性能瓶頸點不在gopacket。
3.3 CPU畫像分析
丟失率與CPU消耗二者密不可分,為了探究如此高CPU消耗的原因,我們用Go自帶的pprof工具對進程的CPU消耗進行了畫像分析,從下面火焰圖的調(diào)用函數(shù)可以歸納出幾個大頭:SQL脫敏、解包、GC和Goroutine調(diào)度。下面主要介紹一下圍繞它們做的優(yōu)化工作。
3.4 脫敏分析及改進
因為SQL中可能包含敏感信息,出于安全考慮,rds-agent會對每一條SQL進行脫敏處理。
脫敏操作使用了pingcap的SQL解析器對SQL進行模板化:即把SQL中的值全部替換成“?”來達到目的,該操作需要解析出SQL的抽象語法樹,代價較高。當前只有采樣和抓取特定SQL的需求,沒有必要在解析階段對每條SQL進行脫敏。這里在流程上進行了優(yōu)化,把脫敏下沉到上報模塊,只對最終發(fā)送出去的樣本脫敏。
?
這個優(yōu)化取得的效果如下:
3.5 調(diào)度分析及改進
從下面的數(shù)據(jù)流向圖可以看出整個鏈路比較長,容易出現(xiàn)性能瓶頸點。同時存在眾多高頻運行的Goroutine(紅色部分),由于數(shù)量多,Go需要經(jīng)常在這些Goroutine間進行調(diào)度切換,切換對于我們這種CPU密集型的程序來說無疑是一種負擔。
針對該問題,我們做了如下優(yōu)化:
- 縮短鏈路:分流、worker、解析SQL等模塊合并成一個Goroutine解析器。
- 降低切換頻率:解析器每5ms從網(wǎng)絡(luò)協(xié)議包的隊列中取一次,相當于手動觸發(fā)切換。(5ms也是一個多次測試后的折中數(shù)據(jù),太小會消耗更多的CPU,太大會引起數(shù)據(jù)丟失)
這個優(yōu)化取得的效果如下:
3.6 垃圾回收壓力分析及改進
下圖為rds-agent抓包30秒,已分配指針對象的火焰圖??梢钥闯鲆呀?jīng)分配了4千多萬個對象,GC壓力可想而知。關(guān)于GC,我們了解到如下兩種優(yōu)化方案:
- 池化:Go的標準庫中提供了一個sync.Pool對象池,可通過復用對象來減少對象分配,從而降低GC壓力。
- 手動管理內(nèi)存:通過系統(tǒng)調(diào)用mmap直接向OS申請內(nèi)存,繞過GC,實現(xiàn)內(nèi)存的手動管理。
但是,方案2容易出現(xiàn)內(nèi)存泄漏。從穩(wěn)定性的角度考慮,我們最終選擇了方案1來管理高頻調(diào)用函數(shù)里創(chuàng)建的指針對象,這個優(yōu)化取得的效果如下:
3.7 解包分析及改進
MySQL是基于TCP協(xié)議之上的,在功能調(diào)試過程中,我們發(fā)現(xiàn)了很多空包。從下面的MySQL客戶端-服務(wù)端數(shù)據(jù)的交互圖可以看出:當客戶端發(fā)送一條SQL命令,服務(wù)端響應(yīng)結(jié)果,由于TCP的消息確認機制,客戶端會發(fā)送一個空的ack包來確認消息,而且空包在整個流程中的比例較大,它們會穿透到解析環(huán)節(jié),在高QPS下對于Goroutine調(diào)度和GC來說無疑是一個負擔。
下圖是MySQL數(shù)據(jù)包的唯一格式,通過分析,我們觀察到以下特點:
- 一個完整的MySQL數(shù)據(jù)包長度>=4Byte
- 客戶端新發(fā)送命令的sequence id都是為0或者1
而pcap支持設(shè)置過濾規(guī)則,讓我們可以在內(nèi)核層將空包排除掉,下面是上述特點對應(yīng)的兩條過濾規(guī)則:
特點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))
這個優(yōu)化取得的效果如下:
基于上述經(jīng)驗,我們對數(shù)據(jù)采集端進行功能代碼重構(gòu),同時還進行一些其它優(yōu)化。
4 最終成果
下面是優(yōu)化前后的數(shù)據(jù)對比,丟失率從最高60%下降到了0%, CPU消耗從最高占用6個核下降到了1個核。
為了探究抓包功能對MySQL性能損耗,我們用Sysbench做了一個性能對比測試。從下面的結(jié)果數(shù)據(jù)可以看出功能對MySQL的TPS、QPS和響應(yīng)時間99線指標最高大約有6%的損耗。
5 未來規(guī)劃
雖然我們對抓包方案進行了各種優(yōu)化,但對于一些延遲敏感的業(yè)務(wù)來說性能損耗還是偏大,而且該方案對一些特殊場景支持較差:如TCP協(xié)議層發(fā)生丟包、重傳、亂序時,MySQL協(xié)議層使用壓縮、傳輸大SQL時。而業(yè)界普遍采用了直接改造MySQL內(nèi)核的方式來輸出全量SQL,同時也支持輸出更多的指標數(shù)據(jù)。
目前,數(shù)據(jù)庫內(nèi)核團隊也完成了該方案開發(fā),正在線上灰度替換抓包方案中。另外,對于線上全量SQL端到端丟失率指標的缺失,我們也將陸續(xù)進行補齊。
本文作者
粟含,來自于美團基礎(chǔ)研發(fā)平臺/基礎(chǔ)技術(shù)部/數(shù)據(jù)庫技術(shù)中心。