樊書華 王 鵬 汪 衛
1(復旦大學軟件學院 上海 201203)2(復旦大學計算機科學技術學院 上海 200433)
隨著互聯網的蓬勃發展,計算機集群系統已應用在人們生活的各個方面。集群的主要作用是對系統中各個節點的使用情況進行實時的管理和掌握[1],集群監控系統也隨之應運而生[2-4]。通過對集群的各項數據,包括應用負載、CPU利用率、磁盤、網絡流量、內存、應用心跳等系統指標,分別進行收集存儲,并在處理分析后做出相應判斷,當發生異常情況時對系統管理員發送通知,從而達到實時監控的效果。但是以人工的方式對集群進行監控管理存在著各種各樣的問題,比如效率過低、對系統異常的捕獲不夠及時等,因此集群監控系統的實現程度將直接決定集群系統是否能夠平穩運行。
然而隨著實際生產環境中業務的增加,集群規模逐漸擴大,服務器收到密集的高負荷請求,現有的集群監控系統如針對openstack開發的Ceilometer[5-6]、層次化監控系統Ganglia[7-9]、企業級開源系統Zabbix[10]等,在性能表現方面容易產生瓶頸。以目前應用較為廣泛的主流系統為例,Ceilometer在開源集群監控系統中相對于其他幾個監控系統,在完整性、輕量性等幾個方面很有優勢[11]。但由于其使用單層的客戶端服務器模式來搭建監控系統,當集群規模擴大時,數據的種類和規模都會擴大,這樣數據采集、存儲就容易遇到瓶頸。
針對目前集群監控系統存在的性能問題,本文旨在設計并實現一種基于流媒體壓縮算法的高性能集群監控系統。首先,對性能問題的表現和問題產生的原因進行介紹,包括監控數據量的組成分析以及監控數據幀的時間和空間冗余性分析;其次,對系統架構以及對應的功能模塊進行描述;再次,分別從數據幀冗余性優化、系數量化處理等模塊對系統高性能進行設計與實現;最后結合系統實驗,給出性能提升具體結果與評估。
當前常用的壓縮算法包括LZ4、Brotli、LZF、Snappy等,接下來首先對這幾種算法進行簡單介紹。
LZ4算法來源于LZ77,由Yann Collet在2011年發明。該算法采用滑動窗口的原理,通過哈希表對字典進行存儲,從而對匹配的字符串進行查找。其中哈希表的key值代表字符串的二進制值,value值代表字符串在文件中對應的位置。主要優勢為壓縮效率高,當需要壓縮的數據中出現的重復項越多時,壓縮效果越好。
無損壓縮算法Brotli在2015年由Google提出,通過哈夫曼編碼以及變種的LZ77算法等方式對數據進行壓縮,主要用于處理順序數據流。該算法不僅包含常見的滑動窗口字典,也對常見字符串字典進行了預定義,從而增強壓縮效果。Brotli算法已受到絕大多數主流瀏覽器的支持,達到加快傳輸速度的效果。
LZF算法對字符串通過LZ77及LZSS的混合編碼進行壓縮,由入口文件、壓縮和解壓縮文件、配置及接口文件組成。LZF算法較為輕量,Redis中默認采用該算法,在數據存儲至本地時進行壓縮處理。
Snappy算法在2011年開源,來自于Zippy并由C++語言實現,該算法以壓縮率和兼容性為一定代價,實現較高壓縮速度以及壓縮比的目標,壓縮速度達到250 MB/s甚至更高。Snappy算法在Google很多內部項目諸如MapReduce以及BigTable中得到了廣泛使用。
但在這些傳統的數據壓縮算法中,通過字符串匹配和哈希字典等方式進行壓縮,雖然在單個時間片單個數據幀上效果很好,但沒有考慮到集群監控系統中數據幀在時間軸上的冗余性。此外,這些壓縮算法還需要在主從兩個節點之間同步字典數據,這會帶來額外的網絡帶寬開銷。因此本文旨在設計一種新的基于流媒體壓縮的算法用于集群監控系統中,從而對系統性能進行有效提升。
集群監控系統的性能瓶頸主要表現如下,以ceilometer監控系統為例:在其余參數恒定的條件下,服務器端(即時序數據庫gnocchi)單位時間內可以處理的數據量會隨客戶端(數據采集器collectd)的緩沖區大小(單個restful請求包含的數據量)的變化而呈線性增長,即:當緩沖區大小為100條時,單位時間可以處理的restful請求為600條,單位時間可以處理的數據量為6萬條;緩沖區大小為500條時,單位時間可以處理的 restful 請求為533條,單位時間可以處理的數據量為27萬條。
根據目前配置,當客戶端緩沖區的大小為200條時,服務器端時序數據庫在單位時間內能夠處理的restful請求大概在500條,其中包括的數據條目大概為10萬條。以單個被監控節點一次輪詢的6 000條數據為基準,假如被監控節點數目為200臺(在不考慮客戶端相同數據重發的狀態下,實際上隨著寫線程數量的增長,數據采集器緩沖隊列里的內容被重發的概率也越高),在單次輪詢時間10秒內會產生120萬條數據,這樣會使得數據庫阻塞,隨著寫線程的增多,不到200臺主機就會使得數據庫發生阻塞。當然按照網絡流量數據,200臺主機的網絡流量為100 MB/s,達到了百兆網卡極限,也是千兆網卡的10%,會給網絡流量產生很大壓力。
接下來,對目前集群監控系統中存在的性能方面的問題原因,分別對監控數據量以及數據幀在時間和空間上的冗余性進行分析。
當集群規模擴大時,服務器收到的請求密度變大,此時容易產生阻塞現象。以單臺宿主機為例,在單位時間內需要向服務器發送高達0.5~1 MB的時序數據集(包括物理機信息,虛擬機信息和docker信息)。以單次輪詢來看,一次性需要收集的數據就達到了幾千條,當宿主機規模達到一定量以后,監控整體系統性能將會受到嚴重阻塞,影響監控效率。
針對采集到的監控指標,數據類型可以分為以下幾個部分,包括中央處理器、進程、網絡、內存、硬盤等。針對單臺物理計算節點而言,監控數據量具有兩個主要規律:
? 橫向增長:針對單臺物理計算節點而言,隨著主機上搭載的虛擬機數目增加,對應的虛擬機指標,主機全體指標將隨之增長;
? 縱向增長:物理機中隨著CPU核數的增長,對應的各項監控數據指標,包括總使用量、steal百分比、空閑百分比、IO等待百分比、用戶百分比等也呈線性增長趨勢,這一規律對網卡、進程、磁盤也有效。
綜上,對于集群監控系統來說,單個節點需要采集的數據指標就已非常可觀,若對多個節點監控指標同時采集,數量集將是非常龐大的,并且隨著監控節點數的增加數據量也會成倍增加,這無疑會給集群監控系統性能方面的表現造成影響。
數據幀主要具有時間和空間兩方面的冗余性。采集到的數據幀以哈希表(python字典)的形式進行存儲,例如[{′df-sys-fs-cgroup@precent_bytes-free′: {′Timestamp′: 1546498760.7887914, ′Value′: 100.0}}],如表1所示,時空冗余性主要體現在如下兩個方面:
? 空間冗余性方面,在某一個時間點t1上,所有數據項的時間戳在小數點之前(即秒級單位)都是相同的,小數點之后的差別可以忽略不計,故只保留一個時間戳即可。同時形如′Timestamp′和′Value′這樣重復的鍵值也是產生空間冗余的原因。
? 時間冗余性方面,在兩個時間點t1和t2上,兩幀數據的表項名稱都是相同的。因此每隔一定時間對監控數據進行采集時,總會對各個監控項的子條目名稱進行高覆蓋率的重復收集,這是時間冗余數據產生的原因。

