分布式系统不是银弹

113 阅读5分钟

从本质上说,单体系统是以单进程的形式运行在一个计算机节点上,而分布式系统是以多进程的方式运行在多个计算机节点上。

分布式系统不是银弹,它面临四方面的问题:

  • 故障处理
  • 异步网络
  • 时钟同步
  • 共识协同

故障处理

故障处理事所有系统都必须要考虑的重要问题。

单机服务系统的故障模型是“全部失败”模型,它在硬件正常的时候,对于一个确定的输入,总会得到一个确定的输出。当有硬件故障时,对于一个确定的输入,计算机也会直接出现无法启动或者崩溃的情况,而不是给出一个模棱两可的结果。这种方式让我们明确地知道,如果系统内部发生了故障,计算机不会给出错误的结果,而是全部崩溃。

但是对于分布式系统来说,它由多个计算机节点组成,虽然每一个计算机节点都是“全部失败”的模型,但是如果系统中某些节点出现宕机或者网络故障,整个分布式系统就会出现部分失败的情况。

部分失败的情况是构建分布式系统的一大挑战,也深刻影响着分布式系统的设计方式和原则。在分布式系统中,我们需要接受部分失败,接受系统的每一个部分都可能出现故障情况,在不可靠的硬件上通过软件来容错,构建高可用的分布式系统。

本地调用和远程调用

在单机系统中,系统各个组件之间的调用方式非常简单,直接本地调用即可。但是在分布式系统中,不同的组件运行在不同的机器上,通过本地调用是不可行的,我们只能通过网络进行调用,即远程调用。

在分布式系统中引用网络的同时,也给系统带来了非常大的复杂性。

我们假设在分布式系统中,服务A调用服务B,那么在整个远程调用会出现问题的场景包括:

  • 请求可能在服务A运行机器的网络协议的缓冲区中排队等待。
  • 请求可能在服务A和远程服务B之间的网络设备(比如路由器)的协议栈缓冲区中排队等待和丢失。
  • 远程服务B可能失败,比如系统崩溃了。
  • 请求可能在远程服务B运行机器的网络协议栈的缓冲区中排队等待。
  • 远程服务B可能暂时不能处理服务A发送的请求
  • 响应可能要在远程服务B和远程服务A之间的网络设备
  • 响应可能在服务A和远程服务A之间的网络设备的协议栈缓冲区排队
  • 响应可能在服务A运行机器的网络协议的缓冲区中排队。

解决这些问题,一般会采取超时机制,请求方在发起请求后,设置一个超时时间,这样确保请求方在超时间内,一定能得到一个响应。

在分布式系统的设计中,我们要充分考虑通过网络进行远程调用导致的不确定性,比如在响应超时的情况下增加重试机制,确保请求能最少执行一次。在重试的时候增加幂等的机制,确保请求只被精确处理一次,并且对重试机制增加退避策略,确保系统不会因为重试导致雪崩。

全局时钟与多个时钟

在计算机系统中,时间主要有两个作用,第一个是记录事件发生的时间,这是一个绝对时间,是让我们来阅读和理解的。第二个是记录事件之间的发生顺序,这是一个相对时间。

在单机系统中,由于只有一个时钟,先执行的事件一定能获得更小的时间,通过本地时钟就可以确保全局事件之间顺序的正确性,所以单机系统是一个全局时钟的模型。

而分布式系统是由多台计算机节点组成的,每一个节点都有自己的时钟,并且计算机执行的速度非常快,在一个毫秒内可以做非常多的事情。在这种情况下,如果在每一个节点,我们都采用本地的时钟来记录事件的发生时间,然后基于多个节点上的事件按发生时间进行排序,就很容易出现时间穿越的问题。

这个问题的解决思路有两个,一个是回到单机系统的全局时钟的模式,所有节点对于需要排序的事件时间,不使用本地时钟的时间,而是去请求同一个时间服务器获得事件的发生时间,然后依据这个时间进行排序;另一个是 Google 在Spanner中使用的,通过GPS和原子钟实现TrueTime API 来解决

一言堂与共识

我们在系统设计和运行时,经常会遇到这样的场景:同时只允许一个线程操作某一个数据,和同时只允许一个线程执行某一个操作,如果不遵守这个规则,就可能会导致数据错误等不可预料的后果,这就是线程之间的同步操作。

单机系统中需要协调的多个线程属于同一个进程,所以同步操作很简单,直接使用进程内的资源来做协同就可以了。

分布式系统中各个组件都是独立的进程,运行在不同的机器上,所以,我们需要处理的是一个跨机器的多进程同步问题。

这是一个共识问题,需要分布式系统中参与同步的进程之间能达成共识,目前我们是通过Paxos或者Raft这样的共识算法来解决问题的。


此文章为极客时间4月份Day02学习笔记,内容来自《深入浅出分布式技术原理》课程。