ZAB问题总结
花了一个月的时间把Zab弄好,总的来说这个过程比较顺利,以下记录几点比较重要的讨论。
1 同步完成的定义
只要有多数派记录了该消息,我们认为同步完成,而不管是因为内部错误或Socket异常等。
同步什么时候完成是一个见仁见智的问题,有人认为是只要有一个节点记录了消息,同步就算完成;有人认为要在commit提交后。显然第一种容易导致一些后续问题和用户体验不好,第二种又过于保守也会造成问题,既然已经决定要commit说明消息已经很安全的存在于集群中。最后定义多数派记录消息为正式同步完成,而这样也可以很好的处理客户体验和后续效率。
2 同步出错怎么处理
同步进程出错了,大多数的做法是捕获这个错误,尽量处理掉。这种方法会导致要带来很多的状态维护,而用出错就节点自身进行重新选举是一个很好的选择(不论是follower还是leader),这样控制简单而且更安全。
3 消息顺序保证
每条同步的消息都是上一条加1,而且同步完后才能同步下一条,这样看似乎follower在接收到消息都是顺序的。但这个也会被忽悠,在我的测试中,让集群同步2000+条消息,总会有一个follower出错重选后进行恢复,然后数据丢失,而且问题一直出现在同一个follower上,其他节点没事。比如测试发现该节点接收到消息的顺序是3、1、2,导致1、2写不进去,下次恢复后直接从4开始,1、2永远丢失了。先不管为什么该follower接收到消息的顺序会部分乱掉,就这个问题,可以加一个判断,在每条消息写入前,判断它是否是上一条加1,如果不是说明该消息跳号。3、1、2都写不进去,这时可以让该节点自选并通过数据恢复从leader上得到正确的数据1、2、3。虽然恢复会消耗部分时间但这样处理最起码保证了消息顺序最终一致性。
4 选举死循环
快速选举有两种,第一种是传统的将别人更大的zxid更新给自己,第二种是每次都用自己的不更新,我们用第一种,第一种有传染性,我们每次的比较都是各个节点所期望的leader和该leader的参数来对比,不要用节点每次都是自己,而期望的zxid又是别人的,这样也可以选出leader,但逻辑混乱,zxid一样有可能小的节点反而当选。以上这种方式可以造成死循环。
如果我们坚持两个节点坚持比较的参数都是期望的,逻辑正确,但当最高的节点挂机后它的参数一致在集群中传染导致无人认领的死循环,采取的办法是每个节点在发送选票的时候都将验证所选节点是否在线,如不离线自身进入重选,脏数据很快就被清理了。
5 性能调优
第一次测试结果是300/s,这个性能勉强可以接受。后来发现,每个节点要处理三个写文件,第一个同步的transaction log,第二个是记录lastcommit的文件,第三个是我们模拟的一个应用数据库.db文件。每同步一条都会去执行这三个文件的写操作,虽然说后两个文件与第一是并行的,但核心文件transaction log写磁盘等待的时间占用太长。后来将后两个文件的写入做缓冲,500条写一次或者每隔1分钟将未commit的commit掉,测试后结果是1200/s。
6 leader自检
如很多人所关注的那样,zab在发送同步消息而此时集群崩溃,会造成最后一条数据时脏数据,有很多人认为要重选发送,而我们的处理方式是将脏数据truncate掉,因为集群崩溃后客户端调用会得到错误的反馈,如socket断开或集群不可用,而这个时候调用者应该认为该消息未同步成功,所以集群重启后重发不是一个很好的处理方式。leader一旦选出并不会立即进入恢复状态,而是要求所有follower来注册,leader验证自己的最后一条last_zxid是否具有多数派,否则自己truncate掉。
如果你有5个节点,此时开启了3个节点,这个时候自己可能无法完成(大多数时候正常完成),因为在集群数量不够的前提下leader无法验证自己的last_zxid的合法性,你需要开启更多的节点,否则始终重复选举。
1 同步完成的定义
只要有多数派记录了该消息,我们认为同步完成,而不管是因为内部错误或Socket异常等。
同步什么时候完成是一个见仁见智的问题,有人认为是只要有一个节点记录了消息,同步就算完成;有人认为要在commit提交后。显然第一种容易导致一些后续问题和用户体验不好,第二种又过于保守也会造成问题,既然已经决定要commit说明消息已经很安全的存在于集群中。最后定义多数派记录消息为正式同步完成,而这样也可以很好的处理客户体验和后续效率。
2 同步出错怎么处理
同步进程出错了,大多数的做法是捕获这个错误,尽量处理掉。这种方法会导致要带来很多的状态维护,而用出错就节点自身进行重新选举是一个很好的选择(不论是follower还是leader),这样控制简单而且更安全。
3 消息顺序保证
每条同步的消息都是上一条加1,而且同步完后才能同步下一条,这样看似乎follower在接收到消息都是顺序的。但这个也会被忽悠,在我的测试中,让集群同步2000+条消息,总会有一个follower出错重选后进行恢复,然后数据丢失,而且问题一直出现在同一个follower上,其他节点没事。比如测试发现该节点接收到消息的顺序是3、1、2,导致1、2写不进去,下次恢复后直接从4开始,1、2永远丢失了。先不管为什么该follower接收到消息的顺序会部分乱掉,就这个问题,可以加一个判断,在每条消息写入前,判断它是否是上一条加1,如果不是说明该消息跳号。3、1、2都写不进去,这时可以让该节点自选并通过数据恢复从leader上得到正确的数据1、2、3。虽然恢复会消耗部分时间但这样处理最起码保证了消息顺序最终一致性。
4 选举死循环
快速选举有两种,第一种是传统的将别人更大的zxid更新给自己,第二种是每次都用自己的不更新,我们用第一种,第一种有传染性,我们每次的比较都是各个节点所期望的leader和该leader的参数来对比,不要用节点每次都是自己,而期望的zxid又是别人的,这样也可以选出leader,但逻辑混乱,zxid一样有可能小的节点反而当选。以上这种方式可以造成死循环。
如果我们坚持两个节点坚持比较的参数都是期望的,逻辑正确,但当最高的节点挂机后它的参数一致在集群中传染导致无人认领的死循环,采取的办法是每个节点在发送选票的时候都将验证所选节点是否在线,如不离线自身进入重选,脏数据很快就被清理了。
5 性能调优
第一次测试结果是300/s,这个性能勉强可以接受。后来发现,每个节点要处理三个写文件,第一个同步的transaction log,第二个是记录lastcommit的文件,第三个是我们模拟的一个应用数据库.db文件。每同步一条都会去执行这三个文件的写操作,虽然说后两个文件与第一是并行的,但核心文件transaction log写磁盘等待的时间占用太长。后来将后两个文件的写入做缓冲,500条写一次或者每隔1分钟将未commit的commit掉,测试后结果是1200/s。
6 leader自检
如很多人所关注的那样,zab在发送同步消息而此时集群崩溃,会造成最后一条数据时脏数据,有很多人认为要重选发送,而我们的处理方式是将脏数据truncate掉,因为集群崩溃后客户端调用会得到错误的反馈,如socket断开或集群不可用,而这个时候调用者应该认为该消息未同步成功,所以集群重启后重发不是一个很好的处理方式。leader一旦选出并不会立即进入恢复状态,而是要求所有follower来注册,leader验证自己的最后一条last_zxid是否具有多数派,否则自己truncate掉。
如果你有5个节点,此时开启了3个节点,这个时候自己可能无法完成(大多数时候正常完成),因为在集群数量不够的前提下leader无法验证自己的last_zxid的合法性,你需要开启更多的节点,否则始终重复选举。