众所周知, Clickhouse是没有事务能力的, 对写入数据也不承诺原子性保护, 但在业务使用过程中, 用户往往需要数据量尽量一致, 现在在使用CK时, 用户经常在校对离线和实时数据时, 发现数据行数都不一致.
因此, 我们需要给力CK加上一个原子性写入的保护, 但基于OLAP系统的吞吐量, 给CK加强一致事务保护是不现实的, 所以我们的目标并不是100%的原子性, 而是一个折中.
有个要注意的点, 这篇文章讨论的是写入的原子性
, 它只能保证写入要么全部成功, 那么全部失败; 它能保证最终一致性
, 但无法保证强一致性; 也无法承诺任何隔离性
; 但可以保证持久性.
实际上用ACID来评价这个原子性, 并不是很恰当, 例如最终一致性
是分布式复制协议里面的概念; 这里只是用数据库的ACID做为评价的指标.
实时数据写入流程
目前实时数据导入的流程大致入上图所示:
数据存储在Kafka中, 数据从Kafka读取, 并流向Flink程序. 由于Flink有比较完善的Checkpoint机制, 只要Source能够回放, 就能保证数据是一致的. 因此这个步骤不会丢失数据
数据在Flink中做完转化后, 会写入到CHProxy中. CHProxy是一个Clickhouse的代理, 负责鉴权或者流程控制等功能. 这个步骤比较容易丢数据, 因为Flink要求Sink算子能够幂等写入, 但CHProxy和Clickhouse现有功能都无法保证.
CHProxy接受到写入请求后, 会随机选择一个Clickhouse后端执行写入SQL. 由于随机选择, Flink重试后有可能导致数据写入到不同shard节点, 导致重复
- Flink选CHProxy也是随机的
- 写入时, 直接写入本地表, 数据没有做hash
Clickhouse节点接收到SQL请求后, 会将数据按照分区切分数据, 写入临时文件, 再分批提交. 提交过程失败, 会导致任务重试后数据重复(部分提交成功的情况)
多replica集群, 数据写入到主节点后, 会异步的同步到从节点, 此时主节点下线会导致数据丢失 (节点重启后, 还能恢复)
从上面的分析可知, 2,3,4,5都存在着数据重复或者丢失的风险, 其中2和3是机制问题, 一旦触发Flink重试(重启应用必然触发)/CHProxy随机路由(重试写入时就触发), 数据就会出现大概率重复. 而4和5是小概率实践, 4是一个磁盘写入操作, 即使顺序执行多个DP操作时, 出现异常的概率都是相对较小的;5的情况, 只要节点能够重启, 就能够保证最终一致性.