Kafka技术专题之总体原理和分析介绍(上)
Posted Java_Pluto
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kafka技术专题之总体原理和分析介绍(上)相关的知识,希望对你有一定的参考价值。
背景介绍
发布订阅模式
点对点传递模式
该模式即使有多个消费者同时消费数据,也能保证数据处理的顺序。
发布者将消息投递到Topic中,数据在持久化在Topic中,只有订阅了相应Topic的消费者才可以消费这个消息,一个Topic可以允许多个消费者订阅,一个消费者可以订阅多个Topic,所以Topic可以被所有订阅者消费,而被消费掉后不会立即删除,会保留历史消息。
kafka用的就是发布订阅模式:
kafka就是一个流处理平台,是一个高效和实时具有发布订阅模式、分布式的、多副本的消息系统,kafka具有横行扩展,高容错,高性能的特点。
kafka的特性
高容错:多分区多副本增强了容错和可扩展性,多订阅者支持将消息广播到多个订阅者,支持zookeeper调度。
高性能、高吞吐量:kafka吞吐量非常大,每秒可以处理和产生几十万条消息,延迟能控制在毫秒级,超高性能特性,使kafka能够很好的应对高并发的应用场景。
持久性和扩展性:数据可持久化的特点使kafka有别于其他的消息队列,多副本和基于组(consumer group)的消费使得kafka拥有不错的容错性,基于组的消费模式使得kafka能很好的支持水平扩展。
topic、partition、offset、comsumer group、replication之间的关系傻傻分不清:
kafka的消息以分门别类的形式,分成了很多个Topic,每个Topic又分成了很多个partition,而topic只是一个逻辑概念消息真正存储在这些物理层面的partition中,partition又可均匀的分布到集群的各个broker(kafka实例)中,生产或者消费的时候会被路由到相应的partition。
partition中的消息会被标识上递增系列号,代表着先后进入partition中存储的顺序,这个序列号就是offset,消费者通过offset指定从哪里开始进行消费,生产者生成消息时,消息会通过应用的路由策略将数据加到partition的末尾,消息被消费后不会马上删除,而是记录在日志中保存一定的期限,默认是7天。
消费者可以通过重置offset来重新消费。
高吞吐性
topic可能会非常大,所以topic可以通过partition将消息分成几个分区,然后将partition均匀放置到不同的broker中,这样是为了实现Kafka的broker端和消费端的负载均衡,同时提高kafka的吞吐量,不同的partition的数据都不相同。
高可用性
partition又被备份为多个replication,同时replication也会被均匀分布到不同的broker中,第一个启动并在zookeeper注册的broker将被选举为kafka controller,所有的Topic中第一个启动的partition将被kafka controller授权为leader。
其他的replication都会成为follower,leader负责写和读,消费者和生产者的读写只会找leader,follower只负责从leader pull数据,通过ISR实现数据同步,当leader故障时kafka controller就会选择一个follower当leader,这种多分区多副本的机制让kafka拥有了容错性、横行扩展性和负载性
-
通常kafka的消费者是以分组comsumer group的形式:一个组的消费者的数量要小于等于topic中的partition的数量(不然就会有消费者是处于空闲状态),不允许一个partition被同一个组的多个comsumer消费;
-
分组可以订阅多个topic,topic内的partition可以给多个组消费,但是一个组内的只能由一个consumer消费同一个topic的同一个partition,也就是说在一个组内partition只能对应一个consumer,而一个consumer可以对应多个partition;
-
partition可以对应多个组,topic可以对应多个组,partition可以把同一条消息发送到不同的分组去,而consumer可以消费多个partition,一个partition可以被多个小组消费但是只能给各个小组内的一个consumer消费,组内不允许多个consumer消费同一个partition。
Kafka分区分配
消费者默认通过range(范围)策略对消费者进行分区的分配,
生产者默认使用Round-robin(轮询)策略将消息发送到不同的分区,这样达到了分区的负载均衡的目的。
生产者在发送消息失败时是可以自动重试的,可以通过retries参数设置,重试时间间隔默认100ms- , 需要处理失败上限后的不可重试的错误,可以记录入库
分组分区的模式让kafka保证了消息消费的有序性和负载均衡,但是这个有序性只能是一个partition能的消息有序,而多个partition之间是不能保证有序性的,想要topic所有的消息有序,只能让使topic只有一个partition
topic-消息主题
kafka将消息分门别类形成了不同的主题,一个kafka实例可以创建多个topic,一个topic可以存保存在多个kafka实例中,生产者投递消息或是消费者消费消息,只需要关心将选择哪个topic,而不需要关心topic存放在何处。
partition-消息分区
topic可以分成多个partition,topic中至少要有一个partition,partition中保存着topic的订阅消息,partition可以存放在各个不同的broker中,一个partition只能对应一个分组的一个消费者,partition可以提供不同的分组的不同消费者消费,partition中保存的数据是有序的,但是当topic存在多个partition时,topic将不能保证数据的顺序,所以在需要保证数据顺序时,需要将partition设置为1个。
replication-副本
副本保存在不同的broker,保证了一个节点挂了,不影响集群的高可用效果,partition有leader和follower之分,每个分区只有一个是partition leader其余副本都作为follower,当partition leader挂掉时,kafka controller将选择一个follower代替,副本存放在不同的broker中,Replica的个数小于等于Broker的个数,对于每个Partition而言,每个Broker上最多只会有一个Replica,因此可以使用Broker id 指定Partition的Replica
Partition的Replica默认情况会均匀分布到所有Broker上。
partition leader负责提供给消费者和生产者读写功能,partition follower负责从leader中pull数据,进行数据的备份,当集群中某个节点挂掉了,其他节点上的副本就会顶上,这一机制使得kafka增加了容错性和可扩展性
offset-偏移量
offset记录消了费者在partition中消费到哪里,offset原本存放于zk中,但是zk不适合大批量的频繁写入操作,所以将offset移动到了一个broker中名叫__consumer_offsets topic的topic中,当kafka在被消费的过程中即使挂了,可以通过offset恢复数据,它就像是书的目录能快速找到该从哪里开始看。
comsumer group-消费组
comsumer group是一个kafka集群中的一个comsumer小组,它共享一个group.id**,组内协调小组的comsumer成员共同消费小组中被订阅的topic的所有partition**,组内不允许多个comsumer共同消费一个partition,但是允许一个comsumer消费多个partition,由于comsumer group的变动对kafka集群的影响很小
kafka的持久化
topic的被分成多个partition,每个partition在物理层面上对应的是一个文件夹,文件夹下存储的partition所有的消息的日志文件和索引文件,任何发布到partition的消息都会记录到这个日志文件中末尾,使得kafka拥有了数据持久化的能力
offset的控制
比较老的kafka版本中,offset是存放在zookeeper中的,由于Zookeeper并不适合大批量的频繁写入操作,新版Kafka已推荐将consumer的位移信息保存在Kafka内部的topic中,即__consumer_offsets topic,在消费失败时可以重置offset达到重新消费的效果。
kafka数据丢失和重复消费:
生产者的数据丢失:
kafka的ack机制
kafka发送数据的时候,每次发送消息都会有一个确认反馈机制,确保消息正常的能够被收到。
Kafka消息发送分同步(sync)、异步(async)两种方式,默认使用同步方式,可通过 producer.type 属性进行配置;
通过 request.required.acks 属性进行配置:值可设为 0, 1, -1;
- 0 :相当于异步发送,消息发送完毕即offset增加,继续生产;
- 1: leader对消息落盘成功后返回 ack 才增加offset,然后生产者才继续生产;
- -1:leader收到所有replica对一个消息的接受ack才增加offset,然后生产者才继续生产;
生产者发送时数据丢失的情形:
- 1、acks=0,不和kafka集群进行消息接收的确认,那么当网络异常、缓冲区满了等情况时,就会出现数据丢失;
- 2、acks=1,同步模式下,在只有leader接收成功并确认后,leader挂了,副本没有同步完成,就会造成数据丢失;
消费者消费数据的丢失和重复:
消费者消费消息数据丢失的情景:
设置offset为自动定时提交,当offset被自动定时提交时,数据还在内存中未处理,此时刚好把线程kill掉,那么offset已经提交,但是数据未处理,导致这部分内存中的数据丢失。
消费者消费的数据丢失解决办法:
通过用手动提交来保证数据的不丢失
数据重复的原因:
消费后的数据,当offset还没有提交时,partition就断开连接。比如,通常会遇到消费的数据,处理很耗时,导致超过了Kafka的session timeout时间,此时有一定几率offset没提交,会导致重复消费。
解决办法:
可以通过将每次消费的数据的唯一标识存入Redis中,每次消费前先判断该条消息是否在Redis中,如果有则不再消费,如果没有再消费,消费完再将该条记录的唯一标识存入Redis中,并设置失效时间,防止Redis数据过多、垃圾数据问题。
ISR
kafka动态维护了一个同步状态的副本的集合,简称ISR。ISR是Kafka用来保证数据可靠性的机制,即保证每个分区都收到生产者生产的消息。Kafka默认的副本同步策略是全部同步完成再返回ack(-1)。这样的效率是很慢的。于是提出了ISR机制。
- 同步复制: 只有所有的follower把数据拿过去后才commit,一致性好,可用性不高。
- 异步复制: 只要leader拿到数据立即commit,等follower慢慢去复制,可用性高,立即返回,一致性差一些。
kafka不是完全同步,也不是完全异步,是一种ISR机制:每个Partition的leader会维护一个与其基本保持同步的Replica列表,该列表称为ISR(in-sync Replica),每个Partition都会有一个ISR,而且是由leader动态维护,**当ISR中所有Replica都向Leader发送ACK时,leader才commit,如果一个follower比一个leader落后太多,或者超过一定时间未发起数据复制请求,则leader将其重ISR中移除
既然所有Replica都向Leader发送ACK时,leader才commit,那么follower怎么会leader落后太多?
producer往kafka中发送数据,不仅可以一次发送一条数据,还可以发送message的数组;批量发送,同步的时候批量发送,异步的时候本身就是就是批量;
底层会有队列缓存起来,批量发送,对应broker而言,就会收到很多数据(假设>1000),这时候leader发现自己有1000条数据,follower只有500条数据,落后了500条数据,就把它从ISR中移除出去,这时候发现其他的follower与他的差距都很小,就等待,主要是因为内存,服务器性能等原因,差距很大,就把它从ISR中移除出去。也就是说ISR中机器的速度存在差异,当积少成多,就会把太落后的剔除出去
- follower移除:follower如果落后leader太多,就把它从ISR中移除出去,如果发现follower与leader的差距都很小,就等待;如果因为内存等原因,差距很大,就把它从ISR中移除出去。
- leader移除:如果是leader挂掉了,从它的follower中选举一个作为leader,并把挂掉的leader从ISR中移除,继续处理数据。一段时间后该leader重新启动了,它知道它之前的数据到哪里了,尝试获取它挂掉后leader处理的数据,获取完成后它就加入了ISR。
- ISR选举:当leader挂了之后kafka controller会从ISR中选择一个副本当leader,在ISR集合中,kafka
controller会选择ISR序号最靠前的broker上的副本当leader,原因在于序号越靠前说明节点机子的性能越好,原因是每次启动所有的broker会向zookeeper注册节点,越先注册,序号越靠前,默认代表机器性能越好,但是这提前是副本必须是在ISR集合中没有被剔除。
配置:
server配置:
replica.lag.time.max.ms=10000
如果leader发现follower超过10秒没有向它发起fetch请求,那么leader考虑这个follower是不是程序出了点问题或者资源紧张调度不过来,它太慢了,不希望它拖慢后面的进度,就把它从ISR中移除。
replica.lag.max.messages=4000
相差4000条就移除follower慢的时候,保证高可用性,同时满足这两个条件后又加入ISR中,在可用性与一致性做了动态平衡 。
topic配置:
min.insync.replicas=1
需要保证ISR中至少有多少个replica
Producer配置:
request.required.asks=0
- 0:相当于异步的,不需要leader给予回复,producer立即返回,发送就是成功:(那么发送消息网络超时或broker
crash(1.Partition的Leader还没有commit消息 2.Leader与Follower数据不同步),既有可能丢失也可能会重发) - 1:当leader接收到消息之后发送ack,丢会重发,丢的概率很小
- -1:当所有的follower都同步消息成功后发送ack. 丢失消息可能性比较低
消费方式
consumer采用pull(拉)模式从broker中读取数据。
- push(推)模式很难适应消费速率不同的消费者,因为消息发送速率是由broker决定的。它的目标是尽可能以最快速度传递消息,但是这样很容易造成consumer来不及处理消息,典型的表现就是拒绝服务以及网络拥塞。而pull模式则可以根据consumer的消费能力以适当的速率消费消息。
- pull(拉)模式不足之处是,如果kafka没有数据,消费者可能会陷入循环中,一直返回空数据。针对这一点,Kafka的消费者在消费数据时会传入一个时长参数timeout,如果当前没有数据可供消费,consumer会等待一段时间之后再返回,这段时长即为timeout。
duration,拉去poll的间隔时间。
消费者分区分配策略
consumer group中有多个consumer,一个topic有多个partition,所以必然会涉及到partition的分配问题,即确定哪个partition由哪个consumer来消费。 Kafka有两种分配策略,一是RoundRobin(轮询),一是range(范围)。
示例:0、1、2、3、4、5、6、 7、8、 9 、总共十个分区,四个consumer
采取RoundRobin策略:
即轮询。比如一个topic下有10个分区,那么第一个分区0被分配给C0,第二个分区1被分配给C1,第三个分区2被分配给C2,第四个分区3被分配给C3,以此类推。
C0 : 0、4、8
C1: 1、5、9
C2: 2、6
C3: 3、7
- 优点,同一个消费者组的不同消费者之间所消费的分区数量相差最大不会超过1。均衡性比较好一点
- 缺点,就是一个消费者组中的消费者各自指定消费的主题并不会由这个消费者消费,而是拿出来作为整个组要消费的主题。
采取range(默认)策略:
针对每一个topic:
n = 分区数/消费者数量
假如有10个分区,3个消费者,把分区按照序号排列0,1,2,3,4,5,6,7,8,9;消费者为C1,C2,C3,那么用分区数除以消费者数来决定每个Consumer消费几个Partition,除不尽的前面几个消费者将会多消费一个
最后分配结果如下
C1:0,1,2,3
C2:4,5,6
C3:7,8,9
如果有11个分区将会是:
C1:0,1,2,3
C2:4,5,6,7
C3:8,9,10
假如我们有两个主题 T1, T2,分别有10个分区,最后的分配结果将会是这样:
C1:T1(0,1,2,3) T2(0,1,2,3)
C2:T1(4,5,6) T2(4,5,6)
C3:T1(7,8,9) T2(7,8,9)
- 缺点,就是当同一个消费者组的多个消费者消费的主题相同,并且消费的相同主题比较多时,按照上面步骤(2)的情况,可能会导致消费者A消费十个分区,而消费者B只消费了五个分区,这样不平衡。
- 优点,同一个消费者组的不同消费者可以指定自己单独需要消费的主题。。只有当其他消费者与当前消费者指定的主题相同时,才会一起分配这个主题的不同分区。
以上是关于Kafka技术专题之总体原理和分析介绍(上)的主要内容,如果未能解决你的问题,请参考以下文章
推荐面试使用 |夯实Kafka知识体系及基本功分析一下Kafka总体原理和分析介绍「上篇」
分布式技术专题「系统功能原理分析」缓存淘汰算法之LRU和LFU及FIFO介绍
#私藏项目实操分享#Alibaba中间件技术系列「RocketMQ技术专题」RocketMQ消息发送的全部流程和落盘原理分析
精华推荐 |算法数据结构专题「延时队列算法」史上非常详细分析和介绍如何通过时间轮(TimingWheel)实现延时队列的原理指南