初识Kafka
初识Kafka
基础概念
- Broker:代表Kafka节点,一个Broker就是一个Kafka服务
- Topic:消息主题,是对消息的分类,类似数据库中的表的概念
- Partition:分区,一个主题下的消息可以有多个分区,且可以在不同的Kafka节点上,由此实现Kafka的伸缩性,Partition类型数据库分表的概念
- Producer:生产者,向某个主题发布消息的客户端统一称之为生产者
- Comsumer:消费者,消费某个主题中的消息的客户端统一称之为消费之
- Group:消费者组,一个消费者组里可以有多个消费者,这些消费者消费同一个主题
下图为官方给出的最经典的各个概念之间的关系图
如图所示,图中共有两个Kafka节点,即两个Broker,每个节点上分别有两个分区,一共有四个Partition,下面有两个消费者组,GroupA和GroupB,GroupA中有两个消费者C1,C2,C1负责消费P0和P3,C2负责消费P1和P1,GroupB中有四个消费者C3,C4,C5,C6,各自负责一个Partition进行消费,需要注意的是,消费者组中的消费者数量不应大于分区数,因为一个分区最多只能由一个消费者进行处理,多余的消费者会空闲下来,最好消费者和分区数是相等的。
注意:Kafka保证同一个分区下的消息具有顺序性,因此对于对消息有顺序消费的要求的业务,应只能建立一个分区,且只有一个消费者
Kafka安装
kafka下载地址:https://kafka.apache.org/downloads
启动zookeeper
Kafka的运行依赖zookeeper,因此需要先部署zookeeper,kafka安装包中默认集成了一个单节点zookeeper,解压kafka安装包之后,进入bin目录下 执行如下命令启动一个单节点zookeeper
./zookeeper-server-start.sh ../config/zookeeper.properties
备注:zookeeper默认端口为2181,如需修改,修改zookeeper.properties中clientPort配置
启动kafka
使用如下命令启动一个kafka
./kafka-server-start.sh ../config/server.properties
备注:Kafka默认端口为9092,如需修改,修改server.properties中port配置
上述命令启动了一个单节点的Kafka,也就是一个broker,如需进行多节点集群部署,只需指定不同的配置文件再启动即可,通常不同节点的Kafka都部署在不同的宿主机上,接下来复制一份配置文件,再启动一个节点
cp server.properties server-1.properties
vim server-1.properties
修改server-1.properties中的如下配置
# 节点ID,各个节点保持唯一
broker.id=1
# 日志存储位置
log.dirs=/home/kafka-logs-1
# 启动端口
port=9093
执行Kafka启动命令,启动第二个节点
./kafka-server-start.sh ../config/server-1.properties
这样就有两个Kafka节点组成的一个Kafka集群了
Kafka配置解读
broker端配置
- broker.id
每个 kafka broker 都有一个唯一的标识来表示,这个唯一的标识符即是 broker.id,它的默认值是 0。这个值在 kafka 集群中必须是唯一的,这个值可以任意设定,
- port
如果使用配置样本来启动 kafka,它会监听 9092 端口。修改 port 配置参数可以把它设置成任意的端口。要注意,如果使用 1024 以下的端口,需要使用 root 权限启动 kakfa。
- zookeeper.connect
用于保存 broker 元数据的 Zookeeper 地址是通过 zookeeper.connect 来指定的。比如我可以这么指定 localhost:2181
表示这个 Zookeeper 是运行在本地 2181 端口上的。我们也可以通过 比如我们可以通过 zk1:2181,zk2:2181,zk3:2181
来指定 zookeeper.connect 的多个参数值。该配置参数是用冒号分割的一组 hostname:port/path
列表,其含义如下
hostname 是 Zookeeper 服务器的机器名或者 ip 地址。
port 是 Zookeeper 客户端的端口号
/path 是可选择的 Zookeeper 路径,Kafka 路径是使用了 chroot
环境,如果不指定默认使用跟路径。
如果你有两套 Kafka 集群,假设分别叫它们 kafka1 和 kafka2,那么两套集群的
zookeeper.connect
参数可以这样指定:zk1:2181,zk2:2181,zk3:2181/kafka1
和zk1:2181,zk2:2181,zk3:2181/kafka2
- log.dirs
Kafka 把所有的消息都保存到磁盘上,存放这些日志片段的目录是通过 log.dirs
来制定的,它是用一组逗号来分割的本地系统路径,log.dirs 是没有默认值的,你必须手动指定他的默认值。其实还有一个参数是 log.dir
,如你所知,这个配置是没有 s
的,默认情况下只用配置 log.dirs 就好了,比如你可以通过 /home/kafka1,/home/kafka2,/home/kafka3
这样来配置这个参数的值。
- num.recovery.threads.per.data.dir
对于如下3种情况,Kafka 会使用可配置的线程池
来处理日志片段。
服务器正常启动,用于打开每个分区的日志片段;
服务器崩溃后重启,用于检查和截断每个分区的日志片段;
服务器正常关闭,用于关闭日志片段。
默认情况下,每个日志目录只使用一个线程。因为这些线程只是在服务器启动和关闭时会用到,所以完全可以设置大量的线程来达到井行操作的目的。特别是对于包含大量分区的服务器来说,一旦发生崩愤,在进行恢复时使用井行操作可能会省下数小时的时间。设置此参数时需要注意,所配置的数字对应的是 log.dirs 指定的单个日志目录。也就是说,如果 num.recovery.threads.per.data.dir 被设为 8,并且 log.dir 指定了 3 个路径,那么总共需要 24 个线程。
- auto.create.topics.enable
默认情况下,kafka 会使用三种方式来自动创建主题,下面是三种情况:
当一个生产者开始往主题写入消息时
当一个消费者开始从主题读取消息时
当任意一个客户端向主题发送元数据请求时
auto.create.topics.enable
参数我建议最好设置成 false,即不允许自动创建 Topic。在我们的线上环境里面有很多名字稀奇古怪的 Topic,我想大概都是因为该参数被设置成了 true 的缘故。
主题默认配置
Kafka 为新创建的主题提供了很多默认配置参数,下面就来一起认识一下这些参数
- num.partitions
num.partitions 参数指定了新创建的主题需要包含多少个分区。如果启用了主题自动创建功能(该功能是默认启用的),主题分区的个数就是该参数指定的值。该参数的默认值是 1。要注意,我们可以增加主题分区的个数,但不能减少分区的个数。
- default.replication.factor
这个参数比较简单,它表示 kafka保存消息的副本数,如果一个副本失效了,另一个还可以继续提供服务default.replication.factor 的默认值为1,这个参数在你启用了主题自动创建功能后有效。
- log.retention.ms
Kafka 通常根据时间来决定数据可以保留多久。默认使用 log.retention.hours 参数来配置时间,默认是 168 个小时,也就是一周。除此之外,还有两个参数 log.retention.minutes 和 log.retentiion.ms 。这三个参数作用是一样的,都是决定消息多久以后被删除,推荐使用 log.retention.ms。
- log.retention.bytes
另一种保留消息的方式是判断消息是否过期。它的值通过参数 log.retention.bytes
来指定,作用在每一个分区上。也就是说,如果有一个包含 8 个分区的主题,并且 log.retention.bytes 被设置为 1GB,那么这个主题最多可以保留 8GB 数据。所以,当主题的分区个数增加时,整个主题可以保留的数据也随之增加。
- log.segment.bytes
上述的日志都是作用在日志片段上,而不是作用在单个消息上。当消息到达 broker 时,它们被追加到分区的当前日志片段上,当日志片段大小到达 log.segment.bytes 指定上限(默认为 1GB)时,当前日志片段就会被关闭,一个新的日志片段被打开。如果一个日志片段被关闭,就开始等待过期。这个参数的值越小,就越会频繁的关闭和分配新文件,从而降低磁盘写入的整体效率。
- log.segment.ms
上面提到日志片段经关闭后需等待过期,那么 log.segment.ms
这个参数就是指定日志多长时间被关闭的参数和,log.segment.ms 和 log.retention.bytes 也不存在互斥问题。日志片段会在大小或时间到达上限时被关闭,就看哪个条件先得到满足。
- message.max.bytes
broker 通过设置 message.max.bytes
参数来限制单个消息的大小,默认是 1000 000, 也就是 1MB,如果生产者尝试发送的消息超过这个大小,不仅消息不会被接收,还会收到 broker 返回的错误消息。跟其他与字节相关的配置参数一样,该参数指的是压缩后的消息大小,也就是说,只要压缩后的消息小于 mesage.max.bytes,那么消息的实际大小可以大于这个值
这个值对性能有显著的影响。值越大,那么负责处理网络连接和请求的线程就需要花越多的时间来处理这些请求。它还会增加磁盘写入块的大小,从而影响 IO 吞吐量。
- retention.ms
规定了该主题消息被保存的时常,默认是7天,即该主题只能保存7天的消息,一旦设置了这个值,它会覆盖掉 Broker 端的全局参数值。
- retention.bytes
retention.bytes
:规定了要为该 Topic 预留多大的磁盘空间。和全局参数作用相似,这个值通常在多租户的 Kafka 集群中会有用武之地。当前默认值是 -1,表示可以无限使用磁盘空间。
Kafka常用命令
# 创建一个名为test的topic,设置其分区数1 副本数1
./kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test
# 创建一个名为my-replicated-topic的topic,设置分区数1 副本数2
./kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 2 --partitions 1 --topic my-replicated-topic
# 查看topic test的详细信息
./kafka-topics.sh --describe --zookeeper localhost:2181 --topic test
# 启动一个producer,向topic my-replicated-topic中发送消息
./kafka-console-producer.sh --broker-list localhost:9092 --topic my-replicated-topic
...
# 启动一个consumer,从topic my-replicated-topic中接收信息,并制定从头开始接收
./kafka-console-consumer.sh --bootstrap-server localhost:9092 --from-beginning --topic my-replicated-topic
...
SpringBoot集成Kafka
pom依赖
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
配置文件
spring:
kafka:
bootstrap-servers: 127.0.0.1:9092,127.0.0.2:9092,127.0.0.3:9092
producer:
retries: 0
batch-size: 16384
buffer-memory: 33554432
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.apache.kafka.common.serialization.StringSerializer
properties:
linger.ms: 1
consumer:
enable-auto-commit: false
auto-commit-interval: 100ms
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
properties:
session.timeout.ms: 15000
producer生产者
public void sendMessage(String topic, String data) {
LOG.info("kafka sendMessage start");
ListenableFuture<SendResult<Integer, String>> future = kafkaTemplate.send(topic, data);
future.addCallback(new ListenableFutureCallback<SendResult<Integer, String>>() {
@Override
public void onFailure(Throwable ex) {
LOG.error("kafka sendMessage error, ex = {}, topic = {}, data = {}", ex, topic, data);
}
@Override
public void onSuccess(SendResult<Integer, String> result) {
LOG.info("kafka sendMessage success topic = {}, data = {}",topic, data);
}
});
LOG.info("kafka sendMessage end");
}
consumer消费者
@KafkaListener(topics = "test", groupId = "group-1")
public void processMessage(ConsumerRecord<Integer, String> record) {
LOG.info("kafka processMessage start");
LOG.info("processMessage, topic = {}, msg = {}", record.topic(), record.value());
// do something ...
LOG.info("kafka processMessage end");
}