clickhouse集群zookeeper平滑搬迁实践

本文涉及的产品
注册配置 MSE Nacos/ZooKeeper,118元/月
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
云原生网关 MSE Higress,422元/月
简介: clickhouse集群zookeeper平滑搬迁实践

背景


注:为简化表述,本文中将clickhouse简称为ck, 将zookeeper简称为zk。


我司从去年年底开始启动从香港到新加坡机房的迁移。目前Clickhouse集群所有实例都已经搬迁从香港搬迁到了新加坡机房,还剩下其依赖的Zookeeper集群在香港机房,因此我们近期准备将Zookeeper集群平滑搬迁到香港机房。

image.png


0.1 目标与挑战


0.1.1 zk跨洲搬迁需对用户基本无感知


ck集群发展到现在已经承载了整个公司的实时数据分析需求,还支持了许多在线服务。这要求ck集群不能够停机,在任何时候都是可用的。


ck集群每时每刻都在执行数据插入和查询、表变更,而ck在架构设计上重度依赖zk做元数据存储、副本同步和表变更,zk一旦不可用,ck的读写都会受到影响。zk集群的迁移中间必然引起leader的切换,如何在zk集群搬迁的过程中保证读写,这是一个不小的挑战。


0.1.2 热升级+动态配置更新


为了实现上面的目标,我们在迁移的过程中,一方面要从写入层做好重试,避免zk切主过程中的失败。同时,也要尽可能的缩短zk不可用的时间。对zk的操作都要采用热升级的方式,滚动操作,同时因为zk的集群ip都换了,必然要更改很多配置,所有的配置也尽量采用reload的方式,而不是重启服务。



一、整体方案


1.1 第一步:zk从静态配置版本升级到动态配置版本

zk 3.5.0之后支持动态配置特性。利用动态配置特性可方便进行扩容和缩容操作,而不需要对整个zk集群中的所有实例进行滚动重启。但是不巧的是,ck用的zk集群还没有使用动态配置,因此zk集群搬迁的第一步就是将zk集群从静态配置版本平滑升级到动态配置版本,简化后续的扩容和缩容操作。



1.2 第二步:zk扩缩容实现搬迁



第二步,在ck集群升级到动态配置版本之后,通过扩容和缩容操作实现zk集群从香港老机器到新加坡新机器的平滑搬迁:


  • 扩容:将新加坡机房的新机器一台一台加入到zk集群中


  • ck实例修改zk配置:将zk配置全部从老机器换成新机器


  • 缩容:将香港机房的老机器一台一台从zk集群中摘除。


image.png


二、遇到的问题和解决方案


为了保证线上zk搬迁过程中不出问题,我们事前进行充分的影响面预估和线下演练,在这个过程中发现了以下问题:


2.1 zk静态配置版本与动态配置版本不兼容


在1.1中,首先将zk中的Follower实例从静态配置升级到动态配置版本时,发现升级中的Follower实例报错:


Follower报错日志如下:


2021-02-25 11:07:03,081 [myid:5] - WARN  [QuorumPeer[myid=5](plain=/0:0:0:0:0:0:0:0:2185)(secure=disabled):Follower@96] - Exception when following the leader  
java.io.EOFException  
        at java.io.DataInputStream.readInt(DataInputStream.java:392)  
        at org.apache.jute.BinaryInputArchive.readInt(BinaryInputArchive.java:63)  
        at org.apache.zookeeper.server.quorum.QuorumPacket.deserialize(QuorumPacket.java:85)  
        at org.apache.jute.BinaryInputArchive.readRecord(BinaryInputArchive.java:99)  
        at org.apache.zookeeper.server.quorum.Learner.readPacket(Learner.java:158)  
        at org.apache.zookeeper.server.quorum.Learner.registerWithLeader(Learner.java:336)  
        at org.apache.zookeeper.server.quorum.Follower.followLeader(Follower.java:78)  
        at org.apache.zookeeper.server.quorum.QuorumPeer.run(QuorumPeer.java:1271)  
2021-02-25 11:07:03,081 [myid:5] - INFO  [QuorumPeer[myid=5](plain=/0:0:0:0:0:0:0:0:2185)(secure=disabled):Follower@201] - shutdown called  
java.lang.Exception: shutdown Follower  
        at org.apache.zookeeper.server.quorum.Follower.shutdown(Follower.java:201)  
        at org.apache.zookeeper.server.quorum.QuorumPeer.run(QuorumPeer.java:1275)  


