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 知识体系总览

下面这张图,把本章的核心逻辑串起来了。你可以对照着看:

ClickHouse集群架构设计原则 高可用设计 数据一致性 扩展性设计 多副本 + 故障转移 ReplicatedMergeTree Quorum写入 insert_quorum参数 分片策略 线性扩展 三者缺一不可:高可用保命,一致性保真,扩展性保量 金融级集群 = 三者平衡的艺术

这张图把三个设计原则的关系讲得很清楚。高可用是基础,一致性是保障,扩展性是未来。三者缺一不可。

最后说一句:架构设计没有银弹。我见过很多团队一上来就追求「完美架构」,结果半年过去了还在讨论方案。我的建议是:先跑起来,再优化。用最简单的方案解决80%的问题,剩下的20%等遇到了再说。

专注资料整理