Julia並行計算筆記(五)

八、集羣管理器(ClusterManager)

特意追加一節來介紹集羣管理器。集羣管理器也是一個抽象對象,類似於抽象數組AbstractArray對象。

Julia裏的抽象對象相當於“模板”。這種模板可以添加自定義內部變量派生出許多子類型對象。抽象數組和集羣管理器都是一種抽象對象。當我們在下文中提到ClusterManager時,要記得它是抽象對象,別搞糊塗了。

在Julia裏,主進程和Worker聯網組成的結構稱爲一個“集羣”,是ClusterManager抽象對象的一個子類型,無論單機或多機。主進程和Worker的概念我們已經很熟悉了。主進程(master process)恆有PID=1,只有在這個進程上可以增加或移除其他進程。從這個角度上說,Julia的集羣管理是單向的,集羣管理器相當於主進程的助理。不過,任意兩個進程之間都可以互相通訊,所以通訊是平等的。

集羣可以在單臺機器或多臺機器上創建。爲了避免混淆,我們稱單臺機器上的集羣爲“單機集羣”,在多臺機器上稱爲“多機集羣”。單機的情況大概是相當於把單機虛擬爲多機,故又稱爲“虛擬集羣”。

Julia的集羣管理器,是包括了管理單機集羣和多機集羣的一組函數和命令。前者就是我們已經認識的各種進程操作的彙總。後者也是一樣的操作,但爲了管理多機器而加入了一些特殊命令。

所謂的集羣管理器的功能有三個方面:

  1. 在集羣環境中啓動Worker。
  2. 管理每個Worker的生命週期,比如發送中斷信號。
  3. (可選)提供數據傳輸。

多機集羣的進程間的連接基於內置的TCP/IP傳輸協議,連接過程的內部原理是這樣的:

  • 首先,在主進程上調用帶有ClusterManager對象的addprocs
  • addprocs調用某種合適的方法在合適的機器上啓動所需數量的Worker。
  • 每個Worker會撥出一個空閒端口,把自己的host和端口信息寫入stdout
  • 集羣管理器讀取每個Worker的stdout並傳給主進程。
  • 主進程解析信息並設置Worker的TCP/IP連接。
  • 集羣裏的每個進程都會被告知其他進程的連接信息。
  • 每個進程都與PID更小的所有進程連接,照此方式組成一個兩兩聯通的網絡。

這些過程都是隱式的,用戶真正要做的顯式操作就是addprocs(參數),分爲三種情況:

  • 假如參數爲整數n,那麼它會構建一個有n個Worker的單機集羣。爲空則默認n=Sys.CPU_THREADS,此時總進程數等於CPU邏輯線程數+1。
  • 假如參數爲hostname::Array,即各機器的hostname的數組,那麼它會構建一個多機集羣。在官方的標準庫文檔裏,又寫作addprocs(machines),其中machines是一個由”機器參數“組成的向量,每個機器參數對應啓動一臺機器。機器參數的格式爲:字符串machine_spec或元組(machine_spec,count)。具體地,字符串machine_spec=[user@]hostname[:port] [bind_addr[:port]],其中[]表示可省略。hostname是唯一必寫的,user默認爲當前用戶,port默認爲標準SSH端口,它們共同提供了主進程和目標Worker之間的連接信息。假如寫了bind_addr[:port],那麼其他Worker將可以通過bind_addr[:port]連接到這個Worker上,也就是說這是一個自定義的額外地址和端口。元組參數的第二個元素count是整數,表示要在該機器上創建幾個Worker。如果令count=:auto(注意冒號),那麼會創建等於該機器CPU邏輯線程總數的Worker。
  • 假如參數爲manager::ClusterManager,那麼會創建一個自定義的集羣。這裏的manager::ClusterManager是一個自定義的ClusterManager。官方文檔中給的例子是:擴展包ClusterManagers.jl中通過一個自定義ClusterManager構造了一個所謂的“Beowulf集羣”。具體怎麼自定義恐怕要去翻翻源代碼。

小貼士:可以在Windows或Linux的終端中輸入hostname命令查看該機器的hostname。

最後,創建好集羣后就可以按照前幾節講的命令操作各進程了,無論單機或多機。

還有個--machinefile命令,用於在啓動Julia REPL時連接各機器。用法爲在終端輸入:

julia --machinefile 文件路徑
# 或簡寫爲
julia -m 文件路徑

其中文件路徑是指向一個自定義文件的路徑。這個文件由自己創建,內容是每臺機器的hostname或IP地址,一行只寫一臺機器。例如在julia可執行文件的目錄裏放一個名爲machinefile的文件然後:

julia --m ~/machinefile

其中machinefile文件的內容是兩臺電腦的hostname:

host1
host2

現在我們有了兩種方式創建多機集羣:addprocs(machines)--machinefile。我讀到一篇博客在吐槽後一種方式,觀點大概是醬紫:

--machinefile在連接機器後自動在每臺機器上創建等於CPU邏輯線程數的Worker。它允許額外添加一些自定義信息,但自由度不高,比如不能自定義網絡拓撲和Julia可執行文件的位置。如果想完整地控制集羣創建過程,應使用addprocs(machines)。具體做法是:

  1. 首先創建一個startupfile.jl文件,把addprocs(machines)寫在文件裏。
  2. 在終端中julia -L startupfile.jl。它會在Julia REPL啓動時立即執行startupfile.jl

與每次手動 addprocs()或把addprocs()寫在代碼開頭相比,這種方法在有多個程序要並行時顯然方便得多。在startupfile.jl裏我們還可以精細地設定集羣參數。以下是詳細的演示:

假定有一個集羣包含4個服務器。除了主服務器外,另外3臺遠程服務器分別爲:

  • host1,24核(實際上是邏輯線程,以下用“核”代指“邏輯線程”。)
  • host2,12核
  • host3,8核

machinefile裏寫的是:

host1
host2
host3

假定需要的Worker數量等於核數,那麼直接julia -m ~/machinefile即可。如果你面對的是非常多服務器組成的超級計算機或超大集羣,那麼可設法生成一個machinefile文件。具體生成辦法請諮詢MPI用戶。

但是,如果你想搭建一個具有:master_slave拓撲的集羣,使得所有Worker只能與主進程通訊而不能互相通訊(在很多集羣中經常要這麼做),可以寫一個startupfile.jl文件,內容爲:

  • 先啓動位於主服務器的Worker,比如addprocs(4)。注意這一步與下一步毫無關係,如果你不需要主服務器上有Worker,甚至可以省略這一步。(當然通常不希望主服務器閒着。)
  • 把3臺遠程服務器加入集羣:
for host in ["host1","host2","host3"]
	addprocs(host;topology=:master_slave)  # 注意分號和冒號
end
# 或者寫作
addprocs(["host1", "host2", "host3"]; topology=:master_slave)

這樣每臺服務器上會自動創建等於核數的Worker。也可以逐個規定Worker數量,只要把hostname改成(hostname,數量)

addprocs([("host1", 24), ("host2", 12), ("host3", 8)]; topology=:master_slave)

如果服務器很多,寫這麼多hostname會很麻煩。可以先把hostname都寫在一個machinefile文件裏,或用MPI用戶的辦法自動導出一個machinefile文件(如果導出的文件裏只有hostname沒有Worker數量,只能自己再補上。不補上就是默認等於核數。),然後逐行讀取:

addprocs(collect(eachline("~/machinefile")); topology=:master_slave)

除此之外,addprocs()還有幾個自定義參數,詳見官方文檔。

總之,用julia -L startupfile.jl方式能精細且方便地定義集羣,值得推薦。

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