天天看點

基于表格存儲的高性能監控資料存儲計算方案

        随着軟體架構的愈發複雜,了解系統現狀、調查問題的困難度也增加了很多。此時,一套完善的監控方案能夠讓開發和運維工程師快速排查問題,更好的維護系統的穩定性。

       開源監控方案中,zabbix、nagios都是不錯的監控軟體,可以針對數十萬的裝置監控數百萬的名額,強大的功能讓開發和運維都很贊歎。但是,網上經常看到的抱怨是其寫入和存儲能力的不足,以zabbix為例,文章[1]提到使用nosql方案(hbase、cassandra、riak)比利用傳統rdbms方案(mysql、postgresql、oracle)其性能提高了1.5-3倍。如果考慮到架構的擴充性以及存儲空間的成本,nosql方案還會更有優勢,因為一般來說,nosql的壓縮效果都會更好。

       下面我們将基于一個執行個體案例,來講解如何使用阿裡雲nosql服務“表格存儲”來進行監控資料的處理。下面叙述的過程中,我們不會直接拿出最好的方案,而是将逐漸優化的思路整理出來分享給大家,期望大家跟我們一起尋求更優的解決方案。心急的同學可以直接跳到最後看結論。

       一個典型的監控系統包括資料的采集、計算(實時計算和離線計算)、存儲和展示,采集和展示是相對獨立的子產品,這裡我們隻以存儲為重點,在必要的時候也會對計算做相應的說明。

       從業務角度看,監控系統需要完成如下功能:

任何時候都要求能寫入,不得丢點;

給定某個機器,某個名額,能夠查詢該機器該名額在一段時間内的連續的值;

給定某個機器,查詢該機器所有名額在一段時間内的連續的值;

給定某個名額,查詢所有機器在一段時間内的連續的值;

以上三點,時間段可以任意指定,從分鐘到月均支援;

 從系統設計角度看,監控對存儲系統的核心要求如下:

線上變更表模式:監控名額變動頻繁,表模式必須做到自由改變,同時不對線上業務産生任何影響;

寫入性能高:一般批量寫數百條資料延時在數百ms内;

高擴充性:随着監控的裝置和名額越來越多,寫入能力和存儲能力需求越來越大,寫入要支援每秒千萬行監控資料,存儲系統要支援數十p資料;擴容過程使用者無感覺;

低存儲成本:監控資料一般比較多,存儲成本需要重點關注,存儲系統要能夠在滿足通路需求的前提下盡可能的降低成本;

老資料自動清理:一段時間前的資料一般不再需要,為了節約成本需要系統自動删除,友善使用者;

上面列的需求是從宏觀層面上了解的,是架構師和cto關心的事情。而作為一個幹活的程式員,我們需要從微觀層面,從動手寫代碼的角度再一次細化需求。擴充、成本咱就不管了,這些老大都決定好了(關于成本最後有個具體的示例),我們就關心如何快速的建構系統。

對某個機器的某個名額,采集間隔為5秒,每行記錄約100byte;平均寫入延時低于50ms;

資料保留6個月;

機器個數可以數百萬,名額個數可以數千萬,資料量可能過p;

典型名額查詢的時間範圍是最近10分鐘、1小時、24小時,亦可自定義起始和終止時間;

對大範圍資料做聚合:如果查詢的時間範圍太大,傳回資料點不可太多,否則傳輸和展示都是負擔;

查詢要求,已知機器、名額,查詢某個時間範圍内的名額資料;

查詢要求,已知機器,查詢某個時間範圍内的所有名額資料;

查詢要求,已知名額,查詢某個時間範圍内所有機器的該名額資料;

典型的業務查詢操作,延時要求低于200ms;

        下面圖1描述了通用的監控系統架構圖,以友善繼續講解。監控系統是一個龐大複雜的體系,網上找了個更完善的監控架構圖,見[2]。

基于表格存儲的高性能監控資料存儲計算方案

圖1 通用報警系統架構,其中資料聚合任務亦可以使用流計算工具代替,道理類似。某些情況下,資料聚合任務也可以和采集代理合并為一個程序,簡化架構。

各個子產品的作用大概如下:

監控名額采集代理:部署在應用伺服器上收集名額的軟體,一些流行的開源軟體比如mysql/nginx等本身就有很多第三方的監控代理;對于自己開發的系統,可以在程式中埋點,定制化代理;