同时,尚未升级的Leader实例报错如下:


2021-02-26 19:35:08,065 [myid:6] - WARN  [LearnerHandler-/xx.xx.xx.xx:52906:LearnerHandler@644] - ******* GOODBYE /xx.xx.xx.xx:52906 ********  
2021-02-26 19:35:08,066 [myid:6] - INFO  [WorkerReceiver[myid=6]:FastLeaderElection$Messenger$WorkerReceiver@285] - 6 Received version: b00000000 my version: 0
2021-02-26 19:35:08,066 [myid:6] - INFO  [WorkerReceiver[myid=6]:FastLeaderElection@679] - Notification: 2 (message format version), 5 (n.leader), 0xb0000000c (n.zxid), 0x2 (n.round), LOOKING (n.state), 5 (n.sid), 0xb (n.peerEPoch), LEADING (my state)b00000000 (n.config version)  
2021-02-26 19:35:08,067 [myid:6] - ERROR [LearnerHandler-/xx.xx.xx.xx:52908:LearnerHandler@629] - Unexpected exception causing shutdown while sock still open  
java.io.IOException: Follower is ahead of the leader (has a later activated configuration)  
        at org.apache.zookeeper.server.quorum.LearnerHandler.run(LearnerHandler.java:398)

经过定位,发现问题出在静态版本的Leader实例与动态版本的Follower实例不可共存。在串行升级zk的过程中,为了尽量减少zk集群不可用时间,我们先升级完所有Follower, 最后再升级Leader。 当一个Follower实例从静态配置版本升级到动态配置版本之后,此时Leader还处于静态配置版本,其config version是0; 而Follower此时处于动态版本,config version大于0。


Follower启动之后会请求Leader,请求数据中带上config version. Leader收到请求之后会对Follower的config version做校验,如果发现对方config version大于自己,便抛异常(Follower is ahead of the leader)并主动关闭连接。

if (learnerInfoData.length >= 20) {
long configVersion = bbsid.getLong();
if (configVersion > leader.self.getQuorumVerifier().getVersion()) {
throw new IOException("Follower is ahead of the leader (has a later activated configuration)");
}
}

而Follower读到EOF之后也会抛异常(EOFException),并不断重试。


摆在眼前的解决方案有两种:


  • 方案一:串行升级改成并行升级,避免静态版本与动态版本的实例同时存在。


  • 方案二:由于静态版本和动态版本同时存在的时间很短,zk增加一个临时版本,该版本中去掉Leader对Follower的config version检查,绕过上述问题


考虑到zk集群中数据较多,zk实例启动时间较长,并行升级会导致zk集群在2-4min内不可用。一旦出现问题发生回滚,zk集群不可用时间还会翻倍,风险较大。于是我们摒弃方案一,选择方案二:先将静态版本串行升级到去掉config version检查的静态版本,再升级到动态版本。




2.2 ck无法动态加载zk配置


在线下演练过程中发现,在1.2中,当修改clickhouse配置文件中的zk server列表后,配置变更并不会被ck动态加载。而clickhouse上的应用已经如此之多,其中不乏一些2B业务或对实时性要求非常强的业务,通过重启ck集群去加载新的zk server列表显然不可接受。


最终的解决方案是增加clickhouse对zk配置动态加载的支持,从而避免重启ck集群影响用户。目前这一优化已经合并到社区,PR: https://github.com/ClickHouse/ClickHouse/pull/14678



2.3 zk实例重启导致少量ck查询失败


ck查询一般不涉及zk交互,因此zk搬迁大部分情况下不影响ck查询。但是在线下演练过程中发现,当clickhouse开启了开关optimize_trivial_count_query之后, 对应PR: https://github.com/ClickHouse/ClickHouse/pull/7510,执行一些简单的select count查询会被zk搬迁所影响。


追查代码发现,optimize_trivial_count_query开启后,对于简单的select count查询,ck会跳过常规的查询过程,转而从metadata中获取总行数(在此过程中会访问zk),以此来提高select count的查询速度。因此,在zk集群搬迁之前,我们将clickhouse集群中的开关optimize_trivial_count_query设置为0,待zk搬迁完成之后再将其开启。



2.4 zk实例重启导致写入ck失败


