分散式系統-實驗-shardkv

weixin_34119545發表於2017-03-01

介紹

基於上個實驗的成果 kvraft,我們將 kv 分成多個部分(shard)以提高效能。怎麼分呢?比如講以 "a" 開頭的 key 分到一個 shard,以 "b" 開頭的 key 分到另一個 shard。如此一來,每個 shard 只需要處理與他相關的 key 的操作,其他 shard 於此同時就可以接受其他的請求,系統就可增加 shard 以提高吞吐量。

我們的整個系統有兩個基本元件:shard master 和 shard group。整個系統有一個 master 和多個 group,master 一個 raft 叢集,每一個 shard group 是由 kvraft 例項構成的叢集。shard master 負責排程,客戶端向 shard master 傳送請求,master 會根據配置(config)來告知客戶端服務這個 key 的是哪個 group。 每個 group 負責部分 shard。

對於各個 group,我們需要實現 group 之間的 shard 資料轉移。因為我們 group 是動態變化的,有新加入的,也有要退出,有時負載均衡需要重新配置各個 group 的 shard。

本次實驗最大的挑戰是如何處理在服務的過程中修改配置檔案,即 group 與 shard 的對應關係。比如說,客戶端此時請求的資料恰好是正在遷移的資料,我們需要確認本次請求是在遷移操作之前,還是之後,如果是之前,請求的資料所對應的新 shard 會收到通知;若是之後,請求只需重試。所以我們 Raft 不要把 Put, Appends, Gets 寫進 log 中,Reconfiguration 也要寫進去。總之,我們要確保每個請求只有一個 group 服務。

我們的程式碼主要在 src/shardmastet 和 src/shardkv 中實現。

Part A: Shard Master

shardmaster 負責管理所有的 configuration 改動,每個 configuration 記錄了所有的 shardgroup 以及 shards 與 group 的對應關係。每當有修改時,master 都會生成一個新的 configuration。

在 shardmaster/common.go 中,描述了 configuration 的對外介面:Join, Leave, Move 和 Query。

  1. Join 主要是用來增加 group。引數是 GID(Grop ID) 及其對應的 server 列表。master 需要重新分配 group 與 shard 的對映關係,當然資料移動的越少,資料分佈的越平均越好,然後建立新的 configuration。

  2. Leave 和 Join 相反,用於刪除一個 group。引數是 GID。 master 需要重新分配 group 與 shard 的對映關係,當然資料移動的越少,資料分佈的越平均越好,然後建立新的 configuration。

  3. Move 的引數是 shard 和 GID,用於將 shard 資料移動到 GID 對應的 group 中去。主要用於測試方便。

  4. Query 用於獲取某個版本的 configuration,引數即為版本號。若引數是 -1 或大於最新的版本號,則返回最新的 configuration,如此時 master 正在執行 Join, Leave 或 Move,則需等操作完成再處理 Query(-1)。

在數值方面,configuration 應該從 0 開始計算,然後自增。shard 的數量應該大於 group 的數量。

別忘了 Go 的 map 變數是引用。

Part B: Sharded 鍵值服務

有了 master,我們就可以開始構建 shard group 了,程式碼主要在 shardkv/client.go,shardkv/common.go,and shardkv/server.go。

我們要實現的 shardkv 其實就是組成一個 group 叢集的一個備份伺服器。一個 group 叢集對外接受 Get,Put 和 Append 請求,但只是部分 shard,例如以 "a" 或 "b" 開頭的 key。使用基礎程式碼中的 key2shard() 方法得到 key 與 shard 之間的對映關係。全部的 group 叢集會服務所有的 shard。master 負責將分配 shard 與 group 的對映關係,當對映關係發生變化後,叢集之間需要轉移資料,以確保 client 的操作結果一致性。

作為一個分散式儲存系統,基本的要求對外看起來和單機沒啥區別即保證客戶端請求的順序執行。Get() 應該看到最新的結果,所有的操作需要在有 master 配置改動的同時,保持正確性。除此之外,我們還要保證可用性,即當有多數 group 正常執行,彼此能溝通且能和 master 叢集通訊時,整個系統依然能對外服務和內部配置自動調整。

一個 shardkv 只屬於一個 group,這個關係在本實驗中假設不會改變的。

基礎程式碼中的 client.go 會將 PRC 傳送給正確的 group,如果 group 響應說你要的 key 不是我負責的,client 會向 master 請求最新的 configuration,然後確定向那個 group 傳送 RPC。我們要做是為每個 RPC 增加 ID,以實現判斷重複請求的邏輯,就像上個實驗 kvraft 一樣。

寫完程式碼測試一下,通過 Challenge 以前的測試用例。

$ cd ~/6.824/src/shardkv
$ go test
Test: static shards ...
  ... Passed
Test: join then leave ...
  ... Passed
Test: snapshots, join, and leave ...
  ... Passed
Test: servers miss configuration changes...
  ... Passed
Test: concurrent puts and configuration changes...
  ... Passed
Test: more concurrent puts and configuration changes...
  ... Passed
Test: unreliable 1...
  ... Passed
Test: unreliable 2...
  ... Passed
Test: shard deletion (challenge 1) ...
  ... Passed
Test: concurrent configuration change and restart (challenge 1)...
  ... Passed
Test: unaffected shard access (challenge 2) ...
  ... Passed
Test: partial migration shard access (challenge 2) ...
  ... Passed
PASS
ok      shardkv 206.132s
$

我們的實現基本不需要主動向 master 傳送 Join 請求,這些邏輯都放在基礎程式碼的測試用例中。

Step 1 從單 group 開始

第一步我們先假設所有的 shard 都分配到一個 group 上,所以程式碼實現和 Lab 3 非常類似。只是要在 group 啟動時,根據 configuration 確定自己的應該接受那些 key 的請求。完成後能通過第一個測試用例。

