searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

Clickhouse协调系统的一致性

2023-04-20 09:57:55
67
0

前言

目前Clickhouse开源版本,已默认使用CH Keeper替代Zookeeper作为协调系统(coordination system),来处理数据复制和分布式DDL执行。

官方文档里面提到:

在早期的版本里面,Clickhouse是使用Zookeeper作为其协调系统的。但由于每个zk节点提供读服务都是通过本地读完成的,其协调算法ZAB (ZooKeeper Atomic Broadcast) 不能提供线性(linearizable)读取的保证。但CH Keeper使用RAFT算法的一种开源实现,允许线性读写。

那么什么是linearizability,为什么Clickhouse要关注这点呢?

一致性模型

要搞清楚什么是linearizability,需要先了解当前公认的一致性模型,不同的一致性等级之间的关系,以及各等级的优缺点和代价。

Jepsen模型

Jepsen(一款测试分布式系统一致性的主流工具) 在其官网上面,给出了根据两篇论文(高可用事务的特质与限制非事务分布式存储的一致性)总结了并发系统的常用一致性模型及它们之间的关系。

在上面这张图里面,每个圆角矩形方框代表一种一致性模型,箭头表示两个模型之间的从属关系(上面一层属于下面一层的子集,达到了上面一层的要求,通常也表示满足了下面一层的要求,也就是说越往上要求越高)。

比如:

  • 如果系统满足Strict Serializable,意味着同时达到了Serializable和Linearizable的一致性要求;
  • 假如系统满足Linearizable,也就意味着达到了Sequential的一致性要求;

……

图上不同颜色,代表了各种一致性等级,在网络故障情况下的系统可用性差异:

 不可用                   在某些类型的网络错误情况下不可用,部分或所有节点必须暂停对外服务以便保证数据安全

 部分可用               只要客户端的保证连接到相同的服务器上(不能切换服务节点),则服务在所有非故障节点上可用。

 完全可用               在所有非故障节点上均可用(即便各节点之间的网络隔离)

会话保证

上图的树状结构根节点往下分出两个分支,其中左子树为事务系统相关的ACID中的隔离性保证(Isolation Guarantee),右子树为会话保证(Session Guarantee)或非事务系统的一致性。由于Zookeeper和CH Keeper不属于事务数据库,因此本文不讨论左子树的事务隔离性。

目前Linearizability基本上是目前非事务分布式系统领域最强要求之一了,达到这个要求,系统基本上属于CAP里面的CP。

下面是Jepsen图对右子树各概念的解释

Writes Follow Reads

即会话因果性,这种情况保证了:假如一个进程读到一次写入 w1 的值 v,然后它再执行一次写入w2,那么w2的值一定在w1之后可见。一旦发生读取操作,就无法改变读之前发生的变化。

Monotonic Reads

假如一个进程执行读操作r1,然后执行r2,那么r2无法观察到r1已读到的写入之前的状态;也就是说读操作无法回溯;Monotonic Reads无法应用于多个进程读,只应用于同一个进程的读操作。

Monotonic Writes

假如一个进程执行写操作w1,然后执行w2,那么所有进程都能观察到w1在w2之前发生。Monotonic Writes无法应用于多进程写,只应用于同一个进程的写操作。

Read Your Writes

如果一个进程执行一次写操作w,然后执行一次读操作r,那么r一定能观察到w操作带来的影响。Read your writes无法应用于多进程操作,即无法保证多进程能满足这个要求。

PRAM

即Pipeline Random Access Memory,它尝试缓解现有的coherent内存模型来获得更高的并发性能。它要求同一个进程的所有写操作都可以被任何地方按其执行顺序观察到;然而它并不保证多进程写入的顺序。PRAM完全等价于Read Your Writes+Monotonic Writes+Monotonic Reads。

Causal

Causal一致性着重于一点:有因果关系的所有操作,都需要按一致的因果顺序出现在所有进程观察中;对因果无关的操作无此要求(与Sequential的区别)。

Sequential

Sequential意味着按一定顺序发生的所有操作,在每个独立进程中观察到的顺序是与操作顺序一致的(比Causal要求更高)。一旦发生网络分隔,部分或所有节点都会变成无法服务。

在一个Sequential一致系统里,一个进程可能会落后或超前于其他进程,比如它可能读到陈旧的状态,但一旦进程A从进程B处观察到某项操作,它就无法观察到此项操作之前的B的状态了。

Linearizable

Linearizability源自这篇论文的描述,它是最强的单对象一致性模型之一,意味着每个操作都是按照一定顺序 原子地发生,而这个顺序与操作的实时顺序一致。比如如果操作A在操作B开始前就结束了,那么操作B原则上只会在操作A之后才会生效。这个模型在网络分隔情况下,部分或所有节点都无法提供服务。

Linearizability虽然是单对象模型,但对象的范围是需要各系统定义的。比如,某些系统在KV存储上为单独的每个key提供Linearizability;某些系统基于一个表之上的多个key,或者基于一个DB库里面的多个表提供linearizable操作。

CK协调系统的一致性

ZK读不符合Linearizability

ZK写是符合Linearizability的:

ZK保证整个集群只有一个节点作为leader,写请求都要提交到leader排队,并按顺序发送到集群中所有节点,然后得到大多数节点的同意后(避免网络分区导致的脑裂),才确认一次写请求有效并返回结果。

但ZK读却不符合:

ZK读为了保证性能,一般从本地节点读取数据。但这种方式显然无法保证Linearizable的一致性,因为多个节点之间的数据写入都不一定已经同步成功。

即便是通过ZK sync read接口读虽然是通过leader读取结果,看起来保证写入成功后才读取;但一旦出现脑裂,则客户端可能会从少数区leader处读取到旧数据

CH Keeper读符合Linearizability

ZK不同,CH Keeper从leader处读数据,必须得到整个集群里大部节点的同意(Raft协议保证)因此CH Keeper避免了脑裂情况下读不一致的问题。

由此可以看出,要达到Linearizability,就要付出性能降低的代价。

社区对ZK和CH Keeper的读写压测对比也可以侧面反映此点(单节点部署时读性能CH Keeper显著高于ZK,而3节点集群部署时却低于ZK)

0条评论
0 / 1000