“消息队列”是在消息的传输过程中保存消息的容器。“消息”是在两台计算机间传送的数据单位。
英文名:Message Queue,经常缩写为MQ
可以简单理解消息队列就是将需要传输的数据存放在队列中
1、应用耦合处理:多应用间通过消息队列对同一消息进行处理,避免调用接口失败导致整个过程失败;
2、异步处理:多应用对消息队列中同一消息进行处理,应用间并发处理消息,相比串行处理,减少处理时间;
3、限流削峰:广泛应用于秒杀或抢购活动中,避免流量过大导致应用系统挂掉的情况;
4、消息驱动的系统:系统分为消息队列、消息生产者、消息消费者,生产者负责产生消息,消费者(可能有多个)负责对消息进行处理;
消息队列中间件就是用来存储消息的软件应用。例如分析网站的用户行为,需要记录用户的访问日志,访问了什么点击了什么收藏了什么等等,这些一条条的信息日志,可以看成是一条条的消息,可以将它们保存到消息队列中。将来有一些应用程序需要处理这些日志,就可以随时将这些消息取出来处理。
常见消息队列有很多,例如:Kafka、RabbitMQ、ActiveMQ、RocketMQ、ZeroMQ等
Apache Kafka是一个分布式消息发布订阅系统。它最初由LinkedIn公司基于独特的设计实现为一个分布式的提交日志系统( a distributed commit log),之后成为Apache项目的一部分。
这是一款为大数据而生的消息中间件,在数据采集、传输、存储的过程中发挥着举足轻重的作用。
RabbitMQ 2007年发布,是一个在 AMQP (高级消息队列协议)基础上完成的,可复用的企业消息系统,是当前最主流的消息中间件之一。
Java世界的中坚力量。它有很长的历史,而且被广泛的使用。它还是跨平台的,给那些非微软平台的产品提供了一个天然的集成接入点
RocketMQ出自阿里公司的开源产品,用 Java 语言实现,在设计时参考了 Kafka,并做出了自己的一些改进。
ZeroMQ具有一个独特的非中间件的模式,不需要安装和运行一个消息服务器,或 中间件。只需要简单的引用ZeroMQ程序库就可以在应用程序之间发送消息。
消息队列的应用场景和它的作用有关,根据作用特点可以归纳出对应的使用场景
电商网站中,新的用户注册时,需要将用户的信息保存到数据库中,同时还需要额外发送注册的邮件通知、以及短信注册码给用户。但因为发送邮件、发送注册短信需要连接外部的服务器,需要额外等待一段时间,此时,就可以使用消息队列来进行异步处理,从而实现快速响应
多应用间通过消息队列对同一消息进行处理,避免调用接口失败导致整个过程失败
例如上方没有MQ中间件时,serverB或者serverC服务中断,则整个服务相当于都中断
而加入MQ中间件后,serverB或者serverC服务有一个出问题,那么它的处理消息是在MQ中的,此时serverA就不会有影响,可以正常运转,等到serverB或者serverC故障恢复了再处理对应的工作,从而提高了系统的可用性
广泛应用于秒杀或抢购活动中,避免流量过大导致应用系统挂掉的情况
大型电商网站(淘宝、京东、国美、苏宁等等)、App(抖音、美团、滴滴等)等需要分析用户行为,要根据用户的访问行为来发现用户的喜好以及活跃情况,需要在页面上收集大量的用户访问信息,可以有效实现实时的推荐以及喜好推送
生产者消费者模型是针对在任务处理中既要产生数据,又要处理数据这一情景而设计出来的一种解决方案。如果生产者生产资源很快,消费者处理资源的速度很慢,则生产者就必须等待消费者处理完数据才能继续生产,反之同理,这样的话生产者与消费者之间的耦合度较高(依赖关系),导致总体效率较低。
于是通过引入一个交易场所(缓冲区),来解决生产者与消费者之间的强耦合关系,生产者与消费者之间直接通讯,而是通过这个交易场所来间接通讯,将两者的直接关系转变成间接关系,生产者生产完数据直接交给仓库,而消费者要使用则直接从缓冲区取出数据,这样效率就大大的提高。
生产者与消费者之间不直接交互
消息发送者生产消息发送到消息队列中,然后消息接收者从消息队列中取出并且消费消息。消息被消费以后,消息队列中不再有存储,所以消息接收者不可能消费到已经被消费的消息。
点对点模式特点:
发布者发送到topic的消息,只有订阅了topic的订阅者才会收到消息。topic实现了发布和订阅,当你发布一个消息,所有订阅这个topic的服务都能得到这个消息,所以从1到N个订阅者都能得到这个消息的拷贝。
发布/订阅模式特点:
Kafka是由Apache软件基金会开发的一个开源流平台,由Scala和Java编写。Kafka的Apache官网是这样介绍Kakfa的。
Apache Kafka是一个分布式流平台。一个分布式的流平台应该包含3点关键的能力:
发布和订阅流数据流,类似于消息队列或者是企业消息传递系统
以容错的持久化方式存储数据流
处理数据流
我们通常将Apache Kafka用在两类程序:
建立实时数据管道,以可靠地在系统或应用程序之间获取数据
构建实时流应用程序,以转换或响应数据流
上图,我们可以看到:
Producers:可以有很多的app应用程序,将消息数据放入到Kafka集群中。
Consumers:也可以有很多的app应用程序,将消息数据从Kafka集群中拉取出来。
Connectors:Kafka的连接器可以将数据库中的数据导入到Kafka,也可以将Kafka的数据导出到
数据库中。
- Stream Processors:流处理器可以Kafka中拉取数据,也可以将数据写入到Kafka中。
kafka的诞生,是为了解决linkedin的数据管道问题,起初linkedin采用了ActiveMQ来进行数据交换,大约是在2010年前后,那时的ActiveMQ还远远无法满足linkedin对数据传递系统的要求,经常由于各种缺陷而导致消息阻塞或者服务无法正常访问,为了能够解决这个问题,linkedin决定研发自己的消息传递系统,当时linkedin的首席架构师jay kreps便开始组织团队进行消息传递系统的研发。
在大数据技术领域,一些重要的组件、框架都支持Apache Kafka,不论成成熟度、社区、性能、可靠性,Kafka都是非常有竞争力的一款产品。
kafka与竞品的对比表
特性 | ActiveMQ | RabbitMQ | Kafka | RocketMQ |
---|---|---|---|---|
所属社区/公司 | Apache | Mozilla Public License | Apache | Apache/Ali |
成熟度 | 成熟 | 成熟 | 成熟 | 比较成熟 |
生产者-消费者模式 | 支持 | 支持 | 支持 | 支持 |
发布-订阅 | 支持 | 支持 | 支持 | 支持 |
REQUEST-REPLY | 支持 | 支持 | - | 支持 |
API完备性 | 高 | 高 | 高 | 低(静态配置) |
多语言支持 | 支持JAVA优先 | 语言无关 | 支持,JAVA优先 | 支持 |
单机呑吐量 | 万级(最差) | 万级 | 十万级 | 十万级(最高) |
消息延迟 | - | 微秒级 | 毫秒级 | - |
可用性 | 高(主从) | 高(主从) | 非常高(分布式) | 高 |
消息丢失 | - | 低 | 理论上不会丢失 | - |
消息重复 | - | 可控制 | 理论上会有重复 | - |
事务 | 支持 | 不支持 | 支持 | 支持 |
文档的完备性 | 高 | 高 | 高 | 中 |
提供快速入门 | 有 | 有 | 有 | 无 |
首次部署难度 | - | 低 | 中 | 高 |
在kafka的主项目外,还有大量的支持工具。在官方项目confluence上,列出了这些工具,包括流处理系统、hadoop聚合接口、监控工具、部署工具等。
https://cwiki.apache.org/confluence/display/KAFKA/Ecosystem
截止到目前,3.2.1 是最新版本。kafka当前最新的稳定版本是 3.2.1;于2022 年 7 月 29 日发布。
kafka的所有版本下载连接:http://archive.apache.org/dist/kafka/
本文章介绍kafka3.1.0的安装部署搭建
如果想部署比较早的版本可以点击(2.4.1版)https://blog.csdn.net/wt334502157/article/details/116518259
搭建服务机器资源准备可以自定义,企业测试环境、虚拟机均可。
公网IP | 内网IP | 主机名 | 操作系统 | cpu | 内存 |
---|---|---|---|---|---|
8.130.40.212 | 172.28.54.203 | kafka01 | CentOS 7.4 | 2C | 8g |
8.130.33.123 | 172.28.54.202 | kafka02 | CentOS 7.4 | 2C | 8g |
8.130.41.170 | 172.28.54.201 | kafka03 | CentOS 7.4 | 2C | 8g |
[root@kafka01 ~]# cat /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
# kafka
172.28.54.203 kafka01
172.28.54.202 kafka02
172.28.54.201 kafka03
注意公有云主机有公网IP和内网IP,服务之间配置可以使用内网IP即可,而本地电脑去访问服务时则需要使用公网IP
注意:您的本地环境必须安装 Java 8+
[root@kafka01 local]# java -version
java version "1.8.0_211"
Java(TM) SE Runtime Environment (build 1.8.0_211-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.211-b12, mixed mode)
zookeeper官方下载地址:http://archive.apache.org/dist/zookeeper/
本次安装部署zookeeper-3.5.7
# 创建应用部署目录
[root@kafka01 ~]# mkdir /opt/software
[root@kafka01 ~]# cd /opt/software
# 下载zookeeper安装包
[root@kafka01 software]# wget http://archive.apache.org/dist/zookeeper/zookeeper-3.5.7/apache-zookeeper-3.5.7-bin.tar.gz
# 解压缩包
[root@kafka01 software]# tar -xf apache-zookeeper-3.5.7-bin.tar.gz
# 创建软连接
[root@kafka01 software]# ln -s apache-zookeeper-3.5.7-bin zookeeper
[root@kafka01 software]# cd zookeeper/
[root@kafka01 zookeeper]# mkdir -p zkData
[root@kafka01 zookeeper]# cd zkData/
[root@kafka01 zkData]# echo 1 > myid
[root@kafka01 conf]# cd /opt/software/zookeeper/conf/
[root@kafka01 conf]# cp zoo_sample.cfg zoo.cfg
[root@kafka01 conf]# vim zoo.cfg
# 数据存储路径
dataDir=/opt/software/zookeeper/zkData
# 增加集群信息
server.1=kafka01:2888:3888
server.2=kafka02:2888:3888
server.3=kafka03:2888:3888
[root@kafka01 conf]# vim /etc/profile
# zookeeper
export ZOOKEEPER_HOME=/opt/software/zookeeper
export PATH=$PATH:$ZOOKEEPER_HOME/bin
[root@kafka01 zookeeper]# source /etc/profile
[root@kafka01 software]# cd /opt/software/
[root@kafka01 software]# scp -r apache-zookeeper-3.5.7-bin/ kafka02:/opt/software/
[root@kafka01 software]# scp -r apache-zookeeper-3.5.7-bin/ kafka03:/opt/software/
[root@kafka01 software]# rm -f /opt/software/apache-zookeeper-3.5.7-bin.tar.gz
【注意1】:
1 . server.1 / server.2 / server.3 中的 1 2 3 对应的就是zkData目录中myid中的值
- server后面的kafka01 / kafka02 / kafka03 没采用ip是因为配置了/etc/hosts的解析
- dataDir=/data/zookeeper路径可以自定义,建议3台服务器尽量配置一样的路径,便于管理
【注意2】:
配置cluster信息解读:
server.A=B:C:D [ server.1=kafka01:2888:3888 ( A=1 , B=kafka01 , C=2888, D=3888)
A 是一个数字,表示这个是第几号服务器;
集群模式下配置一个文件myid,这个文件在配置的data目录下,这个文件里面数值就是A的值,Zookeeper启动时读取此文件,拿到里面的数据与zoo.cfg里面的配置信息比较从而判断到底是哪个server。
B 是这个服务器的地址;
C 是这个服务器Follower与集群中的Leader服务器交换信息的端口;
D 是万一集群中的Leader服务器挂了,需要一个端口来重新进行选举,选出一个新的Leader,而这个端口就是用来执行选举时服务器相互通信的端口。
[root@kafka02 ~]# cd /opt/software/
[root@kafka02 software]# ln -s apache-zookeeper-3.5.7-bin zookeeper
[root@kafka02 software]# echo 2 > zookeeper/zkData/myid
[root@kafka02 software]# vim /etc/profile
# zookeeper
export ZOOKEEPER_HOME=/opt/software/zookeeper
export PATH=$PATH:$ZOOKEEPER_HOME/bin
[root@kafka02 software]# source /etc/profile
[root@kafka03 ~]# cd /opt/software/
[root@kafka03 software]# ln -s apache-zookeeper-3.5.7-bin zookeeper
[root@kafka03 software]# echo 3 > zookeeper/zkData/myid
[root@kafka03 software]# vim /etc/profile
# zookeeper
export ZOOKEEPER_HOME=/opt/software/zookeeper
export PATH=$PATH:$ZOOKEEPER_HOME/bin
[root@kafka03 software]# source /etc/profile
[root@kafka01 ~]# zkServer.sh start
[root@kafka02 ~]# zkServer.sh start
[root@kafka03 ~]# zkServer.sh start
# kafka01
[root@kafka01 ~]# jps -l
20353 org.apache.zookeeper.server.quorum.QuorumPeerMain
20406 sun.tools.jps.Jps
[root@kafka01 ~]#
[root@kafka01 ~]# zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /opt/software/zookeeper/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: follower
# kafka02
[root@kafka02 zookeeper]# jps -l
20228 sun.tools.jps.Jps
20173 org.apache.zookeeper.server.quorum.QuorumPeerMain
[root@kafka02 zookeeper]# zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /opt/software/zookeeper/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: leader
# kafka03
[root@kafka03 conf]# jps -l
20204 sun.tools.jps.Jps
20158 org.apache.zookeeper.server.quorum.QuorumPeerMain
[root@kafka03 conf]# zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /opt/software/zookeeper/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: follower
[root@kafka01 ~]# cd /opt/software
#下载kafka3.1.0安装包
[root@kafka01 software]# wget https://archive.apache.org/dist/kafka/3.1.0/kafka_2.13-3.1.0.tgz
[root@kafka01 software]# tar -xzf kafka_2.13-3.1.0.tgz
[root@kafka01 software]# ln -s kafka_2.13-3.1.0 kafka
[root@kafka01 software]# cd kafka
[root@kafka01 kafka]# vim config/server.properties
broker.id=0
listeners=PLAINTEXT://kafka01:9092
log.dirs=/opt/software/kafka/kafka-logs
zookeeper.connect=kafka01:2181,kafka02:2181,kafka03:2181
[root@kafka01 software]# scp -r kafka_2.13-3.1.0/ kafka02:/opt/software/
[root@kafka01 software]# scp -r kafka_2.13-3.1.0/ kafka03:/opt/software/
[root@kafka01 software]# vim /etc/profile
#kafka
export KAFKA_HOME=/opt/software/kafka
export PATH=$PATH:$KAFKA_HOME/bin
[root@kafka01 software]# source /etc/profile
[root@kafka02 ~]# cd /opt/software
[root@kafka02 software]# ln -s kafka_2.13-3.1.0 kafka
[root@kafka02 software]# cd kafka
[root@kafka02 kafka]# vim config/server.properties
broker.id=1
listeners=PLAINTEXT://kafka02:9092
[root@kafka02 kafka]# vim /etc/profile
#kafka
export KAFKA_HOME=/opt/software/kafka
export PATH=$PATH:$KAFKA_HOME/bin
[root@kafka02 kafka]# source /etc/profile
[root@kafka03 conf]# cd
[root@kafka03 ~]# cd /opt/software
[root@kafka03 software]# ln -s kafka_2.13-3.1.0 kafka
[root@kafka03 software]# cd kafka
[root@kafka03 kafka]# vim config/server.properties
broker.id=2
listeners=PLAINTEXT://kafka03:9092
[root@kafka03 kafka]# vim /etc/profile
#kafka
export KAFKA_HOME=/opt/software/kafka
export PATH=$PATH:$KAFKA_HOME/bin
[root@kafka03 kafka]# source /etc/profile
[root@kafka01 ~]# kafka-server-start.sh /opt/software/kafka/config/server.properties &
[root@kafka02 ~]# kafka-server-start.sh /opt/software/kafka/config/server.properties &
[root@kafka03 ~]# kafka-server-start.sh /opt/software/kafka/config/server.properties &
# kafka01
[root@kafka01 ~]# jps -l | grep kafka
20955 kafka.Kafka
[root@kafka01 ~]# netstat -tnlpu|grep 9092
tcp 0 0 172.28.54.203:9092 0.0.0.0:* LISTEN 20955/java
# kafka02
[root@kafka02 ~]# jps -l | grep kafka
20322 kafka.Kafka
[root@kafka02 ~]# netstat -tnlpu|grep 9092
tcp 0 0 172.28.54.202:9092 0.0.0.0:* LISTEN 20322/java
# kafka03
[root@kafka03 ~]# jps -l | grep kafka
20293 kafka.Kafka
[root@kafka03 ~]# netstat -tnlpu|grep 9092
tcp 0 0 172.28.54.201:9092 0.0.0.0:* LISTEN 20293/java
# 创建一个topic
[root@kafka01 ~]# kafka-topics.sh --create --topic wangting --bootstrap-server kafka01:9092,kafka02:9092,kafka03:9092
[root@kafka01 ~]# kafka-topics.sh --create --topic wangting666 --bootstrap-server kafka01:9092,kafka02:9092,kafka03:9092
[root@kafka01 ~]# kafka-topics.sh --create --topic wangting_666 --bootstrap-server kafka01:9092,kafka02:9092,kafka03:9092
# topic创建wangting_666时,友情提示不建议使用"_"符号,所以尽量都使用字母以及数字的组合
[2022-09-17 18:03:41,646] INFO Creating topic wangting with configuration {} and initial partition assignment HashMap(0 -> ArrayBuffer(2)) (kafka.zk.AdminZkClient)
Created topic wangting.
# 查看某个topic信息
[root@kafka01 ~]# kafka-topics.sh --describe --topic wangting --bootstrap-server kafka01:9092,kafka02:9092,kafka03:9092
Topic: wangting TopicId: K9LpoplPTWCEnB6idWWHQw PartitionCount: 1 ReplicationFactor: 1 Configs: segment.bytes=1073741824
Topic: wangting Partition: 0 Leader: 2 Replicas: 2 Isr: 2
# 查看topic列表清单
[root@kafka01 ~]# kafka-topics.sh --list --bootstrap-server kafka01:9092,kafka02:9092,kafka03:9092
wangting
wangting666
wangting_666
# 在其中一个节点使用kafka-console-producer.sh生产消息
[root@kafka01 ~]# kafka-console-producer.sh --topic wangting --bootstrap-server kafka01:9092,kafka02:9092,kafka03:9092
>
# 在另一个节点使用kafka-console-consumer.sh消费消息
[root@kafka02 ~]# kafka-console-consumer.sh --topic wangting --from-beginning --bootstrap-server kafka01:9092,kafka02:9092,kafka03:9092
# 开始在生产端尝试输入消息
[root@kafka01 ~]# kafka-console-producer.sh --topic wangting --bootstrap-server kafka01:9092,kafka02:9092,kafka03:9092
>now is 2022-09-17 19:47
>i am wangting_666
>this is kafka_v3.1.0
>
# 查看消费端的控制台输出情况
[root@kafka02 ~]# kafka-console-consumer.sh --topic wangting --from-beginning --bootstrap-server kafka01:9092,kafka02:9092,kafka03:9092
now is 2022-09-17 19:47
i am wangting_666
this is kafka_v3.1.0
Apache Kafka 的 UI 工具 ,通过官方信息得知:可视化工具现在改名Offset Explorer
Offset Explorer(kafka tools)可视化工具官方下载地址:https://www.kafkatool.com/download.html
找到对应版本下载安装即可
编辑本地电脑hosts文件:C:\Windows\System32\drivers\etc
增加kafka集群的公网IP的解析并保存
# kafka
8.130.40.212 kafka01
8.130.33.123 kafka02
8.130.41.170 kafka03
点击Test保存,之后在左边Clusters中就可以看到新加的连接信息
在命令行查看验证是否已经有topic为FromKafkaTool的信息
[root@kafka03 ~]# kafka-topics.sh --describe --topic FromKafkaTool --bootstrap-server kafka01:9092,kafka02:9092,kafka03:9092
Topic: FromKafkaTool TopicId: lqLPMOQJSoqoZZOwcP_8WA PartitionCount: 1 ReplicationFactor: 1 Configs: segment.bytes=1073741824
Topic: FromKafkaTool Partition: 0 Leader: 1 Replicas: 1 Isr: 1
[root@kafka03 ~]#
基准测试(benchmark testing)是一种测量和评估软件性能指标的活动。我们可以通过基准测试,了解到软件、硬件的性能水平。主要测试负载的执行时间、传输速度、吞吐量、资源占用率等。
测试步骤:
[root@kafka01 ~]# kafka-topics.sh --create --topic benchmark --bootstrap-server kafka01:9092,kafka02:9092,kafka03:9092 --partitions 1 --replication-factor 1
Created topic benchmark.
# 查看topic信息
[root@kafka01 ~]# kafka-topics.sh --describe --topic benchmark --bootstrap-server kafka01:9092,kafka02:9092,kafka03:9092
Topic: benchmark TopicId: cJN7vW7qQYK_XlPAYlazuQ PartitionCount: 1 ReplicationFactor: 1 Configs: segment.bytes=1073741824
Topic: benchmark Partition: 0 Leader: 2 Replicas: 2 Isr: 2
[root@kafka01 ~]# kafka-producer-perf-test.sh --topic benchmark --num-records 5000000 --throughput -1 --record-size 1000 --producer-props bootstrap.servers=kafka01:9092,kafka02:9092,kafka03:9092 acks=1
164089 records sent, 32804.7 records/sec (31.28 MB/sec), 718.9 ms avg latency, 940.0 ms max latency.
338462 records sent, 67692.4 records/sec (64.56 MB/sec), 300.9 ms avg latency, 699.0 ms max latency.
351983 records sent, 70396.6 records/sec (67.14 MB/sec), 1.5 ms avg latency, 72.0 ms max latency.
201106 records sent, 40221.2 records/sec (38.36 MB/sec), 14.6 ms avg latency, 2090.0 ms max latency.
374303 records sent, 74860.6 records/sec (71.39 MB/sec), 244.9 ms avg latency, 2129.0 ms max latency.
344476 records sent, 68895.2 records/sec (65.70 MB/sec), 0.6 ms avg latency, 20.0 ms max latency.
331722 records sent, 64200.1 records/sec (61.23 MB/sec), 91.3 ms avg latency, 540.0 ms max latency.
361251 records sent, 72250.2 records/sec (68.90 MB/sec), 13.2 ms avg latency, 267.0 ms max latency.
351073 records sent, 70214.6 records/sec (66.96 MB/sec), 0.5 ms avg latency, 18.0 ms max latency.
337815 records sent, 64260.0 records/sec (61.28 MB/sec), 49.8 ms avg latency, 572.0 ms max latency.
367673 records sent, 73534.6 records/sec (70.13 MB/sec), 40.3 ms avg latency, 390.0 ms max latency.
345800 records sent, 69160.0 records/sec (65.96 MB/sec), 0.7 ms avg latency, 33.0 ms max latency.
336629 records sent, 62792.2 records/sec (59.88 MB/sec), 0.6 ms avg latency, 552.0 ms max latency.
374130 records sent, 74826.0 records/sec (71.36 MB/sec), 43.0 ms avg latency, 555.0 ms max latency.
349857 records sent, 69971.4 records/sec (66.73 MB/sec), 0.5 ms avg latency, 17.0 ms max latency.
5000000 records sent, 65110.101181 records/sec (62.09 MB/sec), 79.74 ms avg latency, 2129.00 ms max latency, 0 ms 50th, 591 ms 95th, 894 ms 99th, 2115 ms 99.9th.
结果分析:
测试指标 | 测试数值 |
---|---|
吞吐量 | 65110.101181 records/sec 每秒6.5W条记录 |
吞吐速率 | (62.09 MB/sec)每秒约62.09MB数据 |
平均延迟时间 | 79.74 ms avg latency |
最大延迟时间 | 2129.00 ms max latency |
kafka-producer-perf-test.sh --topic benchmark --num-records 5000000 --throughput -1 --record-size 1000 --producer-props bootstrap.servers=kafka01:9092,kafka02:9092,kafka03:9092 acks=1
kafka-producer-perf-test.sh # 测试脚本
–topic topic benchmark # 测试主题topic为benchmark
–num-records # 总共指定生产数据量(5000000条,不设置则默认5000W条)
–throughput # 指定吞吐量——限流(-1不指定限流)
–record-size # record数据大小(单位字节)
–producer-props bootstrap.servers=192.168.1.20:9092,192.168.1.21:9092,192.168.1.22:9092 # 指定Kafka集群地址
acks=1 # ACK模式
[root@kafka01 ~]# kafka-consumer-perf-test.sh --broker-list kafka01:9092,kafka02:9092,kafka03:9092 --topic benchmark --fetch-size 1048576 --messages 5000000
start.time, end.time, data.consumed.in.MB, MB.sec, data.consumed.in.nMsg, nMsg.sec, rebalance.time.ms, fetch.time.ms, fetch.MB.sec, fetch.nMsg.sec
2022-09-17 21:23:43:574, 2022-09-17 21:23:58:547, 4768.3716, 318.4647, 5000000, 333934.4153, 423, 14550, 327.7231, 343642.6117
测试项目 | 测试数值 |
---|---|
data.consumed.in.MB 共计消费的数据 |
4768.3716MB |
MB.sec 每秒消费的数量 |
318.4647 每秒318MB |
data.consumed.in.nMsg 共计消费的数量 |
5000000 |
nMsg.sec 每秒的数量 |
333934.4153 每秒34.36W条 |
测试步骤:
[root@kafka01 ~]# kafka-topics.sh --create --topic benchmark2 --bootstrap-server kafka01:9092,kafka02:9092,kafka03:9092 --partitions 3 --replication-factor 1
Created topic benchmark2.
[root@kafka01 ~]# kafka-topics.sh --describe --topic benchmark2 --bootstrap-server kafka01:9092,kafka02:9092,kafka03:9092
Topic: benchmark2 TopicId: P9MPJSyVT2inSU-G1uZhbg PartitionCount: 3 ReplicationFactor: 1 Configs: segment.bytes=1073741824
Topic: benchmark2 Partition: 0 Leader: 0 Replicas: 0 Isr: 0
Topic: benchmark2 Partition: 1 Leader: 2 Replicas: 2 Isr: 2
Topic: benchmark2 Partition: 2 Leader: 1 Replicas: 1 Isr: 1
[root@kafka01 ~]# kafka-producer-perf-test.sh --topic benchmark2 --num-records 5000000 --throughput -1 --record-size 1000 --producer-props bootstrap.servers=kafka01:9092,kafka02:9092,kafka03:9092 acks=1
149989 records sent, 29997.8 records/sec (28.61 MB/sec), 359.5 ms avg latency, 2125.0 ms max latency.
239592 records sent, 47861.0 records/sec (45.64 MB/sec), 447.2 ms avg latency, 2000.0 ms max latency.
332516 records sent, 66436.8 records/sec (63.36 MB/sec), 250.1 ms avg latency, 1227.0 ms max latency.
340064 records sent, 68012.8 records/sec (64.86 MB/sec), 6.3 ms avg latency, 76.0 ms max latency.
331992 records sent, 66398.4 records/sec (63.32 MB/sec), 2.2 ms avg latency, 65.0 ms max latency.
344061 records sent, 68812.2 records/sec (65.62 MB/sec), 2.4 ms avg latency, 42.0 ms max latency.
339294 records sent, 67858.8 records/sec (64.72 MB/sec), 5.8 ms avg latency, 123.0 ms max latency.
337419 records sent, 67483.8 records/sec (64.36 MB/sec), 2.3 ms avg latency, 45.0 ms max latency.
337766 records sent, 67553.2 records/sec (64.42 MB/sec), 4.9 ms avg latency, 145.0 ms max latency.
339366 records sent, 67873.2 records/sec (64.73 MB/sec), 1.8 ms avg latency, 52.0 ms max latency.
341334 records sent, 68266.8 records/sec (65.10 MB/sec), 1.5 ms avg latency, 45.0 ms max latency.
339645 records sent, 67929.0 records/sec (64.78 MB/sec), 1.8 ms avg latency, 37.0 ms max latency.
336588 records sent, 67317.6 records/sec (64.20 MB/sec), 1.4 ms avg latency, 30.0 ms max latency.
335838 records sent, 67167.6 records/sec (64.06 MB/sec), 80.9 ms avg latency, 634.0 ms max latency.
331386 records sent, 66277.2 records/sec (63.21 MB/sec), 3.4 ms avg latency, 70.0 ms max latency.
5000000 records sent, 63858.591535 records/sec (60.90 MB/sec), 56.74 ms avg latency, 2125.00 ms max latency, 1 ms 50th, 422 ms 95th, 1395 ms 99th, 2006 ms 99.9th.
结果分析:
测试指标 | 3分区1个副本 | 对比单分区单副本 |
---|---|---|
吞吐量 | 63858.591535 records/sec | 65110.101181 records/sec |
吞吐速率 | 60.90 MB/sec | 62.09 MB/sec |
平均延迟时间 | 56.74 ms avg latency | 79.74 ms avg latency |
最大延迟时间 | 2125.00 ms max latency | 2129.00 ms max latency |
因为云主机都是低配置,所以分区多和单分区相比,效果没有明显区别,但如果是真实的高配服务器,分区多效率是会有明显提升
[root@kafka01 ~]# kafka-consumer-perf-test.sh --broker-list kafka01:9092,kafka02:9092,kafka03:9092 --topic benchmark2 --fetch-size 1048576 --messages 5000000
start.time, end.time, data.consumed.in.MB, MB.sec, data.consumed.in.nMsg, nMsg.sec, rebalance.time.ms, fetch.time.ms, fetch.MB.sec, fetch.nMsg.sec
2022-09-17 21:50:39:601, 2022-09-17 21:50:51:955, 4768.3716, 385.9779, 5000000, 404727.2139, 409, 11945, 399.1939, 418585.1821
[root@kafka01 ~]#
结果比对
测试指标 | 3分区1个副本 | 对比单分区单副本 |
---|---|---|
data.consumed.in.MB 共计消费的数据 |
4768.3716MB | 4768.3716MB |
MB.sec 每秒消费的数量 |
385.9779 每秒385MB |
318.4647 每秒318MB |
data.consumed.in.nMsg 共计消费的数量 |
5000000 | 5000000 |
nMsg.sec 每秒的数量 |
404727.2139 每秒41.85W条 |
333934.4153 每秒34.36W条 |
groupid
artifactId
pom配置文件
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>cn.wangtinggroupId>
<artifactId>kafka_testartifactId>
<version>1.0-SNAPSHOTversion>
<repositories>
<repository>
<id>centralid>
<url>http://maven.aliyun.com/nexus/content/groups/public/url>
<releases>
<enabled>trueenabled>
releases>
<snapshots>
<enabled>trueenabled>
<updatePolicy>alwaysupdatePolicy>
<checksumPolicy>failchecksumPolicy>
snapshots>
repository>
repositories>
<dependencies>
<dependency>
<groupId>org.apache.kafkagroupId>
<artifactId>kafka-clientsartifactId>
<version>3.1.0version>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-ioartifactId>
<version>1.3.2version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
<version>1.7.6version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.16version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.7.0version>
<configuration>
<source>1.8source>
<target>1.8target>
configuration>
plugin>
plugins>
build>
project>
创建包cn.wangting.kafka,并创建KafkaProducerTest类
并且println(“hello kafka”)测试是否可以正常运行
导入log4j.properties文件:
下载地址:https://osswangting.oss-cn-shanghai.aliyuncs.com/kafka/log4j.properties
将log4j.properties配置文件放入到项目的resources目录中
KafkaProducerTest代码:
package cn.wangting.kafka;
import org.apache.kafka.clients.producer.*;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
public class KafkaProducerTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1. 创建用于连接Kafka的Properties配置
Properties props = new Properties();
props.put("bootstrap.servers", "kafka01:9092,kafka02:9092,kafka03:9092");
props.put("acks", "all");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
// 2. 创建一个生产者对象KafkaProducer
KafkaProducer<String, String> producer = new KafkaProducer<String, String>(props);
// 3. 调用send发送1-100消息到指定Topic test
for(int i = 0; i < 100; ++i) {
try {
// 获取返回值Future,该对象封装了返回值
Future<RecordMetadata> future = producer.send(new ProducerRecord<String, String>("wangting", null, i + ""));
// 调用一个Future.get()方法等待响应
future.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
执行代码后控制台输出:
"C:\Program Files\Java\jdk1.8.0_192\bin\java.exe" "-javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2020.1\lib\idea_rt.jar=11533:D:\Program Files\JetBrains\IntelliJ IDEA 2020.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program ...
...
INFO - Kafka version: 3.1.0
INFO - Kafka commitId: 37edeed0777bacb3
INFO - Kafka startTimeMs: 1663426544939
INFO - [Producer clientId=producer-1] Resetting the last seen epoch of partition wangting-0 to 0 since the associated topicId changed from null to K9LpoplPTWCEnB6idWWHQw
INFO - [Producer clientId=producer-1] Cluster ID: m9Q1ky6jT2-UdtVA_AoUsw
Process finished with exit code 0
通过界面工具去验证是否有数据写入
从 wangting topic中,将消息都消费,并将记录的offset、key、value打印出来
在cn.wangting.kafka包下创建KafkaConsumerTest类
KafkaConsumerTest代码:
package cn.wangting.kafka;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collections;
import java.util.Properties;
public class KafkaConsumerTest {
public static void main(String[] args) throws InterruptedException {
// 1.创建Kafka消费者配置
Properties props = new Properties();
props.setProperty("bootstrap.servers", "kafka01:9092,kafka02:9092,kafka03:9092");
// 自动提交offset
props.setProperty("enable.auto.commit", "true");
// 自动提交offset的时间间隔
props.setProperty("auto.commit.interval.ms", "1000");
// 拉取的key、value数据的
props.setProperty("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.setProperty("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
// 2.创建Kafka消费者
KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<>(props);
// 3. 订阅要消费的主题
// 指定消费者从哪个topic中拉取数据
kafkaConsumer.subscribe(Collections.singletonList("wangting"));
// 4.使用一个while循环,不断从Kafka的topic中拉取消息
while(true) {
// Kafka的消费者一次拉取一批的数据
ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(5));
// 5.将将记录(record)的offset、key、value都打印出来
for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
// 主题
String topic = consumerRecord.topic();
// offset:这条消息处于Kafka分区中的哪个位置
long offset = consumerRecord.offset();
// key\value
String key = consumerRecord.key();
String value = consumerRecord.value();
System.out.println("topic: " + topic + " offset:" + offset + " key:" + key + " value:" + value);
}
Thread.sleep(1000);
}
}
}
执行效果:
...
...
topic: wangting offset:188 key:null value:84
topic: wangting offset:189 key:null value:85
topic: wangting offset:190 key:null value:86
topic: wangting offset:191 key:null value:87
topic: wangting offset:192 key:null value:88
topic: wangting offset:193 key:null value:89
topic: wangting offset:194 key:null value:90
topic: wangting offset:195 key:null value:91
topic: wangting offset:196 key:null value:92
topic: wangting offset:197 key:null value:93
topic: wangting offset:198 key:null value:94
topic: wangting offset:199 key:null value:95
topic: wangting offset:200 key:null value:96
topic: wangting offset:201 key:null value:97
topic: wangting offset:202 key:null value:98
topic: wangting offset:203 key:null value:99
成功获取到消息队列中的数据
如果我们想获取生产者消息是否成功,或者成功生产消息到Kafka中后,执行一些其他动作。此时,可以很方便地使用带有回调函数来发送消息。
需求:
在发送消息出现异常时,能够及时打印出异常信息
在发送消息成功时,打印Kafka的topic名字、分区id、offset
在cn.wangting.kafka包下创建KafkaProducerTest2类
package cn.wangting.kafka;
import org.apache.kafka.clients.producer.Callback;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
public class KafkaProducerTest2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1. 创建用于连接Kafka的Properties配置
Properties props = new Properties();
props.put("bootstrap.servers", "kafka01:9092,kafka02:9092,kafka03:9092");
props.put("acks", "all");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
// 2. 创建一个生产者对象KafkaProducer
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(props);
int MAX = 10000000;
// 3. 发送1-100的消息到指定的topic中
for(int i = 1000000; i < MAX; ++i) {
// 二、使用异步回调的方式发送消息
ProducerRecord<String, String> producerRecord = new ProducerRecord<>("wangting666", null, i + "");
kafkaProducer.send(producerRecord, (metadata, exception) -> {
// 1. 判断发送消息是否成功
if(exception == null) {
// 发送成功
// 主题
String topic = metadata.topic();
// 分区id
int partition = metadata.partition();
// 偏移量
long offset = metadata.offset();
System.out.println("topic:" + topic + " 分区id:" + partition + " 偏移量:" + offset);
}
else {
// 发送出现错误
System.out.println("生产消息出现异常!");
// 打印异常消息
System.out.println(exception.getMessage());
// 打印调用栈
System.out.println(exception.getStackTrace());
}
});
Thread.sleep(1000);
}
// 4.关闭生产者
kafkaProducer.close();
}
}
控制台输出内容:
topic:wangting666 分区id:0 偏移量:221
topic:wangting666 分区id:0 偏移量:222
topic:wangting666 分区id:0 偏移量:223
topic:wangting666 分区id:0 偏移量:224
topic:wangting666 分区id:0 偏移量:225
topic:wangting666 分区id:0 偏移量:226
...
...
打开界面管理工具验证消息
在kafka里面,broker是消息的中介,生产者producer往broker里面指定的topic中写消息,消费者consumer从broker里面拉取指定topic的消息,然后进行业务处理,broker在中间起到一个代理保存消息的中转站。
通过命令行进入到zookeeper可以查看kafka的元数据
[root@kafka01 ~]# zkCli.sh
Connecting to localhost:2181
...
...
Welcome to ZooKeeper!
JLine support is enabled
# topics信息
[zk: localhost:2181(CONNECTED) 4] ls /brokers/topics
[FromKafkaTool, __consumer_offsets, benchmark, benchmark2, wangting, wangting666, wangting_666]
quit退出
producer生产者负责将数据推送给broker的topic,是kafka中消息的产生方,产生消息并提交给kafka集群完成消息的持久化,这个过程中主要涉及ProducerRecord对象的构建、分区选择、元数据的填充、ProducerRecord对象的序列化、进入消息缓冲池、完成消息的发送、接受broker的响应
ProducerRecord:
ProducerRecord 对象比较核心的信息有:topic、partition(这个信息是根据分区选择器来确定的)、key、value、timestamp
consumer消费者负责从broker的topic中拉取数据,并自己进行处理
当一个Topic有两个消费者或者更多时,而只有一个partition时,只有一个消费者程序能够拉取到消息。想要让两个消费者同时消费消息,必须要给这个Topic主题,添加一个分区,也就是说分区数量小于消费者数量时,会有消费者消费不到数据
partition(分区)是kafka的一个核心概念,kafka将1个topic分成了一个或多个分区,每个分区在物理上对应一个目录,分区目录下存储的是该分区的日志段(segment),包括日志的数据文件和两个索引文件。然后每个分区又对应一个或多个副本,由一个ISR列表来维护。
注意:分区数可以大于节点数,但是副本数不能大于节点数,因为副本需要分不到不同的节点上,才能达到备份的目的。
# 例如创建benchmark2主题时设置为3个分区,创建wangting_666主题时设置为1个分区
[root@kafka01 ~]# ll /opt/software/kafka/kafka-logs | grep benchmark2
drwxr-xr-x 2 root root 4096 Sep 17 21:38 benchmark2-0
[root@kafka01 ~]# ll /opt/software/kafka/kafka-logs | grep wangting_666
drwxr-xr-x 2 root root 4096 Sep 17 19:02 wangting_666-0
[root@kafka01 ~]#
[root@kafka02 ~]# ll /opt/software/kafka/kafka-logs | grep benchmark2
drwxr-xr-x 2 root root 4096 Sep 17 21:38 benchmark2-2
[root@kafka02 ~]# ll /opt/software/kafka/kafka-logs | grep wangting_666
[root@kafka02 ~]#
[root@kafka03 ~]# ll /opt/software/kafka/kafka-logs | grep benchmark2
drwxr-xr-x 2 root root 4096 Sep 17 21:38 benchmark2-1
[root@kafka03 ~]# ll /opt/software/kafka/kafka-logs | grep wangting_666
[root@kafka03 ~]#
# 可以看出来wangting_666主题只对应在kafka01上有wangting_666-0
# 而benchmark2主题在kafka01上有benchmark2-0,在kafka02上有benchmark2-2,在kafka03上有benchmark2-1
replicas(副本)就是一个只能追加写消息的提交日志。根据 Kafka 副本机制的定义,同一个分区下的所有副本保存有相同的消息序列,这些副本分散保存在不同的 Broker 上,从而能够对抗部分 Broker 宕机带来的数据不可用。
在 Kafka 中,Topic可以认为是一个消息集合。每条消息发送到 Kafka 集群的消息都有一个类别。物理上来说,不同的 Topic 的消息是分开存储的,每个 Topic 可以有多个生产者向它发送消息,也可以有多个消费者去消费其中的消息。
[root@kafka01 ~]# zkCli.sh
Connecting to localhost:2181
[zk: localhost:2181(CONNECTED) 0] ls /brokers/topics
[FromKafkaTool, __consumer_offsets, benchmark, benchmark2, wangting, wangting666, wangting_666]
[zk: localhost:2181(CONNECTED) 2] ls /brokers/topics/__consumer_offsets/partitions
[0, 1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 2, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 3, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 4, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 5, 6, 7, 8, 9]
[zk: localhost:2181(CONNECTED) 3]
Kafka接收到生产者发送的消息实际上是以日志文件的形式保存在对应分区的磁盘上。每条消息都有一个offset值来表示它在分区中的位置。每次写入都是追加到文件的末尾
上图代表一个日志文件,这个日志文件中有 9 条消息,第一条消息的 offset( logStartOffset)为 0,最后一条消息的 offset 为 8,LEO(Log End Offset)为 9 ,代表下一条待写入的消息。日志文件的 HW(Low Watermark)为 6,表示消费者只能拉取到 offset 在 0 至 5 之间的消息, 而 offset 为 6 的消息对消费者而言是不可见的
消费者在消费时,也维护一个offset,表示消费到分区中的某个消息所在的位置
ConsumerA的offset=9,表示ConsumerA已经消费完offset为8的那条数据,提交的offset值为9,下次消费从offset为9的数据开始消费
Kafka分生产者producer和消费者consumer,生产者负责把消息写入topic,topic类似于离线的hive表,生产者会记录自己写到哪个位置,这个位置信息就是生产者的offset,并不断地追加写入,更新offset信息,同样消费者也会有消费者自己的状态,一个消费者会根据自己的程序消费数据情况记录当前消费的位置,即消费的offset,与生产者的offset以及其他消费者的offset互相独立。
生产者在发送消息的时候难免会有数据重复,这时候如何来保障呢?生产者有重试机制,在出现故障,网络异常等情况,导致消息重复发送。而Kafka的“幂等性”,可以保证即使有重复消息发送也只写入一条有效消息。
当生产者发送消息给Broker时,Broker接收到消息并追加到消息流。这时Broker会返回Ack信号给生产者时,发生异常导致生产者接收Ack信号失败。
对于生产者来说,会触发重试机制,再次发送消息,但是,由于引入了幂等性,在每条消息中附带了生产者id和SequenceNumber。相同的PID(每个新的Producer初始化时,会被分配一个唯一的ProducerID,这个ProducerID对客户端使用者是不可见的。)和相应的SequenceNumber(可以理解成生产者的offset)发送给Broker,而之前Broker缓存过之前发送的相同的消息,那么在消息流中的消息就只有一条,不会出现重复发送的情况。
简单来说有点像传统数据库的事务,也就是追加数据和offset更新必须都成功,才能都算成功。幂等性可以较好的保障数据重复写入和数据丢失问题。
在生产者生产消息时,如果出现retry时,有可能会一条消息被发送了多次,如果Kafka不具备幂等性的,那么就有可能会在partition中保存多条一模一样的消息
# 命令行中配置,执行脚本命令中加入如下参数
enable.idempotence=true
// 代码中配置
props.put("enable.idempotence",true);
Kafka事务是2017年Kafka 0.11.0.0引入的新特性。类似于数据库的事务。Kafka事务指的是生产者生产消息以及消费者提交offset的操作可以在一个原子操作中,要么都成功,要么都失败。尤其是在生产者、消费者并存时,事务的保障尤其重要。(consumer-transform-producer模式)
Producer接口中定义了以下5个事务相关方法:
initTransactions(初始化事务):要使用Kafka事务,必须先进行初始化操作
beginTransaction(开始事务):启动一个Kafka事务
sendOffsetsToTransaction(提交偏移量):批量地将分区对应的offset发送到事务中,方便后续一块提交
commitTransaction(提交事务):提交事务
abortTransaction(放弃事务):取消事务
默认的策略,也是使用最多的策略,可以最大限度保证所有消息平均分配到一个分区
如果在生产消息时,key为null,则使用轮询算法均衡地分配分区
随机策略,每次都随机地将消息分配到每个分区。在较早的版本,默认的分区策略就是随机策略,也是为了将消息均衡地写入到每个分区。但后续轮询策略表现更佳,所以基本上很少会使用随机策。
按key分配策略,有可能会出现「数据倾斜」,例如:某个key包含了大量的数据,因为key值一样,那么所有的数据将都分配到一个分区中,造成该分区的消息数量远大于其他的分区。
轮询策略、随机策略都会导致一个问题,生产到Kafka中的数据是乱序存储的。而按key分区可以一定程度上实现数据有序存储——也就是局部有序,但这又可能会导致数据倾斜,所以在实际生产环境中要结合实际情况来做取舍。
用户自行定义分配的策略机制
Kafka中的Rebalance称之为再均衡,是Kafka中确保Consumer group下所有的consumer如何达成一致,分配订阅的topic的每个分区的机制
消费者组中consumer的个数发生变化。例如:有新的consumer加入到消费者组,或者是某个consumer停止
订阅的topic个数发生变化
订阅的topic分区数发生变化
Range范围分配策略是Kafka默认的分配策略,它可以确保每个消费者消费的分区数量是均衡的。
注意:Rangle范围分配策略是针对每个Topic的
算法公式
n = 分区数量 / 消费者数量
m = 分区数量 % 消费者数量
前m个消费者消费n+1个
剩余消费者消费n个
RoundRobinAssignor轮询策略是将消费组内所有消费者以及消费者所订阅的所有topic的partition按照字典序排序(topic和分区的hashcode进行排序),然后通过轮询方式逐个将分区以此分配给每个消费者
分区分配尽可能均匀
在发生rebalance的时候,分区的分配尽可能与上一次分配保持相同
没有发生rebalance时,Striky粘性分配策略和RoundRobin分配策略类似
Striky粘性分配策略,保留rebalance之前的分配结果。这样,只是将原先consumer2负责的两个分区再均匀分配给consumer0、consumer1。这样可以明显减少系统资源的浪费,例如:之前consumer0、consumer1之前正在消费某几个分区,但由于rebalance发生,导致consumer0、consumer1需要重新消费之前正在处理的分区,导致不必要的系统开销。(例如:某个事务正在进行就必须要取消了)
副本的目的就是冗余备份,当某个Broker上的分区数据丢失时,依然可以保障数据可用。因为在其他的Broker上的副本是可用的
对副本关系较大的就是,producer配置的acks参数,acks参数表示当生产者生产消息的时候,写入到副本的要求严格程度。它决定了生产者如何在性能和可靠性之间做取舍
// props.put("acks", "all");为定义副本写入严格级别程度
Properties props = new Properties();
props.put("bootstrap.servers", "kafka01:9092,kafka02:9092,kafka03:9092");
props.put("acks", "all");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
当生产者的ACK配置为0时,生产者写入消息不等待broker确认,直接发送下一条消息数据,这种策略性能最高,但也存在数据丢失的可能性
当生产者的ACK配置为1时,生产者会等待leader副本确认接收后,才会发送下一条数据,性能中等。
当生产者的ACK配置为-1或者all时,生产者会等待所有副本已经将数据同步完成后,才会发送下一条数据,性能最慢
在开发工作中,当业务前提不复杂时,可以使用Kafka命令来进行一些集群的管理工作。但如果业务变得复杂,例如:我们需要增加group、topic分区,此时,我们再使用命令行就感觉很不方便,此时,如果使用一个可视化的工具帮助我们完成日常的管理工作,将会大大提高对于Kafka集群管理的效率,而且我们使用工具来监控消费者在Kafka中消费情况。
早期,要监控Kafka集群我们可以使用Kafka Monitor以及Kafka Manager,但随着我们对监控的功能要求、性能要求的提高,这些工具已经无法满足。
Kafka Eagle是一款结合了目前大数据Kafka监控工具的特点,重新研发的一块开源免费的Kafka集群优秀的监控工具。它可以非常方便的监控生产环境中的offset、lag变化、partition分布、owner等。
官方地址:https://www.kafka-eagle.org/
极力推荐此管理工具
注意:
安装使用kafka-eagle工具需要有java环境
[root@kafka01 software]# java -version
java version “1.8.0_211”
Java™ SE Runtime Environment (build 1.8.0_211-b12)
Java HotSpot™ 64-Bit Server VM (build 25.211-b12, mixed mode)
[root@kafka01 ~]# cd /opt/software/
[root@kafka01 software]# wget https://github.com/smartloli/kafka-eagle-bin/archive/v3.0.1.tar.gz
[root@kafka01 software]# tar -xf v3.0.1.tar.gz
[root@kafka01 software]# ln -s kafka-eagle-bin-3.0.1 eagle
[root@kafka01 software]# cd eagle/
[root@kafka01 eagle]# ll
total 87840
-rw-rw-r-- 1 root root 89947836 Sep 6 12:45 efak-web-3.0.1-bin.tar.gz
[root@kafka01 eagle]# tar -xf efak-web-3.0.1-bin.tar.gz
[root@kafka01 eagle]# cd efak-web-3.0.1
[root@kafka01 eagle]# mv efak-web-3.0.1 efak-web
[root@kafka01 efak-web]# vim /etc/profile
# efak
export KE_HOME=/opt/software/eagle/efak-web
export PATH=$PATH:$KE_HOME/bin
[root@kafka01 efak-web]# source /etc/profile
[root@kafka01 efak-web]# cd /opt/software/eagle/efak-web/conf/
[root@kafka01 conf]# vim system-config.properties
efak.zk.cluster.alias=cluster1
cluster1.zk.list=kafka01:2181,kafka02:2181,kafka03:2181
#cluster2.zk.list=xdn10:2181,xdn11:2181,xdn12:2181
efak.metrics.charts=true
efak.metrics.retain=30
efak.driver=com.mysql.cj.jdbc.Driver
efak.url=jdbc:mysql://47.64.11.13:3306/ke?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
efak.username=root
efak.password=123456
[root@kafka01 ~]# ke.sh start
Welcome to
______ ______ ___ __ __
/ ____/ / ____/ / | / //_/
/ __/ / /_ / /| | / ,<
/ /___ / __/ / ___ | / /| |
/_____/ /_/ /_/ |_|/_/ |_|
( Eagle For Apache Kafka庐 )
Version v3.0.1 -- Copyright 2016-2022
*******************************************************************
* EFAK Service has started success.
* Welcome, Now you can visit 'http://172.28.54.203:8048'
* Account:admin ,Password:123456
*******************************************************************
* <Usage> ke.sh [start|status|stop|restart|stats] </Usage>
* <Usage> https://www.kafka-eagle.org/ </Usage>
*******************************************************************
根据输出可以看到打开的地址和用户密码
注意远程访问需要有公网IP地址,内网访问,则需要内网ip互联互通
通过本地访问:http://kafka01:8048打开
通过Overview- TV Dashboard可以看到炫酷的大屏监控
点击Topic下的List菜单,就可以展示当前Kafka集群中的所有topic
指标 | 意义 |
---|---|
Topic Name | 当前的Topic名称 |
Partitions | 对应Topic的分区数 |
Brokers Spread | broker使用率,红色提醒为使用率较低 |
Brokers Skew | 分区是否有倾斜 |
Brokers Leader Skew | leader partition是否存在倾斜 |
Created | Topic的创建时间 |
Modify | Topic的最近一次修改时间 |
红色信息仅为提示,并非报错以及警告
[root@kafka01 ~]# kafka-topics.sh --list --bootstrap-server kafka01:9092,kafka02:9092,kafka03:9092
FromKafkaTool
__consumer_offsets
benchmark
benchmark2
wangting
wangting0918
wangting666
wangting_666
从工具可以看到Topic主题benchmark2的partiton0分区信息Leader在Broker0上,partiton1分区信息Leader在Broker2上,而partiton2分区信息Leader在Broker1上
KSQL 是 Apache Kafka 的开源流 SQL 引擎。 它为 Kafka 的流处理提供了一个简单而完整的 SQL 界面;不需要再用编程语言(如 Java 或 Python )编写代码。 KSQL 是分布式、可扩展、可靠的和实时的,支持多种流式操作,包括聚合(aggregate)、连接(join)、时间窗口(window)、会话(session)等等。基于 Apache 2.0 协议开源。
KSQL 的两个核心概念是流(Stream)和表(Table),它将流和表集成在一起,允许将代表当前状态的表与代表当前发生事件的流连接在一起
在Kafka中,每个topic都可以配置多个分区以及多个副本。每个分区都有一个leader以及0个或者多个follower,在创建topic时,Kafka会将每个分区的leader均匀地分配在每个broker上。我们正常使用kafka是感觉不到leader、follower的存在的。但其实,所有的读写操作都是由leader处理,而所有的follower都复制leader的日志数据文件,如果leader出现故障时,follower就会被选举为leader
通过命令行查看某个Topic的分区信息:
[root@kafka01 ~]# kafka-topics.sh --describe --topic benchmark2 --bootstrap-server kafka01:9092,kafka02:9092,kafka03:9092
Topic: benchmark2 TopicId: P9MPJSyVT2inSU-G1uZhbg PartitionCount: 3 ReplicationFactor: 1 Configs: segment.bytes=1073741824
Topic: benchmark2 Partition: 0 Leader: 0 Replicas: 0 Isr: 0
Topic: benchmark2 Partition: 1 Leader: 2 Replicas: 2 Isr: 2
Topic: benchmark2 Partition: 2 Leader: 1 Replicas: 1 Isr: 1
或者通过EFAK界面查看Topic
在实际环境中,leader有可能会出现一些故障,所以Kafka一定会选举出新的leader。在讲解leader选举之前,我们先要明确几个概念。Kafka中,把follower可以按照不同状态分为三类——AR、ISR、OSR
leader在崩溃后,Kafka又从其他的follower中快速选举出来了leader
为什么不能通过ZK的方式来选举partition的leader?
- Kafka集群如果业务很多的情况下,会有很多的partition
- 假设某个broker宕机,就会出现很多的partiton都需要重新选举leader
- 如果使用zookeeper选举leader,会给zookeeper带来巨大的压力。所以,kafka中leader的选举不能使用ZK来实现
生产者通过分区的leader写入数据后,所有在ISR中follower都会从leader中复制数据,这样,可以确保即使leader崩溃了,其他的follower的数据仍然是可用的
生产者连接leader写入数据时,可以通过ACK机制来确保数据已经成功写入。ACK机制有三个可选配置
生产者可以采用同步和异步两种方式发送数据
在消费者消费数据的时候,只要每个消费者记录好offset值即可,就能保证数据不丢失。
Kafka消费者消费数据的速度是非常快的,但如果由于处理Kafka消息时,由于有一些外部IO、或者是产生网络拥堵,就会造成Kafka中的数据积压(或称为数据堆积)。如果数据一直积压,会导致数据出来的实时性受到较大影响。
Kafka的消息存储在磁盘中,为了控制磁盘占用空间,Kafka需要不断地对过去的一些消息进行清理工作。Kafka的每个分区都有很多的日志文件,这样也是为了方便进行日志的清理。在Kafka中,提供两种日志清理方式:
在Kafka的broker或topic配置中:
配置项 | 配置值 | 说明 |
---|---|---|
log.cleaner.enable | true(默认) | 开启自动清理日志功能 |
log.cleanup.policy | delete(默认) | 删除日志 |
log.cleanup.policy | compaction | 压缩日志 |
log.cleanup.policy | delete,compact | 同时支持删除、压缩 |
日志删除是以段(segment日志)为单位来进行定期清理的
Kafka日志管理器中会有一个专门的日志删除任务来定期检测和删除不符合保留条件的日志分段文件,这个周期可以通过broker端参数log.retention.check.interval.ms来配置,默认值为300,000,即5分钟。当前日志分段的保留策略有3种:
以下三种配置可以指定如果Kafka中的消息超过指定的阈值,就会将日志进行自动清理:
其中,优先级为 log.retention.ms > log.retention.minutes > log.retention.hours。默认情况,在broker中,配置如下:
log.retention.hours=168
也就是,默认日志的保留时间为168小时,相当于保留7天
删除日志分段时:
从日志文件对象中所维护日志分段的跳跃表中移除待删除的日志分段,以保证没有线程对这些日志分段进行读取操作
将日志分段文件添加上“.deleted”的后缀(也包括日志分段对应的索引文件)
Kafka的后台定时任务会定期删除这些“.deleted”为后缀的文件,这个任务的延迟执行时间可以通过file.delete.delay.ms参数来设置,默认值为60000,即1分钟
日志删除任务会检查当前日志的大小是否超过设定的阈值来寻找可删除的日志分段的文件集合。可以通过broker端参数 log.retention.bytes 来配置,默认值为-1,表示无穷大。如果超过该大小,会自动将超出部分删除。
注意:
log.retention.bytes 配置的是日志文件的总大小,而不是单个的日志分段的大小,一个日志文件包含多个日志分段
每个segment日志都有它的起始偏移量,如果起始偏移量小于 logStartOffset,那么这些日志文件将会标记为删除
Log Compaction是默认的日志删除之外的清理过时数据的方式。它会将相同的key对应的数据只保留一个版本