Kafka
一.概述
1.定义
- Kafka是一个分布式的基于发布/订阅模式的消息队列(Message Queue)
- Kafka是一个开源的分布式事件流平台
2.消息队列
- 应用:
- 同步处理
- 异步处理
- 优点:
- 解耦
- 可恢复性
- 缓冲
- 峰值处理
- 异步通信
- 消息队列两种模式:
- 点对点模式
- 消费者主动拉取数据,并在收到后清除。故一个消息仅能被消费一次
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GCBcKJsf-1625981231681)(C:\Users\Jzs\AppData\Roaming\Typora\typora-user-images\image-20210706194226978.png)]
- 发布/订阅模式
- 消息生产者将消息发布到topic中,消费者订阅消费该消息。
- 发布到topic的消息会被所有消费者订阅消费。消费后不清除消息。
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7FI0f8Ed-1625981231682)(C:\Users\Jzs\AppData\Roaming\Typora\typora-user-images\image-20210706194403990.png)]
- 点对点模式
3.Kafka基础架构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qKk4nr3G-1625981231683)(C:\Users\Jzs\AppData\Roaming\Typora\typora-user-images\image-20210706194926673.png)]
-
Producer
生产者,向kafka broker中发送消息的客户端
-
Consumer
消费者,向kafka broker中拉取消息的客户端
-
Consumer Group(CG)
-
消费者组,由多个Consumer组成。
-
消费者组内每个消费者负责消费不同分区的数据,一个分区只能由一个组内消费者消费;(防止出现数据重复)
-
消费者组之间互不影响、所有的消费者都属于某个消费者组,即消费者组是逻辑上的一个订阅者。
-
-
Broker
一台kafka服务器就是一个broker。一个kafka集群由多个broker组成,一个broker可以容纳多个topic
-
Topic
可以理解为一个队列,成产者和消费者面向的都是一个topic
-
Partition
一个topic可以分布到多个broker上,即一个topic可以分为多个Partition,每个Partition是一个有序的队列
-
Replica
副本,为保证不发展单点故障问题,kafka提供了副本机制,可以设置每个topic的每个分区有若干的副本。副本由一个leader和多个follower
-
leader
副本的"主",生产者和消费者处理的对象都是leader
-
follower
副本中的"从",实时同步leader。在leader故障时,保障数据安全,从多个follower中选择一个成为新年的leader
-
二.kafka架构深入
1.kafka工作流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CzdqjdQV-1625981231685)(C:\Users\Jzs\AppData\Roaming\Typora\typora-user-images\image-20210706203901091.png)]
- kafaka中消息是以topic进行分类的,成产者成产的消息和消费者消费的消息,都是面向topic的
- 一个topic下的每个partition(分区)对消息都维护了两个offset,一个记录生产者发送消息位置,即消息数量。另一个消费者消费到那一条消息。
- Topic是逻辑上的概念,partition是物理的概念,每个partition对应一个log文件,log文件中存储的就是producer生产的数据。partition生产的数据不断追加到log文件的末端,同时每条数据都有一个offset。消费者组中每个消费者,通过offset记录自己消费到哪一个数据,以便出错后再次恢复时回到上次消费的位置,即实现断点续传功能。
2.kafka文件存储机制
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I45gS64M-1625981231686)(C:\Users\Jzs\AppData\Roaming\Typora\typora-user-images\image-20210706204833637.png)]
- 生产者生产的消息(数据)存在在一个由topic+分区号命名的文件夹中。其数据不断追加到文件夹中的log文件中的末尾。
- 为了防止log文件过大导致数据查找效率过低。kafka采用分片和索引的机制。通过将将每个partition分为多个sagment。每个sagment对应有log和index两个文件。每个index和log文件命名为一个sagment的offset。如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AMLYrGKz-1625981231687)(C:\Users\Jzs\AppData\Roaming\Typora\typora-user-images\image-20210706213019602.png)]
- 通过文件名即offset可以算出消息输入哪一个具体sagment。再通过index文件中存储的索引信息,对应log中物理偏移地址。
- index采用数组的数据结构,提高查询速率。
3.Producer发送消息的流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-98yoWJ2K-1625981231687)(C:\Users\Jzs\AppData\Roaming\Typora\typora-user-images\image-20210707012354761.png)]
- Producer采用异步发送的方式发送消息。发送过程中使用main线程和Sender两个线程,以及一个线程共享变量RecordAccumulator。main线程将消息发送给线程共享变量,Senser线程不断从线程共享变量中拉取数据到Kafka Broker。
- main线程需经过拦截器Interceptor,序列化器Serializer以及分区器Partition。其中拦截器用来过滤数据,序列化器将数据转为为byte数组提高传输效率,最后通过分区器将数据分区发送到线程共享变量RecordAccumulator中,最后再由sender线程拉取数据并发送到topic中。
- Sender线程拉取数据时机由两个参数决定:
- batch.size:批处理大小,当线程共享变量达到batch.size大小时,sender才会发送出数据
- linger.ms:等待时间。即数据没有达到batch.size,但超过linger.ms时间时。sender也会开启发送
4.kafka生产者的分区策略
-
分区原因:
- 方便在集群中扩展
- 提高并发,以partition为单位进行读写
-
分区规则:
-
指明partition
按照指明的partition传输数据
-
没有指明partition,但有key值
将key值与topic的partition数量取余得到partition值
-
没有指明partition值,也没哟具体key值
- 默认采用高Sticky Partition(粘性分区器)的方式分区
- 粘性分区器规则:随机分配一个分区,并尽可能一直使用此分区。知道超过batch.size或已完成,下次数据进入的分区号将再次随机分配。
-
5.Kafka消息可靠性保证(无丢失,无重复)
1.生产者(producer)发送数据到topic-partition时可靠性保证
-
再producer向topic-partition中发送数据时,每个topic-partition收到数据后都向producer发送ack(acknowledgement确认收到),producer收到ack后,将进行下一轮数据发送,否则重新发送数据。
-
发送ack的时机:
-
为减少副本冗余,在保障数据可靠情况下使用最少的副本数量。Kafka使用副本follower全部同步leader后再发送ack的方式。但是带来一个问题,即存在一个follower出现故障或同步速率过慢,将会导致长时间无法完成同步工作,kafka将无法返回ack。导致整个系统运行速度过慢甚至无法继续运行。故kafka引入了一个动态消息同步队列ISR机制。
-
ISR:
leader中维护一个ISR队列,队列中为保持和leader同步的follower集合。
当ISR中全部follower同步完成后,leader再向producer发送ack。如果存在follower出现故障或同步速率过慢的情况。当在规定时间(由参数***replica.lag.time.max.ms***设置,默认为10s)无法完成同步的follower,leader将把它踢出ISR。从而不影响发送速度。当leader出现故障时,会在ISR队列中选举新的leader。
-
ack应答级别
kafka提供了三种可靠性级别
-
ack = 0
partition中的leader在接收到producer发送的消息后,存入内存中,返回ack。此时消息数据还没有存入磁盘中落盘。follower也没有开始同步数据。若发生leader故障,则会导致数据丢失。producer收到ack则会继续发送新消息。即消息只发送一次(at most once)
-
ack = 1
partition中leader在数据落盘后,即从内存写入磁盘中后,返回ack。此时follower还未完成消息同步。若此时leader发生故障,则会导致数据丢失。
-
ack = -1(all)
等partition的leader和follower全部落盘(ISR队列模式)后在返回ack。但存在小概率在全部落盘后,leader发送ack前,leader故障。未完成ack发送,但消息已经落盘。producer没有收到ack则会再次发送该消息,造成数据重复。
-
-
2.leader和follower故障处理的方式
LEO:每个副本offset即副本长度
HW:消费者能看到的最大offset,即ISR队列中最小的LEO(同步队列副本最小同步到长度)
-
follower故障
故障的follower被踢出ISR,待follower恢复以后,读取本地磁盘,获取上次记录的HW。将LEO高于HW的部分截取,从HW部分向leader开始同步。等follower的LEO大于等于现在partition的HW,即以追上当前leader最新ack响应进度,就可以再次加入ISR了。
-
leader故障
leader发生故障后,将在ISR队列中选举一个新的leader。为保障副本之间数据一致,其余follower将各自高于HW部分截取,然后从新的leader同步数据。选举的方式是选择最稳定的那个follower,即长时间未脱离ISR的副本。
3.解决数据重复问题Exactly Once
-
ack=-1 (all)时,可以保证producer到server之间不丢数据。称之为At least once 至少一次。
-
ack = 0 时,每个消息只发送一次,称之为At most Once 最多一次。
At more Once(ack = 0)无法保障数据不丢失,At least Once(ack = -1)可以保证数据不丢失,但是无法保障数据不重复。若想保证数据既不丢失也不重复,从而引出Exactly Once(精准一次性)。
-
Exactly Once:(精准一次)
At least Once + 幂等性 = Exactly Once
(即把ack设置为-1,producer的参数enable.idempotence设置为true,默认就是true)
**幂等性:**无论多少次重复动作,只会操作一次。即partition重复发送数据也只会持久化一条数据。
实现方式:producer初始化分配一个PID,发送同一个消息附带一个Sequence Number。
在Broker端存储一个<PID, Partition, SeqNumber>做缓存。提交相同主键消息时,通过缓存识别为相同数据,Broker只持久化一次。
但是一旦重启producer,PID改变,无法识别是否同一条消息,故幂等性无法保证跨分区,跨会话的Exactly Once。
4.Producer事务
- 在0.11版本中,produce引入事务特性。实现了跨分区跨会话的方式。即使服务重启,也能实现。
6.Kafka消费者consumer
-
消费方式
-
Consumer采用pull(拉)的模式从broker中读取数据。
若采用push(推)模式,很难适应消费不同速率的消费者。消息发送速率由broker决定。很容易造成Consumer来不及处理的情况,形成网路拥塞。因此采用pull模式。
-
但是pull模式也存在不足:若kafka中没有数据,消费者会一直pull拉取数据,一直返回空数据。因此,kafka通过向消费者传递一个长参数timeout,若没有数据供消费者消费。Consumer等待timeout所设置时间后再返回。
-
-
分区分配策略
一个Consumer group中存在多个Consumer,一个topic中存在多个partition。一个partition只能由Consumer group组中一个成员消费,因此kafka需要对组中Consumer分配partition。kafka设置了三种分配策略:RoundRobin,Range和Sticky。以下对三种策略进行介绍:
-
RoundRobin
- 分区按照排序依次分配给Consumer group组中每个消费者,轮询分配。
-
Range(默认的分配方式)
- (partition总数/Consumer数量),按顺序将partition一次性分配完成,多余分配给第一个Consumer。
- 一旦一个Consumer发生故障,再次计算新的(partition总数/Consumer数量)的值,重新分配,但是会造成同一个分区前后处理的partition发生变化。
-
Sticky
- StickyAssignor策略,kafka从0.11.x版本开始使用这种策略。首先会尽力均衡分配partition到个个consumer。在同一组内消费者出现故障时候,会保持原来每个消费者消费的分区不变。将故障消费者处理分区均衡分给其他组内消费者消费。
-
-
offset维护
由于Consumer存在故障宕机等可能,所以需要Consumer消费具有断点续传的功能。故Consumer会记录一个offset标记自己消费的位置,以便恢复后继续上次消费。
-
自动提交offset
kafka提供了自动提交offset功能。通过以下两个参数设置:
- enable.auto.commit:是否开启自动提交offset功能
- auto.commit.interval.ms:自动提交offset的时间间隔
-
重置offset
-
auto.offset.reset = earliest | latest | none |
当Kafka没有初始偏移量(第一次消费)或者服务器在offset(被删除)后处理方式:
- earlist:自动将offset设置为最早偏移量(从头开始消费即from begging)
- latest(默认):自动将offset设置为最新偏移量(从当前开始消费)
- none:找不到offset后则抛出异常
-
-
手动提交offset
-
自动提交offset方式虽然方便,但是其基于时间提交。kafka无法知道消费者是否消费完成数据,因此存在数据丢失但是offset已经提交的情况。所以为了保障数据不丢失,需要将offset设置为手动提交模式。
-
手动提交分为:commitSync(同步提交),commitAsync(异步提交)两种。
相同点:本次pull拉取的一批数据最高偏移量提交
不通点:同步提交会阻塞线程,等提交成功后再继续。若提交失败则自动重试。
异步提交不等待,可能存在提交失败。
-
无论同步还是异步提交offset,都不能完全保证数据不丢不重。先提交后消费,可能造成数据丢失。先消费后提交可能造成数据重复。想要实现数据不重不丢,需要实现kafka消费端将消费过程和提交offset过程做原子绑定,才能实现。
-
-
Kafka高效读写数据
-
顺序读写磁盘
producer写入log文件中采用一直追加到文件末端的顺序写方式。对于磁盘而言,顺序写的速度远远超过随机读写的速度。主要在于省去了大量磁头寻址的时间。
-
零拷贝技术
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mqWohBTl-1625981231688)(C:\Users\Jzs\AppData\Roaming\Typora\typora-user-images\image-20210711122306460.png)]
- kafka不同于常见JavaIO程序。在数据读写时,直接通过操作系统层的page cache写入写出。实现高速读写。
-
过程做原子绑定,才能实现。