Lab 1: MapReduce
目標:實現一個MapReduce系統。其中包含:
worker
程序:呼叫Map和Reduce程式並處理檔案的讀寫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
,其由兩種程式組成:coordinator
和worker
。整個MapReduce將會只有一個coordinator
和一個或多個並行執行的worker
。
現實中,workers
會在分佈在不同的機器上執行,但本次Lab將在一臺機器上執行。
-
worker:
- 透過RPC與
coordinator
通訊。 - 向
coordinator
請求任務,從一個或多個檔案中讀取輸入, - 執行任務,將任務的輸出寫入一個或多個檔案。
- 透過RPC與
-
coordinator
:如果Worker未在設定時間內完成任務(本次實驗為 10 秒),
coordinator
應將該任務交給其他worker
。
提供了一些初始程式碼. coordinator
和 worker
的程式碼分別在main/mrcoordinator.go
和 main/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
當worker
和coordinator
完成後,檢視mr-out-*
中的輸出。結果應該和單Worker版本的結果相同。
$ cat mr-out-* | sort | more
A 509
ABOUT 2
ACT 8
...
測試指令碼為main/test-mr.sh
。輸入pg-xxx.txt
檔案時,測試會檢查wc
和indexer
能否產生正確結果。測試還會檢查是否並行執行Map和Reduce任務,以及能否從執行任務時崩潰的worker
中恢復。
現在執行測試,它會掛起,因為coordinator
永遠不會結束:
$ cd ~/6.824/src/main
$ bash test-mr.sh
*** Starting wc test.
可將mr/coordinator.go
的Done函式
中的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.go
和mr/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.go
的Worker()
傳送一個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 -race
和go 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 系統可能會返回不正確的值。