《数据密集型应用系统设计》分布式系统的挑战和一致性

柏舟   新冠4年 06-15

《数据密集型应用系统设计》讨论了:

我个人觉得数据库的数据结构和复制、分区、事务属于技术细节,看起来很无聊。而一致性和共识是一个分布式比较突出的特点,与原子操作等具有惊人的相似性,能够从一个更高的角度看待分布式系统状态问题。

在之前的文章:状态资源分类和《领域驱动设计》我从领域对象的状态,分析了如何对系统进行设计。而《数据密集型应用系统设计》这本书更侧重于从基础设施的角度思考现有的计算设备存储的问题和如何进行状态管理。

核心思路和问题

在单节点开发程序时,程序要么工作,要么出错。而分布式系统中,各种各样的事情都可能出错,可能会出现系统一部分工作正常,但某些部分出现难以预料的故障,主要有物理硬件和底层软件的限制和问题:

分布式系统需要使用不可靠的组件搭建可靠的系统。它主要存在两方面的可靠性问题:

一般来讲,我们更希望当出现故障的时候,系统宁愿崩溃也不要返回错误的结果。

线性化模型

提供只有单副本的假象,且所有操作都是原子的。可线性化与可串行化是不同的,可线性化是读写寄存器的最新值保证,它并不要求将操作组合到事务中,而可串行化可以保证事务与串行执行是相同的(即每次执行一个事务)。

我个人的理解是线性化只提供了一组原子操作,如:set, add, cas(比较和设置)。在此基础上的组合操作并不能满足原子操作的要求。但是,可以在此基础上实现信号量等锁机制,使竞争资源操作串行化。所以不管锁的具体如何实现,它必须满足可线性化。

常见用途

实现方式

代价

只要有不可靠的网络,都会发生违背线性化的风险。一旦发生网络故障,必须要么选择一致性(失效/只读),要么选择可用性(数据不一致)。

如果满足线性化,读写请求的响应时间与网络延迟成正比。

因果关系

一个请求可能存在两种情况:

大部分系统要求的是因果一致性而不是可线性化,可线性化一定意味着因果关系,放弃可线性化能够获得更好的性能。为保持因果关系,需要知道那个操作发生在前,在处理一个请求时,必须确保所有因果在前的请求都完成处理。

一个请求可能读取修改多个表的字段,为了追踪整个数据库的因果关系,可以使用一种类似于版本向量的技术。为了维护版本序列号的因果关系,有以下的解决方法:

C++ Atomic memory order的例子

名字 规则
memory_order_relaxed 不对执行顺序做任何保障
memory_order_acquire 所有后续的读操作均在本条原子操作完成后执行
memory_order_release 所有之前的写操作完成后才能执行本条原子操作
memory_order_acq_rel 同时包含memory_order_acquire和memory_order_release标记
memory_order_consume 本线程中,所有后续的有关本原子类型的操作,必须在本条原子操作完成后执行
memory_order_seq_cst 全部存取都按顺序执行

从Atomic的设计也能看出因果一致性和可线性化的区别。

共识

几个节点就某件事达成一致。场景:

实现方式:

容错算法的性质:

总结

《数据密集型应用系统设计》详细的介绍了基础设施层的状态问题和管理方法。在实际设计中,需要综合考虑领域对象的复杂性,尽量选择简单的方案,将领域的状态管理复杂性托管到基础设施中。比如,面临的最终问题可以归结于共识,并且有容错需求,那么最好采用ETCD等验证过的系统。

领域层的开发更多的是将多种数据系统组合起来,满足领域对象不同的事务性、分析性需求。基础设施的一致性、容错性等复杂度在领域层发生了转移,领域层的复杂性更多的体现在异构数据系统的组合使用上。在CQRS架构中介绍了领域层数据系统的设计方法。