然後我們要處理 configuration 更改時的邏輯。首先每個 group 都要監聽 configuration 的改變,當其版本更新後,我們要開始進行 shard 遷移。當某個 shard 不在歸該 group 管理時,該 group 應該立刻停止響應與這個 shard 相關的請求,然後開始將這個 shard 的資料遷移到另一個負責管理它的 group 那裡。當一個 group 有了一個新的 shard 所有權時,它需要先等待 shard 的資料完全遷移完成後,才能開始接受這個 shard 相關的請求。

要注意的是在 shard 資料遷移的過程中,要保證所有一個 group 內所有的副本伺服器同時進行資料遷移,然後讓整個 group 對併發請求保持結果溢脂性。

程式碼中應該定期從 shardmaster 中拉取 configuration 資訊。測試用例會測試 100ms 左右時間間隔下,configuration 改變是,邏輯是否依然正確。

伺服器之間需要使用 RPC 的方式進行資料遷移。shardmaster 的 Config 裡面有伺服器的名字,但是我們的 RPC 需要向 labrpc.ClientEnd 傳送資訊。沒事,在呼叫 StartServer() 時,傳入了一個 make_end() 方法,呼叫他可以將伺服器的名字轉化為一個 labrpc.ClientEnd.

當 group 收到請求的資料不在自己管轄範圍內時,應當返回 ErrWrongGroup,但是我們得在 configuration 變化的情況下,做出正確的判斷。

由於我們要能判斷出請求的重複性,所以當一個 shard 從一個 group 轉移到另一個 group 時,不僅要帶有 Key/Value 資料還得帶上一些判斷重複請求的資訊。考慮一下在何種情況下接受新 shard 的 group 需要更新自己狀態,完全覆蓋本地資料一定就是對的嗎?

接著還得考慮伺服器和客戶端如何處理 ErrWrongGroup。收到 ErrWrongGroup 響應的客戶端需要改變自己的 sequence number 嗎?伺服器在給 Get/Put 請求恢復 ErrWrongGroup 的時候需要更新本地對這個客戶端的狀態記錄嗎?

其實當 group 對一個 shard 失去了管理權後,沒必要立即將其刪除,雖然在生產環境下可能會造成空間浪費,但是在我們本次實驗中可以簡化邏輯。

Hint: When group G1 needs a shard from G2 during a configuration change, does it matter at what point during its processing of log entries G2 sends the shard to G1?
You can send an entire map in an RPC request or reply, which may help keep the code for shard transfer simple.

用 RPC 傳送 map 的時候記得複製到一個新的 map 裡面去哦,傳引用就會有問題。

Challenge exercises

For this lab, we have two challenge exercises, both of which are fairly complex beasts, but which are also essential if you were to build a system like this for production use.
Garbage collection of state

When a replica group loses ownership of a shard, that replica group should eliminate the keys that it lost from its database. It is wasteful for it to keep values that it no longer owns, and no longer serves requests for. However, this poses some issues for migration. Say we have two groups, G1 and G2, and there is a new configuration C that moves shard S from G1 to G2. If G1 erases all keys in S from its database when it transitions to C, how does G2 get the data for S when it tries to move to C?

CHALLENGE: Modify your solution so that each replica group will only keep old shards for as long as is absolutely necessary. Bear in mind that your solution must continue to work even if all the servers in a replica group like G1 above crash and are then brought back up. You have completed this challenge if you pass TestChallenge1Delete and TestChallenge1Concurrent.

Hint: gob.Decode will merge into the object that it is given. This means that if you decode, say, a snapshot into a map, any keys that are in the map, but not in the snapshot, will not be deleted.

Client requests during configuration changes

The simplest way to handle configuration changes is to disallow all client operations until the transition has completed. While conceptually simple, this approach is not feasible in production-level systems; it results in long pauses for all clients whenever machines are brought in or taken out. A better solution would be if the system continued serving shards that are not affected by the ongoing configuration change.

CHALLENGE: Modify your solution so that, if some shard S is not affected by a configuration change from C to C', client operations to S should continue to succeed while a replica group is still in the process of transitioning to C'. You have completed this challenge when you pass TestChallenge2Unaffected.

While the optimization above is good, we can still do better. Say that some replica group G3, when transitioning to C, needs shard S1 from G1, and shard S2 from G2. We really want G3 to immediately start serving a shard once it has received the necessary state, even if it is still waiting for some other shards. For example, if G1 is down, G3 should still start serving requests for S2 once it receives the appropriate data from G2, despite the transition to C not yet having completed.

CHALLENGE: Modify your solution so that replica groups start serving shards the moment they are able to, even if a configuration is still ongoing. You have completed this challenge when you pass TestChallenge2Partial.

Handin procedure

Before submitting, please run all the tests one final time. You are responsible for making sure your code works.

Also, note that your Lab 4 sharded server, Lab 4 shard master, and Lab 3 kvraft must all use the same Raft implementation. We will re-run the Lab 2 and Lab 3 tests as part of grading Lab 4.

Before submitting, double check that your solution works with:

$ go test raft/...
$ go test kvraft/...
$ go test shardmaster/...
$ go test shardkv/...

Submit your code via the class's submission website, located at https://6824.scripts.mit.edu:444/submit/handin.py/.

You may use your MIT Certificate or request an API key via email to log in for the first time. Your API key (XXX) is displayed once you logged in, which can be used to upload the lab from the console as follows.

For part A:

$ cd "$GOPATH"
$ echo "XXX" > api.key
$ make lab4a

For part B:

$ cd "$GOPATH"
$ echo "XXX" > api.key
$ make lab4b

相關文章