在向ck写入数据时,ck依赖zk集群分配blockid,并将数据从当前副本同步到配偶副本。因此zk搬迁过程中必定会影响ck写入。而我们要做的便是将影响写入的时间段尽量缩短;同时一旦发现写入失败针对同一个分片下的副本不断重试,保证zk集群恢复时,ck写入也能自动恢复。


目前有Flink、Spark和clickhouse_sinker三个入口往ck写数据,在zk搬迁之前我们需要提前确保它们写ck的过程已经有了重试机制。



2.5 zk实例重启导致ck表变更失败


表变更操作包括创建/删除表、新增/删除/修改字段。ck集群在执行表变更时必然会访问ck集群,因此zk搬迁过程中会影响ck集群中的表变更。因为ck表变更相对于ck写入与查询来说频率较低。因此在zk搬迁过程中,我们对ck平台进行功能降级,即此时不支持ck表变更操作,以此避免zk实例重启导致ck表变更失败。



三、最终的搬迁方案


3.0 初始状态


假设香港机器:A1, A2, A3, A4, A5,新加坡机器:B1, B2, B3, B4, B5

初始状态下,zk集群部署于香港机房,初始版本为静态配置版本。


我们的目标是将zk集群搬迁到新机房,最终版本为动态配置版本。



3.1 升级到动态配置版本



版本升级路线:静态版本 -> (不带config version检查的)静态版本 -> 动态版本


3.1.1 静态版本 -> 不带(config version检查的)静态版本


串行升级,先升级Follower, 最后升级Leader。每升级完一台机器,检查集群中Leader/Follower状态,检查ck查询和写入是否有异常。等待zk实例完成启动后,接着再升级下一个zk实例。


预期影响:


  • 升级Follower实例时,ck到该zk实例上的连接被断开,部分ck写入可能会报zookeeper session expired错误,ck重连其他zk实例后恢复正常,恢复时间不超过40s。


  • 升级Leader实例时,zk会进入选举周期并会产生新的Leader, ck写入会报table in readonly mode错误,新Leader产生之后ck写入恢复正常,恢复时间不超过3min.

回滚方案:直接并行回滚到初始的静态版本



3.1.2 不带(config version检查的)静态版本 -> 动态版本


升级步骤、预期影响面、回滚方案同3.1.1


3.2 动态扩缩容



3.2.1 扩容:将新加坡新机器加入到zk集群中


串行扩容步骤:


  • 在新机器B1上部署zk实例, 其配置中包含A1-A5和B1。通过reconfig -add 6=B1:2888:3888;2181将B1加入到集群中。接着检查当前所有zk实例的本地配置是否已更新,检查Leader/Follower状态,检查ck读写是否有异常,确认无问题后扩容下一台机器


  • 在新机器B2上部署zk实例, 其配置中包含A1-A5和B1-B2。通过reconfig -add 7=B2:2888:3888;2181将B2加入到集群中。检查步骤同上



  • 重复执行以上步骤,直到所有新机器都已经加入到zk集群中。


预期影响:无


回滚步骤:在串行扩容过程中,如果有任何一步出现异常,则将新实例通过reconfig -remove <id>命令从集群中摘掉。


3.2.2 修改ck配置:将zk配置改成新加坡新机器


修改ck配置,将zookeeper-servers从旧机器A1-A5改成新机器B1-B5,并下发到所有ck实例。netstat命令检查ck是否与新zk实例建立连接。

<zookeeper-servers>
<node index="0">
<host>A1</host>
<port>2181</port>
</node>
<node index="1">
<host>A2</host>
<port>2181</port>
</node>
<node index="2">
<host>A3</host>
<port>2181</port>
</node>
<node index="3">
<host>A4</host>
<port>2181</port>
</node>
<node index="4">
<host>A5</host>
<port>2181</port>
</node>
</zookeeper-servers>

预期影响:无


回滚:一旦检查过程中发现异常,将ck配置回滚并重新下发。


3.2.3 缩容:将香港老机器从zk集群中摘掉


串行缩容过程中,应当遵循先缩容Follower实例,最后缩容Leader实例的顺序,具体步骤如下:


  • 缩容A1: 通过reconfig -remove 1=A1:2888:3888;2181命令,将老机器A1从集群中摘除。接着检查当前所有zk实例的本地配置是否自动更新,检查Leader/Follower状态,检查ck读写是否出现异常。确认无问题后,将老机器A1上的zk实例下线。


  • 缩容A2, 操作同上



  • 重复执行以上操作,直到所有香港老机器都已经从zk集群中摘除。


预期影响:同3.1.1


