一致性

来自智得网
跳转至: 导航、​ 搜索

简介

一致性模型

在分布式环境下,因为多副本,网络分区,硬件故障,物理距离等问题导致的数据乱序问题,延迟问题,不可达问题是不可避免的,一致性问题就是在诸多不确定因素下,保证程序的正确性。

影响正确性主要有以下的原因:

  • 并发

在单线程单核场景下,程序的执行顺序和程序表达的逻辑顺序是一致的,不会出现数据不一致的情况,但是在多线程场景下,多个线程共享存储器,线程读取的数据可以是其他线程写入的值,此时从当前线程的视角来看,数据的正确性因为共享存储的原因,需要更多的进行协调。

  • 延迟

延迟不仅仅发生在分布式系统下,即使是单机的数据读写也不是一个瞬时的过程,从距离CPU 30厘米的内存条中读取数据至少需要几个纳秒,而不同的数据机房甚至远隔几千公里。物理距离导致了不同服务器之间的数据会因为不同的延迟而数据不一致。

而在分布式系统中,因为延迟问题被放大,甚至会产生不可达的情况,所以我们没办法完全避免歧义的产生。只能增加一些特定的约束。这些约束就是一致性的理论基础,分为线性一致性和顺序一致性。

原理

线性一致性

虽然每个操作都是有耗时的,线性一致性的约束是指每个涉及到数据的操作都是原子的,这里的原子并非是指这些操作是物理上不可中断的,可以横跨多个系统组件甚至多台机器,但是其外部表现和一个原子操作是等效的。

我们可以利用线性一致性的原子性约束来安全地修改状态。我们定义一个类似CAS(compare-and-set)的操作,当且仅当寄存器持有某个值的时候,我们可以往它写入新值。 CAS操作可以作为互斥量,信号量,通道,计数器,列表,集合,映射,树等等的实现基础,使得这些共享数据结构变得可用。线性一致性保证了变更的安全交错。

此外,线性一致性的时间界限保证了操作完成后,所有变更都对其他参与者可见。于是线性一致性禁止了过时的读。每次读都会读到某一介于调用时间完成时间的状态,但永远不会读到读请求调用之前的状态。线性一致性同样禁止了非单调的读,比如一个读请求先读到了一个新值,后读到一个旧值。

由于这些强约束条件的存在,可线性化的系统变得更容易推理,这也是很多并发编程模型构建的时候选择它作为基础的原因。Javascript中的所有变量都是(独立地)可线性化的,其他的还有Java中的volatile变量,Clojure中的atoms,Erlang中独立的process。大多数编程语言都实现了互斥量和信号量,它们也是可线性化的。强约束的假设通常会产生强约束的保证。

顺序一致性

顺序一致性放松了对一致性的要求,不要求操作按照真实的时间序发生。不同进程间的操作执行先后顺序也没有强制要求,但必须是原子的,只是要求单个进程内的操作顺序必须和编码时的顺序一致。

因果一致性

因果一致性比同一进程下对每个操作严格排序的一致性(即顺序一致性)来的更宽松——属于同一进程但不同因果关系链的操作能以相对的顺序执行(也就是说按因果关系隔离,无因果关系的操作可以并发执行),这能防止许多不直观的行为发生。

串行一致性

关于线性一致性和串行一致性,看似十分相似,其实不然。串行一致性是数据库领域的概念,是针对事务而言的,描述对一组事务的执行效果等同于某种串行的执行,没有ordering的概念,而线性一致性来自并行计算领域,描述了针对某种数据结构的操作所表现出的顺序特征。串行一致性是对多操作,多对象的保证,对总体的操作顺序无要求;线性一致性是对单操作,单对象的保证,所有操作遵循真实时间序。