这一章我们会讨论分布式系统中一些常见的问题,在下一章中我们会讨论这些问题的解决办法。
1.故障与部分失败
在分布式系统中,系统的部分组件常常会以以某种未知的方式被破坏,我们称之为部分失败/部分故障。而我们的目标就是在不可靠的组件上构建一个可靠的系统。
为了容忍部分失败:
- 第一步是检测失败:大多数系统利用超时来判断节点是否可用,但是超时机制没办法区分是网络失效还是节点宕机
- 在检测到失败之后,如何使系统去容忍失败也是个大问题:节点间通信的唯一方法是通过不可靠的网络发送信息,不能由一个节点来做决策,因此我们需要一致性协议算法来帮助我们完成这个目标。
2.不可靠的网络
在分布式系统中,节点与节点间的网络通信是不可靠的,它表现在:
- 网络分区:网络的一部分发生了故障而被切断
- 延迟问题:TCP协议虽然提供了数据校验、超时重传等机制,但是没办法保证数据包在一定时间内保证到达,这种延迟可能是无限的。
3.不可靠的时钟
在分布式系统中,时钟是一个非常重要且棘手的问题:
- 节点与节点间的通信是有延迟的,我们很难确定多个节点间事件的顺序问题
- 每台机器都有自己的时钟(硬件设备),这些设备不都是完全准确的
时钟分为两种,单调时钟与Time-of-Day时钟:
- 单调时钟一般用于计算持续时间,如Java中的System.nanoTime()
- Time-of-Day时钟根据某个日历返回当前的日期和时间,一般依赖于NTP服务器或其他外部时间源
时钟不同步导致的问题
时钟的不同步会导致很多问题,比如前面的博客中,我们谈到多主备份数据库解决写入冲突的一个常见办法是LWW,即最后写入者胜出,在时钟不同步的情况下可能会导致如下问题:
ClientA在node1上写入x=1,并同步到node3。 ClientB在node3上写入x=x+1,也同步到node2。但是B的写入同步比A的更早到达,从而导致在node2上x+1的操作消失了。
进程的暂停问题
假设在一个分布式数据库中,节点通过选举方式成为leader(只有leader能接受写请求),并获得一个租约,且同一时间只有一个节点能获得租约。假设有这么一段代码:
while (true) {
request = getIncomingRequest();
// Ensure that the lease always has at least 10 seconds remaining
if (lease.expiryTimeMillis - System.currentTimeMillis() < 10000) {
lease = lease.renew();
}
if (lease.isValid()) {
process(request);
}
}
这段代码的问题:
如果当前线程在lease.isValid()之后暂停了15秒,然后才继续运行process(request),而这时候租约可能已经到期了,它已经不再是leader了!
那么这个进程为什么要暂停这么久呢? 可能的原因如下:
- GC运行了很久
- 虚拟机被挂起,当前所有进程被暂停并把内存内容保存到磁盘
- 进程接收到了SIGSTOP信号,暂停使用CPU周期;之后又接收到SIGCONT信号才恢复
- 执行了同步访问磁盘的操作,如果磁盘需要走网络(比如EBS),可能延迟很大