Kafka 在 Yelp 的应用十分普遍。事实上,咱们 天天经过各类集群发送数十亿条消息。在这背后,Kafka 使用 Zookeeper 完成各类分布式协调任务,例如决定哪一个 Kafka broker 负责分配分区首领,以及在 broker 中存储有关主题的元数据。java
Kafka 在 Yelp 的成功应用说明了咱们的集群从其首次部署 Kafka 以来经历了大幅的增加。与此同时,其余的 Zookeeper 重度用户(例如 Smartstack 和 PaasTA)规模也在增加,给咱们的共享 Zookeeper 集群添加了不少负担。为了缓解这种状况,咱们决定让咱们的 Kafka 集群使用专门的 Zookeeper 集群。node
因为咱们很是依赖 Kafka,因维护形成的任何停机都会致使连锁反应,例如显示给业务全部者的仪表盘出现延迟、日志堆积在服务器上。那么问题就来了:咱们是否能够在不引发 Kafka 及其余 Zookeeper 用户注意的状况下切换 Zookeeper 集群?安全
通过团队间对 Kafka 和 Zookeeper 的几轮讨论和头脑风暴以后,咱们找到了一种方法,彷佛能够实现咱们的目标:在不会致使 Kafka 停机的状况下让 Kafka 集群使用专门的 Zookeeper 集群。bash
咱们提出的方案能够比做天然界的 细胞有丝分裂:咱们复制 Zookeeper 主机(即 DNA),而后利用防火墙规则(即细胞壁)把复制好的主机分红两个独立的集群。服务器
有丝分裂中的主要事件,染色体在细胞核中分裂网络
让咱们一步一步深刻研究细节。在本文中,咱们将会用到源集群和目标集群,源集群表明已经存在的集群,目标集群表明 Kafka 将要迁移到的新集群。咱们要用到的示例是一个包含三个节点的 Zookeeper 集群,但这个过程自己可用于任何数量的节点。架构
咱们的示例将为 Zookeeper 节点使用如下 IP 地址:tcp
源 192.168.1.1-3分布式
目标 192.168.1.4-6工具
首先,咱们须要启动一个新的 Zookeeper 集群。这个目标集群必须是空的,由于在迁移的过程当中,目标集群中的内容将被删除。
而后,咱们将目标集群中的两个节点和源集群中的三个节点组合在一块儿,获得一个包含五个节点的 Zookeeper 集群。这么作的缘由是咱们但愿数据(最初由 Kafka 保存在源 Zookeeper 集群中)被复制到目标集群上。Zookeeper 的复制机制会自动执行复制过程。
把来自源集群和目标集群的节点组合在一块儿
每一个节点的 zoo.cfg 文件如今看起来都像下面这样,包含源集群的全部节点和目标集群中的两个节点:
server.1=192.168.1.1:2888:3888
server.2=192.168.1.2:2888:3888
server.3=192.168.1.3:2888:3888
server.4=192.168.1.4:2888:3888
server.5=192.168.1.5:2888:3888复制代码
注意,来自目标集群的一个节点(在上面的例子中是 192.168.1.6)在该过程当中保持休眠状态,没有成为联合集群的一部分,而且 Zookeeper 也没有在其上运行,这是为了保持源集群的 quorum。
此时,联合集群必须重启。确保执行一次滚动重启(每次重启一个节点,期间至少有 10 秒的时间间隔),历来自目标集群的两个节点开始。这个顺序能够确保源集群的 quorum 不会丢失,并在新节点加入该集群时确保对其余客户端(如 Kafka)的可用性。
Zookeeper 节点滚动重启后,Kafka 对联合集群中的新节点一无所知,由于它的 Zookeeper 链接字符串只有原始源集群的 IP 地址:
zookeeper.connect=192.168.1.1,192.168.1.2,192.168.1.3/kafka复制代码
发送给 Zookeeper 的数据如今被复制到新节点,而 Kafka 甚至都没有注意到。
如今,源集群和目标集群之间的数据同步了,咱们就能够更新 Kafka 的链接字符串,以指向目标集群:
zookeeper.connect=192.168.1.4,192.168.1.5,192.168.1.6/kafka复制代码
须要来一次 Kafka 滚动重启,以获取新链接,但不要进行总体停机。
拆分联合集群的第一步是恢复原始源 Zookeeper 及目标 Zookeeper 的配置文件(zoo.cfg),由于它们反映了集群所需的最终状态。注意,此时不该重启 Zookeeper 服务。
咱们利用防火墙规则来执行有丝分裂,把咱们的联合集群分红不一样的源集群和目标集群,每一个集群都有本身的首领。在咱们的例子中,咱们使用 iptables 来实现这一点,但其实能够两个 Zookeeper 集群主机之间强制使用的防火墙系统应该都是能够的。
对每一个目标节点,咱们运行如下命令来添加 iptables 规则:
$source_node_list = 192.168.1.1,192.168.1.2,192.168.1.3
sudo /sbin/iptables -v -A INPUT -p tcp -d $source_node_list -j REJECT
sudo /sbin/iptables -v -A OUTPUT -p tcp -d $source_node_list -j REJECT复制代码
这将拒绝从目标节点到源节点的任何传入或传出 TCP 流量,从而实现两个集群的分隔。
经过防火墙规则分隔源集群和目标集群,而后重启
分隔意味着如今两个目标节点与其余节点是分开的。由于它们认为本身属于一个五节点的集群,并且没法与集群的大多数节点进行通讯,因此它们没法进行首领选举。
此时,咱们同时重启目标集群中每一个节点的 Zookeeper,包括那个不属于联合集群的休眠节点。这样 Zookeeper 进程将使用步骤 2 中提供的新配置,并且还会强制在目标集群中进行首领选举,从而每一个集群都会有本身的首领。
从 Kafka 的角度来看,目标集群从发生网络分区那一刻起就不可用,直到首领选举结束后才可用。对 Kafka 来讲,这是整个过程当中 Zookeeper 不可用的惟一一个时间段。从如今开始,咱们有了两个不一样的 Zookeeper 集群。
如今咱们要作的是清理。源集群仍然认为本身还有两个额外的节点,咱们须要清理一些防火墙规则。
接下来,咱们重启源集群,让只包含原始源集群节点的 zoo.cfg 配置生效。咱们如今能够安全地删除防火墙规则,由于集群之间再也不须要相互通讯。下面的命令用于删除 iptables 规则:
$source_node_list = 192.168.1.1,192.168.1.2,192.168.1.3
sudo /sbin/iptables -v -D INPUT -p tcp -d $source_node_list -j REJECT
sudo /sbin/iptables -v -D OUTPUT -p tcp -d $source_node_list -j REJECT复制代码
咱们用于测试迁移过程正确性的主要方法是分布式压力测试。在迁移过程当中,咱们经过脚本在多台机器上运行数十个 Kafka 生产者和消费者实例。当流量生成完成后,全部被消费的数据有效载荷被汇集到单台主机上,以便检测是否发生数据丢失。
分布式压力测试的工做原理是为 Kafka 生产者和消费者建立一组 Docker 容器,并在多台主机上并行运行它们。全部生成的消息都包含了一个序列号,能够用于检测是否发生消息丢失。
为了证实迁移的正确性,咱们须要构建一些专门用于测试的集群。咱们不是经过手动建立 Kafka 集群,而后在测试完之后再关掉它们,而是构建了一个工具,能够在咱们的基础架构上自动生成和关闭集群,从而能够经过脚原本执行整个测试过程。
这个工具链接到 AWS EC2 API 上,并用特定的 EC2 实例标签激活多台主机,容许咱们的 puppet 代码配置主机和安装 Kafka(经过 External Node Classifiers)。这样咱们就能够从新运行迁移脚本,并屡次模拟迁移过程。
这个临时集群脚本后来被用于建立临时 Elasticsearch 集群进行集成测试,这证实了它是一个很是有用的工具。
咱们发现,phunt 的 Zookeeper smoketest 脚本在迁移过程当中可用于监控每一个 Zookeeper 集群的状态。在迁移的每一个阶段,咱们在后台运行 smoketest,以确保 Zookeeper 集群的行为符合预期。
咱们的第一个用于迁移的计划涉及关闭 Kafka、把 Zookeeper 数据子集复制到新集群、使用更新过的 Zookeeper 链接重启 Kafka。迁移过程的一个更精细的版本——咱们称之为“阻止和复制(block & copy)”——被用于把 Zookeeper 客户端迁移到存有数据的集群,这是由于“有丝分裂”过程须要一个空白的目标 Zookeeper 集群。用于复制 Zookeeper 数据子集的工具是 zkcopy,它能够把 Zookeeper 集群的子树复制到另外一个集群中。
咱们还添加了事务支持,让咱们能够批量管理 Zookeeper 操做,并最大限度地减小为每一个 znode 建立事务的网络开销。这使咱们使用 zkcopy 的速度提升了约 10 倍。
另外一个加速迁移过程的核心功能是“mtime”支持,它容许咱们跳过复制早于给定修改时间的节点。咱们所以避免了让 Zookeeper 集群保持同步的第 2 个“catch-up”复制所需的大部分工做。Zookeeper 的停机时间从 25 分钟减小为不到 2 分钟。
Zookeeper 集群是轻量级的,若是有可能,尽可能不要在不一样服务之间共享它们,由于它们可能会引发 Zookeeper 的性能问题,这些问题很难调试,而且一般须要停机进行修复。
咱们能够在 Kafka 不停机的状况下让 Kafka 使用新的 Zookeeper 集群,可是,这确定不是一件小事。
若是在进行 Zookeeper 迁移时容许 Kafka 停机,那就简单多了。
欢迎学Java和大数据的朋友们加入java架构交流: 855835163
加群连接:jq.qq.com/?_wv=1027&a…群内提供免费的架构资料还有:Java工程化、高性能及分布式、高性能、深刻浅出。高架构。性能调优、Spring,MyBatis,Netty源码分析和大数据等多个知识点高级进阶干货的免费直播讲解 能够进来一块儿学习交流哦