Zabbix數據結構及並行計算實現

本文原創作者鮑光亞,京東商城基礎平臺部軟件開發工程師,經作者同意發表於本人博客,如需轉載需經本人同意。

一、 前言

我部門對數據庫的監控使用的是開源的Zabbix系統,目前監控了上萬臺主機。本文旨在通過分析Zabbix系統server端的數據結構和並行計算的實現方法,嘗試探尋Zabbix系統server端的潛在擴展能力,同時希望有助於在實際應用過程中進一步優化運行效率和穩定性。

Zabbix系統採用server-proxy-agent架構,其server端的主要功能是收集監控數據並基於所收集的數據觸發報警動作。在實際應用中,zabbix有可能會監控10000臺主機(host,由hostid唯一標識),如果每臺主機設置50個監控指標(item,由itemid唯一標識),並且每分鐘收集一次數據,則一共有50萬個item,每秒鐘需要接收並處理8333項數據(value),即vps(values per second)爲8333。如果有三分之一的item設置了報警觸發器(trigger,由triggerid唯一標識),則共有17萬個trigger。

在以上情境中,爲了保證監控的有效性和及時性,zabbix接收到每個value後需要立即在50萬個item中找到正確的item,並獲取該item的前一個值(previous value,last(),以便計算增量),或者計算前5分鐘內的平均值(avg(5m)),以便根據觸發器表達式(trigger expression,由functionid唯一標識)判斷是否應該觸發報警事件(event,由eventid唯一標識)。同時如果item返回值類型爲數字型,還需要計算該item在一個小時內的平均值(value_avg)、最大值(value_max)、最小值(value_min)。按照上面的vps數據,zabbix至少需要每秒鐘搜索8333*500000次。此外,item和trigger並不是靜態數據,用戶隨時可能會增加、修改、刪除、禁用(disable)、啓用(enable)某些item和trigger,zabbix需要在處理value時查詢該item和trigger最新的狀態。如何在一秒時間內完成如此大量的操作,zabbix給出的方案是: 哈希表。

在並行計算的軟件方面,由於Zabbix系統監控的各個主機之間是相對獨立的,無論在任務還是在數據方面都非常便於計算的並行化。服務器硬件方面,我們實際使用的服務器結構是2*8處理器+三級緩存+16G*8內存+SSD硬盤+10Gbps網卡(數據庫、zabbix server和web服務共用)。Zabbix的並行計算主要採用的是共享內存模式。

二、 Zabbix中的哈希表種類

Zabbix使用的哈希表是鏈式哈希表,主要有以下五類(都是在共享內存中分配空間):

1. Valuecache

Valuecache中包含兩個哈希表vc_cache->items(itemid作爲鍵值進行哈希)和vc_cache->strpool(字符串作爲鍵值),用於存儲收集到的values(包括數字型和字符串型),每個item佔用一個slot,每個槽位都是一個鏈表,鏈表節點存儲實際需要的信息。

Valuecache的哈希表在服務啓動時創建,服務退出時銷燬,初始槽數爲1009(1000之後的第一個素數),隨着表中元素數量的增加,槽數也會按照一定的規則增多。Valuecache可使用的最大空間由配置文件中的ValueCacheSize參數控制,允許的範圍是128K-64G。

2. Dbcache

Dbcache中的cache->trends哈希表,用於緩存trends表(每個item的小時平均值、最大值、最小值)的數據。Zabbix server的history_syncer進程會持續接收來自agent或者proxy的數據後會將其加載到緩存中,同時更新cache->trends哈希表。該哈希表中的元素是ZBX_DC_TREND結構體。

Cache->trends表中的數據時間超過整點時會被flush到數據庫中,例如10點之後會將9-10點之間的數據flush到數據庫中。

Cache->trends哈希表在服務啓動時創建,初始槽數與vc_cache->items相同,爲1009(1000之後的第一個素數)。Cache->trends哈希表的最大可用空間由配置文件中的TrendCacheSize參數控制,允許的範圍是128K-2G。

3. Dbconfig

Dbconfig緩存中存儲了多個與監控有關的配置信息的哈希表,包括config->hosts、config->items、config->functions、config->triggers等等。配置信息哈希表的鍵值包括hostid、itemid、functionid、triggerid、triggerdepid、expressionid、globalmacroid、hostmacroid、hosttemplateid、interfaceid、host_inventory等,其中數量最多的往往是itemid、functionid和triggerid,會在數十萬級別(以10000個host計)。

以config->items爲例,該哈希表的元素是ZBX_DC_ITEM結構體。Config->items中的數據是從數據庫中查詢獲得的,zabbix server的configuration syncer進程會週期性地從數據庫同步數據到緩存中。

Dbconfig緩存中的其他哈希表與config->items表類似,都是從數據庫同步數據,都是在服務啓動時創建,初始槽數都是1009,並隨着數據量的增加動態擴展。整個dbconfig緩存可用空間大小由CacheSize參數決定,取值範圍爲128K-8G。

