2、集群架构设计原则:高可用设计、数据一致性保证、扩展性设计
好,咱们直接切入正题。集群架构设计,说白了就是三个核心问题:挂了怎么办?数据乱不乱?不够用怎么加? 这三个问题搞定了,你的ClickHouse集群才算真正能扛得住金融级的生产压力。
我这些年帮不少银行和券商搭过集群,踩过的坑能绕西湖一圈。今天就把这些经验掰开揉碎了讲给你听。
2.1 高可用设计:多副本与故障转移
先聊高可用。金融场景下,节点宕机不是「如果」的问题,而是「什么时候」的问题。你想想看,凌晨三点磁盘满了、网络闪断、甚至机房空调坏了,这些我都遇到过。
ClickHouse的高可用,核心靠的是多副本 + 分布式DDL。
2.1.1 多副本机制
每个分片的数据,至少存2份。我习惯用ReplicatedMergeTree引擎族,它基于ZooKeeper或ClickHouse Keeper来协调副本。
核心配置示例:
-- 建表时指定副本
CREATE TABLE my_table ON CLUSTER my_cluster
(
id UInt64,
ts DateTime,
value Float64
) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/{table}', '{replica}')
PARTITION BY toYYYYMM(ts)
ORDER BY (ts, id);
注意看那个路径参数:{shard}和{replica}是宏变量。每个节点启动时配置好,系统自动识别。我曾经见过有人把这两个宏写反了,结果数据全写到同一个副本上,另一个副本永远空着——嗯,这种低级错误排查起来特别痛苦。
2.1.2 故障转移策略
当某个副本挂了,查询会自动路由到健康副本。这里有个关键点:读请求的负载均衡。
我个人习惯在config.xml里这样配:
<remote_servers>
<my_cluster>
<shard>
<internal_replication>true</internal_replication>
<replica>
<host>node1</host>
<port>9000</port>
</replica>
<replica>
<host>node2</host>
<port>9000</port>
</replica>
</shard>
</my_cluster>
</remote_servers>
internal_replication设为true,意思是写入时由副本自己内部同步,不用外部操心。这样即使一个节点挂了,写入也能继续。
避坑指南:我曾经遇到一个案例,某团队把internal_replication设成了false,结果写入时数据被重复插入,导致副本间数据不一致。排查了整整两天才发现是这里的问题。
2.2 数据一致性保证:Quorum写入
高可用搞定了,下一个问题就是:副本之间数据到底一致不一致?
ClickHouse默认是最终一致性。什么意思?就是写入一个副本后,其他副本异步同步。在同步完成前,你读到的数据可能不一样。
金融场景下,这显然不行。比如交易流水,必须保证「写进去就能读到」。
2.2.1 Quorum写入机制
解决方案就是Quorum写入。说白了,就是写入时要求至少N个副本确认成功,才返回给客户端「写入成功」。
用法很简单,在INSERT语句后加个设置:
INSERT INTO my_table VALUES (...)
SETTINGS insert_quorum = 2;
这里insert_quorum = 2表示至少2个副本写入成功才算数。如果副本总数是3,那Quorum就是2。你想想看,这样即使一个副本挂了,数据也不会丢。
我的经验:Quorum值一般设为N/2 + 1,其中N是副本总数。比如3副本就设2,5副本就设3。设太高会影响写入性能,设太低又保证不了一致性。
2.2.2 一致性级别选择
ClickHouse提供了几种一致性级别,我整理了个表格:
| 级别 | 说明 | 适用场景 |
|---|---|---|
| 最终一致性 | 默认模式,异步复制 | 日志、监控等非关键数据 |
| Quorum写入 | 至少N个副本确认 | 交易流水、订单等关键数据 |
| 强一致性 | 所有副本都确认 | 极少用,性能代价太大 |
我个人建议:金融核心业务用Quorum写入就够了。强一致性虽然听起来好,但性能下降太明显,实际收益不大。
2.3 扩展性设计:线性扩展与分片策略
最后聊扩展性。数据量大了怎么办?加机器呗。但加机器能不能线性提升性能,这就看分片策略了。
2.3.1 线性扩展的真相
ClickHouse的扩展性在OLAP领域是数一数二的。理论上,你加一台机器,查询性能就提升一份。但前提是——数据分布要均匀。
我见过最典型的反面案例:某公司用rand()做分片键,结果数据分布倒是均匀了,但查询时每个分片都要扫描全量数据,根本没法做分区裁剪。性能反而比单机还差。
2.3.2 分片策略选择
分片策略说白了就三种:
- 哈希分片:按某个字段的哈希值分片。适合点查询,比如按用户ID查。
- 范围分片:按时间或ID范围分片。适合范围查询,比如查某个月的数据。
- 随机分片:用
rand()。适合写入均衡,但查询效率低。
我个人的推荐:用时间+业务ID的复合分片键。比如:
ENGINE = Distributed(my_cluster, my_database, my_table,
cityHash64(toYYYYMM(ts) * 1000000 + id))
这样既保证了时间维度的分区裁剪,又让数据在分片间均匀分布。
核心原则:分片键要同时满足「写入均匀」和「查询局部性」两个条件。做不到完美时,优先保证查询效率。
2.4 知识体系总览
下面这张图,把本章的核心逻辑串起来了。你可以对照着看:
这张图把三个设计原则的关系讲得很清楚。高可用是基础,一致性是保障,扩展性是未来。三者缺一不可。
最后说一句:架构设计没有银弹。我见过很多团队一上来就追求「完美架构」,结果半年过去了还在讨论方案。我的建议是:先跑起来,再优化。用最简单的方案解决80%的问题,剩下的20%等遇到了再说。