对于大型分布式系统,部分组件失效几乎是一定的。
分布式系统故障或部分失效的一大原因。
检测故障没有什么万能的方法,如果能收到响应,通常使用各种状态码标识,另一些情况下通用的办法就是超时了。
超时成因:
TCP与UDP
UDP不支持流量控制也不支持重传丢失的数据包,速度较快。如果延迟或丢失的数据价值不大,UDP是一个不错的选择。比如IP电话,应用程序会采用静音填充丢失的位置(出现短暂声音中断)。
超时设置长了或短了都不好。更好的方法是自动调整,比如Phi Accrual故障检测器,已经在Akka和Cassandra中使用。TCP重传也采用了类似的机制。
固定电话网在打电话时会动态建立一条电路:一个固定的、带宽有保证的通信链路,这种网络本质是同步的,不会受到排队的影响。由于没有排队,网络最大的端到端延迟是固定的。我们称之为有界延迟。
究其根本是目地不同,电话网线路持续时间短,要求及时性,而以太网追求资源的最大化利用。有了这个目的以太网才采用了分组交换协议,这样可以最大化的利用带宽:带宽小的时候我就传少点,带宽大的时候我就传多点。
简言之,网络中可变延迟并不是一种自然规律,只是成本与收益相互博奕的结果。
计算机内部至少有两种时钟:墙上时钟(或称钟表时间)和单调时钟。
墙上时钟返回当前日期与时间。例如,Linux的clock_gettime(CLOCK_REALTIME)
和Java中的System.currentTimeMillis()
会返回子1970年1月1日(UTC)以来的秒数和毫秒数,不含闰秒。墙上时钟在和NTP同步的时候,可能会跳回先前的时间点,不适合测量时间间隔。
单调时钟如Linux的clock_gettime(CLOCK_MONOTONIC)
和Java中的System.nanoTime()
。单调时钟保证总是向前。
如果一些服务依赖时间戳,比如LWW系统,时间如果不准将会造成严重的后果。例如下面的例子,错误的时间戳导致客户端B的更新丢失。
某些软件如果在指定时间内无法相应则会导致严重后果,比如飞机、火箭、机器人、汽车和其他需要对输入传感器快速做出响应的组件等。对于这些系统,软件有一个必须做出响应的上限:如果无法满足,会导致系统级故障,这就是所谓的硬实时系统。
在嵌入式系统中,实时通常意味着系统经过了精心设计和测试,以满足各种情况下执行时间约束。Web中的实时则通常是指一种持续的流式处理方式,并没有强的时间约束。
在很多情况下,系统范围内只能有一个实例。例如:
在分布式系统中,某个节点自认为它是“唯一的那个”,但不一定获得了系统法定票数的统一!例如下面由于不正确加锁而导致数据破坏的例子。
一种简单的解决办法是使用栅栏(fencing):每次锁服务在授予锁或租约时,还会同时返回一个fencing令牌,该令牌每授予一次都会递增。然后,要求客户端每次向存储系统发送写请求时,都必须包含所持有的fencing令牌。
使用ZooKeeper作为锁服务时,事务标识zxid或节点版本cversion都可以充当令牌。
fencing令牌可以检测并阻止哪些无意的误操作。如果节点存在“撒谎”的情况(即故意发送错误或破坏性的响应),这种行为称为拜占庭故障,在这样的不信任环境钟需要达成共识的问题也被称为拜占庭将军问题。
拜占庭将军问题指有n位将军需要达成共识,并且其中存在一些叛徒试图阻挠达成共识。大多数将军都是忠诚的,而且大家事先并不知道叛徒是谁。
如果系统中部分节点故障,甚至不遵从协议,或者恶意估计、干扰网络,但仍可运行,那么我们称之为拜占庭式容错系统。这些担忧在某些特定场景是合理的。例如:
尽管我们假设节点通常是诚实的,但有必要防范一些不那么恶意的“谎言”。例如:
关于计时,有三种常见的系统模型:
节点失效,也有三种模型:
安全性通常可以理解为“没有发生意外”,活性则类似“预期的事情最终一定会发生”。比如,唯一性和单调递增属于安全性,可用性和一致性属于活性。区分安全性和活性的一个好处是可以帮助简化处理一些具有挑战性的系统模型。安全性是必须要满足的,活性的满足则需要一定的必要条件。