ClickHouse 是一个 MPP 架构的列式 OLAP 系统(如图-2),各个节点是对等的,通过 Zookeeper 协同数据,可以通过并发对各个节点写本地表的方式进行大批量的数据导入。 ClickHouse 的 data part 是数据存储的最小单元,ClickHouse 接收到的数据 Block 在写入时,会按照 partition 粒度进行拆分,形成一个或多个 data part。data part 在写入磁盘后,会通过后台merge线程不断的合并,将小块的 data part 合并成大块的 data part,以此降低存储和读取的开销。 在向本地表写入数据时,ClickHouse 首先会写入一个临时的 data part,这个临时 data part 的数据对客户端不可见,之后会直接进行 rename 操作,使这个临时 data part 成为正式 data part,此时数据对客户端可见。几乎所有的临时 data part 都会快速地成功被 rename 成正式 data part,没有被 rename 成功的临时 data part 最终将被 ClickHouse 清理策略从磁盘上删除。 通过上述分析,可以看出 ClickHouse 的数据写入有一个从临时 data part 转为正式 data part 的机制,加以修改可以符合两阶段提交协议,这是实现分布式系统中事务提交一致性的重要协议。 图-2 Flink作业写入ClickHouse 注:多个 Flink Task 可以写入同一个 shard 或 replica
Client 通过 HTTP Restful API 访问 ClickHouse Server,Client 与 ClickHouse Server 间一次完整事务的交互过程如图-5所示: 图-5 Clickhouse事务处理的时序图
正常流程:
Client 向 ClickHouse 集群任意一个 ClickHouse Server 发送 Begin Transaction 请求,并携带由 Client 生成的全局唯一的 Transaction ID。ClickHouse Server 收到 Begin Transaction 请求时,会向 Zookeeper 注册该Transaction ID(包括创建 Transaction ID 及子 Znode 节点),并初始化该 Transaction 的状态为 Initialized。
Client 接收到 Begin Transaction 成功响应时,可以开始写入数据。当 ClickHouse Server 收到来自 Client 发送的数据时,会生成临时 data part,但不会将其转为正式 data part,ClickHouse Server 会将写入的临时 data part 信息,以 JSON 的形式,记录到 Zookeeper 上该 Transaction 的信息中。
Client 完成数据的写入后,会向 ClickHouse Server 发送 Commit Transaction 请求。ClickHouse Server 在收到 Commit Transaction 请求后,根据 ZooKeeper 上对应的Transaction的 data part 信息,将 ClickHouse Server 本地临时 data part 数据转为正式的 data part 数据,并更新Transaction 状态为Committed。Rollback 的过程与 Commit 类似。
由图-6可以看出,无论ClickHouse 是否开启事务, ClickHouse 的吞吐量都与 Client 端并发写的线程数成正比。开启事务时,ClickHouse中临时 data part 不会立刻被转为正式 data part,所以在事务完成前大量临时 data part 不会参与 ClickHouse merge 过程,降低磁盘IO对写性能的影响,所以开启事务写性能较未开启事务写性能更好;但事务内包含的批次变多,临时 data part 在磁盘上的增多导致了合并时 CPU 的压力增大,从而影响了写入的性能,开启事务的写性能也会降低。 图-6 ClickHouse写入性能压测(一)