[经验分享] 关于 oneSplitTest 卡死的解决方法

问题描述

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 是有效的

以上做法亲测有效~

14 个赞