續表1
在本文實現的基于流媒體壓縮算法的高性能集群監控系統架構中,如圖1所示,以數據收集、數據存儲、數據分析為基本層級,之后將分析處理后的數據實現展示、報警以及自適應等功能。在數據收集層,通過給各個數據采集器collectd節點添加插件(主機監控插件,libvirt插件以及docker插件),從而定期地對系統和程序中各個指標進行收集。

圖1 集群監控系統架構
針對時序數據庫,實現對物理機、虛擬機以及容器的同時監控。數據采集之后采用過濾器連接Gnocchi服務,采用最新的流式壓縮算法實現高效的數據存儲和傳輸機制,從而實現數據存儲層要求。此外,數據展示層是與用戶最相近的一層,對Grafana組件進行定制,以集中統一的方式實現數據的展示,給系統管理員帶來便捷。
本文實現的集群監控系統中包括數據收集、數據存儲、數據處理等在內的各個功能。
在資源采集方面,數據采集階段引入Collectd守護進程[13],并以在Collectd上實現插件的形式,完成對系統以及系統中應用程序的各類性能指標進行定期的收集任務。同時,面對海量的數據收集過程,本文在數據采集器的性能方面進行了有效的提升。首先,對Collectd所采集的大批量數據進行精簡化,其次,采用壓縮數據的方法,減小了網絡中的帶寬壓力。最后,通過調整數據采集器的發送流程,當阻塞的時間超過提前設定的閾值時,對該數據進行丟棄處理。
在高效的數據傳輸和存儲方面,采用了Gnocchi這一款開源的時序數據庫。Gnocchi專門為處理大規模時序數據的存儲和索引而設計,在數據存入之前就對相應數據進行了聚合操作,從而提高了數據訪問效率。
在系統界面方面,隨著系統中業務發展的日益復雜化,若對集群監控的過程中隨時對系統中的運行情況進行掌握,監控數據的可視化展示部分是集群監控系統必不可少的組件。在與用戶主要交互的數據展示層,本文通過對Grafana的組件和模板采用定制的方式,對所需要監控的集群來進行整齊統一的管理。
本文實現的集群監控系統整體環境為:通過Kolla Ansible對OpenStack進行部署。其中,Ansible本身是專門為分布式集群安裝程序的框架,Kolla是Ansible的子項目,以Ansible為基礎,專門為OpenStack提供服務。Docker作為一個開源的應用容器引擎,目標在于為操作系統的虛擬化提供輕量級的解決方案,相當于小的封閉式操作系統環境,同時具有快速的部署和交付能力,以及良好的擴容性和資源利用率。如表2所示,集群搭建的機器環境包括服務器端和客戶端。

