您的位置:首页 > 其它

MIT6.824 Lab2 Raft(3)

2016-10-24 21:42 429 查看
  最后一部分介绍一下测试代码中的测试函数。

  紧接着上次,现在是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}

  

  之后的测试分析根据上面流程即可,等以后有空再补上。。。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: