分布式一致性的一点个人理解

这里纯粹是写一点个人感想,完全不保证正确性。
分布式一致性作为一个巨大的概念,内涵可以非常广。

  • 分布式事务
  • 缓存一致性
  • 消息系统的exactly once语义。
  • zookeeper的最终一致性

在大部分的时候,数据一致性并不是一个非常严重的问题(至少没有人的生命重要)。**除了涉及到钱的时候。**设想,各国的央行都采用数据库来记账。如果一个国家坚决认为另外一个国家欠自己钱而另外一个国家的数据库里没有对应记载并怀疑对方的目的。那么战争就开始了。而我们可能永远不会知道差错只是一个分布式一致性。

从zookeeper谈起

简单而言,你想让A组件做一件事,并让B组件做另一件事, 并试图保证最终一致性,都叫分布式一致性的内涵。为什么是最终一致性?因为BASE理论和CAP理论,在物理学家解决不了网络分区问题前,数学家我们最多只能在系统可用的前提下,实现最终的一致性。理论上讲(参考Fischer等的论文),两台机器理论上无法达到一致的状态。两台都不可能,三台可能吗?当然是更不可能。

实现BASE理论,数学家给我们提供了paxos和其他算法,被应用于zookeeper上。zookeeper虽然是分布式的去中心化的,但是能保证集群内对一个问题最终能有一个一致的算法,只要你保证别有一半(包含)的节点不同时挂掉。虽然我可能一辈子都无法理解这个算法,但请允许我献上自己膝盖。这个算法并非没有缺点,我们知道,一旦leader节点因为某周问题与其他节点失去联系了,其他的节点会重新选举leader。漫长的选举事件可能会持续一两分钟,选取期间新的服务不可能注册上来。这“似乎”是不可容忍的。当你需要访问zookeeper的节点以百万计数的时候,zookeeper一样会成为瓶颈。
http://www.sohu.com/a/246888995_683048
但是paxos算法开销太大,对我们程序员也太复杂了。所以,其实我们做不到BASE。有些解决方案干脆舍弃CAP中的C也就是完全舍弃“分布式事务”这个概念。比如说Euraka,专心保证高可用。所以说Euraka是比Zookeeper更好的服务注册中心。这是在一窝蜂的似的使用ZK然后因为服务数量太大踩坑之后清醒。
我们当然也可以在日常业务中使用paxos算法。比如说用ZK记录一笔订单。不出意外,所有节点最终将对这笔订单形成一致的看法。我们知道,ZK的写是严格顺序性单线程的且写成功必须要等到一半节点对一件事情有统一认知上。然而,我们采用一个单机数据库难道性能不是更好嘛?或者说,一个主库,一群从库?

方案之主从数据库(伪装的分布式一致性)

仿照Zookeeper,我们也可以有一个下面的方案。
在这里插入图片描述
但这个方法一样是以牺牲写吞吐量为代价的。其实,这个方案就是zookeeper的简化版。具体的性能取决于你相较Paxos算法简化了多少(也就是牺牲了多少一致性)。另外一种方案如下。
在这里插入图片描述
当然,你也可以看到,如果在从数据库拉取主数据记录前主数据中间的连接完蛋了,那你也就无法谈及分布式一致性了。
上面我们谈的一致性,只是指主从数据库之间的一致性。同样,两个没有主从关系的数据库一样会有一致性问题。下面这个方案,恐怕就是人类在面对分布式一致性问题时第一个想到的方案了。

方案之数据库分布式事务规范

https://blog.csdn.net/define_us/article/details/108998152
这里不在赘述。
想说的是,其实无论是哪种方式,到最后都是最大努力通知。比如说2PC,如果最后一个C死活不成功也就没有办法了。TCC也是一样。

缓存一致性

数据库是一个节点,缓存是一个节点。两者之间的一致性,就是缓存一致性。
https://blog.csdn.net/define_us/article/details/83339123

但是采用先update数据库,再删除缓存的方式可以避免分布式事务的复杂度。原因是假设了update比select时间更长。这个假设不一定成立。但是缓存一般有超时时间。所以一致性相对保证较低。相对而言效率比较高。

分布式唯一ID的(伪分布式一致性)

如果我们有办法快速获取一个严格顺序分布式唯一ID,分布式一致性的问题也就可以迎刃而解。设想一个有很多人的会议室,每个人都有一个账本,希望和他人保持一致。需要写入数据的人,只要获取一个全局顺序ID,以该ID将消息告诉给别人就可以了。

这样其他人无论在任何时候离开或者进入会场,都会根据当前的全局ID和自己本地存储的ID的差距,向其他人请求自己没有的记录。系统的最终一致性很容易就能保证。即使在传播过程中该消息丢失了,除非彻底丢失,否则整个系统会达到最终一致性。即使彻底丢失,整个系统仍然是最终一致的。

但是显然,这种方法是一个伪分布式,因为一个中心化的全局唯一ID。分布式唯一严格顺序ID问题和全局锁问题等价(全局锁可以使用两个分布式唯一ID采用排队的方式实现),反过来全局锁也很容易实现分布式唯一ID。如果能有全局锁,其实整个系统已经变成了一个串行系统,而不再是分布式了。

消息队列的一致性

消息队列的Exactly Once