4. Strpool

此處的strpool與vc_cache->strpool是相互獨立的兩個哈希表。此Strpool緩存用於存儲配置信息相關的字符串值,它與dbconfig共同分享CacheSize的空間(strpool佔15%)。Strpool存儲的字符串包括host name、item key、item delay_flex、snmp community、snmp securityname、snmp passphrase、logitem format等數據。Zabbix需要使用host name等字符串時,會首先在strpool中查找。

Strpool的哈希表初始槽數爲1009。鍵值是字符串本身,哈希值是對字符串調用哈希函數的返回值。

5. 其他

除了以上哈希表,還有snmpidx、vmware service等哈希表。

三、 哈希表的實現

下面以config->items哈希表爲例,說明zabbix中哈希表的實現方法。

1. 數據結構定義

Zabbix採用的是鏈式哈希表,哈希表中的每個slot都是一個鏈表。具體的數據結構定義如下:

2. clip_p_w_picpath002    
槽數取值及負載因子

Zabbix的哈希過程是先調用哈希函數計算鍵值對應的哈希值,然後用取餘法確定槽位號。因此,取餘計算時的除數就是槽位數,該數值取素數(因爲素數可以做到最大程度上均勻散列)。在config->items哈希表中,槽位數的初始值是1009,隨着數據量的增加,當負載因子(元素數/槽數)達到0.8時,會擴充槽數量(擴充爲當前數量的1.5倍以上,並取素數)。因此,負載因子總是保持在0.8和0.533之間。

按照以上規則,每次擴展哈希表,其槽數如下表示。當item數量爲50萬時,槽數應爲670849。

序號

理論值

素數(槽數)

允許的元素數

0

1000

1009

806

1

1513

1523

1217

2

2284

2287

1828

3

3430

3433

2745

4

5149

5153

4121

5

7729

7741

6191

6

11611

11617

9292

7

17425

17431

13943

8

26146

26153

20921

9

39229

39229

31382

10

58843

58889

47110

11

88333

88337

70668

12

132505

132511

106007

13

198766

198769

159014

14

298153

298153

238521

15

447229

447233

357785

16

670849

670849

536678

17

1006273

1006279

805022

18

1509418

1509427

1207540

19

2264140

2264149

1811318

3. 哈希函數

