Lab 1: MapReduce

INnoVation-V2發表於2024-08-25

Lab 1: MapReduce

目標:實現一個MapReduce系統。其中包含:

  1. worker程序:呼叫Map和Reduce程式並處理檔案的讀寫
  2. coordinator程序:負責將任務分發給worker並處理失敗的worker。(注:本Lab使用coordinator而不是論文的master進行管理。)

Getting started

src/main/mrsequential.go中提供了單執行緒版本的MapReduce。每次只啟動一個Map和一個Reduce。還提供了幾個MapReduce應用程式:

  • 單詞計數器:mrapps/wc.go
  • 文字索引器mrapps/indexer.go.

可以使用下列指令執行wordCount:

$ cd ~/6.824
$ cd src/main
$ go build -race -buildmode=plugin ../mrapps/wc.go
$ rm mr-out*
$ go run -race mrsequential.go wc.so pg*.txt
$ more mr-out-0
A 509
ABOUT 2
ACT 8
...

(注意:-race會啟用Go競態檢測器。建議測試程式碼時啟用它)

mrsequential.go 讀取輸入pg-xxx.txt,輸出結果到 mr-out-0

可以借鑑mrsequential.go中的程式碼,同時也看看mrapps/wc.go 。瞭解應用程式如何MapReduce。

任務

實現一個分散式的MapReduce,其由兩種程式組成:coordinatorworker。整個MapReduce將會只有一個coordinator和一個或多個並行執行的worker

現實中,workers會在分佈在不同的機器上執行,但本次Lab將在一臺機器上執行。

  • worker:

    • 透過RPC與coordinator通訊。
    • coordinator請求任務,從一個或多個檔案中讀取輸入,
    • 執行任務,將任務的輸出寫入一個或多個檔案。
  • coordinator

    如果Worker未在設定時間內完成任務(本次實驗為 10 秒),coordinator應將該任務交給其他worker

提供了一些初始程式碼. coordinatorworker 的程式碼分別在main/mrcoordinator.gomain/mrworker.go; 不要更改這些檔案。把你的實現放在 mr/coordinator.go, mr/worker.go, 以及 mr/rpc.go.

下面展示如何執行WordCount。首先,確保wc是最新編譯版本:

$ go build -race -buildmode=plugin ../mrapps/wc.go

main目錄下,執行coordinator

$ rm mr-out*
$ go run -race mrcoordinator.go pg-*.txt

將輸入檔案 pg-*.txt 的名字作為引數傳遞給mrcoordinator.go ;每個檔案對應一個 split, 是一個Map任務的輸入.

在一個或多個視窗執行一些worker:

$ go run -race mrworker.go wc.so

workercoordinator完成後,檢視mr-out-*中的輸出。結果應該和單Worker版本的結果相同。

$ cat mr-out-* | sort | more
A 509
ABOUT 2
ACT 8
...

測試指令碼為main/test-mr.sh。輸入pg-xxx.txt檔案時,測試會檢查wcindexer能否產生正確結果。測試還會檢查是否並行執行Map和Reduce任務,以及能否從執行任務時崩潰的worker中恢復。

現在執行測試,它會掛起,因為coordinator永遠不會結束:

$ cd ~/6.824/src/main
$ bash test-mr.sh
*** Starting wc test.

可將mr/coordinator.goDone函式中的ret:= false改為true。這樣coordinator會立即退出:

$ bash 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
$

測試指令碼沒有在mr-out-X中看到結果,每個檔案對應一個reduce任務。當前mr/coordinator.gomr/worker.go沒有實現,因此不會生成這些檔案,測試失敗。

程式碼正確實現時,測試指令碼的輸出應該像這樣:

$ bash 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

忽略這些資訊;將coordinator註冊為RPC伺服器,檢查它的所有方法是否適合RPCS(有3個輸入);我們知道Done不是透過RPC呼叫的。

一些規則

  • Map Worker:

    • 將輸入劃分到各個桶中,以供nReduce個reduce任務使用
    • nReduce指reduce worker個數(由main/mrcoordinator.go 傳遞給 MakeCoordinator()),每個map worker都應建立nReduce箇中間檔案,供reduce使用。
  • worker應該把第X個reduce的輸出放在mr-out-X中。

  • mr-out-X檔案的每一行對應Reduce函式的一次輸出。這行使用Go %v %v 格式列印生成,分別是Key和Value。在main/mrsequential.go中檢視註釋為this is the correct format的行。如果您的輸出與這種格式偏離太多,測試指令碼將會失敗。

  • 你可以修改 mr/worker.go, mr/coordinator.go, 以及 mr/rpc.go. 你可以臨時修改其他檔案進行測試,但要確保你的程式碼能夠與原始版本相容;我們將使用原始版本進行測試。

  • worker應該把Map的中間輸出放在當前目錄下的檔案中,之後將這些檔案作為輸入傳遞給Reduce任務。

  • main/mrcoordinator.go 希望 mr/coordinator.go 實現 Done() 方法,當MapReduce任務完成時,返回true; 之後,mrcoordinator.go 會退出.

  • 作業完成時,worker應該退出. 一種簡單的實現是使用call()的返回值,如果worker未能聯絡到coordinator,可以預設coordinator已退出。也可由coordinator向worker傳送please exit指令。

