进入到第五章了,来到了分布式系统之中最核心与复杂的内容:副本与一致性。通常分布式系统会通过网络连接的多台机器上保存相同数据的副本,所以在本篇之中,我们来展开看看如何去管理和维护这些副本,以及这个过程之中会遇到的各种问题。
在数据系统之中,我们通常会有这样几个原因来使用副本技术:
首先,如果副本的数据不随时间变化,那么副本的管理是比较简单的:只需要将数据复制到每个节点一次,就OK了。副本管理真正的困难在于对副本数据的修改,这会涉及到很多琐碎的问题。其次,副本复制时要考虑许多权衡,使用同步还是异步复制,以及如何处理失效的副本?接下来我们来一一探讨这个问题。
如何保障多个副本在不同节点上的一致性一直分布式系统之中的一个核心问题。分布式系统在写入数据时,需要由每个副本进行处理;否则,副本将不再包含相同的数据。Leader-Follower是一种常见的机制,我们来梳理一下它的原理:
Leader-Follower机制
许多关系数据库在同步副本时使用这样的机制,如PostgreSQL,MySQL,Oracle Data Guard 和SQL Server。同时许多非关系型数据库与分布式消息队列也采用这样的机制,包括MongoDB,Rethinkd,Kafka,RabbitMQ。
在副本进行主从复制时一个重要细节是复制是同步还是异步发生的?(在关系数据库中,这往往是一个可配置的选项。在其他系统之中,如Ceph,是系统默认的)
同步复制与异步复制的响应时间的比较
由上图可知,同步复制有相当大的延迟,而异步复制的响应相当快速。但是异步复制却不能保证完成所需要多长时间。有些情况下,Follower的数据可能比Leader上的数据落后几分钟或更多。如:节点之间存在网络问题或节点的故障恢复。如果Leader失败且不可恢复,则尚未复制到Follower的任何写操作都将丢失。
而同步复制的优点是保证了Follower与Leader之间的副本一致性,一旦任意一个Leader失效了,任何一个Follower的数据都与Leader相同。但是同步复制一旦出现网络或节点的故障,会导致无法处理写入。Leader必须阻止所有写入并等待Follow上的副本再次可用。如果所有的Follower都是同步复制,那么任何一个节点的中断都会导致整个系统瘫痪。在实际运用之中,如果在数据库上启用同步复制,通常其中一个副本是同步复制的,而另一个是异步复制的。如果同步的副本变得不可用或十分缓慢,可以将同步操作切换到另一个异步副本之中。这样保证了至少两个节点上有一个数据的最新副本:Leader和一个同步Follower。这种配置称之为半同步。(链式复制也是类似于半同步的一种复制机制,不丢失数据但仍能提供良好性能和可用性的复制方法。)
有时我们需要添加新的Follower来增加副本的数量,或者替换失败的节点。此时就需要确保新的Follower拥有一个正确的副本的数据。仅仅将数据文件从一个节点复制到另一个节点通常是不够的:客户端不停向系统写入数据,所以数据副本总是处于不断变化的状态。这里可以简单地通过锁定系统,使其拒绝客户端的写请求来使各个副本上保持一致,但这样会大大降低系统的可用性。所以我们需要一个不停机的方式来添加新的Follower:
在分布式系统之中,任何节点都可能出现故障,而能够在不停机的情况下重新启动单个节点是操作和维护是十分必要的。尽管每个节点故障,但我们需要让一个节点停机的影响尽可能小。
在Follower的本地磁盘上,都保存着从Leader收到的数据更改的日志。当一个Follower崩溃并重新启动,或者Leader与Follower之间的网络暂时中断。Follower可从它的日志找到故障发生之前处理的最后一个事务,然后连接到Leader并请求在Follower断开连接的时候发生的所有数据变化。(这个流程和添加新的Follower其实是同样的思路)
在处理Leader的失败时显然会更为棘手:其中一个Follower需要被提升为新的Leader,客户端也需要识别并且将后续的请求发送给新的Leader,而其他的Follower则需要开始在新Leader之下工作。处理Leader故障通常是如下的流程:
如果是异步复制的场景,新的Leader可能旧的Leader之前的完整的写入信息。最常见的解决方案是丢弃旧Leader之前写入多于新Leader的信息丢弃,但是这显然违反了数据系统写入持久性的要求。 在某些故障场景中,可能会出现两个节点都认为他们是Leader,这种情况被称为脑裂。此时两个Leader都会接受写请求,数据很可能会出现丢失或损坏。 什么时候进行故障切换也是一个值得探讨的问题:较长的超时时间意味着在Leader失效的情况下恢复时间更长。然而,如果时间太短,可能会有不必要的故障转移。例如,临时负载高峰时刻可能导致节点的响应时间增加到超时,那么不必要的故障转移会使情况变得更糟,而不是更好。为此,一些运营团队更愿意执行手动的故障转移,即使系统本身支持自动的故障转移。
日志在副本的一致性之中是至关重要的,所以我们接下来简要的梳理一下日志复制可用的方法:
副本可以增加系统的可伸缩性(处理比单个机器处理更多的请求)和降低延迟(将副本放置在离用户更近的地方)。写入操作必须通过Leader副本,但是只读查询可以在任何副本上进行。 对于一次写入,多次读取的应用来说,采用读扩展架构是十分合理的。但是由于上文提及的原因,我们通常不会采用同步复制的方式。这将导致数据出现明显的不一致性:如果您同时对Leader和Follwer执行相同的查询,可能会得到不同的结果,因为并不是所有的写入实时在Follower上反馈。这种不一致性仅仅是暂时状态,所以这种情况被称为最终一致性。
对于这种情况我们应该这么去处理和理解,我们下回分解~~~(第五章的内容炒鸡多,接下来会通过多篇读书笔记来给大家梳理,讲解,下一篇再见~~)