Zabbix使用的哈希函數是在fnv-1a函數(http://www.isthe.com/chongo/tech/comp/fnv/index.html)的基礎上稍微進行了改進。該函數採用乘積和位操作達到快速哈希的目的。具體實現如下:

clip_p_w_picpath004

按照以上函數,模擬620000個itemid的哈希過程(槽數取1006279),哈希效率如下:

總桶數

1006279

空桶數量

543405

深度大於1的桶數

127940

載荷因子

0.616131311

最大桶深

7

深桶佔有值桶比例

0.276403514

深桶佔總桶數比例

0.127141677

空桶佔總數比例

0.540014251

四、 任務和數據的並行化

1. 任務的並行

Zabbix系統的任務基本上都是基於所監控的host和item,各個host和item之間有較強的獨立性。爲了並行化,Zabbix將任務拆分爲相對獨立的子任務,各個子任務由一個或者多個進程來執行。Zabbix server端的進程劃分如下表所示:

啓動

順序

process title

允許

進程數

默認值

任務

1

configuration syncer

1-1

1

從數據庫同步數據到Dbconfig緩存

2

db watchdog

1-1

1

週期性地檢查server端數據庫是否可用,如果不可用則發送報警信息

3

poller #n

0-1000

5

根據dbconfig中的數據,從passive agent和snmp設備採集數據,並flush到共享內存cache->history中

4

unreachable poller #n

0-1000

1

當設備處於unreachable狀態時,週期性地polling設備

5

trapper #n

0-1000

5

從socket接收並處理active agent和active proxy發來的數據(json格式,zabbix通訊協議),並flush到共享內存cache->history中

6

icmp pinger #n

0-1000

1

根據dbconfig中的數據,批量採集icmpping相關的item數據,並flush到共享內存cache->history中

7

alerter

1-1

1

發送各種報警通知

8

housekeeper

1-1

1

週期性地刪除過期的歷史數據

9

timer #n

1-1000

1

計算與時間相關的trigger表達式等

10

node watcher

1-1

1

處理與node之間的交互

11

http poller #n

0-1000

1

收集web監控相關的數據,並flush到共享內存cache->history中

12

discoverer #n

0-250

1

按照指定規則掃描網絡,自動發現host、interface等

13

history syncer #n

1-100

4

將共享內存cache->history中的數據批量更新到數據庫中,並flush到共享內存vc_cache、cache->trends、config->items等中

14

escalator

1-1

1

當報警操作需要分步連續執行時,控制各步驟之間的escalations

15

ipmi poller #n

0-1000

0

與poller進程類似,處理ipmi items

16

java poller #n

0-1000

0

與poller進程類似,處理JMX items

17

snmp trapper #n

0-1

0

與trapper進程類似,處理snmp items

18

proxy poller #n

0-250

1

與passive proxy交互,以設定的頻率獲取所需要的json格式數據並將數據flush到共享內存cache->history中

19

self-monitoring

1-1

1

處理與zabbix自身運行狀態相關的item信息,訪問共享內存中的collector變量

20

vmware collector #n

0-250

0

採集vmware虛擬機相關的數據,並flush到共享內存中

所有進程中比較關鍵的進程有兩類:poller/trapper類進程,用於採集數據並加載到共享內存中;history syncer進程,用於更新數據庫及觸發events和報警。邏輯上這兩類任務是先後執行的,首先要採集到數據然後才能觸發報警。而每類任務的各個進程之間是獨立的,多個poller/trapper進程可以同時執行,多個history syncer進程也可以同時執行。

2. Socket multiplexing對多進程的支持

Zabbix監控系統的數據最終來源是被監控的主機,數據通過socket監聽端口接收(監聽端口允許的最大連接數由操作系統決定)。Zabbix通過fork多個子進程來共享同一個socket,在讀socket時則通過基於select()函數的multiplexing實現多進程同時讀取。

按照10000個host,每分鐘採集一次數據(假設每個host上的所有item同時採集數據,事實可能並非如此),平均每秒鐘有167個連接請求。

3. Mysql數據庫的讀寫

Zabbix支持多種數據庫,包括Mysql、Oracle、IBM DB2、PostgreSQL、SQLite,我們實際使用的是Mysql。爲了保證數據的持續性,zabbix在觸發報警前會先將數據插入到數據庫中。History syncer進程數允許最多100個,每個進程可以與數據庫建立獨立的連接,進行數據更新。

五、 共享內存與進程間通信

1. 共享內存的創建

共享內存是進程間通信中最簡單並且速度最快的一種機制。Zabbix的進程間通信主要採用共享內存的方式,主進程在fork出所有子進程之前調用shmget創建共享內存,並attach到地址空間中。

Zabbix調用shmget創建的共享內存segment共有8個,爲config_mem、trend_mem、history_mem、history_text_mem、vc_mem、vmware_mem、strpool.mem_info、collector,分別用於dbconfig緩存、cache->trends數據、cache->history(數字和string)、vc_cache、vmware數據、strpool、監控zabbix自身狀態的collector結構。如果實際應用中沒有啓用vmware,則只有7個共享內存被attach到各子進程的地址空間中,如下圖所示,這些共享內存段將一直保持attach狀態,直到服務停止。

clip_p_w_picpath006

從上圖可以看出,每個共享內存段都attach到了553個進程中,即zabbix server的每個進程都可以訪問所有七個共享內存。

2. 信號量機制

Zabbix使用二進制信號量機制來協調多個進程對共享內存的同時訪問,避免資源爭用。系統在創建共享內存之前會調用semget函數,創建一個包含13個信號量的信號量集,並將每個信號量的值初始化爲1。各個信號量用於對不同的共享內存進行訪問控制,具體如下所示:

# define ZBX_MUTEX_LOG 0

# define ZBX_MUTEX_NODE_SYNC 1

# define ZBX_MUTEX_CACHE 2

# define ZBX_MUTEX_TRENDS 3

# define ZBX_MUTEX_CACHE_IDS 4

# define ZBX_MUTEX_CONFIG 5

# define ZBX_MUTEX_SELFMON 6

# define ZBX_MUTEX_CPUSTATS 7

# define ZBX_MUTEX_DISKSTATS 8

# define ZBX_MUTEX_ITSERVICES 9

# define ZBX_MUTEX_VALUECACHE 10

# define ZBX_MUTEX_VMWARE 11

# define ZBX_MUTEX_SQLITE3 12

當進程需要對某個共享內存進行寫操作時,會首先lock(調用semop函數將信號量-1),執行寫操作完畢後將再unlock(將信號量+1)。如果執行lock時信號量爲0,則等待,直到信號量非0。Zabbix的信號量在釋放共享內存時銷燬。

六、 聲明與結論

本文創作基於zabbix 2.2.10版本的源碼分析,歡迎批評指正。

Zabbix所採用的哈希函數效果比較理想。但在實際應用中,仍然可以根據需要和資源情況對負載因子、槽數擴展速度、槽數初值、哈希函數定義進行改進。

在Zabbix的並行計算方面,由於監控系統的特點,數據和任務之間有較強的獨立性,非常便於並行化。Zabbix通過多進程+共享內存實現並行,資源爭用問題通過信號量進行控制。從實際應用效果來看,並行的性能非常理想。

p_w_picpath

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