提示

  • 指南頁面提供有一些關於開發和除錯的提示

  • 一種開始的方法是修改 mr/worker.goWorker() 傳送一個RPC到coordinator 請求任務.,coordinator返回尚未處理的檔名。之後修改worker以讀取該檔案並呼叫應用程式Map函式, 如mrsequential.go.

  • 應用程式的Map和Reduce函式在執行時使用Go外掛包,從以.so結尾的檔案中載入。

  • 如果你修改了' mr/ '目錄中的任何內容,你可能需要重新構建你使用的MapReduce外掛,比如go build -race -buildmode=plugin ../mrapps/wc.go

  • 這個LAB依賴於工作人員共享一個檔案系統。當所有工作程式執行在同一臺機器上時,這很簡單,但如果工作程式執行在不同的機器上,則需要像GFS這樣的全域性檔案系統。

  • 中間檔案的命名約定是mr-X-Y,其中X是Map任務號,Y是reduce任務號。

  • worker的map任務程式碼需要在檔案中儲存中間鍵值對,並且保證reduce任務可以正確讀取。一種解決方案是使用Go's encoding/json 包。將JSON格式的鍵值對寫入開啟的檔案:

    enc := json.NewEncoder(file)
    for _, kv := ... {
    	err := enc.Encode(&kv)
    

    之後這樣將檔案讀出:

    dec := json.NewDecoder(file)
    for {
    var kv KeyValue
    if err := dec.Decode(&kv); err != nil {
    	break
    }
    kva = append(kva, kv)
    }
    
  • worker的map可以使用ihash(key)函式(在worker.go中)來為給定的Key選擇reduce任務。

  • 你可以參考 mrsequential.go 的程式碼,用於讀取Map輸入檔案, 在Map和Reduce之間排序中間K\V對,以及將Reduce輸出儲存在檔案中。

  • 作為RPC伺服器,coordinator將是併發的;不要忘記給共享資料加鎖。

  • 使用Go的競態檢測器, 使用 go build -racego run -race. test-mr.sh 預設使用競爭檢測器執行測試。

  • Workers 有時需要等待, 例如,直到最後一個map完成,reduce才能啟動. 一種解決方案是worker定期向coordinator請求工作, 在每次請求之間使用time.Sleep()休眠. 另一種方案是coordinator中相關的RPC處理程式有一個等待迴圈, 可以使用time.Sleep()sync.Cond.Go在自己的執行緒中為每個RPC執行處理程式, 因此一個正在等待的處理程式不會阻止coordinator處理其他RPC。

  • coordinator無法準確地區分崩潰的、還活著但由於某種原因停止工作的、以及正在執行但速度太慢而無法發揮作用的worker。你能做的最好的事情是讓協調器等待一段時間,然後放棄並重新將任務傳送給另一個worker。對於這個LAB,讓協調者等待十秒鐘了;十秒內未完成,coordinator就可假定worker已經死亡(當然,它也可能沒有死亡)。

  • 如果你選擇實現備份任務(Backup Tasks)(論文第3.6節),請注意,我們測試了你的程式碼在worker執行任務沒有崩潰時不會排程多餘的任務。備份任務應該只安排在一段相對較長的時間之後(例如10秒)。

  • 要測試崩潰恢復,你可以使用mrapps/crash.go外掛。它隨機地存在於Map和Reduce函式中。

  • 為了確保沒有人在崩潰的情況下觀察到部分寫入的檔案,那篇關於MapReduce的論文提到了使用臨時檔案的技巧,在檔案寫入完成後原子性地重新命名它。你可以使用ioutil.TempFile建立一個臨時檔案,並使用os.Rename對其進行原子重新命名。

  • test-mr.sh執行子目錄mr-tmp中的所有程序,所以如果出現問題後你想檢視中間檔案或輸出檔案,請檢視那裡。你可以在測試失敗後臨時將test-mr.sh修改為exit,這樣指令碼就不會繼續測試(並覆蓋輸出檔案)。

  • test-mr-many.sh提供了一個帶有超時功能的執行test-mr.sh的基本指令碼(這也是我們之後測試程式碼的方式)。它接受執行測試的次數作為引數。你不應該並行執行多個test-mr.sh例項,因為協調器將重用相同的socket,從而導致衝突。

  • Go RPC只傳送以大寫字母開頭的結構體欄位。子結構的欄位名也必須是大寫。

  • 當將一個reply結構體的指標傳遞給RPC系統時,*reply指向的物件應該是零分配的。RPC呼叫的程式碼應該是這樣的

      reply := SomeType{}
      call(..., &reply)
    

    在呼叫前不設定任何回覆欄位,如果不遵循這個規定,RPC 系統可能會返回不正確的值。

相關文章