回滚:在串行缩容过程中,如果有任何一步出现异常,通过reconfig -add <id>=<ip>:2888:3888;2181命令将待下线实例重新加入zk集群中。



四、总结



通过线下环境中充分的zk搬迁演练,我们得以及时发现zk搬迁中出现的各种问题,并一一加以解决。最终在ck用户基本无感知的情况下,完成了zk集群从香港到新加坡的平滑迁移。

相关文章
|
1月前
|
消息中间件 分布式计算 关系型数据库
大数据-140 - ClickHouse 集群 表引擎详解5 - MergeTree CollapsingMergeTree 与其他数据源 HDFS MySQL
大数据-140 - ClickHouse 集群 表引擎详解5 - MergeTree CollapsingMergeTree 与其他数据源 HDFS MySQL
45 0
|
17天前
|
存储 Prometheus 监控
构建高可用性ClickHouse集群:从理论到实践
【10月更文挑战第27天】在数据驱动的时代,构建一个稳定、高效的数据库系统对于企业的业务发展至关重要。作为一名数据工程师,我深知数据库系统的高可用性和可扩展性对于支撑企业应用的重要性。在这篇文章中,我将分享如何构建一个高可用性的ClickHouse集群,从分布式表的设计到数据复制与分片,再到故障恢复机制,确保系统在大规模数据处理中的稳定性和可靠性。
49 0
|
18天前
|
存储 监控 大数据
构建高可用性ClickHouse集群:从单节点到分布式
【10月更文挑战第26天】随着业务的不断增长,单一的数据存储解决方案可能无法满足日益增加的数据处理需求。在大数据时代,数据库的性能、可扩展性和稳定性成为企业关注的重点。ClickHouse 是一个用于联机分析处理(OLAP)的列式数据库管理系统(DBMS),以其卓越的查询性能和高吞吐量而闻名。本文将从我的个人角度出发,分享如何将单节点 ClickHouse 扩展为高可用性的分布式集群,以提升系统的稳定性和可靠性。
44 0
|
1月前
|
SQL 消息中间件 分布式计算
大数据-143 - ClickHouse 集群 SQL 超详细实践记录!(一)
大数据-143 - ClickHouse 集群 SQL 超详细实践记录!(一)
72 0
|
1月前
|
SQL 大数据
大数据-143 - ClickHouse 集群 SQL 超详细实践记录!(二)
大数据-143 - ClickHouse 集群 SQL 超详细实践记录!(二)
57 0
|
1月前
|
存储 SQL 分布式计算
大数据-142 - ClickHouse 集群 副本和分片 Distributed 附带案例演示
大数据-142 - ClickHouse 集群 副本和分片 Distributed 附带案例演示
133 0
|
1月前
|
SQL 消息中间件 分布式计算
大数据-141 - ClickHouse 集群 副本和分片 Zk 的配置 Replicated MergeTree原理详解(一)
大数据-141 - ClickHouse 集群 副本和分片 Zk 的配置 Replicated MergeTree原理详解(一)
56 0
|
1月前
|
SQL 大数据
大数据-141 - ClickHouse 集群 副本和分片 Zk 的配置 Replicated MergeTree原理详解(二)
大数据-141 - ClickHouse 集群 副本和分片 Zk 的配置 Replicated MergeTree原理详解(二)
65 0
|
1月前
|
存储 SQL 分布式计算
大数据-139 - ClickHouse 集群 表引擎详解4 - MergeTree 实测案例 ReplacingMergeTree SummingMergeTree
大数据-139 - ClickHouse 集群 表引擎详解4 - MergeTree 实测案例 ReplacingMergeTree SummingMergeTree
31 0
|
5月前
|
存储 关系型数据库 数据库
【DDIA笔记】【ch2】 数据模型和查询语言 -- 多对一和多对多
【6月更文挑战第7天】该文探讨数据模型,比较了“多对一”和“多对多”关系。通过使用ID而不是纯文本(如region_id代替&quot;Greater Seattle Area&quot;),可以实现统一、避免歧义、简化修改、支持本地化及优化搜索。在数据库设计中,需权衡冗余和范式。文档型数据库适合一对多但处理多对多复杂,若无Join,需应用程序处理。关系型数据库则通过外键和JOIN处理这些关系。文章还提及文档模型与70年代层次模型的相似性,层次模型以树形结构限制了多对多关系处理。为克服层次模型局限,发展出了关系模型和网状模型。
59 6