我们知道分布式系统中的每台服务器都是通过网络连接的,所以结果是你很难知道每台服务器的真实状态,比如你判断另一个服务器是否有问题的唯一方法就是向他发送请求,只有收到响应你才会认为是好的。没有响应可能是由于网络故障,或对等机器的实际问题。那么,我们如何在分布式系统中准确判断这些问题呢?本文详细介绍了相关方法。基于多数(Majority)事实,很多时候一个节点可能真的没有问题。比如它正处于GC进程中,那么在GC期间是不能响应任何请求的。此时从节点本身来看,本身是没问题的,没有问题。但是,从其他节点的角度来看,GC节点与问题节点完全一样。没有回复请求,重试也没有反应。所以其他节点会认为这是一个问题。从这个角度来看,节点本身其实很难知道是不是出了问题。现在流行的判断节点是否有问题的算法是基于多数决定的。比如我有5个节点,那么大家一起投票。如果有超过一定数量的节点(一般超过一半,这里是三个节点)认为它有问题,那么我们就认为这个节点真的有问题。即使节点本身没有问题,只要多数人认为有问题,我们就认为它有问题。这里用多数来决定,因为多数意味着不会有冲突,因为一个系统不可能有两个多数,只有一个。Leader和Lock为什么要判断一个节点是否有问题呢?其实在分布式系统中,有很多场景只用到一个概念。例如,数据库分区中只有一个节点可以成为领导者。为了防止同时写入,只允许一个事务或客户端持有某个对象的锁。用户名只能由用户注册,因为它必须是唯一的。这些场景需要我们在设计的时候要小心。例如,即使一个节点认为自己是唯一被选中的(例如,认为自己是领导者,认为自己拥有该对象的锁等),其他大多数节点可能认为自己有问题.如果这时候设计不好,就会出现问题。我们看下面的例子:在这个例子中,为了防止多个客户端访问同一个数据,我们会要求每个客户端先写然后抢锁。这个锁是一个leaselock,也就是超时了就会释放。这里可以看到Client1首先申请了锁,但是很遗憾,拿到锁后,马上就GC了,而GC发生的时候,超过了租约的超时时间,导致锁在租约中超时后来发布了,client2拿到锁,做了更新。GC回来后,client1以为自己拿着锁,所以也直接写了,这时候就出问题了。这里的问题是GC回来后,client1误认为自己还持有锁。FencingTokens那么如何应对以上的误区呢?一种常见的技术是击剑。如下图所示:这里的变化是我们每次去拿锁的时候都会返回一个token值给客户端,这个token会在每次拿到锁的时候递增。这样客户端写的时候必须同时发回token。这样存储就可以根据这个token判断是否拒绝旧token的写入。一个常见的实现是使用ZooKeeper的TransactionID或节点版本作为fencingToken。拜占庭故障(ByzantineFaults)上面提到的FencingToken有一个前提,就是客户端发送的token确实被它接收到了。你可以想象一下,如果客户端在写入时发送的token是假的token,那么显然fencingToken也会有问题。所以对于分布式系统来说,如果某些节点存在谎言,问题就会变得更加复杂。我们把这种情况称为拜占庭问题,也就是我们常说的拜占庭将军问题。我们可以简单地认为,在一个存在拜占庭问题的系统中,可能会有一个或两个节点给出不可靠的消息。这种不可靠性可能是由于:由于某种原因,机器内存或CPU注册表中的数据出现了错误。比如我们在读取注册表的时候出错,我们会返回一个默认值,或者任意值等等。比如出现一些作弊或者攻击。在这种情况下,节点是不可信的。当然,在现实中,我们认为这种不可信的问题出现在比较少的节点上,不是大部分,也不是全部。所以如果大多数节点上发生了任何不可信的事情(例如,有一个代码错误总是在接收到的令牌中添加一个随机数),那么相应的算法就没有办法解决这个问题。减少谎言的存在虽然我们认为有谎言的节点很少。但是如果我们能有一些检测或者保护节点的机制,显然会更好,比如:对于网络包,我们会加一些校验和来检查是否正确。对用户的输入值添加一些检查,比如看是否在合理的范围内。NTP客户端连接到多个地址,然后根据大多数人的反馈来确定实时性等。系统模型与现实我们设计了许多算法来解决各种分布式系统中的问题。而这些算法是建立在一系列的软件和硬件对之上的,也就是说有很多假设,这些依赖关系就是我们通常所说的系统模型。比如我们讲时间假设,下面三种是常见的系统模型:同步模型所谓同步模型就是你知道网络延迟,进程的挂起,时钟的漂移不会超过一定的极限值。当然不是说没有网络延迟,只是你知道它不会超过一个限制。当然,这种模式在现实中其实是不现实的,因为总会有意想不到的延误。部分同步模型所谓部分同步模型就是我们认为大部分时间是同步模型,即不会超过一定的限制,但有时会超过这些限制。这是一个更现实的模型。异步模型是不做任何假设的模型,甚至不信任时钟,比如不使用超时。限制这种模式的局限性非常大。除了上述关于时间的假设外,另一个常见的问题是节点故障的假设。通常有以下三种模型:Crash-stoperror在这种模型中,算法认为一个节点有问题,比如没有响应,永远不会回来。在Crash-Recovery错误模型下,算法认为某个节点有问题,稍后它会回来。当然我不知道回来的是什么。这就需要节点可能需要一些可以普遍保存的介质,比如把很多东西写到磁盘上,这样即使crash了也可以恢复。拜占庭故障任何事情都可能发生在节点上,正如我们上面所说的。我们在现实中看到的最常见的模型是部分同步的崩溃恢复错误。那么分布式系统的算法如何使用这些模型呢?算法的正确性我们在判断算法正确性的时候,需要用到一些属性来判断。例如,一个从小到大的排序算法,输出中的两个不同元素需要满足前者小于后者的要求。这是最简单的判断方法。同样,我们如何判断分布式系统中的算法是否正确?我们还是以上面的锁为例。我们可以通过以下属性来判断:唯一性,请求得到的两个token没有相同的。单调递增如果请求x的token是tx,请求y的token是ty,x在y的前面,则tx
