首發於公眾號:努力學習的阿新
前言
大家好,這裡是阿新。
MIT 6.824 是麻省理工大學開設的一門關於分散式系統的明星課程,共包含四個配套實驗,實驗的含金量很高,十分適合作為校招生的專案經歷,在文章《2022 雙非應屆 CS 碩士校招上岸位元組跳動(校招總結)》中,我也將其推薦給了各位讀者。但由於課程是全英的,實驗說明也是全英的,實驗過程中還需要閱讀相關的英文文獻,很多同學(包括曾經的筆者)受困於英語,對其望而卻步。因此,筆者決定開啟系列文章:MIT 6.824(Spring 2020)實驗文件翻譯。來嘗試為大家翻譯實驗的說明文件和相關論文的關鍵部分,同步更新的還有系列帶做文章,以期能夠幫助各位讀者順利完成該課程實驗。
本文對 MIT 6.824 Lab 1:MapReduce 的說明文件進行了全文翻譯。需要注意的是文中的 job 和 task,其中,job 是指整個 MapReduce 計算,表示的是任務整體,而 task 則是指一次 Map/Reduce 呼叫,表示的是任務區域性,一個完整的 MapReduce job 由一些 Map task 和 Reduce task 組成,筆者無法找到兩個中文詞彙來很好的描述兩者之間的關係,因此並未翻譯。同時,由於筆者水平有限,如有翻譯不恰當的地方還請各位批評指正。
以下為原文翻譯。
作者:MIT 6.824 Staff | 譯者:阿新
依據 CC BY 3.0 US 許可證進行授權
許可證連結:https://creativecommons.org/licenses/by/3.0/us/deed.zh
引言
本實驗的目標是引導您構建一個 MapReduce 系統,您需要實現兩個程式:worker 和 master。其中 worker 程式負責處理檔案讀寫操作以及呼叫 Map/Reduce 函式處理 Task。而 master 程式則負責為 worker 程式分配 Task 並處理崩潰的 worker 程式。您構建的系統與MapReduce 論文中所描述的系統類似。
協作政策
除了我們提供給您的程式碼外,您必須獨立完成實驗要求的程式碼。實現過程中杜絕參考其他同學和前幾年課程實驗的解決方案。允許您與同學討論,但不允許您抄襲他們的程式碼。之所以設立這條規則,是因為我們認為只有獨立完成實驗,您的能力才會得到最大的提升。
請不要公佈您的程式碼,也不要讓它被選修 6.824 的學生通過某種方式獲取到。github.com
中的倉庫許可權預設是公開的,除非您將倉庫許可權設為私有,否則請勿用其儲存實驗程式碼。儲存程式碼可以使用MIT 的 Github,但請確保您建立的是私有倉庫。
軟體
本實驗(以及 6.824 的其他實驗)使用的程式語言是Go語言,如果您不熟悉 Go 語言,其官方網站上有很多教程供您學習。我們將使用 1.13 版本的 Go 語言來評判您的程式碼,因此您也應該使用這一版本(譯者注:筆者所用的 Go 語言版本為 1.17.7,編輯此文章時已經完成了 Lab 2A,無異常)。另外,如果您要檢視計算機中已有的 Go 語言版本,可以執行go version
命令。
我們推薦您在自己的機器上完成實驗,這樣您就可以使用您熟悉的環境,比如工具,文字編輯器等。另外,您也可以在 Athena 上完成實驗。
macOS
您可以使用Homebrew來安裝 Go 語言。安裝好 Homebrew 後,執行brew install go
命令即可。
Linux
根據您使用的 Linux 發行版,您可以從相應的軟體包庫中下載最新版的 Go 語言,比如在 Ubuntu 中可以執行apt install golang
命令來安裝 Go 語言。此外,您還可以從 Go 語言的官方網站中手動下載二進位制包。首先,確保您使用的是 64 位的 Linux 核心(uname -a
會提示"x86_64 GNU/Linux"),然後執行如下命令即可:
$ wget -qO- https://dl.google.com/go/go1.13.6.linux-amd64.tar.gz | sudo tar xz -C /usr/local
如果是國內的同學,可以執行如下命令:
$ wget -qO- https://studygolang.com/dl/golang/go1.13.6.linux-amd64.tar.gz | sudo tar xz -C /usr/local
需要確保/usr/local/bin
在您的環境變數PATH
中。
Windows
實驗可能無法直接執行於 Windows 上。如果您敢於嘗試,可以試試Windows Subsystem for Linux,並執行上述 Linux 命令(譯者注:筆者做實驗用的是 VSCode+WSL2+Golang 1.17.7,需要注意的是一定要用 WSL2,否則無法使用 VSCode 除錯 Go 程式)。否則還是乖乖使用 Athena 吧。
Athena
您可以通過ssh {your kerberos}@athena.dialup.mit.edu
命令登入公共 Athena 主機。一旦登入成功,便可通過如下命令獲取 1.13 版本的 Go 語言:
$ setup ggo
開始
您將使用git(版本控制系統)拉取實驗初始版本程式碼。如果您不熟悉 git,可以通過查閱Git-Book和Git User Manual自行學習。執行以下命令,即可從遠端拉取 6.824 的初始實驗程式碼:
$ git clone git://g.csail.mit.edu/6.824-golabs-2020 6.824
$ cd 6.824
$ ls
Makefile src
$
在src/main/mrsequential.go
中,我們為您實現了一個簡單的順序 MapReduce。由於使用的是單程式,因此該程式同一時刻僅能執行一個 Map/Reduce task。此外,我們還為您提供了多組處理不同 task 的 Map/Reduce 應用程式程式碼:如mrapps/wc.go
中包含單詞計數所需的 Map/Reduce 方法,mrapps/indexr.go
中包含計算文件索引的 Map/Reduce 方法。您可以執行以下命令來執行單詞計數的順序版本 MapReduce:
$ cd ~/6.824
$ cd src/main
$ go build -buildmode=plugin ../mrapps/wc.go
$ rm mr-out*
$ go run mrsequential.go wc.so pg*.txt
$ more mr-out-0
A 509
ABOUT 2
ACT 8
...
mrsequential.go
所需的輸入資料來自名為pg-xxx.txt
的文字檔案,輸出則儲存在檔案mr-out-0
中。
您可以從mrsequential.go
中隨意借鑑您需要的程式碼,同時也可以閱讀mrapps/wc.go
來了解 MapReduce 的應用程式程式碼長啥樣。
您的任務
您的任務是實現一個分散式的 MapReduce 系統,該系統由兩個程式組成:master 和 worker,在系統執行期間共包含一個 master 程式和多個並行執行的 worker 程式。在實際應用的 MapReduce 系統中,worker 程式執行在很多不同的機器上,而在本實驗中,您只需要在一臺機器上執行它們即可。worker 程式和 master 程式之間使用 RPC 通訊。每個 worker 程式需完成以下工作:向 master 程式請求 task,從若干檔案中讀取輸入資料,執行 task,並將 task 的輸出寫入到若干檔案中。master 程式除了為 worker 程式分配 task 外,還需要檢查在一定時間內(本實驗中為 10 秒)每個 worker 程式是否完成了相應的 task,如果未完成的話則將該 task 轉交給其他 worker 程式。
我們為您提供了一些上下文程式碼。master 程式和 worker 程式的 main 函式入口分別位於main/mrmaster.go
和main/mrworker.go
中,不要修改這些檔案,您應該將您需要實現的程式碼寫在mr/master.go
,mr/worker.go
和mr/rpc.go
中。
下面說明如何執行您寫的程式碼,以單詞計數應用程式為例。首先,執行以下命令以確保被構建的單詞計數外掛是最新版:
$ go build -buildmode=plugin ../mrapps/wc.go
cd 到src/main/
中,執行 master 程式:
$ rm mr-out*
$ go run mrmaster.go pg-*.txt
其中pg-*.txt
引數是檔案路徑,這些檔案中儲存著 master 程式的輸入資料;每個檔案都可以作為單個 Map task 的輸入。
在其他若干視窗中,您可以執行如下命令來執行一個或多個 worker 程式:
$ go run mrworker.go wc.so
當 worker 程式和 master 程式執行完畢後,可以顯示 mr-out-*中的內容以檢視程式輸出。如果您的實驗程式碼沒有錯誤的話,經過排序後的輸出結果應與mrsequential.go
的輸出相同,如下所示:
$ cat mr-out-* | sort | more
A 509
ABOUT 2
ACT 8
...
main/test-mr.sh
是我們為您提供的測試指令碼。當給定pg-xxx.txt
作為輸入時,該指令碼會檢查您實現的 MapReduce 系統對兩個不同型別的 Job(單詞計數和計算文件索引)是否產生了正確的輸出。該指令碼也會檢查您實現的 MapReduce 系統的 worker 程式在處理 Map task 和 Reduce task 時是否是並行執行的,以及您的系統是否能正確處理發生崩潰的 worker 程式。
如果您現在執行測試指令碼,它將會掛起,因為 master 並沒有被實現:
$ cd ~/6.824/src/main
$ sh test-mr.sh
*** Starting wc test.
您可以將mr/master.go
中Done
函式內的ret := false
語句修改為 true 來使主程式立刻退出,您將會得到如下輸出:
$ sh ./test-mr.sh
*** Starting wc test.
sort: No such file or directory
cmp: EOF on mr-wc-all
--- wc output is not the same as mr-correct-wc.txt
--- wc test: FAIL
$
測試指令碼期望對於每個 reduce task,都會生成一個被命名為mr-out-X
的輸出檔案。空的mr/master.go
和mr/worker.go
不會生成這些檔案(也少做了很多其他事情),進而導致測試失敗。
當將全部程式碼實現完畢後,執行測試指令碼後的輸出應該看起來像這樣:
$ sh ./test-mr.sh
*** Starting wc test.
--- wc test: PASS
*** Starting indexer test.
--- indexer test: PASS
*** Starting map parallelism test.
--- map parallelism test: PASS
*** Starting reduce parallelism test.
--- reduce parallelism test: PASS
*** Starting crash test.
--- crash test: PASS
*** PASSED ALL TESTS
$
您也會看到一些來自 Go RPC 包輸出的錯誤,看起來像這樣:
2019/12/16 13:27:09 rpc.Register: method "Done" has 1 input parameters; needs exactly three
忽略這些資訊即可。
一些規則:
- map 階段應將中間鍵分割為不同的桶(譯者注:中間鍵指的是 MapReduce 論文中的 intermediate key,這句話的意思是將 map Task 的輸出以某種形式儲存為 reduce 函式能夠讀取的輸入),方便後續
nReduce
個 reduce task 讀取,而nReduce
則是main/mrmaster.go
傳遞給MakeMaster
的引數; - worker 程式應把第 X 個 reduce task 的輸出儲存到檔案
mr-out-X
中; mr-out-X
中每行都應該是呼叫一次 Reduce 函式的輸出,應該按照 Go 語言的"%v %v"
的格式生成,也即 key 和 value,如在main/mrsequential.go
中註釋"this is the correct format"的位置所示。如果您的實現和這一格式相差太多,測試指令碼將會執行失敗;- 您需要修改的檔案為:
mr/worker.go
,mr/master.go
和mr/rpc.go
。儘管您可以暫時地修改其他檔案來輔助您測試,但請確保其他檔案被還原為初始狀態(原始版本)後您的程式仍能正常工作,我們將會使用原始版本的程式碼進行評判; - woker 程式應該將 Map 函式的輸出(intermediate key)儲存在當前目錄的檔案中,使得後續 worker 程式可以讀取它們並將其作為 Reduce task 的輸入;
- 當 MapReduce Job 被計算完畢後,
main/mrmaster.go
希望您實現的mr/master.go
中的Done()
方法會返回 true。這樣mrmaster.go
就能知道 Job 已經順利完成,程式即可退出; - 當 MapReduce job 被做完後,worker 程式就應該退出。實現這一功能的一種笨方法就是令 woker 程式檢查
call()
函式的返回值:如果 woker 程式無法和 master 程式通訊,那麼 worker 程式就可以認為整個 Job 已經全被做完了,自己也就可以退出了。當然是否這麼做還是要取決於您的設計,您也可以設計一個”please exit“的偽任務,當 worker 程式收到這一任務,就自動退出(譯者注:筆者就是這麼做的,看起來更優雅一些)。
提示
-
如果您覺得無從下手,可以從修改
mr/worker.go
中的Worker()
函式開始,在函式中首先實現以下邏輯:向 master 傳送 RPC 來請求 task。然後修改 master:將檔名作為尚未開始的 map task 響應給 worker。然後修改 worker:讀取檔案並像mrsequential.go
程式一樣,呼叫 Map 方法來處理讀取的數; -
MapReduce 應用程式的 Map/Reduce 函式被儲存在以
.so
結尾的檔案中,在執行時使用 Go plugin 包讀取; -
如果您改變了
mr/
資料夾中的檔案,並且該檔案也被用到,那您需要將該檔案使用類似於go build -buildmode=plugin ../mrapps/wc.go
的命令重新編譯成 MapReduce 外掛; -
本實驗要求 worker 程式使用同一個檔案系統,因此所有的 worker 程式必須執行於一臺機器上。如果想要讓 worker 程式執行在不同的機器上,那您需要為他們提供一個全域性的檔案系統,比如 GFS;
-
一個命名中間檔案的合理形式是
mr-X-Y
,其中 X 是 Map task 的編號,Y 是 Reduce task 編號; -
worker 程式處理 Map task 的程式碼中需要一種將中間鍵值對儲存為檔案的方式,也需要一種在處理 Reduce task 時能從檔案中正確讀回鍵值對的方式。一種可能的實現是使用 Go 語言的
encoding/json
包。將鍵值對寫入 JSON 檔案的程式碼如下:enc := json.NewEncoder(file) for _, kv := ... { err := enc.Encode(&kv)
從 JSON 檔案中讀回鍵值對的程式碼如下:
dec := json.NewDecoder(file) for { var kv KeyValue if err := dec.Decode(&kv); err != nil { break } kva = append(kva, kv) }
-
在 worker 中處理 map Task 的部分,對於一個給定鍵,您可以使用
ihash(key)
函式(在worker.go
中)來選擇它屬於哪一個 reduce task; -
您可以將
mrsequential.go
中一些功能的程式碼拿來直接用,比如:讀取 map task 的輸入檔案,在呼叫 Map 方法和呼叫 Reduce 方法之間對中間鍵值對進行排序,以及儲存 Reduce 函式的輸出到檔案。 -
作為一個 RPC 伺服器,master 程式將是併發的,因此不要忘記給共享資源加鎖。
-
可以執行命令
go build -race
和go run -race
來使用 Go 語言的 race detector,在test.sh
中有一條註釋為您展示如何為測試開啟 race detector; -
worker 程式有時需要等待,比如在最後一個 map task 處理完之前,worker 不能開始對 reduce task 的處理。實現這一功能的一種可能方案是 worker 程式週期性的向 master 請求 task,在每次請求間使用
time.Sleep()
阻塞一段時間。另一種可能的方案是 master 在收到 rpc 請求後額外開啟一個 RPC 處理執行緒,在這個執行緒中執行迴圈等待(也可以使用time.Sleep()
和sync.Cond
),這樣使得阻塞的 RPC 不會影響 master 響應其他 RPC; -
master 程式無法可靠的區分崩潰的 worker 程式、活著但因某些原因停止執行的 worker 程式和正在執行但太慢導致無法使用的 worker 程式。這一問題最好的解決方案是令 master 等待一段時間,如果某個 worker 程式在這段時間(在本實驗中,這段時間被設定為 10 秒)內沒有完成相應的 task,就放棄繼續等待並將該 task 重新分配給其他 worker。之後,master 應該假設這個 worker 程式已經死亡了(當然,它可能還活著);
-
為了測試容錯,您可以使用
mrapps/crash.go
外掛,它在 Map 和 Reduce 函式中增加了隨機退出功能; -
為了確保沒人能看到被崩潰程式寫了一半的檔案,MapReduce 論文提到了一個小技巧,那就是使用臨時檔案,一旦該檔案完全寫完,就自動重新命名。您可以使用
ioutil.TempFile
建立臨時檔案,並使用os.Rename
去自動重新命名它。 -
test-mr.sh
將會在子目錄mr-tmp
中執行所有的程式,因此如果有錯誤發生並且您想檢視中間輸出檔案的話,可以檢視這一目錄中的檔案。
提交步驟
注意:在正式提交前,請執行
test-mr.sh
。
使用命令make lab1
命令打包您的實驗程式碼並將其上傳到班級的提交網站,其網址為:https://6824.scripts.mit.edu/2020/handin.py/。
第一次提交時您可能需要通過以下方式之一進行登入:1. 使用您的 MIT 證書;2. 通過 email 申請一個 API key。一旦登入成功,您的 API key(XXX
)將會被顯示,在控制檯中輸入以下命令上傳 lab1 時會用到這一 API key:
$ cd ~/6.824
$ echo XXX > api.key
$ make lab1
注意:檢查提交網站以確保您的實驗程式碼已經成功提交!
提示:您可能提交了多次。我們將會使用時間戳來檢查您的提交是否是最新的。