对于大多数分布式软件而言,数据的一致性问题是其必须要考虑的内容,zookeeper是最常见的也是使用最广泛的一个解决数据一致性问题的工具,clickhouse在其分布式方案的设计中也引入了zookeeper的使用,并强依赖于此。本文根据最近的一些项目经验,从原理和实践上来简单总结一下zookeeper在clickhouse中的使用吧~~

1. zookeeper的用途

zookeeper从存储的角度来看类似于一颗文件树,每个节点称之为znode,基本的工作原理类似于消息的发布订阅模式,客户端程序通过watch某个znode,既可以感知znode节点上内容的变化,也可以感知到该znode下子节点数量的变化。基于此衍生出了zookeeper的四大应用场景:

配置管理

简单来讲就是利用znode节点来存储分布式软件的配置信息,所有的集群节点监听这些znode,当配置内容发生变更时,所有节点都可以感知到并加载更新后的配置。例如HBase即是通过这种方式来获取到集群的配置信息。

命名服务

命名服务有点儿类似于DNS,即在分布式应用中我们希望通过名称而非IP去访问服务,那么就可以借助zookeeper的znode来存储名称与IP的映射关系了。

分布式锁

基于zookeeper实现分布式锁是利用了zookeeper的临时顺序节点的特性:每个微服务应用在访问某个需要加锁的对象的时候,都需要现在该节点下创建一个临时顺序节点,因为是有序的,每个线程可以拿到所有的节点做判断自己是不是最小的那个,如果是则获取锁,使用完之后释放锁(断开连接即可);如果不是则表示锁已经被抢占,则只需监听其上一个节点即可,如果上一个节点释放了锁,则该节点可继续获取到锁。使用网上别人画的一幅图吧,看着就比较清晰了。

在这里插入图片描述

集群管理

因为可监听子节点数量的变化,因此常用来做集群节点状态的管理,当集群添加或移除节点时,其他节点都可以感知到这种变化。kafka就使用了zookeeper来用作consumer的上下线管理。

2. zookeeper之于clickhouse

2.1 为什么要使用zookeeper

众所周知,clickhouse是一款单机性能强悍的数据库,但正所谓“双拳难敌四手,好汉架不住人多”,如果不支持横向扩展的话,那么clichouse相比其他分布式的MPP数据库就没有什么优势了,所以理所当然的,clickhouse引入了分布式集群的工作模式;那么既然是分布式软件,就需要考虑数据的一致性问题,在这种情况下,考虑使用zookeeper也就不足为奇了,尽管它可能不是最优的解决方案,但至少让clickhouse现在具备了“打群架”的能力了~~

2.2 zookeeper的使用原理

TIPS:在clickhouse中,zookeeper不参与任何实质性的数据传输。

zookeeper在clickhouse中主要用在副本表数据的同步(ReplicatedMergeTree引擎)以及分布式表(Distributed)的操作上。

2.2.1 副本表的写入

以一个分片一个副本举例,每次新建一张表后,clickhouse会在zookeeper上创建一个目录,目录结构如图:
在这里插入图片描述
其中test01是库名,test12是表名;每张表下面的数据结构(挑一些主要的):

  • metadata:记录的是该表的元数据信息,包括主键,分区键,索引粒度等;
  • log:每次操作记录,包括INSERT/MERGE等常规操作
  • columns:字段新仙尼
  • leader_election:选举出来的leader信息
  • replicas:副本信息,每个副本是一个子目录
  • mutations:mutation操作的记录

以INSERT操作为例,假设R1和R2是两个副本名称,在R1节点上执行插入操作,其核心流程如下:

  1. 在本地执行分区目录的写入,想zk的blocks目录下写入该分区的block_id
  2. 然后由副本R1向zk上的/log目录推送操作日志,日志的内容如图:在这里插入图片描述
    表示操作内容为get下载,需要下载的分区是202110152110_1327_1327_0
  3. R2会一直监听/log节点,监测到有日志变化,就会从log里读取任务,但不会立刻执行,而是将任务放置到自己目录下的队列里,这样设计时为了避免同时收到多个操作请求的时候处理不过来的情况。在这里插入图片描述
  4. R2基于/queue队列开始执行任务:向远端副本发起get请求,下载指定分区的数据
  5. R1相应请求,将分区数据返回给R2
  6. R2得到数据后在本地完成写入

