不依赖zookeeper的kafka
https://redpanda.com/guides/kafka-tutorial/kafka-without-zookeeper
多年来,人们一直在同时使用Apache ZooKeeper和Apache Kafka。但是自Apache Kafka 3.3发布以来,它就可以在没有ZooKeeper的情况下运行。同时它包含了新的命令kafka-metadata-quorum和kafka-metadata-shell?该如何安装新版kafka,以及如何使用新命令,在本文中,我将回答这些和其他相关的问题。
历史背景和时间线
很久以前,Apache Kafka服务器是一个独立的服务:一个简单而功能强大的应用程序,很快就受到了许多人的喜爱。然而,它缺乏高可用性和弹性。系统管理员解决这个问题的标准方法通常是创建多个代理副本用于复制目的,通过Apache ZooKeeper进行协调,该技术很快成为标准Kafka部署的一部分,两者协同工作。
然而,Apache ZooKeeper 并非万无一失的解决方案。随着时间的推移,Apache Kafka 技术的弹性、可扩展性和性能期望发生了变化,导致了更加严格的需求。进行了许多变更,其中最重要的是将消费者偏移量从 ZooKeeper 迁移到 Kafka,逐步在 Kafka 工具中删除 ZooKeeper 连接主机,并实现了著名的 KIP-500(Kafka 改进提案 500)。
KIP-500 出现在 2.8 版本中,当时 Kafka Raft(KRaft,用于管理元数据的一种一致性协议 Raft 实现)作为早期访问功能出现,尽管在 2022 年 10 月 3 日标记为生产就绪。从 ZooKeeper 迁移到 KRaft 功能的早期访问迁移计划预计将在 Kafka 3.4 版本中发布。其生产就绪版本计划在 Kafka 3.5 版本中发布,同时停止支持 ZooKeeper。最终计划是从 Kafka 4.0 开始,所有部署都将在没有 ZooKeeper 的情况下运行。
Kafka version | State |
---|---|
2.8 | KRaft early access |
3.3 | KRaft production-ready |
3.4 | Migration scripts early access |
3.5 | Migration scripts production-ready; use of ZooKeeper deprecated |
4.0 | ZooKeeper not supported |
使用zookeeper(左)与不用zookeeper(右)
在没有 ZooKeeper 的情况下设置 Kafka 集群,会有一种特殊类型的服务器 - 控制器(controller)。控制器服务器形成一个集群仲裁(quorum)。集群使用 KRaft 算法(我们不在范围内讨论算法的理论描述,更多详情请参考 KRaft 文档或 Raft 文档)选择一个领导者,该领导者开始为连接以拉取集群状态元数据的其他代理(broker)服务的请求提供服务。代理的模型已经发生了变化:以前,活动的控制器将更改推送到代理,而现在代理从领导者控制器中拉取元数据。
Kafka 社区在其最新发布版本中实现了许多内部更改,其中这些是最重要的之一:
- Kafka 集群的扩展限制已得到解决:Kafka 可以处理更多的主题和分区,并且启动和恢复时间得到了显着改善。Kafka 控制器不需要从 ZooKeeper 中读取所有的元数据 — 每个控制器都在本地保存元数据,这节省了将集群恢复运行所需的宝贵时间。
- 使用 Kafka 技术的知识要求和生产设置比同时使用 Kafka 和 ZooKeeper 更简单,因为每种技术都需要设置系统配置、安全性、可观察性、日志记录等。技术越少,依赖关系和相互连接就越少。
不使用 ZooKeeper 部署 Kafka 集群
我们将部署一个由四个节点组成的集群,其中包括三个控制器节点(server1、server2 和 server3),它们形成一个控制器仲裁,以及一个普通的代理节点(server4)。我们将使用 Ubuntu 操作系统,但您也可以使用您喜欢的其他受支持的发行版。
对于每个主机,连接并运行以下操作:
- 安装 Java 运行时环境,这是运行 Apache Kafka 所必需的:
sudo apt-get install -y default-jre
- 下载并解压缩软件包,创建用户和数据目录,然后切换到该用户:
wget https://downloads.apache.org/kafka/3.7.0/kafka_2.13-3.7.0.tgz
sudo tar -xzvf kafka_2.13-3.7.0.tgz -C /opt
sudo mkdir -p /data/kafka
sudo useradd -m -s /bin/bash kafka
sudo chown kafka -R /opt/kafka_2.13-3.7.0 /data/kafka
sudo su - kafka
您将在解压后的目录中找到三个默认的 KRaft 配置文件示例。请注意属性 process.roles,该属性定义了服务器是否同时为控制器、代理或两者兼有。在此步骤中不需要执行任何操作。
ls -1 /opt/kafka_2.13-3.7.0/config/kraft/
Output:
broker.properties
controller.properties
server.properties
现在,我们将生成一个唯一的集群ID。您将创建并复制一个通用唯一标识符(UUID),该标识符用于在同一集群中唯一标识代理。稍后我们将使用UUID值。您还将在某些命令输出中看到UUID值。按照以下步骤进行:
/opt/kafka_2.13-3.7.0/bin/kafka-storage.sh random-uuid
Output:
jkUlhzQmQkic54LMxrB1oA
现在我们准备创建一个由 server1、server2 和 server3 组成的集群仲裁。对于每个服务器,选择一个唯一的(在集群内)节点ID值。为简单起见,我们将使用1表示 server1,2表示 server2,3表示 server3。将相应的ID值设置为 CURRENT_SERVER_INDEX。还要记得更新集群 UUID 为您自己的值。以下命令将更新 controller.property 文件并启动 Kafka 守护进程:
export CURRENT_SERVER_INDEX=1
export CLUSTER_UUID=jkUlhzQmQkic54LMxrB1oA
sed -i "s#node.id=.*#node.id=${CURRENT_SERVER_INDEX}#g" controller.properties
sed -i "s#controller.quorum.voters=.*#controller.quorum.voters=1@server1:9093,2@server2:9093,3@server3:9093#g" controller.properties
sed -i "s#log.dirs=.*#log.dirs=/data/kafka#g" controller.properties
/opt/kafka_2.13-3.7.0/bin/kafka-storage.sh format -t ${CLUSTER_UUID}-c /opt/kafka_2.13-3.7.0/config/kraft/controller.properties
/opt/kafka_2.13-3.7.01/bin/kafka-server-start.sh -daemon /opt/kafka_2.13-3.7.0/config/kraft/controller.properties
请注意,您已经使用 kafka-storage 工具生成了一个 UUID 并为新的 KRaft 格式格式化了数据目录。在执行 kafka-server-start.sh 后,您将获得一个正在运行的 Kafka 控制器服务。
对 server2 和 server3 重复此操作,对应的 ID 分别为 2 和 3。
最后,我们可以通过在 server4 上运行以下命令来创建一个普通的代理。我们将其ID设置为 4:
export CURRENT_SERVER_INDEX=4
sed -i "s#node.id=.*#node.id=${CURRENT_SERVER_INDEX}#g" broker.properties
sed -i "s#controller.quorum.voters=.*#controller.quorum.voters=1@server1:9093,2@server2:9093,3@server3:9093#g" broker.properties
sed -i "s#listeners=.*#listeners=PLAINTEXT://server${CURRENT_SERVER_INDEX}:9092#g" broker.properties
sed -i "s#log.dirs=.*#log.dirs=/data/kafka#g" broker.properties
/opt/kafka_2.13-3.7.0/bin/kafka-storage.sh format -t jkUlhzQmQkic54LMxrB1oA -c /opt/kafka_2.13-3.7.01/config/kraft/broker.properties
/opt/kafka_2.13-3.7.0/bin/kafka-server-start.sh -daemon /opt/kafka_2.13-3.7.0/config/kraft/broker.properties
与之前的步骤类似,我们得到一个正在运行的 Kafka 代理守护进程。
为了填充一些元数据,让我们创建一个名为 test1 的主题:
/opt/kafka_2.13-3.7.0/bin/kafka-topics.sh --bootstrap-server
server4:9092 --create --topic test1
/opt/kafka_2.13-3.7.0/bin/kafka-topics.sh --bootstrap-server
server4:9092 –list
Output:
test1
KRaft 集群内部
元数据存储
在上一节中,我们得到了一个运行的 Kafka 集群,没有 ZooKeeper,并且有一个主题。我们现在将从两个不同的角度来检查元数据的存储:元数据日志段和实用工具。
让我们检查每个服务器的数据目录中有什么。控制器节点的存储目录中包含以下内容:
ls -1 /data/kafka/
Output:
bootstrap.checkpoint
__cluster_metadata-0
meta.properties
文件 bootstrap.checkpoints 包含检查点标记。meta.properties 文件包含有关当前服务器、版本和集群 ID 的信息。我们最感兴趣的是目录 __cluster_metadata-0 中的文件,该目录累积了集群的所有元数据更改:
ls -1 __cluster_metadata-0/
Output:
00000000000000000000.index
00000000000000000000.log
00000000000000000000.timeindex
00000000000000000159.snapshot
00000000000000000288.snapshot
00000000000000000459.snapshot
00000000000000000630.snapshot
00000000000000000827.snapshot
00000000000000001032.snapshot
00000000000000001257.snapshot
00000000000000001419.snapshot
00000000000000001585.snapshot
leader-epoch-checkpoint
partition.metadata
quorum-state
让我们检查这些文件。quorum-state 包含有关当前领导者、领导者时期序列号和偏移量以及控制器节点列表的信息:
cat quorum-state
Output:
{"clusterId":"","leaderId":1,"leaderEpoch":137,"votedId":1,"appliedOffset":0,"currentVoters":[{"voterId":1},{"voterId":2},{"voterId":3}],"data_version":0}
partition.metadata 包含了一些普通的信息,只有一个版本和主题 ID:
cat partition.metadata
Output:
version: 0
topic_id: AAAAAAAAAAAAAAAAAAAAAQ
最后,我们来到最有趣的部分,即可以使用两个 Kafka 实用工具来探索的日志段文件:
- kafka-dump-log,使用 --cluster-metadata-decoder 标志。
- kafka-metadata-shell。
kafka-dump-log
我们将运行 kafka-dump-log 命令来解码集群元数据。输出包含不同的事件类型,在我们的示例中,我们将提到三种:
- 主题创建
- 主题的分区创建
- NoOp
如果您检查输出,很可能会注意到有许多 NoOp 事件,它们用于推进日志结尾偏移(LEO)和高水位标记(HW)。当应用时,它们不会更改控制器或代理状态,也不包含在元数据快照中。
另一种类型是 TOPIC_RECORD,通过将新主题添加到集群中来更改状态。您可能会看到我们之前创建的主题的名称和 ID。
TOPIC_RECORD 后面是一个 PARTITION_RECORD(由于默认分区数为一)。该记录通过添加主题的分区以及新分区状态来更改代理状态:其领导副本、副本所在的代理、以及其他元数据,例如初始领导者时期和分区时期。在出现仲裁问题时,您可以转储日志文件,并搜索每个节点的集群状态更改。下面是输出示例:
kafka-dump-log.sh --cluster-metadata-decoder --files
00000000000000000000.log --print-data-log
Output:
#Topic creation:
| offset: 1137 CreateTime: 1666637387916 keySize: -1 valueSize: 26 sequence: -1 headerKeys: [] payload:
{"type":"TOPIC_RECORD","version":0,"data":{"name":"test1","topicId":"v2PBI5LYSBKXvb-8fw8pKQ"}}
#Topic’s partition creation:
| offset: 1138 CreateTime: 1666637387916 keySize: -1 valueSize: 48 sequence: -1 headerKeys: [] payload:
{"type":"PARTITION_RECORD","version":0,"data":{"partitionId":0,"topicId":"v2PBI5LYSBKXvb-8fw8pKQ","replicas":[4],"isr":[4],"removingReplicas":[],"addingReplicas":[],"leader":4,"leaderEpoch":0,"partitionEpoch":0}}
#NoOp heartbeat:
| offset: 1139 CreateTime: 1666637388203 keySize: -1 valueSize: 4 sequence: -1 headerKeys: [] payload:
{"type":"NO_OP_RECORD","version":0,"data":{}}
kafka-metadata-shell
kafka-metadata-shell 命令是探索日志文件的另一种方法。让我们选择一个日志文件,并简要查看其内部结构:
kafka-metadata-shell.sh --snapshot /data/kafka/__cluster_metadata-0/00000000000000000000.log
>> ls /
brokers features local metadataQuorum topicIds topics
正如您所看到的,我们已经进入了一个 shell 环境。您可以通过运行 help 命令来查看可用的命令:
>>
cat Show the contents of metadata nodes.
cd Set the current working directory.
exit Exit the metadata shell.
find Search for nodes in the directory hierarchy.
help Display this help message.
history Print command history.
ls List metadata nodes.
man Show the help text for a specific command.
pwd Print the current working directory.
布局直观易懂,特别是如果熟悉 ZooKeeper 的数据布局,对这个就不陌生了。
主题被放置在 /topics 中。在更深的目录中,您可能会找到带有底层分区元数据的主题名称。例如:
>> ls /topics
test1
>> ls /topics/test1
0 id name
>> ls /topics/test1/name
name
>> cat /topics/test1/name
test1
>> cat /topics/test1/id
v2PBI5LYSBKXvb-8fw8pKQ
>> cat /topics/test1/0/data
{
"partitionId" : 0,
"topicId" : "v2PBI5LYSBKXvb-8fw8pKQ",
"replicas" : [ 4 ],
"isr" : [ 4 ],
"removingReplicas" : [ ],
"addingReplicas" : [ ],
"leader" : 4,
"leaderEpoch" : 3,
"partitionEpoch" : 3
}
您还可以通过检查 /topicIds 目录来获取主题 ID:
>> ls /topicIds/
v2PBI5LYSBKXvb-8fw8pKQ
代理的列表可以在 /brokers 目录中找到。每个代理都有自己的目录,并且其中包含其状态、元数据以及一些注册属性:
>> cat /brokers/4/registration
RegisterBrokerRecord(brokerId=4, incarnationId=aUuLVzc7QsmV5hJKiOAdlg, brokerEpoch=4438, endPoints=[BrokerEndpoint(name='PLAINTEXT', host='server4', port=9092, securityProtocol=0)], features=[BrokerFeature(name='metadata.version', minSupportedVersion=1, maxSupportedVersion=7)], rack=null, fenced=true, inControlledShutdown=false)
>> cat /brokers/4/isFenced
false
>> cat /brokers/4/inControlledShutdown
true
还有一个目录是 /features,目前包含协议版本信息:
>> cat /features/metadata.version
{
"name" : "metadata.version",
"featureLevel" : 7
}
仲裁同步元数据存储在 /metadataQuorum 目录中。在那里,您可以找到偏移量和领导者信息:
>> cat /metadataQuorum/offset
18609
>> cat /metadataQuorum/leader
LeaderAndEpoch(leaderId=OptionalInt[2], epoch=138)
在 /local 目录中,您可以检查 Kafka 的版本和源代码提交 ID:
>> cat /local/commitId
e23c59d00e687ff5
>> cat /local/version
3.7.0
kafka-metadata-quorum
让我们检查另一个实用工具 kafka-metadata-quorum,它有两个标志:--status 和 --replicas。以下示例打印出集群的状态:
kafka-metadata-quorum.sh --bootstrap-server server4:9092 describe --status
Output:
ClusterId: jkUlhzQmQkic54LMxrB1oA
LeaderId: 2
LeaderEpoch: 138
HighWatermark: 9457
MaxFollowerLag: 9458
MaxFollowerLagTimeMs: -1
CurrentVoters: [1,2,3]
CurrentObservers: [4]
在这里,您可以找到集群 ID、当前领导者的 ID、领导者时期计数器、高水位值、最大追随者、追随者滞后时间,以及控制器仲裁成员和普通代理的列表。
为了显示更多多样化的输出,针对 --replicas 选项,让我们停止其中一个控制器(NodeId=1),并查看输出:
kafka-metadata-quorum.sh --bootstrap-server server4:9092 describe --replication
Output:
NodeId LogEndOffset Lag LastFetchTimestamp LastCaughtUpTimestamp Status
2 10486 0 1666642071929 1666642071929 Leader
1 9952 534 1666641804786 1666641804285 Follower
3 10486 0 1666642071608 1666642071608 Follower
4 10486 0 1666642071609 1666642071609 Observer
您可以看到节点的状态:控制器的状态可以是领导者(leader)或追随者(follower)。普通的代理处于观察者(observer)状态,观察者不是仲裁的一部分,尽管观察者也有元数据的本地副本。还有一个可能的状态,即候选者(candidate),它是从追随者升级到领导者的中间状态。您可以在 KIP-595 中了解更多信息。
部署kafka controller与broker脚本
#!/bin/bash
// 需要首先修改正确的集群机器ip
server1="host1"
server2="host2"
server3="host3"
// 每台机器设置一个独立的index id
SERVER_INDEX="1"
if [ -f "kafka_2.13-3.7.0.tgz" ]; then
echo "文件存在: kafka_2.13-3.7.0.tgz"
else
echo "开始下载kafka_2.13-3.7.0.tgz..."
wget https://downloads.apache.org/kafka/3.7.0/kafka_2.13-3.7.0.tgz
fi
mkdir -p /data/kafka/
tar -xzvf kafka_2.13-3.7.0.tgz -C /data/kafka/
mkdir -p /data/kafka/kafka_2.13-3.7.0/data
useradd -m -s /bin/bash kafka
chown kafka -R /data/kafka/kafka_2.13-3.7.0 /data/kafka/kafka_2.13-3.7.0/data
su - kafka <<EOF
uuid=$(/data/kafka/kafka_2.13-3.7.0/bin/kafka-storage.sh random-uuid)
echo "CLUSTER_UUID: $uuid"
echo "$uuid" > /data/kafka/kafka_2.13-3.7.0/CLUSTER_UUID
export CURRENT_SERVER_INDEX=$SERVER_INDEX
export CLUSTER_UUID=$uuid
// 修改controller配置
sed -i "s#node.id=.*#node.id=${CURRENT_SERVER_INDEX}#g" /data/kafka/kafka_2.13-3.7.0/config/kraft/controller.properties
sed -i "s#controller.quorum.voters=.*#controller.quorum.voters=1@$server1:9093,2@$server2:9093,3@$server3:9093#g" /data/kafka/kafka_2.13-3.7.0/config/kraft/controller.properties
sed -i "s#log.dirs=.*#log.dirs=/data/kafka/kafka_2.13-3.7.0/data#g" /data/kafka/kafka_2.13-3.7.0/config/kraft/controller.properties
// 格式化controller
/data/kafka/kafka_2.13-3.7.0/bin/kafka-storage.sh format -t ${CLUSTER_UUID} -c /data/kafka/kafka_2.13-3.7.0/config/kraft/controller.properties
// 修改broker配置
sed -i "s#node.id=.*#node.id=${CURRENT_SERVER_INDEX}#g" /data/kafka/kafka_2.13-3.7.0/config/kraft/broker.properties
sed -i "s#controller.quorum.voters=.*#controller.quorum.voters=1@$server1:9093,2@$server2:9093,3@$server3:9093#g" /data/kafka/kafka_2.13-3.7.0/config/kraft/broker.properties
sed -i "s#listeners=.*#listeners=PLAINTEXT://server${CURRENT_SERVER_INDEX}:9092#g" /data/kafka/kafka_2.13-3.7.0/config/kraft/broker.properties
sed -i "s#log.dirs=.*#log.dirs=/data/kafka/kafka_2.13-3.7.0/data#g" /data/kafka/kafka_2.13-3.7.0/config/kraft/broker.properties
// 格式化broker
/data/kafka/kafka_2.13-3.7.0/bin/kafka-storage.sh format -t $uuid -c /data/kafka/kafka_2.13-3.7.0/config/kraft/broker.properties
EOF