diff --git a/README.md b/README.md index 1cad832..f1da0e8 100644 --- a/README.md +++ b/README.md @@ -35,3 +35,9 @@ kill -9 杀死进程 通过新开进程的方式创建节点(参考test/common.go中executeNodeI函数 如果通过线程创建,会出现重复注册rpc问题 +## 客户端工作原理 +客户端每次会随机连上集群中一个节点,此时有四种情况: +a 节点认为自己是leader,直接处理请求 +b 节点认为自己是follower,且有知道的leader,返回leader的id。客户端再连接这个新的id,新节点重新分析四种情况。 +c 节点认为自己是follower,但不知道leader是谁,返回空的id。客户端再随机连一个节点 +d 连接超时,客户端重新随机连一个节点 diff --git a/threadTest/network_partition_test.go b/threadTest/network_partition_test.go index 3e23382..c615737 100644 --- a/threadTest/network_partition_test.go +++ b/threadTest/network_partition_test.go @@ -33,7 +33,7 @@ func TestBasicConnectivity(t *testing.T) { } } -func TestElectionWithPartition(t *testing.T) { +func TestSingelPartition(t *testing.T) { // 登记结点信息 n := 3 var peerIds []string @@ -58,7 +58,7 @@ func TestElectionWithPartition(t *testing.T) { } }() - time.Sleep(2 * time.Second) // 等待启动完毕 + time.Sleep(time.Second) // 等待启动完毕 fmt.Println("开始分区模拟1") var leaderNo int for i := 0; i < n; i++ { @@ -147,4 +147,99 @@ func TestElectionWithPartition(t *testing.T) { t.Errorf("日志数量不一致:" + strconv.Itoa(len(nodeCollections[i].Log))) } } +} + +func TestQuorumPartition(t *testing.T) { + // 登记结点信息 + n := 5 // 奇数,模拟不超过半数节点分区 + var peerIds []string + for i := 0; i < n; i++ { + peerIds = append(peerIds, strconv.Itoa(i + 1)) + } + + // 结点启动 + var quitCollections []chan struct{} + var nodeCollections []*nodes.Node + threadTransport := nodes.NewThreadTransport() + for i := 0; i < n; i++ { + n, quitChan := ExecuteNodeI(strconv.Itoa(i + 1), false, peerIds, threadTransport) + quitCollections = append(quitCollections, quitChan) + nodeCollections = append(nodeCollections, n) + } + + // 通知所有node结束 + defer func(){ + for _, quitChan := range quitCollections { + close(quitChan) + } + }() + + time.Sleep(time.Second) // 等待启动完毕 + fmt.Println("开始分区模拟1") + for i := 0; i < n / 2; i++ { + for j := n / 2; j < n; j++ { + threadTransport.SetConnectivity(nodeCollections[j].SelfId, nodeCollections[i].SelfId, false) + threadTransport.SetConnectivity(nodeCollections[i].SelfId, nodeCollections[j].SelfId, false) + } + } + time.Sleep(2 * time.Second) + + leaderCnt := 0 + for i := 0; i < n / 2; i++ { + if nodeCollections[i].State == nodes.Leader { + leaderCnt++ + } + } + if leaderCnt != 0 { + t.Errorf("少数分区不应该产生leader") + } + + for i := n / 2; i < n; i++ { + if nodeCollections[i].State == nodes.Leader { + leaderCnt++ + } + } + if leaderCnt != 1 { + t.Errorf("多数分区应该产生一个leader") + } + + // client启动 + c := clientPkg.Client{PeerIds: peerIds, Transport: threadTransport} + var s clientPkg.Status + for i := 0; i < 5; i++ { + key := strconv.Itoa(i) + newlog := nodes.LogEntry{Key: key, Value: "hello"} + s = c.Write(nodes.LogEntryCall{LogE: newlog}) + if s != clientPkg.Ok { + t.Errorf("write test fail") + } + } + + time.Sleep(time.Second) // 等待写入完毕 + + // 恢复网络 + for i := 0; i < n / 2; i++ { + for j := n / 2; j < n; j++ { + threadTransport.SetConnectivity(nodeCollections[j].SelfId, nodeCollections[i].SelfId, true) + threadTransport.SetConnectivity(nodeCollections[i].SelfId, nodeCollections[j].SelfId, true) + } + } + time.Sleep(1 * time.Second) + + leaderCnt = 0 + for j := 0; j < n; j++ { + if nodeCollections[j].State == nodes.Leader { + leaderCnt++ + } + } + if leaderCnt != 1 { + t.Errorf("多leader产生") + } + + // 日志一致性检查 + for i := 0; i < n; i++ { + if len(nodeCollections[i].Log) != 5 { + t.Errorf("日志数量不一致:" + strconv.Itoa(len(nodeCollections[i].Log))) + } + } } \ No newline at end of file