分散式編譯環境中的負載均衡

火星人 @ 2014-03-12 , reply:0


  
本文標題中的“分散式編譯”是一種通過在區域網內的多個節點上運行編譯進程來提高構建速度的途徑。然而在我們的實踐當中發現,單純的向各個節點分發任務而不考慮負載均衡往往會產生性能上的瓶頸。本文主要介紹如何解決這個問題。

使用 distcc 分散式編譯的特點與潛在問題

作為經典的分散式編譯工具,distcc 在日常工作中常為我們使用來解決大型項目在單一工作站上編譯較慢的問題。其主要用於對 C, Object C 以及 C++ 代碼進行并行編譯,將可以并行的編譯任務分佈於編譯集群中的各個工作站,有效利用各機器資源,達到整體編譯性能的成倍提升。

在類 Unix 系統上,distcc 使用 sendfile 系統調用在不同工作節點之間傳送文件,儘管這種網路文件傳輸會佔用一定的時間,他們對工作機的 CPU 資源佔用卻很小,而且這種分發任務的方式能夠簡化構建環境的配置,distcc 在這方面同早期的一些基於共享文件系統的分佈編譯環境 (dmake, pvmmake 等等 ) 相比幾乎是 0 配置。

distcc 對各個編譯節點的本地系統庫及頭文件基本沒有要求,即使在不同的節點上這些組件的版本不同也不會影響到最終編譯結果的正確性,實際情況是 distcc 會在本地 (client 機 ) 完成存在版本依賴的編譯任務,這一點的實現原理簡要說來式因為 distcc 藉助了 C/C++ 編譯驅動程序的以下特點:

  • cpp(C 預處理器 ) [cpp [arguments] .c 源文件輸入 .i 中間文件輸出 ]
  • ccl(C 編譯器 ) [ccl .i 中間文件輸入 .c 源文件輸入 [arguments] –o .s 彙編文件輸出 ]
  • as( 彙編器 ) [as [arguments] –o .o 目標文件輸出 .s 彙編文件輸入 ]

這個在本地做過預處理的 ASCII 源文件及其他命令行選項即可唯一確定一個目標文件,而與此任務在哪台機器上運行無關,通過分發這種任務到各個節點,即可消除對頭文件的依賴。同理 distcc 通過在任務的分發節點做鏈接來消除對庫文件的依賴。

然而,distcc 的缺點在於其負載均衡演算法過於簡單,distcc 的代理進程對各個工作機當前的負荷沒有感知,分發預處理文件的唯一依據是主機出現在 DISTCC_HOST 環境變數中的次序,主機名越靠前,就會得到更多的編譯任務,然而當編譯場中某些機器性能過差,整體編譯性能會顯著下降,當阻塞 Make 運行的編譯任務運行在這些機器上的時候,這種性能變化尤為明顯。

假設這樣一種極端的情況:集群中兩台工作機中一台使用 4 個強勁的 CPU,而另一台性能較弱的機器只有 2 個較弱的 CPU。如果使用 distcc 啟動并行數量為 6 的編譯任務,這將導致較弱的 2 CPU 工作機負載過載而較強的 4CPU 工作機負載過輕,這樣的非均勻負載分佈將拖累整個編譯任務。如圖所示:


圖 1:無負載均衡的并行編譯任務演示





一種實現負載均衡的解決方案:使用 DMUCS

毫無疑問,在上述編譯集群中,有必要採用負載均衡來使編譯系能得到最大的優化。這就需要在編譯集群中增加監控各工作機工作量的監控程序,動態檢測和平衡編譯機的負載。

一個有效的方案是使用 DMUCS(Distributed Multi-User Compilation System)應用。DMUCS 是一個實現於 distcc 之上的動態平衡和任務分佈程序。它可以:

  • 支持多用戶同時編譯,擴展性好,可以很好處理新增的負載。
  • 支持多種操作系統所組成的編譯集群。
  • 可以使用具有多處理器(多核)編譯主機的所有處理資源。
  • 可以充分使用具有不同處理速度的編譯主機,使整體編譯性能達到最優。
  • 可以保證參與編譯的主機不會由於編譯任務而產生超負載的情況。
  • 考慮到了編譯主機上由非編譯任務所引起的負載情況。
  • 支持從編譯集群中動態的增加或者移除編譯主機。

DMUCS 以下四個部分程序組成。

  • dmucs 主服務程序。

DMUCS 解決方案的核心服務程序。每個編譯集群僅運行一個 dmucs 主服務程序,其運行於哪一台主機上沒有限制。該程序從一個配置文件讀出編譯場里處理器數目和每個可能主機的“潛能”。然後從網路接收每個編譯主機的平均負載信息,編譯任務數量和監控程序得到的編譯請求信息。dmucs 管理一個編譯場里主機的資料庫,並調度主機去編譯任務,當有編譯請求時給出可用的最快的主機 / 處理器。

  • Loadavg 監控程序

編譯場的每個參與編譯的主機上均需要啟動這個程序。loadavg 定期發送編譯主機的平均負載到 dmucs 伺服器。這樣當某個主機的平均負載太高時,dmucs 伺服器會將不會優先給該主機分配編譯任務。

  • gethost 編譯命令

gethost 是具體進行編譯的命令,其運行於 distcc 之上。該命令從運行 dmucs 主服務程序的主機獲取編譯集群中的機器信息來獲得放進 DISTCC_HOSTS 環境變數里的主機,然後調用所分配的編譯機進行編譯。在編譯結束后,gethost 釋放所分配的主機到 dmucs 主伺服器。用戶使用“make CXX=gethost distcc”來啟動編譯。

  • Monitor 管理程序(非必須)

編譯集群的管理員可以使用這個程序監控編譯場的繁忙情況。

在上例中,採用 DMUCS 的負載均衡來配置編譯集群,當其啟動并行數量為 6 的編譯任務時,理想狀態下 DMUCS 會首先將任務優先分佈到高性能的編譯機進行編譯。當高性能編譯機上的負載飽滿以後,餘下的編譯任務將被分佈到低性能的編譯機上進行編譯。由此,每個編譯機資源將得到最大限度的利用,編譯性能得到最大提升。


圖 2:帶負載均衡的并行編譯任務演示





部署負載均衡的分散式編譯環境

本節介紹如何架設 / 配置你的分散式編譯環境來獲得最好的性能,包括如下幾點提示:

  • 使用 distcc 為你的工作節點指定角色;
  • 在此基礎上配置 DMUCS 來實現性能優化;

本節中我們將主要討論 distcc 和 DMUCS 的特性和配置,並針對上述兩點做詳細討論。

distcc 的安裝與配置

distcc 的基本安裝,首先下載源代碼到任意目錄解壓后依次運行

./configure, make,make install

即可完成安裝,如果同時需要安裝圖形化任務監控前端,請根據主機圖形庫支持情況指定—with-gnome 或—with-gtk. distcc 程序組的默認安裝位置是 /usr/local/bin,如果需要安裝到其餘位置,請在配置編譯時修改 configure 的各個路徑變數。

distcc 會安裝如下可執行文件:

  • distcc: 整個編譯任務通常由一台機器發起,在 distcc 編譯環境下,這台機器被稱為 client,client 必須使用 distcc 來替代原有的 GNU 編譯器命令,由於 distcc 的後台編輯程序仍然是 GNU 編譯器,distcc 與 gcc, g++, cc, c++ 等程序的編譯參數兼容。distcc 必須與 Make 命令的 -j 參數協同使用,client 機通過指定此參數來定義併發編譯的任務數。在默認情況下,一台編譯機的 distcc 允許的併發任務的數量是 CPU 數量 +2。
  • distccd: distccd 是運行在編譯場內各個節點上的 distcc 代理程序,distccd 的常用參數如下:
      • -j: 指定可以在本節點上運行的最大任務數;
      • -N: 如果編譯節點上運行有其他重要任務,可以通過指定 -N 參數來調整編譯進程的運行優先順序;
      • -a: 指定 distccd 可以接受來自哪些節點的連接請求,-a 參數的值可以是一個網段,也可以是所有編輯節點主機名 (IP 地址 ) 的列表。
  • distccmon-text: 可以通過運行 distccmon-text 來通過一個字元界面監控整個編譯任務,此命令唯一的參數是監控任務的刷新間隔 ( 秒 )。
  • distccmon-gnome: 是一個圖形化的監控前端,下圖是此程序的一個運行實例。其中,任務進度指示條顏色的意義分別為:綠色:compiling;紫色:preprocessing;藍色:receiving;橙色:connecting;白色:idle;

