Kafka逻辑架构, 部署kafka(kraft方式), K8S部署Kafka-2.8.1(zookeeper模式), k8s基于Strimzi Operator部署kafka集群, kafka部分优化
消息性能对比:
云上kafka不建议开启自动创建topic, 因为大量创建topic会影响kafka性能
Kafka逻辑架构详解:
选举简介: kafka有两种集群管理方式(不能同时使用): 1.基于zookeeper进行集群角色管理。#目前大部分生产环境是这种 2.基于Raft算法改进的kfaft进行集群角色管理,使用Raft的还有Consul、InfluxDB、etcd等。#kafka3.3.0及以上
zookeeper模式:
kraft模式: (一般控制节点和broker节点共用一台服务器)
kafka 主要发行特性:
zk与kraft模式对比:
2.1: 基于kraft方式部署kafka 3.5.1:
#各kafka节点安装jdk 8+ root@mq-server1:~# apt install openjdk-11-jdk root@mq-server2:~# apt install openjdk-11-jdk root@mq-server3:~# apt install openjdk-11-jdk #各kafka节点分发kafka安装文件 root@mq-server1:~# mkdir /apps && cd /apps root@mq-server1:/apps# wget https://downloads.apache.org/kafka/3.5.1/kafka_2.13-3.5.1.tgz root@mq-server1:/apps# scp kafka_2.13-3.5.1.tgz 172.31.4.102:/apps/ root@mq-server1:/apps# scp kafka_2.13-3.5.1.tgz 172.31.4.103:/apps/ root@mq-server1:/apps# tar xvf kafka_2.13-3.5.1.tgz root@mq-server2:/apps# tar xvf kafka_2.13-3.5.1.tgz root@mq-server3:/apps# tar xvf kafka_2.13-3.5.1.tgz root@mq-server1:/apps# ln -sv /apps/kafka_2.13-3.5.1 /apps/kafka root@mq-server2:/apps# ln -sv /apps/kafka_2.13-3.5.1 /apps/kafka root@mq-server3:/apps# ln -sv /apps/kafka_2.13-3.5.1 /apps/kafka #cd kafka #3个节点都修改kafka配置文件 #zookeeper模式用config/server.properties,kraft模式用config/kraft/server.properties #这里用kraft模式 root@mq-server1:/apps/kafka# vim config/kraft/server.properties ------------------------------------------------------------------------------- process.roles=broker,controller #角色,同时作为控制端和工作节点(broker读写数据) node.id=101 #在kraft模式下,集群中id不能相同(另外两个节点配102,103) #下面配置只对控制节点(投票)生效,下面为3个节点的地址和投票端口 controller.quorum.voters=101@172.31.4.101:9093,102@172.31.4.102:9093,103@172.31.4.103:9093#控制节点的配置,会通过以上地址进行集群投票和通告,格式为node.idX@IP:PORT,node.idY@IP:PORT,node.idZ@IP:PORT listeners=PLAINTEXT://:9092,CONTROLLER://:9093 #内网的监听地址,用于通过当前主机IP的直接访问,也就是没有通过其他外网访问入口(如公有云的外网LB)访问kafka,9092为客户端端口,9093为集群端口 inter.broker.listener.name=PLAINTEXT #设置broker监听器名称,被listeners引用(被上面引用,代表0.0.0.0) advertised.listeners=PLAINTEXT://172.31.4.101:9092 #本机的地址(其他节点通告本机),每个节点都要改成自己的 #通告kafka节点的连接信息 #注意:如果这里还有外网访问,或者外网访问,下面根据配置做调整,一般不给外网访问 #下面2个和性能相关 num.network.threads=8 #接受网络请求和响应请求的线程数(根据cpu调整,1倍或2倍cpu) num.io.threads=8 #服务器用于处理请求的线程数,数值应该大于主机硬盘数(高点没事,别是1) socket.send.buffer.bytes=102400#socket的发送缓冲区大小,数据不是一下子就发送的,先会存储到缓中区、到达一定的大小后再发送,能提网络高性能 socket.receive.buffer.bytes=102400 #socket的接受缓中区,当数据到达一定大小后在序列化到磁盘 #t The maximum size of a request that the socket server will accept (protection against 00M) socket.request.max.bytes=104857600 #socket server接收的最大请求大小bytes,用于控制向kafka清求消息或者向kafka发送消息的清请求的最大数,这个值不能超过java的堆栈大小 log.dirs=/data/kraft-logs #kafka接收的消息(kafka数据目录) num.partitions=1 #默认分区数,不要超过broker节点数(建议和kafka的节点数量一致) num.recovery.threads.per.data.dir=1 #启动时,用于日志恢复线程数,默认1(建议1,多了可能有问题) offsets.topic.replication.factor=1 #定位消费位置offset的副本数,如果全丢失,生产者可发送消息但消费者不可用 transaction.state.log.replication.factor=1 #事务主题的副本数 transaction.state.log.min.isr=2 #事务状态主题的每个分区,处于ISR最小节点数量,(lsr=ln-sync,表示可以参加选举成为leader)这里如果3个节点,建议设成2,表示3个可坏1个 #消息存储相关 #消息保存时间(小时),会滚动删除过期数据 log.retention.hours=168 #一周 #日志切分为每段1073741824字节(1G)保存,方便kafka对数据回收和管理,删的时候到期1G一起删 log.segment.bytes=1073741824 #日志清理检查周期,30000ms=300s=5分钟(多久检查一次数据是否需要删除) log.retention.check.interval.ms=30000 ------------------------------------------------------------------------------- #所有节点都建好kafka数据目录 root@mq-server1:/apps/kafka#mkdir /data/kraft-logs
在3.x之前的版本是自动初始化,3开始需要手动初始化
#在任意一台节点生成集群ID: root@mq-server1:/apps# /apps/kafka/bin/kafka-storage.sh random-uuid KNNZHDr1TjuEtR4AZ9A37Q #三台节点都要使用上面生成的唯一集群ID进行格式化数据存储目录 root@mq-server1:/apps/kafka# /apps/kafka/bin/kafka-storage.sh format -t KNNZHDr1TjuEtR4AZ9A37Q -c /apps/kafka/config/kraft/server.properties root@mq-server2:/apps/kafka# /apps/kafka/bin/kafka-storage.sh format -t KNNZHDr1TjuEtR4AZ9A37Q -c /apps/kafka/config/kraft/server.properties root@mq-server3:/apps/kafka# /apps/kafka/bin/kafka-storage.sh format -t KNNZHDr1TjuEtR4AZ9A37Q -c /apps/kafka/config/kraft/server.properties
用bin/kafka-server-start.sh 启动Kafka Server测试:
root@mq-server1:/apps# /apps/kafka/bin/kafka-server-start.sh /apps/kafka/config/kraft/server.properties root@mq-server2:/apps# /apps/kafka/bin/kafka-server-start.sh /apps/kafka/config/kraft/server.properties root@mq-server3:/apps# /apps/kafka/bin/kafka-server-start.sh /apps/kafka/config/kraft/server.properties #可以用python脚本写入测试下 #写入脚本 from kafka import KafkaProducer conn = KafkaProducer(bootstrap_servers=['172.31.4.3:9092','172.31.4.3:9092','172.31.4.3:9092'],compression_type='gzip') conn.send('foobar', key=b'foo', value=b'bar') for i in range(3): msg = "msg%d" % i #conn.send('myserver', msg) conn.send('myserver',key=b'myserver-key', value=b'msg%d' % i) print('topic-->myserver: msg%d发送成功!' % i) conn.close() #消费脚本 from kafka import KafkaConsumer conn = KafkaConsumer('myserver',bootstrap_servers=['172.31.4.3:9092','172.31.4.3:9092','172.31.4.3:9092']) for message in conn: print ("%s:%d:%d: key=%s value=%s" % (message.topic, message.partition, message.offset, message.key,message.value)) conn.close()
root@mq-server1:/apps# vim /etc/systemd/system/kafka.service root@mq-server2:/apps# vim /etc/systemd/system/kafka.service root@mq-server3:/apps# vim /etc/systemd/system/kafka.service [Unit] Description=Apache Kafka server (broker) Documentation=https://kafka.apache.org After=network.target Wants=network-online.target [Service] Type=forking ExecStart=/apps/kafka/bin/kafka-server-start.sh -daemon /apps/kafka/config/kraft/server.properties Restart=on-failure LimitNOFILE=265535 [Install] WantedBy=multi-user.target #尽量一起启动 root@mq-server1:/apps# systemctl daemon-reload && systemctl restart kafka && systemctl enable kafka root@mq-server2:/apps# systemctl daemon-reload && systemctl restart kafka && systemctl enable kafka root@mq-server3:/apps# systemctl daemon-reload && systemctl restart kafka && systemctl enable kafka #查看日志 root@mq-server1:/apps/kafka#tail -f logs/*.log
2.1.7:部署kafka-ui:
root@ubuntu22-04-2:~# docker run --name kafka-ui -d -p 8080:8080 -e DYNAMIC_CONFIG_ENABLED=true -e KAFKA_CLUSTERS_0_NAME="kafka-cluster1" -e KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS="172.31.4.101:9092,172.31.4.102:9092,172.31.4.103:9092" --restart always provectuslabs/kafka-ui:master #访问 172.31.4.101:8080
注意: 大部分公司不会把kafka和zookeeper部署在k8s中
2.2.1:创建存储类:
#采用动态置备存储 csi-driver-nfs, 准备nfs服务器, 创建路径作为动态置备 #部署csi-driver-nfs,采用local install方式 [root@master01 ~]#git clone https://github.com/kubernetes-csi/csi-driver-nfs.git [root@master01 ~]#cd csi-driver-nfs/deploy #直接部署4.6.0版本 (每个节点下载镜像并运行) [root@master01 deploy]#kubectl apply -f v4.6.0/ #查看(默认在kube-system空间下) [root@master01 deploy]#kubectl get pods -n kube-system -o wide #nfs服务器上 #创建路径作为动态置备 [root@ubuntu ~]#mkdir /nfspv [root@ubuntu ~]#vim /etc/exports /nfspv 10.0.0.0/16(rw,fsid=0,async,no_subtree_check,no_auth_nlm,insecure,no_root_squash) #执行重新导出 [root@ubuntu ~]#exportfs -arv #定义nfs-csi的存储类,以完成csi-driver-nfs和nfs-server之间的对接 [root@master01 ~]#vim storageclass-csi-nfs.yaml apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: nfs-csi provisioner: nfs.csi.k8s.io parameters: server: 10.0.0.155 #nfs服务器地址 share: /nfspv #nfs对应地址(会在该路径下自动创建子目录作为动态置备存储路径) reclaimPolicy: Delete #生产中用delete回收策略有风险 volumeBindingMode: Immediate [root@master01 ~]#kubectl apply -f storageclass-csi-nfs.yaml #查看存储类 [root@master01 ~]#kubectl get sc NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION nfs-csi nfs.csi.k8s.io Delete Immediate false
[root@master01 kafka-cases-n79]#kubectl create namespace myserver [root@master01 ~]#cd kafka-cases-n79/ storageClassName: nfs-csi #修改存储类 [root@master01 kafka-cases-n79]#kubectl apply -f 2.ZooKeeper-StatefulSet.yaml [root@master01 kafka-cases-n79]#kubectl get pods -n myserver NAME READY STATUS RESTARTS AGE zk-statefulset-0 1/1 Running 0 5m35s zk-statefulset-1 1/1 Running 0 5m35s zk-statefulset-2 1/1 Running 0 5m35s #这里要进入pod内部确认zookeeper是否启动正常 [root@master01 kafka-cases-n79]#kubectl exec -it -n myserver zk-statefulset-0 -- /bin/bash $cd /opt/zookeeper/bin/ $bash zkServer.sh status #查看zookeeper状态
[root@master01 kafka-cases-n79]#vim 3.Kafka-StatefulSet.yaml storageClassName: nfs-csi #修改存储类 [root@master01 kafka-cases-n79]#kubectl apply -f 3.Kafka-StatefulSet.yaml [root@master01 kafka-cases-n79]#kubectl get pods -n myserver NAME READY STATUS RESTARTS AGE kafka-statefulset-0 1/1 Running 0 2m16s kafka-statefulset-1 1/1 Running 0 94s kafka-statefulset-2 1/1 Running 0 52s #可以看到对应创建的pvc [root@master01 kafka-cases-n79]#kubectl get pvc -n myserver
#获取每个zk的myid: ]# for i in 0 1 2; do echo "myid zkstatefulset-$i";kubectl exec -n myserver zk-statefulset-$i -- cat /var/lib/zookeeper/data/myid; done myid zk-0 1 myid zk-1 2 myid zk-2 3 #确认zk及kafka配置: #确认zk的配置: ]# kubectl exec -n myserver zk-statefulset-0 -- cat /opt/zookeeper/conf/zoo.cfg #确认kafka的配置: ]# kubectl exec -n myserver kafka-statefulset-0 -- cat /opt/kafka/config/server.properties #kafka写入,读取测试 #测试写入数据: ]# kubectl exec -it kafka-statefulset-0 bash -n myserver $ /opt/kafka/bin/kafka-console-producer.sh --broker-list kafka-statefulset-0.kafka-headless-service:9092 --topic test >msg1 >msg2 >msg3 #测试消费消息: ]# kubectl exec -it kafka-statefulset-2 bash -n myserver $ /opt/kafka/bin/kafka-console-consumer.sh --bootstrap-server kafka-statefulset-0.kafka-headless-service:9092 --topic test --from-beginning #验证会自动获取到消息 msg1 msg2 msg3
用于监控, 可用于Kafka-2.8
#暴露kafka和zookeeper指标给prometheus [root@master01 kafka-cases-n79]#kubectl apply -f 4.kafka-exporter.yaml #k8s部署prometheus和grafana即可查看 #部署Kafdrop [root@master01 kafka-cases-n79]#kubectl apply -f 5.kafka-dashboard-kafdrop.yaml #登录Kafdrop即可显示kafka状态
在Kubernetes集群上,基于Strimzi Operator部署面向生产环境的Kafka集群; https://strimzi.io/ https://strimzi.io/docs/operators/latest/overview #官方文档 #目前还只支持zookeeper模式
#清理前面的环境,以免干扰 [root@master01 ~]#cd kafka-cases-n79/ [root@master01 kafka-cases-n79]#kubectl delete -f 5.kafka-dashboard-kafdrop.yaml -f 4.kafka-exporter.yaml #先删Kafka,再删zookeeper [root@master01 kafka-cases-n79]#kubectl delete -f 3.Kafka-StatefulSet.yaml [root@master01 kafka-cases-n79]#kubectl delete -f 2.ZooKeeper-StatefulSet.yaml #清理pvc [root@master01 kafka-cases-n79]#kubectl get pvc -n myserver [root@master01 kafka-cases-n79]#kubectl delete pvc -n myserver datadir-kafka-statefulset-0 ... [root@master01 kafka-cases-n79]#kubectl delete pvc -n myserver datadir-zk-statefulset-0 ... #如果有对应的pv,也一道删了 #如果数据不要了,在nfs服务器上也可以一起删了
]#kubectl create namespace myserver #已有可跳过 ]#cd /root/kafka-cases-n79/7.strimzi-kafka-operator ]#kubectl apply -f https://strimzi.io/install/latest?namespace=myserver # 等strimzi-cluster-operator pod为running状态 [root@master01 7.strimzi-kafka-operator]#kubectl get pod -n myserver NAME READY STATUS RESTARTS AGE strimzi-cluster-operator-6bf566db79-62g79 1/1 Running 0 2m59s
2.3.2:部署kafka集群:
#下载修改kafka-persistent-single.yaml,已提前下载改好,改了namespace,存储类,zookeeper为3副本(集群高可用) #wget https://strimzi.io/examples/latest/kafka/kafka-persistent-single.yaml [root@master01 7.strimzi-kafka-operator]#ls 1.kafka-persistent-single.yaml 2.create-topic.yaml [root@master01 7.strimzi-kafka-operator]#cat 1.kafka-persistent-single.yaml apiVersion: kafka.strimzi.io/v1beta2 kind: Kafka metadata: name: myserver-kafka-cluster namespace: myserver #指定目的NS spec: kafka: version: 3.9.0 #不支持3.5.1版本了 replicas: 3 #副本数,默认为1 listeners: - name: plain port: 9092 type: internal tls: false - name: tls port: 9093 type: internal tls: true - name: external # 增加外部客户端访问用的linstener port: 9094 #监听端口 type: nodeport #指定nodeport类型 tls: false configuration: bootstrap: nodePort: 30092 # 指定宿主机nodeport端口 config: offsets.topic.replication.factor: 3 transaction.state.log.replication.factor: 3 transaction.state.log.min.isr: 3 default.replication.factor: 1 min.insync.replicas: 1 inter.broker.protocol.version: "3.5" storage: type: jbod volumes: - id: 0 type: persistent-claim class: nfs-csi size: 100Gi deleteClaim: false zookeeper: replicas: 3 storage: type: persistent-claim class: nfs-csi size: 10Gi deleteClaim: false entityOperator: topicOperator: {} userOperator: {} [root@master01 7.strimzi-kafka-operator]#kubectl apply -f 1.kafka-persistent-single.yaml
#可以使用脚本进行测试,也可以如下测试 #kafka服务器地址 myserver-kafka-cluster-kafka-bootstrap:9092 #myserver-kafka-cluster-kafka-brokers为无头服务,可以直接连到pod上 [root@master01 7.strimzi-kafka-operator]#kubectl get svc -n myserver NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) myserver-kafka-cluster-kafka-bootstrap ClusterIP 10.98.250.106 <none> 9091/TCP,9092/TCP,9093/TCP myserver-kafka-cluster-kafka-brokers ClusterIP None <none> 9090/TCP,9091/TCP,8443/TCP,9092/TCP,9093/TCP #测试写入数据: (起一个带生产者/消费者脚本的kafka镜像) root@k8s-master1:/opt/kafka# kubectl -n myserver run kafka-producer -ti --image=quay.io/strimzi/kafka:0.30.0-kafka-3.2.0 --rm=true --restart=Never --bin/kafka-console-producer.sh --bootstrap-server myserver-kafka-cluster-kafka-bootstrap:9092 --topic my-topic >msg1 >msg2 #测试消费数据: (from-beginning从头开始消费) root@k8s-master1:~# kubectl -n myserver run kafka-consumer -ti \ --image=quay.io/strimzi/kafka:0.30.0-kafka-3.2.0 \ --rm=true --restart=Never -- bin/kafka-console-consumer.sh \ --bootstrap-server myserver-kafka-cluster-kafka-bootstrap:9092 \ --topic my-topic --from-beginning msg1 msg2 msg3
#对外暴露服务,上面配置已经改过了: root@k8s-master1:/opt/kafka# vim 1.kafka-persistent-single.yaml apiVersion: kafka.strimzi.io/v1beta2 kind: Kafka metadata: name: myserver-kafka-cluster namespace: myserver #指定目的NS spec: kafka: version: 3.9.0 #不支持3.5.1版本了 replicas: 3 #副本数,默认为1 listeners: - name: plain port: 9092 type: internal tls: false - name: tls port: 9093 type: internal tls: true - name: external # 增加外部客户端访问用的linstener port: 9094 #监听端口 type: nodeport #指定nodeport类型 tls: false configuration: bootstrap: nodePort: 30092 # 指定宿主机nodeport端口
]# docker run --name kafka-ui -d -p 8080:8080 -e DYNAMIC_CONFIG_ENABLED=true -e KAFKA_CLUSTERS_0_NAME="kafka-cluster1" -e KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS="10.0.0.152:30092,10.0.0.153:30092,10.0.0.154:30092" --restart always provectuslabs/kafka-ui:master #浏览器进入网页kafka控制端 http://10.0.0.151:8080/ #上面可以创建topic Number of Partitions #分区数,这里写3 Min In Sync Replicas #最少同步副本,这里写1(至少有1个才能对外提供服务) Replication Factor #副本数,这里写3 点击创建topic即可 #在网页kafka控制端,可以修改brokers的config配置(能动态修改某些默认参数),针对当前主机生效(要改3个一起改) #注意改完重启,修改的参数依然有效
root@k8s-master1:/opt/kafka# cat 2.create-topic.yaml apiVersion: kafka.strimzi.io/v1beta2 kind: KafkaTopic metadata: name: myserver-topic #主题名称 namespace: myserver #topic要创建的目的NS labels: strimzi.io/cluster: "myserver-kafka-cluster" #匹配kafka集群名称 spec: partitions: 3 #3分区,相当于一个完整的数据拆分为3部分 replicas: 3 #每部分数据分别存3份(每份单独一个主机) root@k8s-master1:/opt/kafka# kubectl apply -f 2.create-topic.yaml
#查看topic分区状态(当前默认为1): #kafka版本2的查看方法 root@k8s-master1:/opt/2.kafka-cases# kubectl exec -it kafka-statefulset-0 bash -n myserver bash-5.1# cd /opt/kafka bash-5.1# ./bin/kafka-topics.sh --describe --zookeeper zk-client-service:2181 --topic myserver Topic: myserver TopicId: 6AbgktWpSV6G_FbtbzyOug PartitionCount: 1 ReplicationFactor: 1 Configs: Topic: myserver Partition: 0 Leader: 1003 Replicas: 1003 Isr: 1003 #kafka版本3的查看方法 (下面为三分区) ]# cd /apps/kafka/bin ]# kafka-topics.sh --describe --topic myserver --bootstrap-server=172.31.4.101:9092 Topic: myserver Partition:0 Leader:101 Replicas:101,102,103 Isr:101,102,103 Topic: myserver Partition:1 Leader:102 Replicas:102,103,101 Isr:102,101,103 Topic: myserver Partition:2 Leader:103 Replicas.103,101.102 Isr:101,102,103
写入kafka主节点,主节点默认和从节点同步完再返回,可在写入代码中修改参数
#该参数由开发进行评估,是否要加上 acks=1 #主节点自己写完,不等待所有从节点是否写入 acks=0 #producer不等待任何ack返回值 acks=all #保证消息完整性,会等待所有follower给leader发消息写完后,leader返回ack消息,然后productor继续写下去 #all要慎用(如kafka 任一节点延迟高或宕机且没有新的节点可分配新的副本则会引发生产者写入阳塞)
kafka 部分优化:https://kafka.apache.org/35/documentation.html Kafka 日志保存时间 #1-3天其实就够了 IO线程及网络线程优化 不建议开启自动创建主题, auto.create.topics.enable 设置为 true #生产都是人工创建,没有就报错 内存参数优化 √ buffer.memory:缓冲区总大小,默认 32m,可适当调大(比如64m) √ batch.size: 默认 16k(太小会导致频繁网络请求,吞吐量下降,而如果 batch 太大,会导致一条消息需要等待很久
才能被发送出去, 增加网络延时),适当增加该值可以提高吞吐量 √ linger.ms:如果数据一直没有到达batch.size,sender线程等待 linger.time之后就会发送数据,单位 ms,默认
值是 0ms,表示没有延迟。生产环境建议该值大小为 5-100ms 之间 √ compression.type:指定消息的压缩方式,默认值为"none",可以配置为 "gzip","snappy" 和 "lz4"#程序设定 适当增加分区数 #不超过kafka总数 使用高性能硬件 消费者提高吞吐量 √ max.poll.records:一次 poll 拉取数据返回消息的最大条数, 默认是 500 条 √ fetch.max.bytes:消费者单次从 Broker 获取消息的最大字节数, 默认 57671680即默认最大 55m √ message.max.bytes: broker接收或者发送的消息的最大字节数, 默认值为1048588字节(即1MB),要比
fetch.max.bytes小