大数据基础理论——分布式一致性

存在分布式一致性的原因

在分布式系统中,为了保证数据的可靠性与性能,我们不可避免的对数据进行复制多节点存储,而数据一致性主要为了解决分布式多个存储节点情况下怎么保证逻辑上相同的副本能够返回相同的数据

由于采用多机器进行分布式部署的方式提供服务,必然存在着数据的复制。分布式系统的数据复制需求主要来源于以下两个原因:
1.为了增加系统的可用性,以防止单点故障引起的系统不可用;
2.提高系统的整体性能,通过负载均衡技术,能够让分布式在不同地方的数据副本都能够为用户提供服务。

数据复制在可用性和性能方面给分布式系统带来的巨大好处是不言而喻的,然而数据复制所带来的一致性挑战,也是每一个系统研发人员不得不面对的。
所谓分布一致性问题,是指在分布式环境中引入数据复制机制之后,不同数据节点之间由于网络延时等原因很容易产生数据不一致的情况,并无法依靠计算机应用程序自身解决的数据不一致的情况。简单讲,数据一致性就是指在对一个副本数据进行更新的时候,必须确保也能够更新其他的副本,否则不同副本之间的数据将不一致。
总得来说,我们无法找到一种能够满足分布式系统所有系统属性的分布式一致性解决方案。因此,**如何既保证数据的一致性,同时又不影响系统运行的性能,是每一个分布式系统都需要重点考虑和权衡的。**于是,分布式一致性级别由此诞生。

分布式一致性的类型

强一致性

也称为原子一致性(Atomic Consistency)或者线性一致性(Linearizable Consistency)。

它对一致性的要求有两个:

  • 任何一次读都能读到某个数据的最近一次写的数据。
  • 系统中的所有进程,看到的操作顺序,都和全局时钟下的顺序一致。

强一致性要求无论数据的更新操作是在哪个副本上执行,之后所有的读操作都要能够获取到更新的最新数据。

对于单副本的数据来说,读和写都是在同一份数据上执行,容易保证强一致性,但对于多副本数据来说,若想保障强一致性,就需要等待各个副本的写入操作都执行完毕,才能提供数据的读取,否则就有可能数据不一致,这种情况需要通过分布式事务来保证操作的原子性,并且外界无法读到系统的中间状态。

显然这强一致性对全局时钟有非常高的要求。强一致性,只是存在理论中的一致性模型,比它要求更弱一些的,就是顺序一致性。

顺序一致性

与强一致性相比,它们都能够保证所有进程对数据的读写顺序保持一致(任何一次读都能读到某个数据的最近一次写的数据)。

区别:顺序一致性虽然通过逻辑时钟保证所有进程保持一致的读写操作顺序,但这些读写操作的顺序跟实际上发生的顺序并不一定一致。而线性(强)一致性是严格保证跟实际发生的顺序一致的。

弱一致性

这种一致性级别约束了系统在数据写入成功后,不承诺立即可以读到写入的值,也不久承诺多久之后数据能够达到一致,但会尽可能地保证到某个时间级别(比如秒级别)后,数据能够达到一致状态。

最终一致性

最终一致性是弱一致性的一个特例,系统会保证在一定时间内,能够达到一个数据一致的状态。这里之所以将最终一致性单独提出来,是因为它是弱一致性中非常推崇的一种一致性模型,也是业界在大型分布式系统的数据一致性上比较推崇的模型。

最终一致性同样存在着很多的变种,最终一致性的不同方式可以进行组合,例如「单调读一致性」和「读己之所写一致性」就可以组合实现。并且从实践的角度来看,这两者的组合,读取自己更新的数据,和一旦读取到最新的版本不会再读取旧版本,对于此架构上的程序开发来说,会少很多额外的烦恼。

