Tagged: 分布式系统

3

分布式系统之leader-followers Replication深入介绍

我们在前面有简单讲过Replication的作用,简单说就是为在多个机器上保存同样的拷贝来服务的。有了这个拷贝之后我们就可以做很多事情,比如说它可以成为一个读的源从而分散读的压力,它可以在原来数据机器出问题(或者deploy)等的时候作为一个backup等等。 这个想法其实很简单,但真正在我们做这个拷贝的时候,会遇到很多问题,比如说我们是使用同步还是使用异步来进行同步多个拷贝,如何保证多个拷贝之间的一致性等等。那么本文就来从各个方面详细介绍这些内容。 Leaders和Followers 我们把每一个保存数据的节点称之为replica,当我们有多个节点的时候,最明显的一个问题就是怎么去保证每个节点的内容都是一样的呢?其中最常见的方法就是基于leader的模式(也成为master-slave模式或者active/passive模式)。总得来说,它的工作方法如下: 一个节点是leader。所有的写操作都必须经过leader。 其他的节点我们成为follower,每次leader写数据的时候,也把相关的内容发送到每一个follower(replication log),然后每个follower根据这些log来更新本地的数据。 当有读的操作的时候,既可以从leader读也可以从follower读。 同步VS.异步Replication 上面这种方法其实在很多关系型数据库中很常见。这里遇到的一个最大的问题就是使用同步还是异步来进行replication。 就比如上面的这个case,它实现的功能很简单。就是把user id 1234这个用户的picture更新一下,它首先发送了update的请求到了leader的replica上。在这之后,leader会把这个更新的请求分别发送给两个follower的replica,从而最终达到所有的replica上这个用户的picture都更新了的效果。 我们就以这个例子来分析一下同步和异步在这两个上面的差别。如下图所示,我们看到leader的返回是在Follower1 更新完成之后,也就是说follower1是一个同步的更新。而leader并没有等待follower2完成更新再返回,这也就意味着follower2的更新是一个异步的。 从这个图中可以看到follower1和follower2的更新其实都是由一个延时的,尽管大多数时候这个延时都很小,但是当我们遇到网络问题或者别的情况的时候(CPU占用率很高,内存不够等等),这个延时有可能会很大,谁也不能保证什么时候能被更新。 所以,同步更新的好处就是follower其实和leader是同步的,当leader有问题的时候我们甚至可以直接切换到同步更新的follower,当然它的问题也很明显,就是每一个更新(写操作)都需要等待这个follower的更新完成才行,这有可能会导致整个request延迟很久,甚至超时。所以,假如你想做同步的更新,一般来说也不会想让所有的节点都同步,而是选择leader和一个节点是同步的,这样就可以在leader出问题的切换和同步的效率之间达到一个很好的trade off。 当然,事实情况下基于leader的replication基本都是完全使用异步。这样的问题就是leader出问题,那些没有sync到别的node的数据就会丢失。好处就是leader完全不收别的节点的影响,哪怕别的节点都出问题也可以正常独立运行(通常这个时候就会出alert,然后oncall就到了表现的时候了,哈哈)。生产环境中这样的选择其实有很多原因,比如说为了保证随时都有节点是健康的,这些节点通常会分布在不同的地区(数据中心),所以他们之间的通信通常不是那么可靠,如果使用同步就会很容易出现问题等等。 Follower的建立 有时我们需要从无到有建立一个新的follower,这种情况在生产环境中特别常见,比如说机器的磁盘坏了,原有follower的数据就都不能用了,这个时候就需要从头开始建立,或者说某个时刻你没有足够的follower了(比如说数据中心出问题了,或者机器出问题了),你需要在一个新的机器上建立一个 follower,那么如何从头开始来建立一个节点呢? 首先想到的就是从leader上拷贝过来就好了,但是我们知道leader其实是在不停改变的,也就是说随时都在不停地被写入,而数据的拷贝速度不会很快(主要瓶颈在HDD的写入速度上,目前基本在200MB/s左右),所以在这个过程中不允许写可能也不是一个很好的方法。那么一般来说是怎么做的呢?大概会有以下的步骤: 得到一个数据库某一个节点的snapshot(这个snapshot不影响写)。大多数数据库都支持这样的功能。 拷贝这个snapshot到新的follower Follower联系leader去得到所有的在这个snapshot之后的操作(log sequence)。 然后follower根据得到的信息来apply snapshot之后 的操作,我们称这个过程为catch up。 处理节点的中断 我们上文中也提到了任何节点在任何时候都有可能出问题。那么要是真的出问题了,我们一般会怎么处理呢? 假如这个出问题的节点是follower,假如这个问题只是service...

