6.824 MapReduce lab總結
先總結一下架構:
首先是worker向master申請任務,master分配任務Task給worker,並且開啟一個 go 協程對Task的狀態進行監控,當時間超過10s則由master來將原來的Task重新放進maptasks(一個存放Task的chan),這裡master不會理會worker是crash了,還是某些原因導致執行速度很慢。當worke完成任務後則告訴master自己完成了任務,並且由master來更改相應的資料結構來表示該任務完成。
worker函式其實就是一個無限迴圈來取向master申請任務並做任務,因為worker需要在完成任務後繼續申請任務並做任務。
所以Task結構需要維護一個Status,來表示worker該做map任務還是reduce任務,又或者是此時已經沒有未分配 的任務,但是已分配的任務在其他worker還沒有完成,此時worker向master申請任務時則需要分配一個Status為waiting的任務,worker收到之後就sleep一段時間,然後繼續迴圈申請任務。
type Task struct {
Phase int
Status TaskStatus
MapT MapTask
ReduceT ReduceTask
}
type MapTask struct {
// BaseTask
FileName string // 讀取原始檔案時需要的
TaskID int // 這個id在輸出檔案時需要用到
NReduces int // 這個是為了在輸出檔案時將 ihash(key) % NReduces時會使用到
// Status TaskStatus // 狀態資訊, worker在for迴圈中通過狀態資訊來得知自己要做什麼任務
}
type ReduceTask struct {
// BaseTask
TaskID int // 只需要TaskID而不需要filename, 原因是因為我們輸出時會按約定好的格式來輸出檔案
NMaps int // mr-X-TaskID, 我們需要讀取的檔案格式,其中 0<= X < NMaps
// 這麼處理就不用去存中間檔案的名字,因為每一個ReduceTask都會至多NMaps個檔案,要維護一個[]string,寫起程式碼挺繁瑣的
// Status TaskStatus // 狀態資訊.
}
MapTask需要維護一個filename,因為該filename是一開始輸入的檔案,我們不知道這些檔案的名字,所以給每一個MapTask分配一個filename。
但是在ReduceTask的時候就不需要在維護檔名了,因為我們已經約定好了檔名為 mr-map任務ID-ihash後的reduceID
因此我們的reduceID只需要知道有幾個map任務(nMaps),則worker在做reduce任務的時候只需要讀取 mr - [0, nMaps) - ReduceID 共nMaps個檔案則可以
監控是否超時的工作應該由master來做,一開始我讓worker來做這個任務,最後執行測試時的列印語句表明根本沒有在進行監控。換句話說,如果考慮在不同的機器執行worker,當其中的worker機器因為斷電等原因崩潰時,你開的監控執行緒也沒有了,所以監控應該讓master來做
在生成任務,報告成功,報告失敗等地方新增一個列印語句能讓你在後續執行測試的時候能知道工作的流程,知道哪些worker被測試crash了,知道哪些任務成功退出
更好的資料結構能讓程式碼變得更整潔,一開始我還想著把map輸出的中間檔名回傳,但就像我上面說的,其實沒有這個必要的
坑: 在rpc的時候不要重複使用一個變數,每次呼叫時都重新定義一個變數。不然會出現你意想不到的值
怎麼表示任務完成:我的做法是維護了一個 map[int]bool, 來表示是否完成,一開始我只想用 num == nMaps or nReduces的時候來表示該換成下一個階段,但是後面考慮到如果一個任務超時但是沒有崩潰,只是完成時間晚了一點,那麼這個超時完成任務的worker也會報告成功,但是此時這個任務由於超時已經被分配給另一個worker,也就是說最後會一個任務被兩個worker報告成功,會導致num被加了兩次,所以這裡單純的用num來表示就有點不合理了。
我沒有處理的情況: 可能會有新舊worker完成任務後導致舊檔案被新檔案覆蓋的情況,也許可以 rename的時候再去rpc來查一下該任務是否完成,如果已經完成,則不rename,而是把該臨時檔案刪除。
一定要記得加鎖