Kafka
一、MQ
Message Queue 简称 MQ ;消息队列,也称为消息中间件。
是基础数据结构中“先进先出”的一种数据结构。
一般用来解决:应用解耦、异步消息、流量削峰等问题,实现高性能、高可用、可伸缩和最终一致性架构。
MQ的技术维度
- API 发送和接收
- MQ 的高可用性
- MQ 的集群和容错配置
- MQ 的持久化
- MQ 的延时发送 / 定时发送
- 签收机制
MQ 消费消息的两种模式
点对点模式
一对一,消费者主动拉取数据,消息收到后消息清除
消息生产者生产消息发送到Queue中,然后消息消费者从Queue中取出并且消费消息。
消息被消费以后,queue中不再有存储,所以消息消费者不可能消费到已经被消费的消息。Queue支持存在多个消费者,但是对一个消息而言,只会有一个消费者可以消费,弊端明显。
这种模式中,消费的消息是直接从队列中进行消息的消费。
发布 / 订阅模式
一对多,消费者消费数据之后不会清除消息
消息生产者(发布)将消息发布到topic中,同时有多个消息消费者(订阅)消费该消息。和点对点方式不同,发布到topic的消息会被所有订阅者消费。
这种模式中,消费的消息是直接从队列中的 “Topic” 主题中进行消息的消费。
二、Kafka
kafka是一个分布式的基于发布 / 订阅模式的消息队列,主要应用于大数据实时处理领域。
2.1 Kafka基础架构
2.2 kafka中的角色组成
(1)Producer :消息生产者,就是向kafka broker发消息的客户端;
(2)Consumer :消息消费者,向kafka broker取消息的客户端;
(3)Consumer Group (CG):消费者组,由多个consumer组成。消费者组内每个消费者负责消费不同分区的数据,一个分区只能由一个消费者消费;消费者组之间互不影响。所有的消费者都属于某个消费者组,即消费者组是逻辑上的一个订阅者。
(4)Broker :一台kafka服务器就是一个broker。一个集群由多个broker组成。一个broker可以容纳多个topic。
(5)Topic :可以理解为一个队列,生产者和消费者面向的都是一个topic;
(6)Partition:为了实现扩展性,一个非常大的topic可以分布到多个broker(即服务器)上,一个topic可以分为多个partition,每个partition是一个有序的队列;
(7)Replica:副本,为保证集群中的某个节点发生故障时,该节点上的partition数据不丢失,且kafka仍然能够继续工作,kafka提供了副本机制,一个topic的每个分区都有若干个副本,一个leader和若干个follower。
(8)leader:每个分区多个副本的“主”,生产者发送数据的对象,以及消费者消费数据的对象都是leader。
(9)follower:每个分区多个副本中的“从”,实时从leader中同步数据,保持和leader数据的同步。leader发生故障时,某个follower会成为新的leader。
2.3 kafka架构的最基本的认识
一、
Kafka Cluster由多个Broker组成,每个broker都有自己唯一的ID。
在一个Broker内部可以有多个topic;
在一个topic中可以有多个分区;
一个分区可以有多个副本,副本分为leader 和 follow两个角色【1个leader + n个follower】。
二、
在Kafka中的消费者端:是以消费者组为单位对数据进行消费的。
从消费者角度出发:一个消费者组中的一个消费者可以消费一个topic主题中的多个分区;
从Topic角度出发:但是一个topic主题中的一个分区不可以被一个消费者组中的多个消费者消费【消费者组不可重复消费】。
三、
zookeeper的作用:
1、
①:0.9版本之前,offset偏移量维护在zookeeper中。
②:0.9版本之后,offset偏移量维护在kafka本地中。
Kafka中offset偏移量的维护就是维护3个部分:
把offset中的数据维护在topic中。
G:group 组
T:topic 主题
P:partition 分区
2、
kafka集群的工作是基于zookeeper的。
例如:controller的确定、topic的创建、分区的配置、副本的配置、leader的选举。
三、Kafka 快速入门
注意 ! 注意 ! !注意 ! !!
Kafka Cluster 的工作是基于zookeeper的,所以,我们在启动kafka之前,一定要先启动zookeeper !!!
3.1 Kafka 的集群部署
端口号:
kafka : 9092
zookeeper : 2181
hadoop01 | hadoop02 | hadoop03 |
---|---|---|
zookeeper | zookeeper | zookeeper |
kafka | kafka | kafka |
-
jar 包的下载
http://kafka.apache.org/downloads
-
上传kafka到Linux系统并解压。
-
在kafka中,所有的数据(日志、topic等等 都是以log文件进行看待的。)
我们在kafka根目录下创建一个目录(datas),用来存储topic主题。
也可以把topic主题存储在kafka根目录下的logs目录中
-
kafka的核心配置文件(config 下的server.properties文件中)
vim /opt/module/kafka/kafka_2.11-2.4.1/config/erver.properties
主要的配置信息有:
############################### 主要的配置项()##############################
#broker的全局唯一编号,不能重复,因为我们有3台机器,所以规定broker的序号是:1,2,3
broker.id=1
#删除topic功能使能,kafka2.4版本默认为true,以前的老版本默认是false
delete.topic.enable=true
#kafka中topic主题存放的目录(注意不是kafka运行日志,运行入职存储在bin/kafka-run-class.sh中)
log.dirs=/opt/module/kafka/kafka_2.11-2.4.1/datas
#配置连接Zookeeper集群地址
zookeeper.connect=hadoop01:2181,hadoop02:2181,hadoop03:2181/kafka
########################## 非主要配置项(根据需要配置)#########################
#处理网络请求的线程数量
num.network.threads=3
#用来处理磁盘IO的现成数量
num.io.threads=8
#发送套接字的缓冲区大小
socket.send.buffer.bytes=102400
#接收套接字的缓冲区大小
socket.receive.buffer.bytes=102400
#请求套接字的缓冲区大小
socket.request.max.bytes=104857600
-
配置环境变量
#KAFKA_HOME export KAFKA_HOME=/opt/module/kafka/kafka_2.11-2.4.1 export PATH=$PATH:$KAFKA_HOME/bin
-
向其他两台机器分发kafka和环境变量
注意 ! 注意 ! !注意 ! !!
分发后,一定要修改配置文件中的broker.id,broker.id 不能重复 !!!
hadoop02 : broker.id = 2;
hadoop03 : broker.id = 3;
-
启动集群,首先启动zookeeper集群
-
kafka群启脚本
#! /bin/bash if [ $# -lt 1 ] then echo "Input Args Is Can Not Empty !!!" exit fi # 循环遍历每一台机器,操作kafka for host in hadoop01 hadoop02 hadoop03 do case $1 in "start") echo "================start $host Kafka============" ssh $host '/opt/module/kafka/kafka_2.11-2.4.1/bin/kafka-server-start.sh -daemon /opt/module/kafka/kafka_2.11-2.4.1/config/server.properties' ;; "stop") echo "================stop $host Kafka============" ssh $host '/opt/module/kafka/kafka_2.11-2.4.1/bin/kafka-server-stop.sh' ;; *) echo "Input Args Is Error !!!" exit ;; esac done
-
检验kafka是否正常启动的标准
# 启动zookeeper的客户端工具 [heather@hadoop01 ~]$ zkCli.sh # 查看zookeeper根目录下所有数据 [zk: localhost:2181(CONNECTED) 5] ls / [hadoop-ha, hbase, kafka, zookeeper] # 查看broker中id状态,是否是全部的id [zk: localhost:2181(CONNECTED) 7] ls /kafka/brokers/ids [1, 2, 3]
3.2 命令行操作
3.2.1 Topic 主题
Topic 主题是逻辑单位,真正在存储数据的时候是以分区为单位进行的,分区是物理单位。
-
查看所有的topic 主题
kafka-topics.sh --list --bootstrap-server hadoop01:9092
-
创建topic 主题(同时给主题设置为2个分区,3个副本)
kafka-topics.sh --create --bootstrap-server hadoop01:9092 --topic users --partitions 2 --replication-factor 3
-
查看某个 topic 主题的详情
kafka-topics.sh --describe --bootstrap-server hadoop01:9092 --topic users
-
修改 某个topic 主题的分区个数(只能增加分区数,不能减少分区数)
kafka-topics.sh --alter --bootstrap-server hadoop01:9092 --topic users --partitions 3
-
删除 topic 主题
kafka-topics.sh --delete --bootstrap-server hadoop01:9092 --topic users
2. 生产者 & 消费者
生产者向kafka中生产消息、消费者从kafka中消费消息,都是面向leader的。
-
开启生产者
kafka-console-producer.sh --broker-list hadoop01:9092 --topic users
-
开启消费者
第一种方式:
此处会涉及到offset的重置问题,默认情况下,新启动的消费者是无法消费kafka中的已经存在的数据的,只能消费此消费者启动后kafka新存入的数据消息。
kafka-console-consumer.sh --bootstrap-server hadoop01:9092 --topic users
第二种方式:
在启动消费者时,可以指定从kafka中消费所有已经存在的数据,包含启动之前的数据消息,
在命令行中加上
--from-beginning
kafka-console-consumer.sh --bootstrap-server hadoop01:9092 --topic users --from-beginning
第三种方式:
在启动消费者时,可以指定消费者组,这种情况下就不会出现消息重复消费的问题。实际开发中,一般情况下,Topic 主题有几个分区,消费者组中就应该启动几个消费者。
此时,我们需要提前查看消费者组的配置信息
cd /opt/module/kafka/kafka_2.11-2.4.1/config
vim consumer.properties
可以修改消费者组的名字,也可以不修改。
- 原始的启动消费者方法
kafka-console-consumer.sh --bootstrap-server hadoop01:9092 --topic users --consumer.config /opt/module/kafka/kafka_2.11-2.4.1/config/consumer.properties
- 简洁的启动消费者方法
kafka-console-consumer.sh --bootstrap-server hadoop01:9092 --topic users --group g1
四、Kafka 架构
4.1 kafka的工作流程
kafka维护偏移量offset在topic主题中
4.2. Kafka 的文件存储机制
Kafka中消息是以topic进行分类的,生产者生产消息,消费者消费消息,都是面向topic的。
topic是逻辑上的概念,而partition是物理上的概念,每个partition对应于一个或多个【1个log文件可以存储1G 的数据,当时间超过1G ,会产生新的log文件】log文件,该log文件中存储的就是producer生产的数据。Producer生产的数据会被不断追加到该log文件末端,且每条数据都有自己的offset。
消费者组中的每个消费者,都会实时记录自己消费到了哪个offset,以便出错恢复时,从上次的位置继续消费。
由于生产者生产的消息会不断追加到log文件末尾,为防止log文件过大导致数据定位效率低下,Kafka采取了分片和索引机制,将每个partition分为多个segment[i]。每个segment对应两个文件——“.index”文件和“.log”文件。这些文件位于一个文件夹下,该文件夹的命名规则为:topic名称+分区序号。例如,first这个topic有三个分区,则其对应的文件夹为first-0,first-1,first-2。
index和log文件以当前segment的第一条消息的offset命名。下图为index文件和log文件的结构示意图。
4.3 Kafka生产者
4.3.1 分区策略
-
分区的原因
-
方便在集群中扩展,每个Partition可以通过调整以适应它所在的机器,而一个topic又可以有多个Partition组成,因此整个集群就可以适应任意大小的数据了;
-
可以提高并发,因为可以以Partition为单位读写了。
-
-
分区的原则
JAVA 开发中的常用API ProduceRecord(@NotNull String topic, Integer partition, String key, String value) ProduceRecord(@NotNull String topic, String key, String value) ProduceRecord(@NotNull String topic, String value) - 直接指定分区号:消息发送到指定的分区partition中。
- 指定了一个key : 按照key的hashcodde 值 % 分区数得到具体的分区号。【取余操作】
- 没有指定分区号,没有指定key:
- 在Kafka低版本中,使用轮巡,均匀发送发送到每个分区中。
- 2.4版本中,使用黏性分区。(首先会随机选择一个分区,然后黏住该分区,知道该分区对应的batch满或者超时,再次随机选择一个新的分区,黏住使用,以此类推。)
4.3.2 数据可靠性保证ACK
1. 同步和异步发送消息
Produce生产者消息的发送分为同步和异步两种方式。
- 同步:为保证producer发送的数据,能可靠的发送到指定的topic,topic的每个partition收到producer发送的数据后,都需要向producer发送ack(acknowledgement确认收到),如果producer收到ack,就会进行下一轮的发送,否则重新发送数据。
- 异步:producer向topic发送消息后,不会等待ack,会直接把新的消息发送到别的topic中。然后会在一定的超时时间内等待各个topic的ack,如果超时未收到某个topic的ack,则会向此topic中重新再次发送一次消息。
2. ack的发送时机策略
通过上述的表达已经明确,为了保障数据的可靠性,produce生产者端只有收到了topic主题Leader端发送的ack回复才可以。那么Leader端何时发送ack呢???
首先有一点要保证:确保follower与leader同步完成以后,leader才能发送ack,这样才能保证leader挂掉以后,能在现有的follower中选举出新的leader。在kafka Cluster集群模式中,follower有多个,那么需要多少个follower同步完成之后发送ack呢???现有两种方案如下:
半数以上的follower同步完成,即可发送ack。
全部的follower同步完成,才可以发送ack。
方案 | 优点 | 缺点 |
---|---|---|
半数以上完成同步,就发送ack | 延迟低 | 选举新的leader时,容忍n台节点的故障,需要2n+1个副本 |
全部完成同步,才发送ack | 选举新的leader时,容忍n台节点的故障,需要n+1个副本 | 延迟高 |
Kafka选择了第二种方案,原因如下:
- 同样为了容忍n台节点的故障,第一种方案需要2n+1个副本,而第二种方案只需要n+1个副本,而Kafka的每个分区都有大量的数据,第一种方案会造成大量数据的冗余,并且需要部署更多的节点带来了更大的开销。
- 虽然第二种方案的网络延迟会比较高,但网络延迟对Kafka的影响较小。
3. ISR机制 - 动态集合
采用第二种方案之后,设想以下情景:leader收到数据,所有follower都开始同步数据,但有一个follower,因为某种故障,迟迟不能与leader进行同步,那leader就要一直等下去,直到它完成同步,才能发送ack。这个问题怎么解决呢?
Leader维护了一个动态的in-sync replica set (ISR),意为和leader保持同步的follower集合。当ISR中的follower完成数据的同步之后,leader就会给producer发送ack。如果follower长时间未向leader同步数据,则该follower将被踢出ISR,该时间阈值由replica.lag.time.max.ms参数设定【默认为1000ms】。Leader发生故障之后,就会从ISR中选举新的leader。
4. ack 应答机制
对于某些不太重要的数据,对数据的可靠性要求不是很高,能够容忍数据的少量丢失,所以没必要等ISR中的follower全部接收成功。
所以Kafka为用户提供了三种可靠性级别,用户根据对可靠性和延迟的要求进行权衡,选择以下的配置。acks
- 0:producer不等待broker的ack,这一操作提供了一个最低的延迟,broker一接收到还没有写入磁盘就已经返回,当broker故障时有可能丢失数据;
- 1:producer等待broker的ack,partition的leader落盘成功后返回ack,如果在follower同步成功之前leader故障,那么将会丢失数据;
- -1(all):producer等待broker的ack,partition的leader和follower全部落盘成功后才返回ack。但是如果在follower同步完成后,broker发送ack之前,leader发生故障,在ISR动态集合中会重新选举出新的leader,因为produce没有接收到ack,则会重新发送一次数据,但是此时各个节点中已经有了消息,所以会造成数据重复。
5. 故障处理 - LEO/HW
解决消费者漏消费和重复消费的问题
LEO : 每个副本的最后一个offset ;
HW : 所有副本中最小的 LEO 。
对于消费者而言,能够消费的数据都是 HW 之前的数据 ! ! !
leader 和 follower的故障处理方案:
-
follower故障
follower发生故障后会被临时踢出ISR,待该follower恢复后,follower会读取本地磁盘记录的上次的HW,并将log文件高于HW的部分截取掉,从HW开始向leader进行同步。等该follower的LEO大于等于该Partition的HW,即follower追上leader之后,就可以重新加入ISR了。
-
leader故障
leader发生故障之后,会从ISR中选出一个新的leader,之后,为保证多个副本之间的数据一致性,其余的follower会先将各自的log文件高于HW的部分截掉,然后从新的leader同步数据。
注意:这只能保证副本之间的数据一致性,并不能保证数据不丢失或者不重复。
4.3.3 Exactly Once语义
1. 何为幂等性
幂等性 : 所谓的幂等性,就是对接口的多次调用所产生的的结果和调用一次是一致的。
生产者Producer在进行重试时不论向Server发送多少次重复数据,Server端都只会持久化一条。
幂等性结合At Least Once语义,就构成了Kafka的Exactly Once语义。即:
At Least Once + 幂等性 = Exactly Once
2. 幂等性条件
只能保证produce在单个会话内数据不丢失不重复,如果produce出现意外挂掉再重启是无法保证的
(幂等性情况下,是无法获取之前的状态信息的,因为是无法做到跨会话级别的不丢不重)
幂等性不能跨多个topic-produce,只能保证单个partition内的幂等性,当涉及到多个topic-produce时,这个中间的状态并没有同步。
3. 幂等性的使用
修改produce的配置:
enable.idempotence = true
4.4 Kafka消费者
4.4.1 消费方式
consumer采用pull(拉)模式从broker中读取数据。
**push(推)模式很难适应消费速率不同的消费者,因为消息发送速率是由broker决定的。**它的目标是尽可能以最快速度传递消息,但是这样很容易造成consumer来不及处理消息,典型的表现就是拒绝服务以及网络拥塞。而pull模式则可以根据consumer的消费能力以适当的速率消费消息。
pull(拉)模式不足之处是,如果kafka没有数据,消费者可能会陷入循环中,一直返回空数据。针对这一点,Kafka的消费者在消费数据时会传入一个时长参数timeout,如果当前没有数据可供消费,consumer会等待一段时间之后再返回,这段时长即为timeout。
4.4.2 分区分配策略
最好的情况是:有多少个消费者,就创建多少个分区,这也是最常见的情况,一个消费者对应一个分区。
如果,消费者数量和分区数量不是一一对应的情况,我们采取下面的分区分配策略。
一个consumer group中有多个consumer,一个 topic有多个partition,所以必然会涉及到partition的分配问题,即确定那个partition由哪个consumer来消费。
Kafka有三种分配策略,一是roundrobin(轮巡),一是range(范围分配),最后是Sticky(黏性【已作废不用】)
当前Kafka中默认的分区分配策略是range范围分配
1. roundrobin(轮巡)
roundrobin(轮巡)再分配策略
如果在系统运行时,其中一个消费者(例如Consumer-0)突然挂掉,则Kafka会重置其余消费者消费的消息,Kafka会把这topic主题中的7个分区进行回收再次重新分配,仍然是采用roundrobin轮巡分配策略。
————>分区从0号开始逐个分配一遍。
2. range(范围分配)
范围分区再分配策略
如果在系统运行时,其中一个消费者(例如Consumer-0)突然挂掉,则Kafka会重置其余消费者消费的消息,Kafka会把这topic主题中的7个分区进行回收再次重新分配,仍然是采用range范围分配策略。
————>如果分区数是消费者数据的整倍数,则平均分,如果不是整倍数,则第一个消费者多消费一个分区。
3. Sticky (黏性)
sticky分区策略初始分区和round robin一样是轮询机制。但与round Robin机制不一样的是,在有新的消费者加入到本消费者组时,sticky重新分配的分区个数较少,比较节省性能。
4.4.3 offset 的维护
那么__consumer_offsets中到底使用什么方式进行维护呢 ?
其实,就是维护了**G【group】、T【topic】、P【partition】**三个数据。
由于consumer在消费过程中可能会出现断电宕机等故障,consumer恢复后,需要从故障前的位置的继续消费,所以consumer需要实时记录自己消费到了哪个offset,以便故障恢复后继续消费。
Kafka 0.9版本之前,consumer默认将offset保存在Zookeeper中,从0.9版本开始,consumer默认将offset保存在Kafka一个内置的topic中,该topic为**__consumer_offsets**。
扩展:
在kafka内部有两个内部的话题:
— 、 _consumer_offsets
— 、 _transaction_states
我们可以开启一个消费者去消费**_consumer_offsets **话题中的offset,就可以直观看到offset的维护。
-
修改配置文件consumer.properties
# 不排除内部的topic,在文件的最后一行加上下面这行配置信息。 exclude.internal.topics=false
-
创建一个topic(testOffset)
kafka-topics.sh --create --topic testOffset --bootstrap-server hadoop01:9092 --partitions 2 --replication-factor 2
-
启动生产者和消费者,分别向testOffset中生产数据和消费数据
# 生产者 kafka-console-producer.sh --topic testOffset --broker-list hadoop01:9092 # 消费者 kafka-console-consumer.sh --consumer.config /opt/module/kafka/kafka_2.11-2.4.1/config/consumer.properties --topic testOffset --bootstrap-server hadoop01:9092
-
再次启动一个消费者【黄雀】,这个消费者就是用来消费_consumer_offsets话题中的offset
kafka-console-consumer.sh --topic _consumer_offsets --bootstrap-server hadoop01:9092 "kafka.coordinator.group.GroupMetadataManager\$OffsetsMessageFormatter" --consumer.config /opt/module/kafka/kafka_2.11-2.4.1/config/consumer.properties --from-beginning
-
生产者向testOffset中生产消息,观察黄雀消费者的变化
4.5 Kafka的高效读写
1. 顺序写磁盘
Kafka的producer生产数据,要写入到log文件中,写的过程是一直追加到文件末端,为顺序写。官网有数据表明,同样的磁盘,顺序写能到到600M/s,而随机写只有100k/s。这与磁盘的机械机构有关,顺序写之所以快,是因为其