2.2.2 分布式表的DDL操作

同样先来看一下zk上的目录结构:在这里插入图片描述
这次是在task_queue目录下,主要节点说明如下:

  • active:保存当前集群内状态为active的节点
  • finished目录:用于检查任务的完成情况,每个节点执行完毕后就会在该目录下写入一条记录。

以创建表为例说明一下整个流程:

  1. 在分片shard1上执行操作CREATE TABLE ON CLUSTER,则shard1负责将该语句记录到zk上的ddl目录下,同时由这个节点负责监控任务的执行进度
  2. 分片shard1和shard2都在监听ddl/query-*的内容,当监听到有变化时,将日志拉取到本地,然后判断各自的host是否在hosts列表(zk上query节点的内容)中,如果在则执行该操作,执行完成后将状态写入finished节点下;如果不包含,则忽略。
  3. shard1在执行语句后会阻塞等待所有节点都执行完成,如果指定超时时间(默认为180秒)内未完成,则转到后台线程继续等待。

2.3 zookeeper的配置

在/etc/clickhouse-server目录下创建一个metrika.xml的配置文件(如果已存在,则直接往里面写入即可),新增内容:

 <zookeeper-servers>
                        <node index="1">
                        <host>10.10.1.20</host>
                        <port>2181</port>
        </node>
                        <node index="2">
                        <host>10.10.1.21</host>
                        <port>2181</port>
        </node>
                        <node index="3">
                        <host>10.10.1.22</host>
                        <port>2181</port>
        </node>
 </zookeeper-servers>

其中10.10.1.20~1.22是三个zk节点。
然后在全局配置config.xml文件中引入metrika.xml,并引用zookeeper配置的定义:

<include_from>/etc/clickhouse-server/metrika.xml</include_from>

<zookeeper incl="zookeeper-servers" optional="true" />

注意:该文件中的incl里面的名称应该与metrika.xml文件中的节点名称保持一致。

3. 常见问题

3.1 Table is in readonly mode

  • 问题现象:在笔者的项目环境下,由于存在大数据量的数据写入(200MB/s以上的写入速度),时常会在clickhouse-server的日志中看到Table is in readonly mode的告警信息。
  • 问题原因:翻阅资料后发现这其实是很多人都会遇到的一个问题,大体原因当然是和zk的处理性能相关,随着写入数据量的增多,zk的log和snapshot文件会不断膨胀,有时就会出现zk服务不可用的情况,而clickhouse的ReplicatedMergeTree又强依赖于zk,一旦zk不可用,为了保证数据的一致性,系统就会进行“写保护”,出现上述提示。
  • 解决办法:
    • 方案1:调整use_minimalistic_part_header_in_zookeeper参数; 网上有些朋友说是调整这个参数可以减少写入zk日志的数据量,但因为我们使用的是21的版本,此版本默认这个参数已经是开启了,因此这个方案对我们实际无效。
    • 方案2:一个CH集群使用多个zk集群来提供服务; 这属于压力转移的策略,我们最后也无奈使用了这种办法,当然能解决问题,但带来的副作用就是zk集群运维管理成本的上升。

4. 后记

去zookeeper化现在似乎正在成为一股风潮,包括kafka在内的多个重量级分布式软件都在新版本中取消了对zookeeper的依赖,似乎clickhosue也在朝这方面努力,新的版本已经开始在试用clickhouse-keeper,一个用来替代zookeeper的方案,然后笔者注意到CH作者在最新的2022年Roadmap规划中,clickhouse-keeper满足生产环境的需求赫然在列。后面我们会跟进这方面的测试,看看是不是能够更加契合我们的使用场景。

在这里插入图片描述

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