圖 3:distccmon-gnome: 監控前端

Distcc client 通過配置環境變數 DISTCC_HOSTS 來指定編譯場中的各個節點,具體命令如::

export DISTCC_HOSTS="192.168.1.1, 192.168.1.2"  

此處需注意在此環境變數中位置靠前的主機會得到更多的編譯任務,distcc 工具通過這種機制來實現簡單的負載均衡,雖然我們認為這種機制是不完備的。

DMUCS 的安裝與配置

從 http://prdownloads.sourceforge.net/dmucs/dmucs-0.6.tar.bz2?download 下載 dmucs 的最新源碼包。Dmucs 的安裝使用的是標準的 configure,make,make install 的方式。

首先在解壓開的源碼包的根目錄下運行 ./configure,默認是設置 /usr/local 為安裝目錄,你也可以使用—prefix 參數來指定其他的安裝目錄,這同時也改變了 dmucs 啟動時所需要的 hosts-info 文件(下文會詳細介紹該文件的內容)所在的路徑。默認情況下這個文件的所在路徑是 /usr/local/share/dmucs/hosts-info。

第二步就是執行 make 命令來編譯 dmucs,這裡要注意的是需要使用一個 make 選項 CPPFLAGS=-DSERVER_MACH_NAME=\\\”<DSERVER-IP>\\\”,這裡的 <DSERVER-IP> 是你選擇運行 dmucs 伺服器的 IP 地址。

第三步是運行 make install 將編譯好的可執行文件安裝到指定的安裝目錄下。

以上的安裝步驟在每台機器上(包括 dmucs 主機和實際執行編譯任務的 host 主機)都要執行一遍。

在運行 dmucs 之前要確保完成以下配置:

  1. 確保每個實際參與編譯任務的主機都安裝了 gcc 編譯器和 distcc(至少需要 distcc 和 distccd)。
  2. 確保 loadavg 在每個參與編譯任務的主機上都可以被執行。
  3. 確保 dmucs 程序在被你選作 dmucs sever 的主機上可以被執行。
  4. 確保 hosts-info 文件已被創建。該文件的默認路徑是 /usr/local/share/dmucs/hosts-info,如果你在之前的安裝步驟中指定過其他的安裝目錄(使用過—prefix 參數)那麼這個文件所在路徑是 <prefix>/share/dmucs/hosts-info。這裡簡單介紹一下這個文件的格式:

清單 1:hosts-info 配置
#Format:host-name number-of-cpus power-index   9.123.247.65 2 3   9.123.245.91 2 3   9.47.98.59 1 1   9.47.98.90 8 15  

以上文件格式比較簡單,每一行代表一台實際參與編譯的主機的信息:主機名或 IP 地址,cpu(或者核)的個數和“性能指數。前兩個屬性的含義都是一目了然的,第三個屬性“性能指數”是個相對數值(大於等於 1),如果你確定一台主機的性能強過另一台主機,這台主機的“性能指數就要相對大些。一種確定“性能指數”的方式是,讓幾台主機都單獨執行一次同樣的編譯任務,分別記錄所需的時間。然後指定所花時間最長的那台主機的性能參數(比如說 2),那麼其他主機的性能指數的設定按照其所花編譯時間與最慢的那台主機所花時間的比例來設置。比如說,如果有一台主機只花了最慢主機一半的時間,那麼它的性能指數就應被設置為 4。其他的主機以此類推。