資料聚合任務:上面需求裡面提到需要看長時間周期的資料,如果檢視過去一個月的監控圖,需要将所有秒級的資料直接拿出來展示,對于傳輸帶寬和前端展示系統都是極大的考驗,而且一般業務并無此種精度需求;這個任務的目的就是定時将原始資料拉出來計算更粗粒度的名額,以便觀察名額的趨勢性;

報警系統:資料聚合任務在計算名額過程中會發現異常名額,此時可以将該名額通知報警系統,報警系統從存儲系統裡面拿出更細的資訊之後,可以通過短信、電話等方式通知相關人員;如果相關事件也能輸入報警系統,那麼名額展示界面上就可以将異常名額和事件關聯,加快問題的解決,如圖

可視化展示:豐富的可視化工具可以加速問題的發現,将可視化和後端的資料系統解耦能友善嘗試不同的可視化工具;

表格存儲系統:無限水準擴充的nosql服務,寫入能力強大,過期資料自動清理;

基于表格存儲的高性能監控資料存儲計算方案

 圖2 報警和系統事件存儲在一起,協同處理之後可以得到更有價值的展示

       從上面的介紹中能夠意識到,最困難的問題是表結構的設計,要求該設計能夠避免資料熱點,能讓聚合任務快速的讀取過去一段周期的資料進行聚合,也能讓報警任務快速讀取異常名額細節,下面我們就開始結構設計之旅,看看如何一步步建構高性能的監控資料存儲計算系統。

        上述架構中,一個實際的場景如下:機器n名額m通過名額收集代理每5秒将名額資料寫入秒表table_5s, 一分鐘後n-m向table_5s寫入了12條資料,此時聚合任務可以将table_5s最近寫入的12條資料讀出來,計算均值(也可以按照業務需求做各種計算),然後寫入分鐘表table_1m,這樣1分鐘後table_1m寫入了一條資料。依次類推,60分鐘後小時表table_1h得到了一條資料,24小時後日表table_1d得到了一條資料。如上,有了秒表、分鐘表、小時表、日表之後,我們就可以根據不同的精度需求滿足業務的查詢需要。如果使用者要查詢最近一個月的趨勢,就應該讀日表table_1d,一次查詢資料在數百條内,如果使用者要查一分鐘内的細節問題,就應該讀表table_5s。

        下面表1給出了我們首先想到的表結構設計(秒/分鐘/小時/日表結構是類似的),有3列pk,value是integer類型,

表1 最簡單粗暴的表結構設計

nodename(*)

metricname(*)

timestamp(*)

value

string

integer

node1

cpu

1000

43

1005

50

disk

10

20

node2

60

80

        從上面的需求來看,代理每5秒針對每個機器每個名額寫入一條資料沒有問題,

        而實際上,在某一個時刻會多個機器多個名額都會産生資料,是以更好的做法是使用批量接口減少網絡io次數,也利于存儲系統優化實作;

        聚合任務則定期從高精度表拉資料計算然後寫入低精度表,

        至此,計算、存儲相關的代碼已經寫完了,系統已經可以正常的運轉,如果業務量不大,這個方案是可以的。

       随着業務量的增加,上面的設計可能有如下幾個問題:

表第一列pk為機器名,可能導緻熱點問題。想象一下,你打算做一個監控平台,服務其他的使用者,那麼表第一列不再是機器名,而是使用者名,那麼一些大使用者,其監控名額多達數十萬,而這些監控名額因為擁有共同的第一列pk,表格存儲無法對其做自動分區(這是表格存儲的設計決定的,關于分區的概念,見[4]),進而使得該熱點無法被很好的平衡;

擴充性不夠靈活:比如有個需求是這樣的,業務希望看到機器n的cpu名額超過90%的所有記錄,上面的表結構就沒法滿足了,此時需要的表結構是表2中所示:

表2 為了查找特定機器的cpu高點,需要跟表1不同的表結構

placeholder

 /// 僞代碼,實際示例見各個sdk中example[3]

        這樣就可以将該機器cpu超過90%的記錄都拿出來。大家看到了這個表結構跟上面的不同,因為pk的列數不同,此時要滿足這個業務需求,我們當然可以重建一個表,但是首先表格存儲不鼓勵建立很多小表,其次類似業務變更可能很多,每次建表影響業務靈活性。

        是以,如果我們的表結構變為如表3

表3 新的表結構設計,增加了業務變更的靈活性

nodename_metricname(*)

node1_cpu

node1_disk

