一、kafka集群概述
1.1 集群的概念
在 Kafka当中,集群可以通俗地理解为一群协同工作的 "小伙伴"。你可以理解为现在有一个快递分发中心,这个分发中心就像是 Kafka 集群。 这个快递分发中心里有很多的工作人员,每个工作人员都可以接收包裹(消息)、存储包裹和把包裹交给正确的收件人(消费者)。这些工作人员就是 Kafka 集群中的服务器,也叫 【Brokers】。不同的工作人员可以负责不同的任务,有的可能专门接收来自特定地区的包裹,有的可能擅长把包裹快速交给特定类型的收件人。同样,Kafka 中的不同 Brokers 可以存储不同主题的消息,或者处理不同消费者的请求。
1.2 集群的角色
- Broker:Kafka的部署节点
- Leader【主】: 接收消息、消费消息的的请求.即生产者将消息push到leader节点【主节点】上,同样消费者也是从leader节点【主节点】上去poll消息.
- Follower【从】: 主要做数据冗余,用于备份消息数据,一个leader节点可以有多个follower节点.
kafka是天然支持集群的,实际上哪怕是一个节点也可以当作集群处理,kafka的节点只要注册到同一个zookeeper上代表它们就是同一个集群的,通过BrokerId来区分集群中的不同节点.
1.3 分区【Partition】
分区是将一个主题(Topic)的数据分割成多个小的、独立的部分。每个分区都是一个有序的、不可变的消息序列,类似于一个日志文件。分区的主要作用是实现可扩展性和并行处理。通过将一个主题的数据分布在多个分区上,Kafka 可以同时处理来自多个生产者的消息写入和多个消费者的消息读取,提高系统的吞吐量和性能。
分区的特性:
- 有序性:分区中的消息是按照它们被写入的顺序存储的。这意味着如果一个消费者按照顺序读取分区中的消息,它将看到消息的产生顺序。
- 独立性:每个分区都是独立的,一个分区的故障不会影响其他分区的正常运行。这使得 Kafka 具有较高的容错性。
- 负载均衡:Kafka 会自动将分区分配到不同的 Broker 上,以实现负载均衡。这样可以避免单个 Broker 承担过多的负载,提高整个集群的性能。
分区的管理:
- 创建分区:可以在创建主题时指定分区数量,也可以在后期动态地增加分区数量。增加分区数量可以提高主题的吞吐量,但也会带来一些管理上的复杂性。
- 分配分区:Kafka 会自动将分区分配到不同的 Broker 上。当一个 Broker 加入或离开集群时,Kafka 会重新分配分区,以确保负载均衡。
- 分区副本提供数据冗余
- 每个分区可以有多个副本,这些副本分布在不同的 Kafka 节点(Broker)上。副本的主要作用是提供数据的冗余备份,防止数据丢失。
- 如果某个 Broker 出现故障,存储在该 Broker 上的分区副本可能会丢失,但其他 Broker 上的副本仍然可以提供服务,保证数据的可用性。
- 分区的Leader副本:每个分区都有一个领导者副本(Leader Replica),负责处理来自生产者的写入请求和来自消费者的读取请求。领导者副本是分区的主副本,它的数据是最新的。
- 关于分区的说明
- Partition分区是数据存储的【基本单元】, topic中的数据被分割为一个或多个partition,每个topic至少有一个partition.
- 一个topic的多个分区,被分布在kafka集群当中的多个broker上.
- 消费者的数量要小于等于partition的数量
1.4 副本【Replica】
副本是分区的备份。每个分区可以有多个副本,分布在不同的 Broker 上。副本的主要作用是提供数据的冗余和容错能力,防止数据丢失.当一个分区的领导者副本出现故障时,Kafka 可以自动从副本中选举出一个新的领导者副本,继续提供服务,从而保证系统的高可用性。
副本的类型:
- 领导者副本(Leader Replica):每个分区都有一个领导者副本,它负责处理来自生产者的写入请求和来自消费者的读取请求。领导者副本是分区的主副本,它的数据是最新的。
- 追随者副本(Follower Replica):每个分区可以有多个追随者副本,它们从领导者副本同步数据。追随者副本不处理来自生产者和消费者的请求,它们的作用是在领导者副本出现故障时,能够被选举为新的领导者副本,保证消息的可用性。
- 追随者副本会定期从领导者副本拉取消息,以保持与领导者副本的数据同步。如果一个追随者副本落后领导者副本太多,它将被从同步副本集合(In-Sync Replicas,ISR)中移除。
- 当领导者副本出现故障时,Kafka 会从 ISR 中选择一个副本作为新的领导者副本。ISR 是一组与领导者副本保持同步的副本集合,它确保新的领导者副本是与原领导者副本数据最接近的副本,从而减少数据丢失和不一致的风险
副本的管理:
- 创建副本:在创建主题时,可以指定副本因子(Replication Factor),即每个分区的副本数量。副本因子决定了数据的冗余程度和容错能力。
- 分配副本:Kafka 会自动将副本分配到不同的 Broker 上,以实现数据的冗余和容错。当一个 Broker 加入或离开集群时,Kafka 会重新分配副本,以确保数据的可用性。
对于以上的一些陌生的概念,心里有个数就行,后续会不断的加深强化这些概念,结合搭建集群实例,会有更加深入的理解.
关于副本的小总结:
- 同个Partition会有多个副本replication ,多个副本的数据是一样的,当其他broker下线之后,系统可以主动用副本提供服务.
- 默认每个topic的副本都是1(默认是没有副本,节省资源),也可以在创建topic的时候指定副本的具体数量
- 创建副本的数量要小于等于当前kafka broker的节点数量, 例如当前kafka只有3个节点,则replication-fator最大也就是3了,如果创建副本为4,则会报错
二、kafka集群
基于docker进行搭建,由于机器性能限制,所以我们只搭建一个zookeeper, 根据不同的端口号模拟不同的broker进行演示.
2.1 环境准备
- centos7
- docker
2.2 搭建集群
2.2.1 安装zookeeper
bash
docker pull zookeeper
docker run -itd --name=zk -p 2181:2181 zookeeper
2.2.2 安装kafka
这里由于机器性能限制,所以我们采用三个端口号来模拟三台broker,让这三台broker注册到同一台zk上即可.
bash
docker run --name my-kafka-0 -itd -p 9092:9092 -e KAFKA_BROKER_ID=0 -e KAFKA_ZOOKEEPER_CONNECT=117.72.47.36:2181 -e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://117.72.47.36:9092 -e KAFKA_HEAP_OPTS="-Xmx128M -Xms128M" -e KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9092 wurstmeister/kafka
docker run --name my-kafka-1 -itd -p 9099:9099 -e KAFKA_BROKER_ID=1 -e KAFKA_ZOOKEEPER_CONNECT=117.72.47.36:2181 -e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://117.72.47.36:9099 -e KAFKA_HEAP_OPTS="-Xmx128M -Xms128M" -e KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9099 wurstmeister/kafka
docker run --name my-kafka-2 -itd -p 9010:9010 -e KAFKA_BROKER_ID=2 -e KAFKA_ZOOKEEPER_CONNECT=117.72.47.36:2181 -e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://117.72.47.36:9010 -e KAFKA_HEAP_OPTS="-Xmx128M -Xms128M" -e KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9010 wurstmeister/kafka
docker run --name my-kafka-0 -itd -p 9092:9092 -e KAFKA_BROKER_ID=0 -e KAFKA_ZOOKEEPER_CONNECT=192.168.200.111:2181 -e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://192.168.200.111:9092 -e KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9092 wurstmeister/kafka
docker run --name my-kafka-1 -itd -p 9099:9099 -e KAFKA_BROKER_ID=1 -e KAFKA_ZOOKEEPER_CONNECT=192.168.200.111:2181 -e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://192.168.200.111:9099 -e KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9099 wurstmeister/kafka
docker run --name my-kafka-2 -itd -p 9010:9010 -e KAFKA_BROKER_ID=2 -e KAFKA_ZOOKEEPER_CONNECT=192.168.200.111:2181 -e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://192.168.200.111:9010 -e KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9010 wurstmeister/kafka
注意事项:
- 上述必须要修改的内容是: zookeeper的ip地址,修改为你自己的ip地址
- 容器名称修改为你自己的,容器名称不能重复
- KAFKA_BROKER_ID不能重复,一般从0开始即可,之后依次类推
- KAFKA_ADVERTISED_LISTENERS和KAFKA_LISTENERS端口号跟随你的kafka容器端口号
- 如果是云服务器,则要放行对应的端口号,如果是虚拟机,则必须关闭防火墙
查看zookeeper容器、三台kafka容器是否正常启动了.
bash
docker ps
到此,我们已经将测试的kafka集群搭建完成.
2.3 创建主题
进入到容器my-kafka-0
当中
bash
docker exec -it my-kafka-0 /bin/bash
查看当前的主题信息
bash
root@cd50df0dcd8f:/opt/kafka/bin# ./kafka-topics.sh --list --bootstrap-server localhost:9092
root@cd50df0dcd8f:/opt/kafka/bin#
在my-kafka-0即broder 0
上,创建主题, 并且指定topic的5个分区,创建的5个分区会被分散到创建好的3个broker上.
创建主题并且指定主题,指定主题的分区.
- 创建分区
bash
root@cd50df0dcd8f:/opt/kafka/bin# ./kafka-topics.sh --create --help # 查看create命令
--partitions <Integer: # of partitions> The number of partitions for the topic
being created or altered (WARNING:
If partitions are increased for a
topic that has a key, the partition
logic or ordering of the messages
will be affected). If not supplied
for create, defaults to the cluster
default.
# 这是一个用于创建或修改主题(topic)时指定分区数量的参数。其中<Integer:
# of partitions>表示一个整数,代表要设置的分区数量。
# 例如,--partitions 5表示将创建一个具有 5 个分区的主题。
- 指定副本信息
bash
--replication-factor <Integer: The replication factor for each
replication factor> partition in the topic being
created. If not supplied, defaults
to the cluster default.
# 这是在创建主题时用于指定每个分区的副本因子的参数。
# 其中<Integer: replication factor>表示一个整数,代表副本的数量。
# 例如,--replication-factor 3表示为主题的每个分区创建三个副本。
创建主题命令:
bash
root@cd50df0dcd8f:/opt/kafka/bin# ./kafka-topics.sh --create --bootstrap-server localhost:9092 --replication-factor 3 --partitions 5 --topic rj-topic
Created topic rj-topic.
root@cd50df0dcd8f:/opt/kafka/bin#
查看新创建的主题信息:
bash
root@cd50df0dcd8f:/opt/kafka/bin# ./kafka-topics.sh --describe --topic rj-topic --bootstrap-server localhost:9092
Topic: rj-topic TopicId: rEt7TLMRQraWXtfyVef36w PartitionCount: 5 ReplicationFactor: 3 Configs: segment.bytes=1073741824
Topic: rj-topic Partition: 0 Leader: 1 Replicas: 1,0,2 Isr: 1,0,2
Topic: rj-topic Partition: 1 Leader: 0 Replicas: 0,2,1 Isr: 0,2,1
Topic: rj-topic Partition: 2 Leader: 2 Replicas: 2,1,0 Isr: 2,1,0
Topic: rj-topic Partition: 3 Leader: 1 Replicas: 1,2,0 Isr: 1,2,0
Topic: rj-topic Partition: 4 Leader: 0 Replicas: 0,1,2 Isr: 0,1,2
root@cd50df0dcd8f:/opt/kafka/bin#
主题信息释义:
Topic: rj-topic
:
- 显示主题的名称为 "rj-topic"。
TopicId: rEt7TLMRQraWXtfyVef36w
:
- 每个主题都有一个唯一的主题 ID,这里显示了 "rj-topic" 的主题 ID。
PartitionCount: 5
:
- 表示这个主题被分为 5 个分区。分区是为了实现可扩展性和并行处理,将主题的数据分布在多个分区上,不同的分区可以由不同的生产者和消费者同时处理。
ReplicationFactor: 3
:
- 说明这个主题的每个分区都有 3 个副本。副本的作用是提供数据冗余,防止数据丢失,当一个分区的主副本(领导者副本)出现故障时,其他副本可以被选举为新的主副本继续提供服务。
Configs: segment.bytes=1073741824
:
- 显示了这个主题的配置信息,这里表明该主题的日志段大小为 1073741824 字节(1GB)。这个配置可以影响 Kafka 存储和管理主题数据的方式。
关于分区的信息:
Topic: rj-topic Partition: 0 Leader: 1 Replicas: 1,0,2 Isr: 1,0,2
:
- 对于分区 0:
Leader: 1
表示分区 0 的领导者副本在 Broker ID 为 1 的节点上。领导者副本负责处理该分区的读写请求。Replicas: 1,0,2
表示分区 0 的副本分布在 Broker ID 为 1、0、2 的节点上。Isr: 1,0,2
表示当前与领导者副本保持同步的副本所在的 Broker ID 为 1、0、2。只有在同步副本集合(In-Sync Replicas,ISR)中的副本才有资格被选举为新的领导者副本。Topic: rj-topic Partition: 1 Leader: 0 Replicas: 0,2,1 Isr: 0,2,1
:
- 对于分区 1:
Leader: 0
表示分区 1 的领导者副本在 Broker ID 为 0 的节点上。Replicas: 0,2,1
表示分区 1 的副本分布在 Broker ID 为 0、2、1 的节点上。Isr: 0,2,1
表示当前与领导者副本保持同步的副本所在的 Broker ID 为 0、2、1。Topic: rj-topic Partition: 2 Leader: 2 Replicas: 2,1,0 Isr: 2,1,0
:
- 对于分区 2:
Leader: 2
表示分区 2 的领导者副本在 Broker ID 为 2 的节点上。Replicas: 2,1,0
表示分区 2 的副本分布在 Broker ID 为 2、1、0 的节点上。Isr: 2,1,0
表示当前与领导者副本保持同步的副本所在的 Broker ID 为 2、1、0。Topic: rj-topic Partition: 3 Leader: 1 Replicas: 1,2,0 Isr: 1,2,0
:
- 对于分区 3:
Leader: 1
表示分区 3 的领导者副本在 Broker ID 为 1 的节点上。Replicas: 1,2,0
表示分区 3 的副本分布在 Broker ID 为 1、2、0 的节点上。Isr: 1,2,0
表示当前与领导者副本保持同步的副本所在的 Broker ID 为 1、2、0。Topic: rj-topic Partition: 4 Leader: 0 Replicas: 0,1,2 Isr: 0,1,2
:
- 对于分区 4:
Leader: 0
表示分区 4 的领导者副本在 Broker ID 为 0 的节点上。Replicas: 0,1,2
表示分区 4 的副本分布在 Broker ID 为 0、1、2 的节点上。Isr: 0,1,2
表示当前与领导者副本保持同步的副本所在的 Broker ID 为 0、1、2。
2.4 集群验证
验证一下主题信息,我们创建了了主题rj-topic
,并且指定了分区及副本信息,此时进入到my-kafka-1、my-kafka-2
容器当中,查看是否已经同步了主题信息.
在9092上即broker 0上发送消息:
在另外两台服务器上,开启消费者监听,查看一下,是否收到消息.
9099监听消息即broker 1上监听消息:
9010监听消息即broker 2上监听消息:
下线9010,再次查看主题rj-topic信息:
可以发现,此时的主题信息当中:
- Replicas: 1,0,2依然是三个节点,但是我们已经把9010下线了,
这里显示的节点信息,不管节点是否存活.
- Isr: 此时是实际的节点信息
三、文件存储及可靠性保证
3.1 概述
对于文件存储的可靠性保证,主要依赖于isr【In-Sync Replicas,ISR】的实现.
kafka采取了分片和索引机制,将每个partition
分为多个segment
, 每个segment对应2个文件,分别为log和index.
- 注意事项
这里需要注意的是index文件中并没有为每一条message建立索引,而是采用了稀疏存储的方式.为当前的message每隔一定字节的数据建立一条索引,避免了索引文件占用过多的空间和资源,从而可以将索引文件保存到内存当中
而采用此种方式的缺点也很明显, 在使用没有建立索引的数据在查询过程中需要小范围内的右充扫描操作
3.2 ISR机制
ISR (in-sync replica set ), leader会维持一个与其保持同步的replica集合【同步副本集合】
,该集合就是ISR,每一个leader partition都有一个ISR,leader动态维护, 要保证kafka不丢失message,就要保证ISR这组集合存活(至少有一个存活),并且消息commit成功.
Partition leader 保持同步的 Partition Follower 集合, 当 ISR 中的Partition Follower 完成数据的同步之后,就会给 leader 发送 ack.
如果Partition follower长时间(replica.lag.time.max.ms) 未向leader同步数据,则该Partition Follower将被踢出ISR.
Partition Leader 发生故障之后,就会从 ISR 中选举新的 Partition Leader。
- 如果一个副本能够在一定时间内(由参数
replica.lag.time.max.ms
控制)与领导者副本保持同步,那么它就被认为是在 ISR 中。- 如果一个副本落后领导者副本太多(由参数
replica.lag.max.messages
控制)或者长时间没有与领导者副本通信,它就会被从 ISR 中移除。
特别注意: 当需要选举新的领导者副本时,Kafka 只会从 ISR 中选择。这是为了确保新的领导者副本是与原领导者副本数据最接近的副本,从而减少数据丢失和不一致的风险。
领导者副本(leader)的确定, Kafka 会选择分区副本列表中第一个存活的副本作为领导者副本。副本列表是在创建主题时指定的,或者在后续动态调整分区时确定, 当领导者副本出现故障, 比如所在broker挂掉了, kafka会触发选择新的leader, 此时`kafka会从剩余的同步副本列表【In-Sync Replicas,ISR】选择一个领导者副本.
3.3 Replica和Ack机制
kafka分片之间的副本数据同步如何完成的?数据的一致性如何保证? 数据如何保证不丢失?
首先kafka的副本(replica), 每个主题都可以设置n个副本,副本的数量最好要小于broker的数量, 在kafka分区当中有1个leader和0个或者多个follower,所以把副本也分为两类, 一类是leader replica(领导副本)和follower replica副本.
- 生产者发送数据流程
保证producer 发送到指定的 topic, topic 的每个 partition 收到producer 发送的数据后, 需要向 producer 发送 ack 确认收到,如果producer 收到 ack, 就会进行下一轮的发送,否则重新发送数据.
副本同步机制:
- 当producer在向partition中写数据时,根据ack机制,默认ack=1,只会向leader中写入数据
- 然后leader中的数据会复制到其他的replica中,follower会周期性的从leader中pull数据
那么Partition什么时候发送ack确认机制呢,选择不同的ack策略,会对吞量、可靠性有着影响.
在spring kafka当中可以通过spring.kafka.producer.acks
进行配置.
副本的同步策略, acks有三个值, 分别是0, 1, all
- acks = 0
- producer发送一次就不再发送了,不管是否发送成功
- 发送出去的消息还在还未到达,或者还没写入磁盘, Partition Leader所在Broker就直接挂了,
客户端认为消息发送成功了,此时就会导致这条消息就丢失.
- acks = 1
- 只要Partition Leader接收到消息而且写入【本地磁盘】,就认为成功了,不管他其他的Follower有没有同步过去这条消息了
- acks = all即-1
- producer只有收到分区内所有副本的成功写入全部落盘的通知才认为推送消息成功
- 【注意事项】: leader会维持一个与其保持同步的replica集合,该集合就是
ISR
,leader副本也在ISR里面.
不同的acks的级别可能会产生的问题:
- 当acks = 1时, Partition Leader已经接收到消息,但是此时follower还没有同步消息,结果此时Partition Leader所在的broker宕机了,此时follower无法完成同步,此时这条消息在follower就丢失了.
- 当acks = -1即all时
- 如果在follower同步完成之后, broker发送acks之前,此时leader发生故障, 那么会造成【数据重复】, 当数据发送到leader之后,部分ISR的副本同步, leader此时挂掉. 此时假设有follower0和follower1都有可能变成新的leader, 由于原来的leader挂掉的时候,producer端会得到异常, producer会重新发送数据,造成【数据重复】.
- 当acks = all时,并不代表数据一定不会丢失
- 假设当前分区只有一个副本, 也就是只有一个leader副本, 任何的follower副本都没有, leader在接收完消息之后就宕机的话,也会导致数据丢失, acks = all,必须要结合ISR列表当中的副本数据结合使用, ISR列表当中的数量至少有2个以上才可以.
3.4 HightWatermark
在 Kafka 中,High Watermark(高水位线,以下简称 HW)是一个重要的概念,是一个特定的偏移量值,它代表了分区中已被复制到所有同步副本(In-Sync Replicas,ISR)的消息中最大的偏移量。用于表示分区中消息的可靠范围, 它保证了保证消费数据的一致性和副本数据的一致性.
- 确定消费者可以读取的消息范围。消费者只能读取到偏移量小于等于 HW 的消息,因为这些消息已经被认为是安全的,即已被复制到所有同步副本中,不会因为领导者副本故障而丢失。
- 保证数据的可靠性。HW 确保了即使领导者副本出现故障,消费者也不会读取到未完全复制到其他副本的消息,从而避免了数据丢失或不一致的情况
- 领导者副本负责维护分区的 HW。它会跟踪已复制到 ISR 中消息的最大偏移量,并更新 HW 值。
- 生产者向领导者副本发送消息,领导者副本将消息写入本地日志后,会尝试将消息复制到 ISR 中的其他副本。只有当消息被复制到所有 ISR 副本后,HW 才会被更新。
- 追随者副本从领导者副本拉取消息,并不断更新自己的本地日志。它们会向领导者副本发送请求,以获取当前的 HW 值,并确保自己不会读取超过 HW 的消息。
- 如果追随者副本落后领导者副本太多,它可能会被从 ISR 中移除。一旦它重新与领导者副本同步,它会再次被加入 ISR。
- 消费者(Consumer):
- 消费者通过读取分区的 HW 来确定可以安全读取的消息范围。消费者会记录自己已经消费的消息的偏移量,并定期向 Kafka 提交偏移量。
- 如果消费者在读取消息后但在提交偏移量之前出现故障,重新启动后,它将从上次提交的偏移量开始继续消费,确保不会重复消费已经处理过的消息,也不会跳过未处理的消息。
follower故障:
- 当follower发生故障后会被临时踢出ISR, 待该follower恢复后, follower会读取本地的磁盘记录的上次的HW,并将该log文件高于HW的部分截取掉,从HW开始向leader进行同步,等该follower的Log End Offset 大于等于该Partition的hw,即follower追上leader后,就可以重新加入ISR.
leader故障:
- Leader发生故障后,会从ISR中选出一个新的leader,为了保证多个副本之间的数据一致性,其余的follower会先将各自的log文件高于hw的部分截掉(新leader自己不会截掉),然后从新的leader同步数据
3.5 小总结
- 当broker产生故障后
- ACK保障了【生产者】的投递可靠性
- Partition的多副本保证了【消息存储】的可靠性
- HW保证了消费数据的致性和副本数据的一致性
四、常见面试题
4.1 为什么需要消息队列
这类问题同样适用于其它消息队列,回答的时候重点介绍一下: 解耦、异步、削峰填谷.
首先,在一个复杂的系统当中,不同的模块或服务之间可能存在着相互依赖的情况,如果直接使用API 调用的方式,会造成模块之间的耦合,当其中一个模块发生改变时,需要同时修改调用方和被调用方的代码。而使用消息队列作为中间件,不同的模块可以将消息发送到消息队列中,不需要知道具体的接收方是谁,接收方可以独立地消费消息,实现了模块之间的【解耦】
其次,在执行一些i/o操作或者会产生阻塞的操作,譬如说发送邮件、导出表格、pdf等,如果采用同步方式去处理,会阻塞主线程,导致整个系统性能下降.此时使用消息队列的方式,将这些操作封装到消息队列当中,通过消息中间件异步的执行,不会阻塞住主线程,从而提高了系统的响应速度.
作为高并发两大利器,一个是缓存,而另一个就是消息队列了,消息队列用于平衡在高峰期和低谷期的资源利用率,提高系统的吞吐量和响应速度.在削峰填谷的过程中,通常使用消息队列作为缓冲区,将请求放入消息队列中,然后在系统负载低的时候进行处理。这种方式可以将系统的峰值压力分散到较长的时间段内,减少瞬时压力对系统的影响,从而提高系统的稳定性和可靠性.
除此之外,消息队列还有可靠性高、扩展性好、灵活度高等特点.非常重要的一点,最好结合自己项目当中的使用情况,引入具体的业务场景来具体分析.
4.2 Kafka为什么速度嗷嗷快?
- 消息发送的优化处理:
- 批量发送,Kafka 通过将多个消息打包成一个批次,减少了网络传输和磁盘写入的次数,从而提高了消息的吞吐量和传输效率.
- 异步发送,生产者可以异步发送消息,不必等待每个消息的确认,这大大提高了消息发送的效率。
- 消息压缩,支持对消息进行压缩,减少网络传输的数据量。
- 并行发送,过将数据分布在不同的分区(Partitions)中,生产者可以并行发送消息,从而提高了吞吐量。
- 数据存储优化:
-
零拷贝技术, kafka使用零拷贝的技术, 具体零拷贝技术不再本文讨论范围之内,可以参照其它文章学习.
-
磁盘顺序写入, Kafka把消息存储在磁盘上,且以顺序的方式写入数据。顺序写入比随机写入速度快很多,因为它减少了磁头寻道时间。避免了随机读写带来的性能损耗,提高了磁盘的使用效率。
-
页缓存,Kafka 将其数据存储在磁盘中,但在访问数据时,它会先将数据加载到操作系统的页缓存中,并在页缓存中保留一份副本,从而实现快速的数据访问。
-
稀疏索引:Kafka 存储消息是通过分段的日志文件,每个分段都有自己的索引文件。这些索引文件中的条目不是对分段中的每条消息都建立索引,而是每隔一定数量的消息建立一个索引点,这就构成了稀疏索引。稀疏索引减少了索引大小,使得加载到内存中的索引更小,提高了查找特定消息的效率。
-
分区和副本:Kafka 采用分区和副本的机制,可以将数据分散到多个节点上进行处理,从而实现了分布式的高可用性和负载均衡。
- 消息消费
- 消费者群组:通过消费者群组可以实现消息的负载均衡和容错处理
- 不同的消费者可以独立地消费不同的分区,实现消费的并行处理。
- afka支持批量拉取消息,可以一次性拉取多个消息进行消费。减少网络消耗,提升性能
注意: 所步及的具体细节,自己补充完整.
4.3 Kafka如何保证消息不丢失
Kafka作为一个消息中间件,他需要结合消息生产者和消费者一起才能工作,一次消息发送包含以下是三个过程:
- Producer 端发送消息给 Kafka Broker 。
- Kafka Broker 将消息进行同步并持久化数据。
- Consumer 端从Kafka Broker 将消息拉取并进行消费。
事实上,kafka只能已经提交的消息做最大程度上的持久化,保证消息不丢失,但是没有办法做到100%消息不丢失.
Kafka还是提供了很多机制来保证消息不丢失的。要想知道Kafka如何保证消息不丢失,需要从生产者、消费者以及kafka集群三个方面来分析。
生产者:
-
消息的生产者端,最怕的就是消息发送给Kafka集群的过程中失败,所以,我们需要有机制来确保消息能够发送成功,但是,因为存在网络问题,所以基本没有什么办法可以保证一次消息一定能成功。所以,就需要有一个确认机制来告诉生产者这个消息是否有发送成功,如果没成功,需要重新发送直到成功。
-
我们使用的
producer.send(message)
方法实际上是异步的,发送消息的时候,会立刻返回,但是这只表示消息发送了,并不代表发送的消息一定是成功的,可以使用get()
方法同步等待返回. -
为了保证消息发送的不丢失,通常在发送消息的时候的,通过事件回调的方式判断消息是否成功,另外还可以使用的统一添加监听器的方式来判断消息发送是否成功.
-
另外,可以在配置文件当中或者生产者工厂当中配置一些参数,提升发送消息的成功率
bashacks=-1 // 表示 Leader 和 Follower 都接收成功时确认;可以最大限度保证消息不丢失,但是吞吐量低。 retries=3 // 生产端的重试次数 retry.backoff.ms = 300 //消息发送超时或失败后,间隔的重试时间
Broker
:
- Kafka的集群有一些机制来保证消息的不丢失,比如复制机制、持久化存储机制以及ISR机制。
- 持久化存储:
Kafka使用持久化存储来存储消息
。这意味着消息在写入Kafka时将被写入磁盘,这种方式可以防止消息因为节点宕机而丢失。 - ISR复制机制:
Kafka使用ISR机制来确保消息不会丢失
,Kafka使用复制机制来保证数据的可靠性。每个分区都有多个副本,副本可以分布在不同的节点上。当一个节点宕机时,其他节点上的副本仍然可以提供服务,保证消息不丢失。
同时,在服务端也可以配置一些参数调节,避免消息丢失
java
replication.factor //表示分区副本的个数,replication.factor >1 当leader 副本挂了,follower副本会被选举为leader继续提供服务。
min.insync.replicas //表示 ISR 最少的副本数量,通常设置 min.insync.replicas >1,这样才有可用的follower副本执行替换,保证消息不丢失
unclean.leader.election.enable = false //是否可以把非 ISR 集合中的副本选举为 leader 副本。
消费者
作为Kafka的消费者端,只需要确保投递过来的消息能正常消费,并且不会胡乱的提交偏移量就行了。
- Kafka消费者会跟踪每个分区的偏移量,消费者每次消费消息时,都会将偏移量向后移动。当消费者宕机或者不可用时,Kafka会将该消费者所消费的分区的偏移量保存下来,下次该消费者重新启动时,可以从上一次的偏移量开始消费消息。
- Kafka消费者还可以组成消费者组,每个消费者组可以同时消费多个分区。当一个消费者组中的消费者宕机或者不可用时,其他消费者仍然可以消费该组的分区,保证消息不丢失。
- 为了保证消息不丢失,建议使用手动提交偏移量的方式,避免拉取了消息以后,业务逻辑没处理完,提交偏移量后但是消费者挂了的问题,可以进行如下的配置.
java
enable.auto.commit=false
4.4 kafka如何保证消息不重复消费
Kafka消息只消费一次,这个需要从多方面回答,既包含Kafka自身的机制
,也需要考虑客户端自己的重复处理
。
首先,在Kafka中,每个消费者都必须加入至少一个消费者组
。同一个消费者组内的消费者可以共享消费者的负载。因此,如果一个消息被消费组中的任何一个消费者消费了,那么其他消费者就不会再收到这个消息了。
另外,消费者可以通过手动提交消费位移来控制消息的消费情况
。通过手动提交位移,消费者可以跟踪自己已经消费的消息,确保不会重复消费同一消息。
- 当消费者采用手动提交偏移量的方式时,可以更好地控制消息的处理进度。与自动提交偏移量相比,手动提交允许消费者在确认消息已经被正确处理后再提交偏移量,从而降低了重复消费的可能性。
- 例如,在一个数据处理管道中,消费者从 Kafka 读取消息,进行复杂的数据转换和存储到数据库中。只有在数据库存储成功后,才手动提交偏移量,确保不会因为中间出现故障而导致重复处理消息。
幂等性生产者:
- 幂等性概念
- 幂等性是指一个操作无论执行多少次,产生的效果都是相同的。在 Kafka 中,幂等性生产者可以保证在重复发送相同消息时,Kafka 只会将该消息存储一次,从而避免了重复消费的问题。
- 例如,当生产者由于网络问题或其他原因导致消息发送失败,然后重试发送时,如果生产者是幂等性的,那么即使消息被重复发送,Kafka 也只会存储一份该消息。
- 实现方式
- Kafka 从 0.11.0.0 版本开始引入了幂等性生产者的功能。要使用幂等性生产者,需要在生产者配置中设置
enable.idempotence=true
。 - 幂等性生产者通过为每个生产者实例分配一个唯一的 ID,并为每个发送的消息分配一个序列号来实现幂等性。Kafka 会在服务端验证生产者 ID 和序列号,确保相同的消息不会被重复存储.
- Kafka 从 0.11.0.0 版本开始引入了幂等性生产者的功能。要使用幂等性生产者,需要在生产者配置中设置
事务性生产者:
-
事务的概念:
-
事务性生产者可以保证一组消息要么全部被成功写入 Kafka,要么全部失败。这对于需要保证数据一致性的场景非常有用,例如在将数据同时写入多个主题或者从一个主题读取数据并写入另一个主题时。
-
如果事务中的某个消息写入失败,整个事务将被回滚,所有已经写入的消息将被删除,从而避免了部分消息被成功处理而部分消息失败的情况,也间接避免了重复消费的问题。
-
-
实现方式:
-
要使用事务性生产者,需要在生产者配置中设置
transactional.id
,并在代码中使用initTransactions()
、beginTransaction()
、sendOffsetsToTransaction()
和commitTransaction()
等方法来管理事务。 -
事务性生产者通过在 Kafka 服务端记录事务状态,并在事务提交或回滚时进行相应的操作来保证数据的一致性。
-
另外客户端还需要自己对消息去重处理,要结合业务来说.
4.5 kafka如何实现顺序消费
Kafka的消息是存储在指定的topic中的某个partition中的。并且一个topic是可以有多个partition的。同一个partition中的消息是有序的,但是跨partition,或者跨topic的消息就是无序的了。
为什么同一个partition的消息是有序的?
- 因为当生产者向某个partition发送消息时,消息会被追加到该partition的日志文件(log)中,并且被分配一个唯一的 offset,文件的读写是有顺序的。而消费者在从该分区消费消息时,会从该分区的最早 offset 开始逐个读取消息,保证了消息的顺序性。
经过以上的考量,可以为了解决顺序消费问题使用如下的办法?
- 在一个topic中,只创建一个partition,这样这个topic下的消息都会按照顺序保存在同一个partition中,这就保证了消息的顺序消费。
- 发送消息的时候指定partition,如果一个topic下有多个partition,那么我们可以把需要保证顺序的消息都发送到同一个partition中,这样也能做到顺序消费。
4.6 说明一下Kafka几种选择过程
- Partition Leader 选举
- Kafka 中的每个 Partition 都有一个 Leader,负责处理该 Partition 的读写请求。在正常情况下,Leader 和 ISR 集合中的所有副本保持同步,Leader 接收到的消息也会被 ISR 集合中的副本所接收。当 leader 副本宕机或者无法正常工作时,需要选举新的 leader 副本来接管分区的工作。
Leader 选举的过程如下:
- 每个参与选举的副本会尝试向 ZooKeeper 上写入一个临时节点,表示它们正在参与 Leader 选举;
- 所有写入成功的副本会在 ZooKeeper 上创建一个序列号节点,并将自己的节点序列号写入该节点;
- 节点序列号最小的副本会被选为新的 Leader,并将自己的节点名称写入 ZooKeeper 上的 /broker/.../leader 节点中
- Controller 选举
Kafka 集群中只能有一个 Controller 节点,用于管理分区的副本分配、leader 选举等任务。当一个Broker变成Controller后,会在Zookeeper的/controller节点 中记录下来。然后其他的Broker会实时监听这个节点,主要就是避免当这个controller宕机的话,就需要进行重新选举。
Controller选举的过程如下:
- 所有可用的 Broker 向 ZooKeeper 注册自己的 ID,并监听 ZooKeeper 中 /controller 节点的变化。
- 当 Controller 节点出现故障时,ZooKeeper 会删除 /controller 节点,这时所有的 Broker 都会监听到该事件,并开始争夺 Controller 的位置。
- 为了避免出现多个 Broker 同时竞选 Controller 的情况,Kafka 设计了一种基于 ZooKeeper 的 Master-Slave 机制,其中一个 Broker 成为 Master,其它 Broker 成为 Slave。Master 负责选举 Controller,并将选举结果写入 ZooKeeper 中,而 Slave 则监听 /controller 节点的变化,一旦发现 Master 发生故障,则开始争夺 Master 的位置。
- 当一个 Broker 发现 Controller 失效时,它会向 ZooKeeper 写入自己的 ID,并尝试竞选 Controller 的位置。如果他创建临时节点成功,则该 Broker 成为新的 Controller,并将选举结果写入 ZooKeeper 中。
- 其它的 Broker 会监听到 ZooKeeper 中 /controller 节点的变化,一旦发现选举结果发生变化,则更新自己的元数据信息,然后与新的 Controller 建立连接,进行后续的操作。
4.7 Kafka 为什么有 Topic 还要用 Partition?
先来谈谈Topic
, Topic是Kafka中承载消息的逻辑容器。可以理解为一个消息队列。生产者将消息发送到特定的Topic,消费者从Topic中读取消息。Topic可以被认为是逻辑上的消息流。在实际使用中多用来区分具体的业务。
分区
:Partition。是Topic的物理分区。一个Topic可以被分成多个Partition,每个Partition是一个有序且持久化存储的日志文件。每个Partition都存储了一部分消息,并且有一个唯一的标识符(称为Partition ID)。
这两个都是存储消息的载体,那为啥要分两层呢,有了Topic还需要Partition干什么呢?
通过在Topic的基础上, 再细粒度的划分出Partition,每个Partition可以由不同的消费组进行独立的消费,这样就可以提高整个系统的吞吐量.
- 提升吞吐量:通过将一个Topic分成多个Partition,可以实现消息的并行处理。每个Partition可以由不同的消费者组进行独立消费,这样就可以提高整个系统的吞吐量。
- 负载均衡:Partition的数量通常比消费者组的数量多,这样可以使每个消费者组中的消费者均匀地消费消息。当有新的消费者加入或离开消费者组时,可以通过重新分配Partition的方式进行负载均衡。
- 扩展性:通过增加Partition的数量,可以实现Kafka集群的扩展性。更多的Partition可以提供更高的并发处理能力和更大的存储容量。