表2 集群監控環境
對監控環境Collectd以及Gnocchi來說,服務器端(數據采集端)配置與客戶端(數據發送端)配置如表3所示。

表3 集群服務配置
針對高密度的數據請求容易產生的阻塞現象這一問題,如圖2所示,本文的主要解決思路為:通過引入流式數據壓縮,對傳輸的數據進行壓縮處理。在H.264編碼算法中,主要分為正在編碼的幀和參考幀,參考幀即為已經編碼且經過了重新解碼。根據正在編碼的幀得到當前宏塊,同時結合參考幀進行預測,從而得到預測宏塊。預測宏塊作用于當前宏塊獲得殘差宏塊,并進行變換和量化處理,隨后的過程主要分為兩部分:變換和量化后進行熵編碼,獲取編碼后的比特流;通過反變換和反量化產生解碼殘差宏塊,進而獲得參考幀。

圖2 流式數據壓縮H.264編碼算法
在具體分析如何對系統性能進行提升之前,首先需要對系統中對數據的收集過程進行了解。本文采用數據采集器Collectd及相應插件對集群系統的監控指標進行收集之后,通過Redis寫入Gnocchi數據庫。
如圖3所示,數據采集器Collectd收集到的數據以列表的形式進行存儲。每臺主機上采集到的數據格式包括接口、內存、進程、網絡等監控項,每類監控項又包含數個子條目。列表中的每個元素都包含監測數據項與標簽兩個部分,標簽以0和1進行區分,0代表該項未被線程讀,1則代表已讀。系統中存在n個讀線程read_thread worker,首先確認列表中元素的標簽值,若標簽為0則進行讀取,并寫入到隊列write_queue_s的隊尾處。隊列write_queue_s遵循先入先出的規則,因此對于寫線程write_thread worker,依照從隊列頭部讀取數據的順序通過write-http請求寫入數據庫。

圖3 集群數據讀寫
在數據幀時空冗余性方面,結合第2節問題分析中的冗余性詳解,優化主要分為以下步驟:
? 對采集到的監控數據項來說,列表中每個元素的時間戳屬性都非常接近,因此采取將時間戳規整到同一個值的方法,并歸并到數據幀外達到有效壓縮的效果,從而大大減少同一次收集的數據幀中時間戳出現的頻率。
? 在數據集的Value項中,未經優化的值小數點后取到了十幾位以上。因此本文通過對較低位,即無效位數進行裁剪,達到對系統性能實現優化的目標。
在數據幀的時間冗余性優化方面,首先數據采集器Collectd對節點采集監控指標數據,同時對不同的時間點來說,采集到的兩段計算資源的數據項名稱大部分時間都是相同的。如磁盤的子條目包括df_complext_reserved、df_complex_used,CPU的子條目為percent system、percent interrupt。因此,每隔一定時間對監控數據進行采集時,各個監控項收集的相應子條目名稱重復率也非常高。
為了提升系統性能,本文設計了運行在數據采集器Collect的與數據庫Gnocchi之間的數據幀同步協議(Datagram Synchronization Protocol)。數據采集器Collectd在對監控數據處理進入Gnocchi數據庫之前,使用Redis的緩存設計,在寫入Gnocchi之前,首先進入Redis緩沖區,從而達到加快讀寫速度的效果。同時,采集到的監控先將所有的監控條目名記錄到緩存中,然后將壓縮后的值寫入到對應條目的值中。
如圖4所示,數據庫Gnocchi與數據采集器Collectd之間的數據幀同步協議的整體流程為:

