该模式即使有多个消费者同时消费数据,也能保证数据处理的顺序。
发布者将消息投递到Topic中,数据在持久化在Topic中,只有订阅了相应Topic的消费者才可以消费这个消息,一个Topic可以允许多个消费者订阅,一个消费者可以订阅多个Topic,所以Topic可以被所有订阅者消费,而被消费掉后不会立即删除,会保留历史消息。
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。
消费者默认通过range(范围)策略对消费者进行分区的分配,
生产者默认使用Round-robin(轮询)策略将消息发送到不同的分区,这样达到了分区的负载均衡的目的。
生产者在发送消息失败时是可以自动重试的,可以通过retries参数设置,重试时间间隔默认100ms- , 需要处理失败上限后的不可重试的错误,可以记录入库
分组分区的模式让kafka保证了消息消费的有序性和负载均衡,但是这个有序性只能是一个partition能的消息有序,而多个partition之间是不能保证有序性的,想要topic所有的消息有序,只能让使topic只有一个partition
kafka将消息分门别类形成了不同的主题,一个kafka实例可以创建多个topic,一个topic可以存保存在多个kafka实例中,生产者投递消息或是消费者消费消息,只需要关心将选择哪个topic,而不需要关心topic存放在何处。
topic可以分成多个partition,topic中至少要有一个partition,partition中保存着topic的订阅消息,partition可以存放在各个不同的broker中,一个partition只能对应一个分组的一个消费者,partition可以提供不同的分组的不同消费者消费,partition中保存的数据是有序的,但是当topic存在多个partition时,topic将不能保证数据的顺序,所以在需要保证数据顺序时,需要将partition设置为1个。
副本保存在不同的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记录消了费者在partition中消费到哪里,offset原本存放于zk中,但是zk不适合大批量的频繁写入操作,所以将offset移动到了一个broker中名叫__consumer_offsets topic的topic中,当kafka在被消费的过程中即使挂了,可以通过offset恢复数据,它就像是书的目录能快速找到该从哪里开始看。
comsumer group是一个kafka集群中的一个comsumer小组,它共享一个group.id**,组内协调小组的comsumer成员共同消费小组中被订阅的topic的所有partition**,组内不允许多个comsumer共同消费一个partition,但是允许一个comsumer消费多个partition,由于comsumer group的变动对kafka集群的影响很小
topic的被分成多个partition,每个partition在物理层面上对应的是一个文件夹,文件夹下存储的partition所有的消息的日志文件和索引文件,任何发布到partition的消息都会记录到这个日志文件中末尾,使得kafka拥有了数据持久化的能力
比较老的kafka版本中,offset是存放在zookeeper中的,由于Zookeeper并不适合大批量的频繁写入操作,新版Kafka已推荐将consumer的位移信息保存在Kafka内部的topic中,即__consumer_offsets topic,在消费失败时可以重置offset达到重新消费的效果。
kafka的ack机制
kafka发送数据的时候,每次发送消息都会有一个确认反馈机制,确保消息正常的能够被收到。
Kafka消息发送分同步(sync)、异步(async)两种方式,默认使用同步方式,可通过 producer.type 属性进行配置;
通过 request.required.acks 属性进行配置:值可设为 0, 1, -1;
生产者发送时数据丢失的情形:
消费者消费消息数据丢失的情景:
设置offset为自动定时提交,当offset被自动定时提交时,数据还在内存中未处理,此时刚好把线程kill掉,那么offset已经提交,但是数据未处理,导致这部分内存中的数据丢失。
消费者消费的数据丢失解决办法:
通过用手动提交来保证数据的不丢失
数据重复的原因:
消费后的数据,当offset还没有提交时,partition就断开连接。比如,通常会遇到消费的数据,处理很耗时,导致超过了Kafka的session timeout时间,此时有一定几率offset没提交,会导致重复消费。
解决办法:
可以通过将每次消费的数据的唯一标识存入Redis中,每次消费前先判断该条消息是否在Redis中,如果有则不再消费,如果没有再消费,消费完再将该条记录的唯一标识存入Redis中,并设置失效时间,防止Redis数据过多、垃圾数据问题。
kafka动态维护了一个同步状态的副本的集合,简称ISR。ISR是Kafka用来保证数据可靠性的机制,即保证每个分区都收到生产者生产的消息。Kafka默认的副本同步策略是全部同步完成再返回ack(-1)。这样的效率是很慢的。于是提出了ISR机制。
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中机器的速度存在差异,当积少成多,就会把太落后的剔除出去
配置:
server配置:
replica.lag.time.max.ms=10000
如果leader发现follower超过10秒没有向它发起fetch请求,那么leader考虑这个follower是不是程序出了点问题或者资源紧张调度不过来,它太慢了,不希望它拖慢后面的进度,就把它从ISR中移除。
replica.lag.max.messages=4000
相差4000条就移除follower慢的时候,保证高可用性,同时满足这两个条件后又加入ISR中,在可用性与一致性做了动态平衡 。
min.insync.replicas=1
需要保证ISR中至少有多少个replica
request.required.asks=0
consumer采用pull(拉)模式从broker中读取数据。
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
采取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)