最终一致性细分
  • 因果一致性(Causal consistency):即进程A在更新完数据后通知进程B,那么之后进程B对该项数据的范围都是进程A更新后的最新值
    • 如果没有因果关系的 C 进程将会遵循最终一致性的规则。
    • 因果一致性(Casual Consistency)在一致性的要求上,又比顺序一致性降低了:
    • 它仅要求有因果关系的操作顺序得到保证,非因果关系的操作顺序则无所谓。

因果相关的要求是这样的:
本地顺序:本进程中,事件执行的顺序即为本地因果顺序。
异地顺序:如果读操作返回的是写操作的值,那么该写操作在顺序上一定在读操作之前。
闭包传递:和时钟向量里面定义的一样,如果a->b,b->c,那么肯定也有a->c

  • 谈己之所写一致性(Read your writes):进程A更新一项数据后,它自己总是能访问到自己更新过的最新值
  • 会话一致性(Session consistency):将数据一致性框定在会话当中,在一个会话当中实现读己之所写的一致性。即执行更新后,客户端在同一个会话中始终能读到该项数据的最新值。
  • 单调读一致性(Monotonic read consistency):如果一个进程从系统中读取出一个数据项的某个值后,那么系统对于该进程后续的任何数据访问都不应该返回更旧的值。
  • 单调写一致性(Monotoic write consistency):一个系统需要保证来自同一个进程的写操作被顺序执行(系统保证对同一个进程的写操作串行化)

At Most Once/At Least Once/Exactly Once

At Most Once

本质上是一尽力而为的方法。保证数据或事件最多由应用程序中的所有算子处理一次。 这意味着如果数据在被流应用程序完全处理之前发生丢失,则不会进行其他重试或者重新发送。

At Least Once

应用程序中的所有算子都保证数据或事件至少被处理一次。这通常意味着如果事件在流应用程序完全处理之前丢失,则将从源头重放或重新传输事件。然而,由于事件是可以被重传的,因此一个事件有时会被处理多次,这就是所谓的至少一次。

Exactly Once

即使是在各种故障的情况下,流应用程序中的所有算子都保证事件只会被『精确一次』的处理。
事件的处理可以发生多次,但是该处理的效果只在持久后端状态存储中反映一次。因此,我们认为有效地描述这些处理语义最好的术语是『有效一次』(effectively once)。

通常使用两种流行的机制来实现『精确一次』处理语义。

  • 分布式快照 / 状态检查点
    • 此持久后端状态用于保存流应用程序的全局一致状态检查点(每个算子的检查点状态)
  • 至少一次事件传递和对重复数据去重
    • 持久后端状态用于存储每个算子的状态以及每个算子的事务日志,该日志跟踪它已经完全处理的所有事件。
分布式快照 / 状态检查点

实现『精确一次』的分布式快照/状态检查点方法受到 Chandy-Lamport 分布式快照算法的启发[1]。通过这种机制,流应用程序中每个算子的所有状态都会定期做 checkpoint。如果是在系统中的任何地方发生失败,每个算子的所有状态都回滚到最新的全局一致 checkpoint 点。在回滚期间,将暂停所有处理。源也会重置为与最近 checkpoint 相对应的正确偏移量。整个流应用程序基本上是回到最近一次的一致状态,然后程序可以从该状态重新启动。

至少一次事件传递和对重复数据去重

在每个算子上实现至少一次事件传递和对重复数据去重来。使用此方法的流处理引擎将重放失败事件,以便在事件进入算子中的用户定义逻辑之前,进一步尝试处理并移除每个算子的重复事件。此机制要求为每个算子维护一个事务日志,以跟踪它已处理的事件。利用这种机制的引擎有 Google 的 MillWheel 和 Apache Kafka Streams。

分布式快照与至少一次事件传递和重复数据删除的比较

分布式快照 / 状态检查点的优缺点:
优点:

  • 较小的性能和资源开

缺点:

  • 对性能的影响较大
  • 拓扑越大,对性能的潜在影响越大

至少一次事件传递以及重复数据删除机制的优缺点:
优点:

  • 故障对性能的影响是局部的
  • 故障的影响不一定会随着拓扑的大小而增加