node2_cpu

       這種表結構設計比初始設計就要好很多了,第一列是我們自己拼起來的字元串,我們有足夠的自由按照業務需求來拼,表格存儲也能夠友善的做負載均衡。按照上述結構設計,上面提到的新的業務得到的表資料如下,如表4

表4 按照新的表結構設計,監控資料展示如下

node1_cpu_43

node1_cpu_50

node2_cpu_60

node2_cpu_80

請注意,上面隻是按照業務需求将cpu的pk拼裝格式改變了,disk因為沒有這個需求,并不需要改變,表格存儲允許不同行的列不同,這樣也不會帶來額外的存儲空間占用。由此我們也能看到,在pk的組織方式上,是有很多花樣可以玩的。

       問題都解決了嗎?還沒。我們注意到聚合任務需要定期的讀取最近的k條資料做聚合之用,比如從table_5s中讀12條生成分鐘級别資料,從table_1m中讀60條資料生成小時級别資料等,這些讀都是通過getrange實作的,也即是一個小的scan讀,而表格存儲采用lsm[5]模型實作,在寫入量巨大的時候scan讀會導緻大量磁盤io,進而也容易引起性能下降。而且,随着這些表資料越來越多,這種讀取的性能也會越來越難以保證。

        如何解決呢?答案是對前面的每類表,建立一個表結構相同的buffer表。比如table_5s表會建立一個對應的table_5s_buffer表。這個表裡面隻存最近一段時間的資料,比如1天,超過1天的資料自動過期删除,這樣資料少了,通路的時候io次數可控,性能可控。上面保留1天是假設聚合任務可能出問題而多保留了一段時間,實際上對表table_5s_buffer我們幾乎隻會讀最近數秒的資料,而對這種通路剛剛寫入的資料的場景,表格存儲是有特定的優化的,就是類似檔案系統的page cache的概念,資料寫入磁盤前首先寫入記憶體,這樣通路最新寫入的資料命中記憶體的可能性就變大了。上面的兩個特點共同保障了表table_5s_buffer的讀性能。

       是否還可以繼續優化?答案是yes。我們回頭看看,一個機器可能有數千個名額需要監控,包括系統級别的和應用級别的,那麼聚合任務對每個名額都要執行scan就有點浪費了,scan的次數跟機器x名額數成正比。實際上,我們可以重新設計各個buffer表,結構如表5所示,第二列是時間,也就是按照時間對名額進行排序,

表5 重新設計buffer表,避免針對每個機器、每個名額都要scan讀

nodename

node1_

        有了上面的設計,聚合任務想拿node1最近1分鐘(12行)的所有秒級監控名額,隻要一次getrange查詢就可以了,如果名額太多,sdk會自行分階段多次讀取。

表6 為了避免頭部熱點,對每個機器上的若幹名額做分桶,這樣也利于聚集任務負載均衡

bucketid(*)

00001

00003

00002

        其中bucketid是hash(nodename + metricname) % k得到的,k可以根據自己的業務規模調整。有了這個設計後,即使某個機器下面需要監控數十萬名額,也可以被多個分區均勻處理,避免了隻寫頭部分區的熱點問題。這種方案還有一個好處就是,聚合任務也可以啟動多個執行個體,每個執行個體負責一定數量的桶就可以了,這樣聚合任務的負載也是比較均衡的。

        總結來說,就是基礎表資料采用表1裡面給出的結構,而相對應的buffer表采用表6是一個比較好的方案。

        上面的各個步驟以一個真實的監控資料處理方案為背景,我們能看到系統如何從一個最簡單但是存在性能問題的版本一步步演進到最終的解決性能、通路均勻性等問題的方案。采用最終方案後,該應用至今已經穩定運作4個月,之前的性能問題、通路熱點問題均得到了解決。

        使用高可擴充性的nosql存儲監控資料是一個較為理想的方案,因為監控資料是時間序列資料,定期生産,聚合度較好,寫入效率高。而nosql系統如表格存儲一般采用lsm模型實作,其本身就是利于寫的,正好比對。同時表格存儲還提供強大的水準擴充能力,支援每秒寫入千萬行,支援存儲數十p的資料。

        思路擴充一下,上面的方案對時間序列資料都是可以借鑒的,比如應用性能管理(apm)中某app下pv/uv聚合資訊,比如券商系統中各類交易資料的聚合資訊等。

        關于可用性、可靠性等問題,也可以參考[4],關于價格計算,可以直接使用[7]中提供的工具,需要注意的是,7月份會正式上線大容量存儲産品,采用sata磁盤存儲,價格會大幅下降。