完整啟動 DMUCS 程序由以下步驟組成:

  1. 在 dmucs server 上啟動 dmucs 程序。
  2. 在每台實際參與編譯的主機上運行 distccd 守護進程。
  3. 在每台實際參與編譯的主機上執行 loadavg 程序,該程序默認向編譯時所指定的 <DSERVER-IP> 的 dmucs server 發送該台主機的 IP 地址和負載信息。你也可以通過 -s 參數指定 dmucs server 的地址信息。

這裡有個讀者特別需要注意的地方,loadavg 程序是先獲得主機的 hostname,之後再通過 hostname 去獲取主機 IP 的。這就有個問題就是,假設某台主機的 hostname 是 host1,如果在這台機器上的 /etc/hosts 文件里的信息如下所示:


清單 2:hosts 文件示例
…………….  host1 127.0.0.1  host1 9.123.245.91  ……………..  

也就是說迴環地址的記錄在實際的 IP 地址記錄之前,那麼 loadavg 向 dmucs server 所發送的 IP 信息永遠是 127.0.0.1!這樣就會導致 dmucs 程序不能正確識別這台編譯主機。解決的方法就是將 /etc/hosts/ 文件里的 host 主機的實際 IP 信息記錄寫在最前面。

下圖是 dmucs 程序啟動后的情況,讀者可以從示例當中看到有兩台主機加入到了編譯集群:


圖 4 dmucs 程序運行示例

最後用戶就可以使用“make CXX=gethost distcc”來啟動編譯任務了。





編譯測試與性能分析

我們使用兩台不同性能的工作機搭建分散式編譯的編譯集群,這兩台工作機的配置參數如下表所示。


表 1:測試機配置表
  工作機 1 工作機 2
CPU 數量 8 2
CPU 性能 Intel(R) Xeon(R) CPU E5410 2.33GHz Intel(R) Pentium(R) III CPU family 1266MHz
內存 16G 1G
IP 地址 9.47.98.90 9.47.98.59

每個主機上均運行 distccd 程序“distccd -a 9.47.0.0/16”。其中 DMUCS 服務程序運行在 8 核主機上,其 hosts-info 配置如下所示:


清單 3:hosts-info 配置
#Format:host-name number-of-cpus power-index   9.47.98.59 2 2   9.47.98.90 8 4  

我們使用的測試項目包含個需要編譯 803 個 CPP 文件,通常情況在單個普通雙核的工作機上編譯需要一小時以上。運用此測試樣例,我們分別在單獨使用 distcc 和用 dmucs 進行負載均衡優化的情況下,進行分散式編譯。測試環境的配置與上節內容相同。通過設置不同的并行任務數量,編譯測試依次進行并行任務數量為 2,4,6,8,10,12 和 14 的共 7 組測試。

我們得到了詳細的測試結果,請參見表 2。


表 2:測試結果
并行任務數 Distcc ( 分鐘 ) distcc+dmucs ( 分鐘 )
2 26.3 17.8
4 14.5 10.0
6 11.2 7.5
8 9.5 6.2
10 9.3 6.3
12 9.5 6.1
14 9.2 6.3

測試結果的數據對比請參加圖 5


圖 5:測試結果

通過對比,我們可以發現使用 dmucs 配合 distcc 在分散式編譯中進行負載均衡,使性能強大的節點承擔較重的負載,可以大大提高編譯的效率,從而很好的解決了 distcc 平均分配所帶來的性能瓶頸問題。從在本次測試中可以看出,其編譯時間平均縮短最多至 1/3 左右。

同時,我們可以發現當并行任務到 10 以後,編譯性能已無明顯提升。這也反應了在分散式編譯中,并行并行編譯任務的數量應該根據實際的編譯集群能力設定。單純提高并行編譯任務的數量而忽略編譯集群的實際能力將並不一定給編譯性能帶來好處。(責任編輯:A6)






[火星人 via ] 分散式編譯環境中的負載均衡已經有242次圍觀

http://www.coctec.com/docs/linux/show-post-68881.html