MIT6.824 Lab2 Raft(3)
2016-10-24 21:42
429 查看
最后一部分介绍一下测试代码中的测试函数。
紧接着上次,现在是TestFailNoAgree函数测试在节点Fail太多的情况下无法达成一致性。
通过one函数中打印函数,我们可以查看不同情况下当前系统的Leader(Leader=1)。在TestFailNoAgree函数中,先调用one函数检查一致性(index=1),然后断开3个Follwer节点,此时客户端发出的请求(index=2),raft系统中只剩下2个节点,所以Append Entry同意的节点数少于majority,该序号日志项应该无法committed。紧接着重新将断开连接的3个节点加入网络,由于断开节点数超过majority,所以此时系统可能重新选举Leader,调用checkOneLeader函数发现Leader=2,所以原先index=2的请求就会被抛弃。然后客户端发出请求(index=2),返回的序号应该为2。
TestConcurrentStarts函数测试在一个Term中并发请求的一致性。
先新建1个有3个节点的raft系统,调用checkOneLeader函数来获得raft系统当前的leader序号,调用Start函数给该节点发送命令(Index=1),然后新建1个可容纳5个元素的Channel,创建5个goroutine去并发地发送命令,这里使用sync包中的WaitGroup来保证同步。在每个goroutine发送命令完后检查是否在先前的Term中,如果命令成功则写入序号(这里Index为2-6)。等5个goroutine都结束后判断先前的操作是否在同一Term中,如果不是则重头再来,主要是保证高并发RPC。然后从先前的Channel中取出序号,这里通过wait函数来等待相应序号的命令日志被raft系统所有节点提交。如果Term改变了比如系统出现错误,那么清除Channel中的命令。最后比价从Channel中获得的命令是否与先前发送请求的命令相同,即命令是否被有序的提交了。
TestRejoin函数测试当出现网络分区时的一致性。
先新建1个有3个节点的raft系统,调用one函数来发送命令请求(Index=1,cmd=101,Term=1)并完成提交。然后断开leader节点与网络的连接,此时形成2个网络分区,即Leader节(n1)点和2个Follwer节点(n2,n3)。向n1节点发送3个命令(Term=1),但是无法获得提交,因为该网络分区的节点数目少于Majority,那么此时n1节点的Index=4,Term=1。原先的2个Follwer节点重新选举,向新Leader节点(n2)发送命令请求(Index=2,cmd=103,Term=2)并提交,然后断开n2节点与网络的连接,将n1节点重新纳入网络中。由于n1节点的Term比n3节点的Term小,所以n3节点会成为Leader,n1节点上不同的Log会被抛弃。然后调用one函数再发送命令请求(Index=3,cmd=104,Term=3)并完成提交。最后将n2节点重新加入网络中,n3节点仍然是Leader节点,会追加日志到n2节点。最后调用one函数来发送命令请求(Index=4,cmd=105,Term=3)。
具体变化过程是(Term值不一定与实际符合):
在调用one(101)后Leader为N1
N1:Term=1,log为{101}
N2:Term=1,log为{101}
N3:Term=1,log为{101}
当断开N1并调用start(102)、start(103)、start(104)后
N1:Term=1,log为{101、102、103、104}
N2:Term=1,log为{101}
N3:Term=1,log为{101}
当调用one(103)后Leader为N2
N1:Term=1,log为{101、102、103、104}
N2:Term=2,log为{101、103}
N3:Term=2,log为{101、103}
当断开N2并连接N1并调用one(104)后Leader为N3,这里Term值可能为3或者4,因为当断开N2连接N1之前,N3可能超时
N1:Term=3,log为{101、103、104}
N2:Term=3,log为{101、103、104}
N3:Term=2,log为{101、103}
当连接N2并调用one(105)后Leader仍为N3
N1:Term=3,log为{101、103、104、105}
N2:Term=3,log为{101、103、104、105}
N3:Term=3,log为{101、103、104、105}
之后的测试分析根据上面流程即可,等以后有空再补上。。。
紧接着上次,现在是TestFailNoAgree函数测试在节点Fail太多的情况下无法达成一致性。
func TestFailNoAgree(t *testing.T) { servers := 5 cfg := make_config(t, servers, false) defer cfg.cleanup() fmt.Printf("Test: no agreement if too many followers fail ...\n") cfg.one(10, servers) // 3 of 5 followers disconnect leader := cfg.checkOneLeader() cfg.disconnect((leader + 1) % servers) cfg.disconnect((leader + 2) % servers) cfg.disconnect((leader + 3) % servers) index, _, ok := cfg.rafts[leader].Start(20) if ok != true { t.Fatalf("leader rejected Start()") } if index != 2 { t.Fatalf("expected index 2, got %v", index) } time.Sleep(2 * RaftElectionTimeout) n, _ := cfg.nCommitted(index) if n > 0 { t.Fatalf("%v committed but no majority", n) } // repair failures cfg.connect((leader + 1) % servers) cfg.connect((leader + 2) % servers) cfg.connect((leader + 3) % servers) // the disconnected majority may have chosen a leader from // among their own ranks, forgetting index 2. // or perhaps leader2 := cfg.checkOneLeader() index2, _, ok2 := cfg.rafts[leader2].Start(30) if ok2 == false { t.Fatalf("leader2 rejected Start()") } if index2 < 2 || index2 > 3 { t.Fatalf("unexpected index %v", index2) } cfg.one(1000, servers) fmt.Printf(" ... Passed\n") }
通过one函数中打印函数,我们可以查看不同情况下当前系统的Leader(Leader=1)。在TestFailNoAgree函数中,先调用one函数检查一致性(index=1),然后断开3个Follwer节点,此时客户端发出的请求(index=2),raft系统中只剩下2个节点,所以Append Entry同意的节点数少于majority,该序号日志项应该无法committed。紧接着重新将断开连接的3个节点加入网络,由于断开节点数超过majority,所以此时系统可能重新选举Leader,调用checkOneLeader函数发现Leader=2,所以原先index=2的请求就会被抛弃。然后客户端发出请求(index=2),返回的序号应该为2。
TestConcurrentStarts函数测试在一个Term中并发请求的一致性。
func TestConcurrentStarts(t *testing.T) { servers := 3 cfg := make_config(t, servers, false) defer cfg.cleanup() fmt.Printf("Test: concurrent Start()s ...\n") var success bool loop: for try := 0; try < 5; try++ { if try > 0 { // give solution some time to settle time.Sleep(3 * time.Second) } leader := cfg.checkOneLeader() _, term, ok := cfg.rafts[leader].Start(1) if !ok { // leader moved on really quickly continue } iters := 5 var wg sync.WaitGroup is := make(chan int, iters) for ii := 0; ii < iters; ii++ { wg.Add(1) go func(i int) { defer wg.Done() i, term1, ok := cfg.rafts[leader].Start(100 + i) if term1 != term { return } if ok != true { return } is <- i }(ii) } wg.Wait() close(is) for j := 0; j < servers; j++ { if t, _ := cfg.rafts[j].GetState(); t != term { // term changed -- can't expect low RPC counts continue loop } } failed := false cmds := []int{} for index := range is { cmd := cfg.wait(index, servers, term) if ix, ok := cmd.(int); ok { if ix == -1 { // peers have moved on to later terms // so we can't expect all Start()s to // have succeeded failed = true break } cmds = append(cmds, ix) } else { t.Fatalf("value %v is not an int", cmd) } } if failed { // avoid leaking goroutines go func() { for range is { } }() continue } for ii := 0; ii < iters; ii++ { x := 100 + ii ok := false for j := 0; j < len(cmds); j++ { if cmds[j] == x { ok = true } } if ok == false { t.Fatalf("cmd %v missing in %v", x, cmds) } } success = true break } if !success { t.Fatalf("term changed too often") } fmt.Printf(" ... Passed\n") }
先新建1个有3个节点的raft系统,调用checkOneLeader函数来获得raft系统当前的leader序号,调用Start函数给该节点发送命令(Index=1),然后新建1个可容纳5个元素的Channel,创建5个goroutine去并发地发送命令,这里使用sync包中的WaitGroup来保证同步。在每个goroutine发送命令完后检查是否在先前的Term中,如果命令成功则写入序号(这里Index为2-6)。等5个goroutine都结束后判断先前的操作是否在同一Term中,如果不是则重头再来,主要是保证高并发RPC。然后从先前的Channel中取出序号,这里通过wait函数来等待相应序号的命令日志被raft系统所有节点提交。如果Term改变了比如系统出现错误,那么清除Channel中的命令。最后比价从Channel中获得的命令是否与先前发送请求的命令相同,即命令是否被有序的提交了。
TestRejoin函数测试当出现网络分区时的一致性。
func TestRejoin(t *testing.T) { servers := 3 cfg := make_config(t, servers, false) defer cfg.cleanup() fmt.Printf("Test: rejoin of partitioned leader ...\n") cfg.one(101, servers) // leader network failure leader1 := cfg.checkOneLeader() cfg.disconnect(leader1) // make old leader try to agree on some entries cfg.rafts[leader1].Start(102) cfg.rafts[leader1].Start(103) cfg.rafts[leader1].Start(104) // new leader commits, also for index=2 cfg.one(103, 2) // new leader network failure leader2 := cfg.checkOneLeader() cfg.disconnect(leader2) // old leader connected again cfg.connect(leader1) cfg.one(104, 2) // all together now cfg.connect(leader2) cfg.one(105, servers) fmt.Printf(" ... Passed\n") }
先新建1个有3个节点的raft系统,调用one函数来发送命令请求(Index=1,cmd=101,Term=1)并完成提交。然后断开leader节点与网络的连接,此时形成2个网络分区,即Leader节(n1)点和2个Follwer节点(n2,n3)。向n1节点发送3个命令(Term=1),但是无法获得提交,因为该网络分区的节点数目少于Majority,那么此时n1节点的Index=4,Term=1。原先的2个Follwer节点重新选举,向新Leader节点(n2)发送命令请求(Index=2,cmd=103,Term=2)并提交,然后断开n2节点与网络的连接,将n1节点重新纳入网络中。由于n1节点的Term比n3节点的Term小,所以n3节点会成为Leader,n1节点上不同的Log会被抛弃。然后调用one函数再发送命令请求(Index=3,cmd=104,Term=3)并完成提交。最后将n2节点重新加入网络中,n3节点仍然是Leader节点,会追加日志到n2节点。最后调用one函数来发送命令请求(Index=4,cmd=105,Term=3)。
具体变化过程是(Term值不一定与实际符合):
在调用one(101)后Leader为N1
N1:Term=1,log为{101}
N2:Term=1,log为{101}
N3:Term=1,log为{101}
当断开N1并调用start(102)、start(103)、start(104)后
N1:Term=1,log为{101、102、103、104}
N2:Term=1,log为{101}
N3:Term=1,log为{101}
当调用one(103)后Leader为N2
N1:Term=1,log为{101、102、103、104}
N2:Term=2,log为{101、103}
N3:Term=2,log为{101、103}
当断开N2并连接N1并调用one(104)后Leader为N3,这里Term值可能为3或者4,因为当断开N2连接N1之前,N3可能超时
N1:Term=3,log为{101、103、104}
N2:Term=3,log为{101、103、104}
N3:Term=2,log为{101、103}
当连接N2并调用one(105)后Leader仍为N3
N1:Term=3,log为{101、103、104、105}
N2:Term=3,log为{101、103、104、105}
N3:Term=3,log为{101、103、104、105}
之后的测试分析根据上面流程即可,等以后有空再补上。。。
相关文章推荐
- MIT6.824 Lab2 Raft(1)
- MIT6.824 Lab2 Raft(2)
- MIT6.824-lab2 raft
- MIT 6.824 : Spring 2015 lab2 训练笔记
- MIT 6.824 lab2 启动流程以及raft算法实现
- MIT 分布式系统 6.824 2012 LAB4
- MIT 6.828 Lab2
- MIT6.828 Lab2:第1部分 Physical Page Management
- MIT6.828 Lab2: 第3部分 Kernel Address Space
- mit 6.824 mapreduce
- MIT6.824-lec1 MapReduce
- MIT6.828 Lab2: Challenge
- MIT6.824 分布式系统 lab1
- mapreduce.go源码浅析 MIT 6.824 Spring
- MIT 6.824 分布式系统导论: lab5 Persistence实现设计
- [MIT 6.824 Distributed System] Lab 1: MapReduce (2016)
- MIT 6.824 : Spring 2015 lab3 训练笔记
- MIT 分布式系统 实验 yfs 6.824 2012 LAB6
- MIT 6.824: 分布式系统实验
- MIT6.824 Lab 3: Fault-tolerant Key/Value Service (1)