0

分布式系统简介(总论)

我们在前面几篇文章中简单介绍了单个服务器对数据的处理,而在现实中数据的存储和获取会涉及到多个机器,也就是说我们会把数据分布在多台机器上,这样做有很多好处: 可扩展性(Scalability) 随着你的数据增加,一个机器可能很难处理日益增长的读写需求,你可以把这些负载分散到多个机上。 容错性/高可靠性(Fault Tolerance/ high availability) 在现实环境中,任何一个机器都有可能出现故障,可能是磁盘故障,网络故障等等,假如你希望某一个或几个机器出现问题,你的产品仍然能够继续工作,那么就需要分布式系统。 延迟(Latency) 假如你的用户遍布全球,那么你肯定希望让你的服务器物理上更加靠近对应的用户,这样才能减少相应的延迟。总得来说,就像我们之前介绍的,当需求增加的时候,你就需要进行扩展,要不是垂直扩展,增加机器的处理能力,要不就是水平扩展,使用更多的机器来处理。前者有尽头,后者则无涯。具体可以参见我之前介绍的《负载均衡》。 Replication和Partitioning 我们在之后将重点来介绍如何水平扩展,通常来说有两者方法来实现水平扩展: Replication 这种方法就是在多个节点(机器)上存放同样的数据,并把它们分布到不到的地方(防止自然灾害等),当一个或几个节点出现问题的时候,保证仍然能有一个节点可以工作。同样的这样的处理也能提高性能。 Partitioning 所谓的partitioning就是把一个大的数据分块存在不同的节点上。 它们两相互是独立的,不过通常我们会同时使用它们,具体的如下图所示: 我们将在后面的文章中来具体详细地介绍如何做replica,如何做partition,以及如何处理数据读写过程中发生的常见问题等。

0

Database数据一致性介绍

