MIT 6.824(Spring 2020) Lab1: MapReduce 文件翻譯

努力學習的阿新發表於2022-05-31

首發於公眾號:努力學習的阿新

前言

大家好,這裡是阿新。

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

原文連結:http://nil.csail.mit.edu/6.824/2020/labs/lab-mr.html


引言

本實驗的目標是引導您構建一個 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-BookGit 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.gomain/mrworker.go中,不要修改這些檔案,您應該將您需要實現的程式碼寫在mr/master.gomr/worker.gomr/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.goDone函式內的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.gomr/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.gomr/master.gomr/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 -racego 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

注意:檢查提交網站以確保您的實驗程式碼已經成功提交!

提示:您可能提交了多次。我們將會使用時間戳來檢查您的提交是否是最新的。

相關文章