上次团队内部分享,Skyler同学分享了Raft算法,分享过程中,大家还激烈讨论了一下。说明大家其实对分布式系统的原理和实现还是比较感兴趣的。

当然,我们不是搞科研的,平时工作中可能用不到这些高大上的知识。不过,如果我们能对这些原理性的东西有一个正确的理解,至少能在平时工作中做技术选型时有一个正确的选择。

所以,今天我想尝试给大家梳理一下分布式系统中涉及到的一些概念和原理,不会太深入,就算给大家做一个提纲挈领的资料吧,希望你看过后能对分布式系统有一个整体的了解吧。

分布式解决什么问题

在讨论任何技术之前,我都习惯问一个问题,这个技术要解决什么问题?那么,分布式系统要解决什么问题呢?

当然,这个问题的答案并不复杂,你可以简单回答:解决单台撑不住的问题

深入来看,那什么叫撑不住呢?可能从三个维度来解释吧:可靠性、可扩展性和可维护性

可靠性意味着即使发生故障,系统也可以正常工作。单台的话,如果挂了,那肯定就不可用了,所以也叫单点问题。

可扩展性是指负载增加时,系统性能还能够保持在一个有效的范围。如果是单台,那就只能做垂直扩展,例如增加CPU和内存等等。不过这种扩展方式是非常昂贵和容易达到系统极限的。

可维护性则意味着许多方面,但究其本质是为了让工程和运营团队更为轻松。单台的话,一旦有系统维护,或者发版之类的,系统就不可用了。

所以,分布式主要就是解决这些问题的,于是我们把原本一个程序做了拆分,部署到了一些由网络连接的节点上,共同协作完成原本的功能。

讲到这里,可能需要和我们现在经常提到的微服务做一些区分。我们现在说到的微服务,是不是分布式的呢?可能是,也可能不是。我们这里提到的分布式,更倾向于有状态的服务,也就是服务和服务之间的依赖关系会更强,数据同步会更紧密。而不是像无状态服务那样,程序可以随意扩展和消亡。

好,回到正题,就像俗话说的:知易行难,使应用程序可靠、可扩展或可维护并不容易。

我们接下来逐步分析一下。

数据复制

上一节我们提到,我们探讨的更多是有状态程序,状态是通过什么来体现的呢?对了,数据。所以,我们面临的第一个问题就是数据如何在各个节点上同步(复制)的问题。

主从复制应该是你首先能想到的:所有的客户端写入操作都发送到某一个节点(主节点),由该节点负责将数据更改事件发送到其他副本(从节点)。每个副本都可以接收读请求,但内容可能是过期值

主从复制是非常流行的,主要是因为它很容易理解,也不需要担心冲突问题。复制可以是同步的,也可以是异步的,而一旦发生故障,二者的表现差异会对系统行为产生深远的影响。从性能上来说在系统稳定状态下异步复制是优秀的。

那主从复制有些什么问题呢?主要问题就是写是一个单点,因为写只能写到主节点上,所以,如果主挂了,那么系统就不能再接受写的请求。另外,如果写的请求量很大,那么复制有可能会滞后,造成主从节点间数据不一致的问题。

怎么解决这两个问题呢?

解决写单点的问题,其实有两个方案:

多主节点复制:系统存在多个主节点,每个都可以接收写请求,客户端将写请求发送到其中的一个主节点上,由该主节点负责将数据更改事件同步到其他主节点和自己的从节点。通常这个方案主要运用在跨数据中心的数据复制,因为数据中心间的网络实在抖动太厉害。

无主节点复制:客户端将写请求发送到多个节点上,读取时从多个节点上并行读取,以此检测和纠正某些过期数据。Cassandra就是采用的这种架构。这里面要解决的就是如何定义写成功和读成功,里面的Quorum的设置就是比较关键的。

在这里,我们先暂时不讨论主挂了重新选主的问题,这个话题后面我们再讨论。

解决复制滞后的问题,这个就没那么简单了,这是一个权衡的问题

如果采用同步复制,那么就可以完全解决滞后的问题,但是写入的性能会下降非常快。

如果采用异步复制,那么滞后就一定会存在,问题就变成你对一致性的要求高不高。如果要求不高,允许一定时间的滞后,那么就没什么问题。如果要求很高,那么可以采用一个办法,读自己的写。什么意思?通俗来说,读写都从主节点进行。当然,这就失去了可扩展性。还有一种办法,就是在客户端上下文章,客户端在读取数据时,记录一下版本或者时间戳,如果下一次再次读取同一条数据时,版本更低,那就认为没有读取成功,这样可以避免读取到旧数据的问题。

所以你看,针对复制滞后问题,没有一个一概而论的方法,而是根据实际的场景来进行选择。

主从就说到这里。

在主从架构之后,大家遇到一个问题,当数据量上升到很大时,所有节点都保存同样的数据,其实依然会达到单个节点的存储上限,所以,不管读还是写,性能都会下降比较快。怎么办?答案是做数据分区:将同一份数据按照一定的规则分布到不同的节点上

这里的潜台词就是:不管读还是写,我都需要先计算一下要去哪个分区操作。那么规则就显得比较重要,通常有两种分区方式:

基于关键字区间的分区。先对key进行排序,每个分区只负责一段包含最小到最大关键字范围的一段key。对key排序的优点是可以支持高效的区间查询,但是如果应用程序经常访问与排序一致的某段key,就会存在热点的风险,例如用时间做key。

