问题描述
TestOneSplit3B 对网络做了分区, 5被孤立出来, 如下:
cluster.AddFilter(
&PartitionFilter{
s1: []uint64{1, 2, 3, 4},
s2: []uint64{5},
},
)
正常情况下分区后 5 不会选为 leader, 一切都很快, 但有一种糟糕的情况:
如果在分区之前, 5 被选为 leader, 分区之后其并不会停止 LeaderShip, 此时 {1, 2, 3, ,4} 中1被选为 leader
也即同一个 region 出现了两个 Leader, 在 peerMsgHandler 中 , region leader 会给 pd 发送 regionHeartBeat
pd 无从判断请求中的 leader 是否有效, 那么 pd 中关于该 region 的 leader 信息就会被不断改动, 有时为 1, 有时为5? (应该是这样吧)
这就导致客户端的请求有时会发到 5 上, 这就导致请求没办法正常处理, 出现卡死的现象(通过打印日志, 会发现仍在正常运行, 但是日志复制的非常慢)
解决方案
引入 leader lease check 机制, 也即每隔一段时间, 主动检测当前的 leader 是否是有效的, 如果 leader 无线, 主动 becomeFollower(), 也就不会出现上述的情况了.
我的做法:
- 在 raft/Progress 中加入一个变量 , lastCommunicateTs 代表 leader 和 follower 最后一次通信时间, 可以在 heartbeat response 或者 appendEntryResponse 中记录该值
type Progress struct {
Match, Next uint64
// the last communication ts
lastCommunicateTs int64
}
- 利用 raft/tick() 机制, 加入 tickLeaderLeaseCheck(), 和 tickLeader() 同时进行
- 如果超过了一个时间界限 leaderAliveTimeout, 则开始检测 leader 的有效性, 检测方法:
- 遍历一遍所有的 follower, 判断 curTime - pr.lastCommunicateTs 是否小于 leaderLeaseTimeout
- 如果半数以上都满足, 那么该 leader 仍然是有效的
- leaderAliveTimeout 的设置: 可以设置为 electionTimeout 的两倍或者三倍, 建议两倍
- leaderLeaseTimeout 的设置: 可以设置为 90% * electionTimeout , 也即在这段时间内, 可以保证 leader 是有效的
以上做法亲测有效~