clickhouse集群zookeeper平滑搬迁实践

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
云原生网关 MSE Higress,422元/月
注册配置 MSE Nacos/ZooKeeper,118元/月
简介: 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月前
|
SQL 消息中间件 关系型数据库
ClickHouse(04)如何搭建ClickHouse集群
ClickHouse集群的搭建和部署和单机的部署是类似的,主要在于配置的不一致,如果需要了解ClickHouse单机的安装设部署,可以看看这篇文章,[ClickHouse(03)ClickHouse怎么安装和部署](https://zhuanlan.zhihu.com/p/532431053)。
219 1
|
1月前
|
Java Shell Linux
Zookeeper单机&集群安装
Zookeeper单机&集群安装
34 1
|
1月前
|
存储 分布式计算 资源调度
Hadoop【环境搭建 04】【hadoop-2.6.0-cdh5.15.2.tar.gz 基于ZooKeeper搭建高可用集群】(部分图片来源于网络)
【4月更文挑战第1天】Hadoop【环境搭建 04】【hadoop-2.6.0-cdh5.15.2.tar.gz 基于ZooKeeper搭建高可用集群】(部分图片来源于网络)
78 3
|
6天前
|
Java 网络安全
分布式系统详解--框架(Zookeeper-简介和集群搭建)
分布式系统详解--框架(Zookeeper-简介和集群搭建)
17 0
|
1月前
|
存储 Java 网络安全
ZooKeeper【搭建 03】apache-zookeeper-3.6.0 伪集群版(一台服务器实现三个节点的ZooKeeper集群)
【4月更文挑战第10天】ZooKeeper【搭建 03】apache-zookeeper-3.6.0 伪集群版(一台服务器实现三个节点的ZooKeeper集群)
49 1
|
1月前
|
存储 Java 网络安全
ZooKeeper【搭建 02】apache-zookeeper-3.6.0 集群版(准备+安装配置+启动验证)
【4月更文挑战第8天】ZooKeeper【搭建 02】apache-zookeeper-3.6.0 集群版(准备+安装配置+启动验证)
30 1
|
1月前
|
架构师 Shell Apache
Zookeeper集群搭建
Zookeeper集群搭建
|
1月前
|
Java 网络安全 Apache
搭建Zookeeper集群:三台服务器,一场分布式之舞
搭建Zookeeper集群:三台服务器,一场分布式之舞
142 0
|
1月前
|
消息中间件 存储 Kafka
Kafka【环境搭建 02】kafka_2.11-2.4.1 基于 zookeeper 搭建高可用伪集群(一台服务器实现三个节点的 Kafka 集群)
【2月更文挑战第19天】Kafka【环境搭建 02】kafka_2.11-2.4.1 基于 zookeeper 搭建高可用伪集群(一台服务器实现三个节点的 Kafka 集群)
157 1
|
1月前
|
算法 Java Linux
zookeeper单机伪集群集群部署
zookeeper单机伪集群集群部署
98 0