实际为了保证这一点,Kafka让每一个Producer发送数据的每个<Topic, Partition>都对应一个从 0 开始单调递增的Sequence Number。Broker 端也会为每个<PID, Topic, Partition>维护一个序号,并且每次 Commit 一条消息时将其对应序号递增。对于接收的每条消息,如果其序号比 Broker 维护的序号(即最后一次 Commit 的消息的序号)大一,则 Broker 会接受它。否则就会向Producer返回错误。如果Producer接到来自broker的成功返回,则进行本地记账(认为成功发送了一条数据)。

整个流程如下

发送者发送消息---->接受者接收消息------->接收者记账-------->发送者接收到成功返回------->发送者记账

这种方法是否能保证分布式一致性呢?可以。但是,这就是一个彻头彻底的CP方案了。因为,只要接受者故障,发送者就没有办法记账了。整个系统不再具有更新自己状态的能力。有人提出了另一个方案,就是在发送端增加一个ID。这个ID记录系统的状态。然后按照上述机制向接受者发送事件。即使接受者故障,发送者只要继续增加ID,等待接受者上线再发送积压的时间就可以了。
为了分析这一方案,我们回到事件的本质。单向的Exactly Once消息传递其实很容易实现。只要给每一个消息进行唯一编号,然后无限重发,接收端根据编号进行过滤就行,可以论证只要时间足够长,发送者和接受者能达到一致的状态(最终一致性)。但是此时,整个系统实际上已经不是分布式系统了。因为只有发送者持有系统的状态。和zookeeper的master不同,此时的发送者一旦挂掉并丢失本地所有记录,这个个系统的状态就永久性丢失了。

流数据处理中Exactly Once

不少流处理引擎都宣称自己支持Exactly Once,比如说Flink,JStorm,Storm Trident,Kafka Stream等。实现的机制基本上都是2PC。一个普遍的缺陷是不能实现端到端的Exactly Once。比如说一个组件更新了外部系统,那么回滚,只能回滚自身状态,外部系统就无能为力,依赖使用者的实现了。

事务性消息

看一个常见的金融CASE。
在这里插入图片描述

一个合理可接受的系统是3,4,5必须同时发生或者都不发生。
此时,就需要引入一种机制,也就是事务性消息.事务性消息的实质就是,发布一个消息,和本地的另一个动作(可能是更新数据库,可能是一个外部访问,可能是发送另一条消息,必须同时成功,或者同时失败。)最简单的思路显然就是2PC。先发送消息,成功后进行本地其他动作的预提交,正常后提交消息或者回滚消息。
在这里插入图片描述

kafka的Producer支持发送多条消息作为一个事务。为此引入了broker端的Transaction Coordinator。其维护了Transaction Log。Producer将在处理完多条消息后向Transaction Coordinator发送commit或者abort,Transaction Coordinator会通知broker和consumer关于某个消息决定。在收到最终决定之前,consumer实际上是将已经拉到本地的未决消息缓存或者不从broker拉取未决消息。这个说白了就是一个2PC。producer先发给多个外部(consumer,coordinator,broker)一个消息,成功后,再发送确认信息。至于确认信息有没有被他们收到?我们是伟大的2PC,我说过我保证分布式一致性了吗?

kafka支持的另一种常见事务是接收Kafka——处理——发送到Kafka的例子。同样利用了Producer的上述机制。不过是把消息是否发送成功的依赖由上面的“发送别的消息”变成了“消费处理流程”。唯一需要注意的是,消费这一过程会对外部系统产生影响,所以,Kafka提供了sendOffsetsToTransaction,通知broker端将对自己消费kafka的offset更新也纳入到同一个transaction以内。

RocketMQ也号称自己支持事务消息。
https://blog.csdn.net/define_us/article/details/83309749
其实现的机制无非是2PC加上回查。所谓回查,就是既然分布式一致性如此难做到,那么我们consumer就定期把哪些producer发过来但是没有确认的事件再拉出去问Producer就好了嘛。无论如何,我可以把consumer端的commit和producer的commit进行一下对比,该回滚哪一个就很清楚了。很暴力的解决方式。但是,第一是2PC已经能解决绝大部分问题,第二是这个回查机制本身也很复杂。

总结

物理学上所谓的“量子隧穿效应”:一个能量为E的粒子,向前方一个具有能量大于E的势垒射去,这个粒子有一定的几率从势垒穿过去。这个势垒就类似于宏观世界的墙,所以微观粒子通常可以轻松实现“穿墙”。现实生活中,我们也并非没有穿墙的可能(表面意思)。只是概率很低,低到我们不会去一次次撞墙企盼自己可以穿过去。但是,这里我想说的是,如果连一面墙都不可靠,那么我们为什么要苛求自己的系统保证分布式一致性呢?我们活在一个概率世界里,努力并不一定会被他人认可,偷懒也不一定被他人发现,分布式一致性吗?

  • 永远要重视日志。存储在掉电不失的介质上的记录是我们在寻求外部系统帮助前最后的救命稻草。当然,这种存储介质(最常见就是磁盘)一样是一个容易单点的故障的系统组件。所以我们又发展了磁盘阵列技术(RAID)或者像在程序射击时进行了冗余存储(Kafka,HDFS等的replica)机制。对于后一种方案,如何让不同的磁盘保证一致性同样又回到了原点。不过故障的概率就要小了点。对于前一种方案,RAID组成的磁盘在逻辑上就是一块磁盘,也就不是什么分布式系统了。

  • 永远要重视维护工作。不要允许你的员工踹机箱。机房空调一定不要省电。雇佣更多的客服来解决问题。便宜的农民工没有了,但是便宜的大学生遍地都是。不是吗?开除一个需要哄着供着的科学家可以换来一堆员工,开会也更有面子。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值