缺点:

  • 可能需要大量的存储和基础设施来支持
  • 每个算子的每个事件的性能开销

两阶段提交

两阶段提交就是说要分两步提交。存在一个负责协调各个本地资源管理器的事务管理器,本地资源管理器一般是由数据库实现,事务管理器在第一阶段的时候询问各个资源管理器是否都就绪?如果收到每个资源的回复都是yes,则在第二阶段提交事务,如果其中任意一个资源的回复是no,则回滚事务。

  • 第一阶段(prepare):事务管理器向所有本地资源管理器发起请求,询问是否是ready状态,所有参与者都将本事务能否成功的信息反馈发给协调者;
  • 第二阶段(commit/rollback):事务管理器根据所有本地资源管理器的反馈,通知所有本地资源管理器,步调一致地在所有分支上提交或者回滚。
存在的问题

同步阻塞:当参与事务者存在占用公共资源的情况,其中一个占用了资源,其他事务参与者就只能阻塞等待资源释放,处于阻塞状态。

单点故障:一旦事务管理器出现故障,整个系统不可用。

数据不一致:在阶段二,如果事务管理器只发送了部分 commit 消息,此时网络发生异常,那么只有部分参与者接收到 commit 消息,也就是说只有部分参与者提交了事务,使得系统数据不一致。

不确定性:当协事务管理器发送 commit 之后,并且此时只有一个参与者收到了 commit,那么当该参与者与事务管理器同时宕机之后,重新选举的事务管理器无法确定该条消息是否提交成功。

分布式锁

在很多互联网产品应用中,有些场景需要加锁处理,比如:秒杀,全局递增 ID,楼层生成等等。大部分是解决方案基于 DB 实现的,Redis 为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对 Redis 的连接并不存在竞争关系。

分布式锁是控制分布式系统之间同步访问共享资源的一种方式。
在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。

简单的理解就是:分布式锁是一个在很多环境中非常有用的原语,它是不同的系统或是同一个系统的不同主机之间互斥操作共享资源的有效方法。
1、为避免特殊原因导致锁无法释放, 在加锁成功后, 锁会被赋予一个生存时间(通过 lock 方法的参数设置或者使用默认值), 超出生存时间锁将被自动释放。
2、锁的生存时间默认比较短(秒级, 具体见 lock 方法), 因此若需要长时间加锁, 可以通过 expire 方法延长锁的生存时间为适当的时间. 比如在循环内调用 expire。
3、系统级的锁当进程无论因为任何原因出现 crash,操作系统会自己回收锁,所以不会出现资源丢失。
4、但分布式锁不同。若一次性设置很长的时间,一旦由于各种原因进程 crash 或其他异常导致 unlock 未被调用,则该锁在剩下的时间就变成了垃圾锁导致其他进程或进程重启后无法进入加锁区域

分布式锁的一些特点
  • 互斥性:和我们本地锁一样互斥性是最基本,但是分布式锁需要保证在不同节点的不同线程的互斥。
  • 可重入性:同一个节点上的同一个线程如果获取了锁之后那么也可以再次获取这个锁。
  • 锁超时:和本地锁一样支持锁超时,防止死锁。
  • 高效,高可用:加锁和解锁需要高效,同时也需要保证高可用防止分布式锁失效,可以增加降级。
  • 支持阻塞和非阻塞:和 ReentrantLock 一样支持 lock 和 trylock 以及 tryLock(long timeOut)。
  • 支持公平锁和非公平锁(可选):公平锁的意思是按照请求加锁的顺序获得锁,非公平锁就相反是无序的。这个一般来说实现的比较少。

参考资料

分布式事务,这一篇就够了
分布式一致性
从分布式一致性谈到CAP理论、BASE理论
分布式系统的一致性
谈谈流计算中的『Exactly Once』特性
分布式锁
再有人问你分布式锁,这篇文章扔给他

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值