数据一致性的场景很简单,假如我们把一个值赋给一个变量,那么我们之后立即再去读这个变量,突然发现它的值和我们想象的不一致,是不是很沮丧,对的,这就是数据的一致性。 而在使用分布式的数据库的时候,尤其是那些对数据一致性不是很严格保证的情况下,这种情况经常会发生。也许你会问,什么?难道他们不应该给我们保证数据是一致的吗?我的回答呢,总得来说最后他都会能够保证的,但是这个中间的间隔时间则是取决于不同数据库的实现。 它们这样做是因为有一个trade off在里面,有些数据库为了提供可靠性和性能,则会牺牲掉一致性的保证。而有些数据库则允许你进行选择,你是要强一致性还是要高性能,比如Azure的cosmos DB和Cassandra。 数据库请求分析 我们来分析一下数据请求的时间线是什么样的,理想状况下,你的请求会被立即执行。 然而事实上肯定不是这样,我们需要时间连接存储数据的地方,然后还需要时间返回response给你。如下图所示: 这里最好的保证就是请求是在你调用和完成中间的某个时间点完成的。你可能会想,这个和你给变量赋值是类似,比如x=1,然后再读,你会发现它是1(前提就是这中间没有别的thread再写这个x)。但是假如是一个分布式系统,就不是这么简单了,你需要考虑别的server是如何更新到这个新的值的,这里就有一个设计的trade off,尤其是可靠性,性能和一致性之间的考虑。 假如我们有一个简单的分布式的键值存储,他由一系列的副本组成。这些副本在他们中间选择了一个leader,然后只要这个leader才能做写的操作。当这个leader接收到了一个写的请求,它会广播这个请求到所有的副本。即使这些所有的副本受到的请求是一样的顺序,他们更新的时间都是不同的。 所以,现在假如你来考虑读的情况,你该怎么处理?因为读是可以发生在leader上,也可能发生在任何一个副本上面,但假如所有的读都要到leader这边,那么显然性能就不会好。从另一个方面来说,假如我们让读发生在副本上,这样一来性能就会好很多,不管我们系统扩大到什么程度,性能显然都不会有大的问题。但是这里会有一个额外的问题,那就是因为每一个副本的更新可能都会不及时,所以他们的值就会有差别,这就会导致我们在不同副本读出来的值会有差异。 所以,最终这里有一个trade off,那就是系统的性能,可靠性和一致性。为了更好地理解这个关系,我们来先看看我们是怎么定义一致性的。 强一致性 假如所有的读写请求都是通过leader进行的,那么不管有多少的副本,对客户端来说,他们的操作都是原子的,他们总能够得到他们想要的数据。 当然因为一个请求并不是理解执行的,总归需要一个时间,数据的更新是发生在执行和完成之间,假如我们做这个保证,那这就是强一致性。 但是总是通过leader来执行也是有问题的,比如说假如客户端发送了一个请求给leader,在客户发送请求到leader之后,leader还是认为它自己是leader,但是事实上它已经不是了。这个时候,leader还来处理这个写的请求,那么系统其实就已经不是强一致性了。有处理这种情况,那么leader就需要问一下大多数副本,是不是还认为它是leader,只有这种情况下才能够执行写的请求,并且把response返回给客户端,这样的操作,显然增加了读的时间,性能必然会下降。 顺序一致性 前面我们讨论了强一致性,所有的读都会去leader那边,而事实上,这样做就会导致读的速度很慢,因为leader还需要和大多数副本进行商议。这种情况下为了提高读的性能,我们需要允许副本支持读。 尽管副本的读是在leader之后的,但是它的更新顺序始终是和leader一致的。假如客户端A总是读副本1,而客户端B总是读副本2,那么这两个客户端看到的数据就是不相同的,因为他们的副本并不是总是一致的。如下所示: 这里两个副本虽然更新的时间不同,但是他们的更新顺序则是相同的,这就是所谓的顺序一致性。 最终一致性 尽管我们希望增加读的速率,但是这里还有一个问题,副本假如出问题了怎么办?我们需要允许客户端查询所有的副本,从而来增加一致性。但是这里的对一致性来说就有了新的挑战,比如说假如客户端1开始是在副本1上查询,而副本2是落后副本1的,在某一个时间点,假如客户端切换到了副本2,会发现读出来的值竟然是以前的值,这个显然就是有问题的。这里唯一保证的就是所有的副本最终读出来的值都是一致的,这就是所谓的最终一致性。 使用最终一致性的系统是非常复杂的,因为这个和你平时的读写有很大的差别。可能会遇到很多很难发现的问题。所以这里就需要根据你的应用来看,它最终需要的是什么。比如说,这个最终一致性的系统对你想看看有多少人访问你的系统这样的应用是足够的,但对那些付费系统则显示强一致性是更好的选择。 总结 有很多文章会介绍各种一致性的模型,但是背后的理论其实是一致的:越严格的一致性,则需要牺牲越多的性能,以及发生问题时,可靠性越低。这就是所谓的PACELC理论。这个理论认为,分布式系统中有网络分区P,那么我们就需要在可靠性(A)以及一致性(C)之间进行选择。即使在没有分区的情况下正常运行,我们也必须在延迟(L)和一致性(C)之间进行选择。 参考文章:https://robertovitillo.com/what-every-developer-should-know-about-database-consistency/