圖4 數據幀同步協議
? 首先數據采集器Collectd對需要監控的指標進行數據收集,數據幀格式為包含哈希表的列表,即{監控條目名: [時間戳,值]}。同時,一個寫線程write-thread對應一個plugin-collectd-gnocchi。
? 進入到數據緩沖區Data Buffer后,本文對監控數據項使用字典結構(Dictionary),字典中的key存儲監控項的名稱,value為該監控項的對應值。
? Redis緩沖區中含有Incoming Driver,其中包含各個目錄,如temp/dir1、temp/dir2等,之后每個目錄節點將各自由一個metricd worker進行處理。Redis緩沖區中先存儲監控條目名,即字典中的key,隨后每個條目名的對應值等待下個步驟傳輸。
? 數據采集到服務器端,而數據的預處理過程放到客戶端進行。在數據采集緩沖區中保留最新數據幀,當產生新的數據幀時,與緩沖區中已有的數據幀作對比,檢測兩個數據幀是否高度相似,假如兩者數據相似度非常高,則不再進行發送操作。
? 在本文提出的數據同步協議中,數據分為兩種類型,對于完整數據complete_data來說,以類型為0標記,調用函數0寫入Redis緩存中對應條目名的value值,對于壓縮數據compressed_data來說,以類型為1進行標記,通過函數1寫入Redis緩存。
? 兩個緩沖區中的內容保持同步,假如發現了新的數據,在數據緩沖區Data Buffer與Incoming Driver兩個緩沖區都沒有保存,則認為該數據為complete data,類型為0,發送完整數據包,并在緩沖區中生成對應的數據幀;若發送的數據幀中條目比緩沖區中的條目少,則多出來的條目對應值保持原始值不變。
下面對算法進行定義:
算法1數據幀冗余性優化算法
輸入:來自于單個數據采集節點的日志log={log_msgi|i=1,2,…,N},其中單位子日志log_msgi以字典的形式進行存儲,具體格式如下:log_msgi=[{Entryname:{Timestamp,Value}}]。
輸出:基于流媒體壓縮的日志數據。
1) 數據幀原有序列中的key值,即監控項名稱,轉換為對應序列號;
2) 合并數據幀中的時間戳,并抽取Timestamp中的公共部分進行統一;
3) 消除數據幀條目中的冗余表項名;
4) 當產生新數據時,與緩沖區已有數據作對比,檢測是否高度相似,若相似度較高則不再進行操作。
針對時序數據中因為數據過大而占資源過多的性能問題,本文對數據系數進行了量化處理。找出一系列數據中盡可能接近的公因子進行提取,從而使提取后的數據值變小,來達到減少傳輸數據量的目標。
系數量化之前,取原數據序列{18, 9, 46, 27, 35}中各個數據最為接近的公因子9作為公共系數,在提取該系數之后,產生新序列:9{2,1,5,3,4}。通過量化處理,能夠減少數據存放空間,對系統的表現性能產生優化作用。為了還原原始數據,可以通過量化后的數據序列與系數結合,產生{18, 9, 45, 27, 36}。
集群監控系統中的數據收集依賴于各個Collectd節點,數據存儲結合Gnocchi服務。首先對以下幾個主要組件進行說明。Collectd_gnocchi作為一種插件,幫助Collectd發送給Gnocchi的數據接口。Gnocchi_metricd是單獨的線程Thread Worker,從Gnocchi的前端Sack中取任務節點,并存儲到Gnocchi的后端數據。gnocchiClient為Collectd_gnocchi調用的發送數據包的接口。Gnocchi_api負責接受數據包。
數據壓縮與解壓縮流程如圖5所示。首先Collected_gnocchi將Collectd收集的數據發送到Gnocchi的數據接口Client,數據壓縮完畢之后通過網絡中的http請求與post請求,隨后數據進行解壓縮后,Gnocchi_api負責接收數據包,Gnocchi_metricd中的線程將分別從Gnocchi的前端目錄中調取任務節點。

