一、前言
广泛的讲,只要同一数据被存储于不同的物理位置,就存在一致性问题,就需要考虑如何保持多份数据的一致性,以及提供何种程度的一致性保证。
将数据存储于多个物理位置的原因:
- 数据备份,增强可靠性,比如金融数据、重要文件等
- 高可用,单点故障后仍然可用,通过多机、异地等达到5个9甚至更高的可用性
- 高并发,数据存于多台机器,通过负载均衡可以分担压力,比如海量查询
- 性能,利用局部性原理,将常用数据存放于更快速的设备(内存而不是磁盘),比如Redis、多核CPU的缓存一致性、普通应用中的缓存
- 区块链,安全(这里强调security而不是safety)
二、问题举例
1、区块链平台的高并发、高可用查询服务
场景简介
客户端通过RPC查询账户状态(余额、nonce等),请求将发送给edge集群。edge集群不存储数据,只负责将请求转发给存储数据的节点,edge需要实现负载均衡、限流、容错功能。负载均衡采用随机算法,容错采用forking(发送给两个存储节点),限流采用令牌桶算法。
缺点
没有额外机制的话,只能保证最终一致性(eventual consistency),即用户多次查询的结果可能不一致。比如账户A(余额100)给账户B(余额0)转账100,那么可能存在首次查询A余额=0,B余额=100,第二次查询却是A余额=100,B余额=0。原因在于前后两次查询被edge转发到不同的存储节点,而这两个节点的数据不保证强一致性(即整个存储集群对外看起来只有一台机器)。
这种性质使得RPC接口使用方的处理很复杂,因为使用方的用户可能无法接受上述这种结果,需要使用方封装掉。
可能的解决方案:
- 从超过一半节点查询结果
- leader存储节点,保证该节点正确性
- …
优点
- 高可用,只要存储集群有节点存活,就能获取结果。
- 高并发,通过负载均衡,可以有效利用所有存储节点的能力,远超单机。
这其实是分布式领域的一个著名问题
Is it better to be alive and wrong or right and dead?
正确但死去或者错误但活着
相关理论
CAP
P
指网络分区,这个不可避免,因此一般只能从C
(Consistency)、A
(Availability)中选择,大部分场景牺牲一致性(C
)来实现高可用(A
)。
Linearizability(线性一致性(线性性?))
也称作原子一致性、强一致性、即刻一致性或者外部一致性。准确的定义很晦涩,但基本思想是:
- 让整个系统看起来只有一份数据,
- 数据的操作是原子的。
原文:
linearizability[6] (also known as atomic consistency[7], strong consistency, immediate consistency, or external consistency [8]). the basic idea is to make a system appear as if there were only one copy of the data,and all operations on it are atomic.
causality
保证有因果关系的操作,不出现矛盾的情况,而其他数据,不同客户端看到的可能不一致。这个要求就弱于Linearizability,但肯定强于最终一致性。
2、缓存
主要关心内存数据更新后,并不立即持久化到硬盘,而是必须等待某些条件,这样可以减少硬盘IO,从而提升性能。但这也带来一个问题,DB与内存中的数据并不一致。
因此,必须保证在进程崩溃、机器重启等情况下:
- 能够最终恢复内存中的数据
- 崩溃前获取了内存数据的接口,即便使用了内存中未被持久化的数据,也不会有问题
实践中的问题
区块链系统中,db、cache的不一致,导致部分数据丢失。根本原因是业务获取内存中未持久化的数据,然后据此操作。具体如下:
区块链系统按一定准则存储状态快照,然后将之前的数据裁剪。假定现在最新出块高度100,快照高度80。裁剪模块从内存(cache)获得这两个数据后将高度低于80的数据全部删除。而此时,100、80并没有持久化到DB,DB中的数据分别是70、40。此时,系统崩溃并重启,随即发现没有高度[40-70-80]的数据。
解决方案是增加新接口,返回DB中的数据,或者内存中数据返回前需要先持久化。实际操作可以记录内存中数据的更新次数,保证DB与内存数据的落后量一定范围。裁剪不需要非常实时,关键是足够安全。从系统架构角度看,如果每次更新时,都强制据落db,通过内存cache提高的性能就大幅降低了。
3、P2P节点广播
P2P网络(存储网络)
采用何种算法来尽可能快的、用尽可能少的通信量来保证所有节点收到所有更新(updates)?即保证P2P系统的一致性。
固定广播模式
某个节点拥有全网络信息,它负责收集、广播更新
优点:简单
缺点:
- 无法应对单点故障
- 单点容易成为瓶颈
- 无法适应动态网络
- 无法水平扩展
反熵(anti-entropy,熵减更合适?)
熵指的是系统中的不确定性,反熵就是降低系统的不确定性,即让整个系统趋于一致。
方法:所有节点不断的与邻居节点交互信息。
优点:实现最终一致性
缺点:通信开销大,耗时长
gossip(Rumor mongering谣言)
类似新冠的传染,一传十,十传百…最终所有节点都有相关数据。与反熵不断交互相比,gossip只会和邻居交互热门数据,数据达到一定条件就不再热门。
理论上的通信复杂度O(NlnlnN),时间复杂度O(lnN),N为节点数量。具体的算法参照两篇论文。
4、多核CPU缓存一致性
最著名的应该是这个例子
初始状态
a = false, b = false; x = 0, y = 0
然后有两个线程分别如下执行
thread1
b = true
if a == true
x = 1
thread2
a = true
if b == true
y = 1
正常逻辑,a
和b
先后执行,那么在执行if a == true
和if b == true
,至少一个为true,也就是x, y
至少一个为1。但现实中,x, y
可能都是0。抛开指令重排序、乱序执行等,从一致性角度看,就是thread1、thread2不一致,thread1看到b=true,a=false,然后x=1就没执行,thread2看到a=true,b=false,然后y=1也没执行。
Rust,C++11
专门有内存排序(memory_order)
memory_order_seq_cst
(strong consistency),total order;
memory_order_acq_rel
memory_order_consume
,类比上面的causality
memory_order_relaxed
,
一致性要求一个比一个低,性能也一个比一个高,relax之上还有一个。
详细可以看multi-core memory consistency model
三、分布式存储
副本一致性(replica consistency),异步备份系统,比如:区块链系统中leader与follower之间
主要采用共识算法来实现:
- Raft
- BFT
共识算法的目的,实现total order,atomic broadcast。
四、其它一致性术语
1、数据库
ACID,指数据库从具体应用角度看,处于正常状态。
原文
refers to an application-specific notion of the database being in a “good state.”
简单点,是从客户角度,数据没有逻辑错误。比如两个A、B账号,分别由100,200元, 然后A给B转账10元。那么客户只能看到 A = 90, B = 210 或者A=100,B=200, 而不能是其它。比如A=90,B=200之类。
类比区块链,就是整个系统的账没有问题。
实际上,数据库所宣传的一致性,在某些特定场景下,是由问题的,具体可以参考Designing Data-Intensive Applications
。
2、一致性散列(Consistent hashing)
一种用于负载均衡的分区方法,将数据按key的hash分摊到不同的节点上。
原文
is an approach to partitioning that some systems use for rebalancing
很多P2P网络(kad)使用的DHT,某种程度也可以归为此类,本质都是将某个集合内的元素唯一映射到另一个集合?DHT不允许冲突,一致性散列一般是允许的。