哈希分区。将哈希函数作用于每个key,每个分区负责一定范围的哈希值。这种方法打破了原key的顺序关系,它的区间查询效率比较低,但可以更均匀地分配负载。

目前的Nosql,大部分是key-value类型的数据库,所以,基本上天然就可以很好的支持数据分区。而在传统关系型数据库中,这个就会面临一个问题,如何做join?如何做排序?这个时候就需要二级索引的支持,甚至需要一个数据访问中间层的存在。

当然,分区架构也会面临节点失效的问题,所以,任何分片其实都需要有副本,当主分片所在节点故障时,数据才不会丢失。

容错问题

我们前面说过,分布式架构主要解决单台的单点问题,所以我们把程序部署到了多个节点上。

但是,其实这样情况就复杂了。单台虽然又单点问题,但是挂了就挂了,不会存在其他状态,程序重启后就可以继续挂之前的工作。

在分布式里,有多个节点了,那么就会出现部分失效的情况。注意,我说的是失效,因为失效不一定是挂了,有可能有下面集中情况:

  • 网络延迟或者丢包
  • 机器间时间不一致(有些情况时间非常重要,例如很多产品是靠时间来做最终一致的)
  • 程序真挂了

当然,虽然情况多样,但是我们分布式不就是要通过多台来解决可靠性吗?那么我们的设计就要容忍部分失效的发生。并且大家要做好随时发生的准备。

为了容忍错误,第一步是检测错误,最常见的做法就是维持节点间的一个心跳,但即使这样也很有挑战。心跳不是检测节点是否发生故障的准确机制,大部分场景,我们会设定一个心跳的超时时间,依靠超时来确定远程节点是否仍然可用。但是,超时无法区分网络和节点故障。然而,当某个节点的网络突然拥塞的时候,心跳可能一会有一会没,面对这样一个处于“残废”的节点比彻底挂掉的故障节点更难处理。

检测到错误之后,让系统容忍失效也不容易。在典型的分布式环境下,没有全局变量,没有共享内存,没有约定的尝试或其他跨节点的共享状态。节点甚至不太清楚现在的准确时间,更不用说其他更高级的了。信息从一个节点流动到另一个节点只能是通过不可靠的网络来发送。单个节点无法安全的做出任何决策,而是需要多个节点之间的共识协议,并争取达到法定票数

看到这里,你会发现出错是分布式的常态,而容错并不那么容易。所以,如果单台能解决的问题,尽量不要变成分布式。当然,也不用难过,我们基本上不会重头写一个分布式系统,基本上都有现成的解决方案来解决这些问题。

这里顺便说一下CAP理论,大家可能都知道这个理论,它说的是:一致性、可用性、分区容错性,任何分布式系统只能支持其中的两个特性。

但是,网络分区不管你喜欢还是不喜欢,它都一定会存在,这是不能逃避的问题。所以,CAP理论可以换一个说法:当出现网络分区时,你选择一致性还是可用性?

共识问题

有了前面知识的铺垫,可以来简单谈一下共识问题了。

开篇提到的Skyler同学分享的Raft算法,其实就是一个共识算法。

那么共识算法要解决分布式的一个什么问题呢?

上一篇我们说了,分布式就是一个容易发生部分失效的系统,那么要在这样的环境中进行协作,怎么才能高效可靠的进行呢?

当然,讲到这里,要解释一下,什么类型的工作才是协作。之前我们说的数据复制,其实不算哈。

我们熟悉的分布式锁、分布式事务之类的,都属于协作的范围,他们的特点就是:一个人提议,大多数人同意,达成一致

举个生活中的例子:今天周六,我提议咋们团队中午去吃李庄,team中Lon同意,Victor同意,Skyler同意,Tipo嫌档次低了,但是无奈,少数服从多数我们还是去吃了。这就是共识问题。

当然,这也不足以说明我们需要研发一个巨复杂的算法,因为这个场景更像一个民主选举的场景,而现实世界中还有另外一个方法达成一致:独裁

如果系统只存在一个节点,或者愿意把所有决策功能都委托给某一个节点,那么事情就变得很简单。这和主从复制数据库的情形是一样的,即由主节点负责所有的决策事宜。

然而,如果主节点挂了怎么办?有三种解法

  • 大家一起等主节点恢复。
  • 人工介入指定一个主节点。
  • 自动选举一个主节点。

共识算法在大部分(不是全部哦)的场景下,主要用来解决主从系统中,自动选主的场景。因为有了主节点,我们大部分需要决策的工作,都可以交给主节点来负责。

那么,反过来想,那些无主系统或者多主系统,是不支持共识的。

业界比较知名的共识算法就是:Paxos(zookeeper采用)和Raft(ETCD采用)了,这里我不准备花很大篇幅去讲解算法本身,因为随便网上一搜都有很多这方面的文章。

但是,大家要了解,像zookeeper这类的工具,实际上已经把共识服务做了非常好的封装抽象,假如你遇到共识性问题的场景,那么请不要犹豫,直接用他们即可,毕竟他们是已经验证过的工具。强烈不推荐自己重头造一个轮子。

好,花了一点时间,带大家捋一下分布式,没有什么太高深的知识,就是希望大家能知道里面的来龙去脉,希望对大家有帮助。