圖5 數據壓縮與解壓縮
客戶端和服務器端的部分代碼如下:
# 客戶端部分代碼
Def preparePost(self, typeV)
refreshCache(raw[‘key’])
if data[type] == 0
data[key] = cacheDictionary
else if data[type] == 1
data[key] = valueList
# 服務器端部分代碼
Def processData(request)
getType_Data_Hostname_Timestamp
# 從Redis緩存中提取對應數據,然后還原數據幀
if data[type] == 1
myDictionary = loads(redisGet(HostName))
myDataList = loads(Data);
getResultDictionary
# 存入Redis緩存
else if data[type] == 0
redisSet(myHostName, myData)
客戶端的核心代碼實現思路為:
1) 初始化函數定義整體數據幀緩存,并定義處理后的數據列表;
2) 當新數據產生時,通過refreshCache函數起到刷新數據幀的作用;
3) 根據最新數據幀,generateData函數提取數據,并保留兩位小數;
4) 當數據類型為0時,發送完整數據幀,當數據類型為1時,發送壓縮后的數據幀。
服務器端的核心代碼實現思路為,通過Process_data函數,對兩種數據類型分別進行處理:
1) 針對壓縮過的數據幀,從Redis緩存中提取相應的數據,然后還原數據幀;
2) 針對未壓縮的數據幀,直接存儲Redis緩存。
系統實驗采用1臺物理機搭載18個虛擬機的形式,具體實驗配置如表4所示。

表4 集群監控系統高性能實驗配置
在分別對采集的監控指標進行時間冗余性數據壓縮、空間冗余性數據壓縮、量化數據壓縮等一系列步驟后,數據壓縮前后對比如表5所示。

表5 監控數據壓縮前后對比
本節對壓縮效果進行了實驗測試,在時間間隔為10秒的前提下,每隔10秒對節點進行監控數據收集,為了比較多次實驗下相應的壓縮效果,本文分別在時間軸10、20、30、40和50 s對節點取了5次監控指標數據,得到了數據在各個階段優化后相應壓縮結果。從表6中可以看出,壓縮的數據比例平均為原始數據的88.68%,壓縮后數據大小為原始數據的11.32%,其中時間冗余與空間冗余優化的提升效果最為明顯,多次實驗平均優化結果分別為35.33%和52.60%。

表6 監控數據各流程壓縮比
同時本文將實現的流式高性能壓縮算法與當前包括LZ4、Brotli、LZF、Snappy等在內的主流日志壓縮算法進行效果對比,具體結果如表7所示。可以看到在這些算法中,本文實現的流式算法達到了最好的壓縮效果,在兩組實驗測試數據中,壓縮后的數據對原始數據大小的比例分別為11.22%以及11.25%,其余四種傳統算法分別在14.86%到18.8%之間,說明本文設計的基于流媒體壓縮算法在實際數據采集中也有著可觀的性能表現。

表7 本文算法與LZ4、Brotli、LZF、Snappy算法效率對比
本文提出的基于流媒體壓縮算法在性能方面優于其他主流壓縮算法的主要原因為:針對流式數據的時間冗余性與空間冗余性兩大特征,基于流媒體的壓縮算法通過信息同步的策略使得重復數據不再進行傳輸;同時針對兩個時間節點來看,假如數值相近,本文減少一次信息的發送,從而進一步減少信息的冗余。與傳統的數據壓縮算法相比,對數據幀在時間軸上的冗余性做出了良好的優化。此外,這些壓縮算法會帶來額外網絡帶寬開銷的,本文算法計算大多分布在從屬節點上,因此不會對主節點帶來額外的計算開銷。
針對目前主流集群監控系統在性能方面的問題,本文旨在對一種基于流媒體壓縮算法的高可用集群監控系統進行設計與實現。首先針對系統產生問題的原因,如監控數據量在縱向層面和橫向層面的相應增長規律,數據冗余性體現,結合從實際節點采集的數據幀格式進行展示與分析。為了解決該問題,本文設計了運行在數據采集器Collect的與數據庫Gnocchi之間的新的數據幀同步協議,并先后從數據幀空間冗余性優化、數據幀時間冗余性優化、系數量化處理等步驟進行實現。
在系統實驗中,每隔一段時間對實際節點進行數據采集,經過系統高性能設計與實現之后,數據優化效果明顯,其中對數據幀的時間冗余性和空間冗余性的提升效果最為突出,壓縮比分別在35%與52%左右。從實驗結果可以看出,本文實現的高性能集群監控系統在實際應用場景中具有很大的發展潛力。