6.5840 Lab4

Task

本次实验中,需要在 Lab2 中实现的single server multiple clients 的 KV storage,和 Lab3 实现的 Raft 协议的基础上,构建一个mutiple servers multiple clients 的 KV storage,并且要能支持节点崩溃重启、快照等功能。数据库分区的功能将在 Lab5 进行实现。

本次实验需要修改 /src/kvraft 下的 client.go, server.go, common.go

Implementation

PartA

  • 服务器节点收到请求后,不是直接执行对应的操作,而是先把这个请求送往 Raft 层,即 kv.rf.Start(op)。待 Raft 层对这个 op 取得一致后(已发往大多数节点),当前服务器才能真正执行这个请求的操作。为此,需要开启一个协程 applyTicker,不断轮询通道 kv.applyCh,检查是否还有尚未应用的、取得共识的命令。

  • 一个重要的优化思路:client 应该维护上一次成功发送请求的 leader 的索引,下一次请求时优先发往这个节点,以减少不必要的轮询。

  • 使用 sync.Map 避免手动的锁操作。

    在 PartB 中不能直接将 sync.Map 直接序列化,因为它内部持有了锁。应该先将其中的内容拷贝到一个普通的 map 中,再序列化这个 map。

  • 如何处理冗余的请求?(下面将 Get 请求称为读请求,将 Put/Append 请求称为写请求)

    • 策略 1:每个请求必须附加上一个 UUID 进行标识。节点缓存的 key 是 UUID,value 是这个请求的返回值。如果收到了一个相同 UUID 的请求,将直接返回缓存中的返回值。那么如何清理缓存?目前采用的策略是,客户端确认收到请求的响应后,发送一个特殊的 Get 请求示意服务器删除对应的缓存。不过这种策略不是最佳的,会增加网络负载。

      这种策略可能导致 Snapshot 过大而不能通过 Part B 的一个测试用例。故最终采取了下面一种策略。

    • 策略 2:每个请求再附上一个 <客户端 ID,请求序号 Seq> 的二元组。节点缓存的 key 是这个二元组,value 是这个请求的返回值。对于每个发送过请求的客户端,服务器各为其维护一个 lastSeq 属性,代表最近一次处理的来自该客户端的请求的序号。当收到来自同一个客户端的、Seq <= lastSeq 的 Get 请求时,返回缓存中的返回值;当收到来自同一个客户端的、Seq <= lastSeq 的 Put/Append 请求时,将不对数据库进行任何处理,只是返回一个 OK 即可。那么如何清理缓存?当服务器节点收到来自同一个客户端的、Seq > lastSeq 的请求时,服务器可以确信,对于前面所有请求,这个客户端均已成功收到了响应,此时可以删除所有 seq <= lastSeq 的缓存,并更新 lastSeq = Seq。这种单调增计数器的思想广泛见于多种分布式系统的设计中。为了支持上述算法,必须在 MakeClerk 中初始化客户端的 ID,可以复用 UUID 函数得到一个全局唯一的 ID。

PartB

  • 根据实验要求,maxraftstate 应该与 persister.RaftStateSize() 比较。其中 raftstate 包含:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    func (rf *Raft) persist() {
    // Your code here (PartC).
    w := new(bytes.Buffer)
    e := labgob.NewEncoder(w)
    // the following are raftstate
    e.Encode(rf.CurrentTerm)
    e.Encode(rf.VotedFor)
    e.Encode(rf.Entries)
    e.Encode(rf.LastIncludedIndex)
    e.Encode(rf.LastIncludedTerm)
    raftstate := w.Bytes()
    // PartD, save SnapShots
    rf.persister.Save(raftstate, rf.Snapshots)
    }
    GO

    其中的 rf.Entries 是一个数组,它的大小随着运行时间而膨胀,这就解释了为何要定期检测 raftstate 的大小去调用 rf.Snapshot()

  • k/v server 层为 Raft 层提供的 snapshot 应包含哪些信息?rf.Snapshot 的函数签名如下:

    1
    func (rf *Raft) Snapshot(index int, snapshot []byte)
    GO

    其中,index 必须是在 kv server 中已经 apply 的最大的那条 CommandIndex。snapshot 是 kv server 中的持久化状态。

  • 本节需要实现三个功能

    • 如果一个 applyMsg 的类型是 Snapshot,那么 kvserver 应该读取持久化状态并恢复
    • 在 apply 一个 Command 之后,kvserver 应该检查是否应该 Snapshot
    • kvserver 重启时,应该读取 Snapshot 并修改自身的状态

Result

PartA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
D\C\6\s\kvraft [master] [?⇡]
17:08 HernandoPC ExApricity » go test -run 4A
Test: one client (4A) ...
labgob warning: Decoding into a non-default variable/field Err may not work
... Passed -- 15.2 5 11330 476
Test: ops complete fast enough (4A) ...
... Passed -- 33.0 3 12379 0
Test: many clients (4A) ...
... Passed -- 15.9 5 17956 483
Test: unreliable net, many clients (4A) ...
... Passed -- 16.3 5 5106 424
Test: concurrent append to same key, unreliable (4A) ...
... Passed -- 2.8 3 416 52
Test: progress in majority (4A) ...
... Passed -- 0.5 5 111 2
Test: no progress in minority (4A) ...
... Passed -- 1.1 5 290 3
Test: completion after heal (4A) ...
... Passed -- 1.0 5 154 3
Test: partitions, one client (4A) ...
... Passed -- 22.7 5 24807 408
Test: partitions, many clients (4A) ...
... Passed -- 23.0 5 58421 453
Test: restarts, one client (4A) ...
... Passed -- 19.3 5 27362 470
Test: restarts, many clients (4A) ...
... Passed -- 20.6 5 26981 378
Test: unreliable net, restarts, many clients (4A) ...
... Passed -- 22.7 5 3676 206
Test: restarts, partitions, many clients (4A) ...
... Passed -- 28.2 5 20945 272
Test: unreliable net, restarts, partitions, many clients (4A) ...
... Passed -- 30.2 5 3875 143
Test: unreliable net, restarts, partitions, random keys, many clients (4A) ...
... Passed -- 39.2 7 9502 165
PASS
ok 6.5840/kvraft 292.739s
SUBUNIT

PartB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
hpy@hpy-workdeiMac kvraft % go test -run 4B
Test: InstallSnapshot RPC (4B) ...
labgob warning: Decoding into a non-default variable/field Err may not work
... Passed -- 4.2 3 6902 63
Test: snapshot size is reasonable (4B) ...
... Passed -- 24.9 3 11089 800
Test: ops complete fast enough (4B) ...
... Passed -- 31.1 3 10754 0
Test: restarts, snapshots, one client (4B) ...
... Passed -- 19.2 5 21930 482
Test: restarts, snapshots, many clients (4B) ...
... Passed -- 24.7 5 32506 438
Test: unreliable net, snapshots, many clients (4B) ...
... Passed -- 17.7 5 2034 208
Test: unreliable net, restarts, snapshots, many clients (4B) ...
... Passed -- 22.7 5 2544 190
Test: unreliable net, restarts, partitions, snapshots, many clients (4B) ...
... Passed -- 30.5 5 2830 169
Test: unreliable net, restarts, partitions, snapshots, random keys, many clients (4B) ...
... Passed -- 38.9 7 7108 150
PASS
ok 6.5840/kvraft 214.589s
SUBUNIT

6.5840 Lab4
https://exapricity.tech/6.5840-Lab4.html
作者
Peiyang He
发布于
2024年6月12日
许可协议