分布式消息队列
- 可快速持久化。通过O(1)的磁盘数据结构提供消息的持久化,这种结构对于即使数以TB的消息存储也能够保持长时间的稳定性能
- 高吞吐量。即使是非常普通的硬件Kafka也可以支持每秒数百万的消息
- 完全的分布式系统。它的Broker、Producer、Consumer都原生地支持分布式,自动支持负载均衡
基本概念
- Broker 一个实例或节点
- Topic
- Topic是Kafka中同一类数据的集合,相当于数据库中的表
- Partition
- 分区是一个有序的、不可修改的消息队列,分区内消息有序存储
- 一个Topic可分为多个Partition,相当于把一个数据集分成多份,分别存储不同的分区中
- Parition是物理概念,每个分区对应一个文件夹,其中存储分区的数据和索引文件
- 主要作用是提高并行吞吐率
- partiton命名规则为topic名称+有序序号
- 建议partition的数量也需要大于集群中Broker的数量,这样可以让Partition Leader尽量均匀地分布在各个Broker中
- Replication
- 每个partition都有多个replicate
- 每个partition都有一个leader(主副本),零个或多个follower(从副本)
- 所有的读写都只由leader来完成,follower只从leader同步消息,并不对外服务
Kafka将元数据保存在Zookeeper中,负责Kafka集群管理,包括配置管理、动态扩展、Broker负载均衡、Leader选举,以及 Consumer Group变化时的Rebalance等
工作机制
- Kafka发送端采用push模式将消息发送到broker
- Kafka消费端采用pull模式订阅并消费消息
- Partition是一个FIFO队列, 写入消息采用在队列尾部追加的方式,消费消息采用顺序读取的方式
- producer往broker发布一条消息,broker仅仅持久化消息
- consumer主动发起pull,告诉broker一个offset,broker把offset对应的消息返回给consumer
kafka可以设置多久删除和多大删除
数据存储
实现上一个partition对应一个文件夹,一个segment对应两个文件:一个数据文件,一个索引文件
- Segment(段文件)
- 最小数据存储单元
- 一个Partition包含多个Segment文件,每个Segment大小相等,这种特性方便old segment file快速被删除。
- *.index 索引文件
- 索引内容格式:offset,position
- 采用稀疏存储方式
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-prBaHO1b-1613137854776)(assets/c415ed42.png)]
- *.log 数据文件
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8YAjQKgK-1613137854778)(assets/69e4b0a6.png)]
- segment文件命名规则:partion全局的第一个segment从0开始,后续每个segment文件名为上一个segment文件最后一条消息的offset值。数值最大为64位long大小,19位数字字符长度,没有数字用0填充。
- offset
- partition中的每个消息都有一个连续的序列号叫做offset,用于partition唯一标识一条消息,也可以根据Offset快速定位一个数据文件
- 这个offset是partition内部的,而不是全局的
问题
如何保证消息可靠性?
ack + offset
即保证不重复,保证不丢失
最理想的情况是消息发送成功,并且只发送了一次,这种情况叫做exactly-once,但是不可避免的会发生消息发送失败以及消息重复发送的情况
为了解决这类问题,在producer端,当一个消息被发送后,producer会等待broker发送响应,收到响应后producer会确认消息已经被正确发送给kafka,否则就会重新发送
在consumer端,因为broker记录了partition中的offset值,这个值指向consumer下一个消费的消息,如果consumer收到消息但是消费失败,broker可以根据offset值来找到上一个消息,同时consumer还可以控制offset值,来对消息进行任意处理
为什么具有如此高性能
https://mp.weixin.qq.com/s?__biz=MzIxMjAzMDA1MQ==&mid=2648945468&idx=1&sn=b622788361b384e152080b60e5ea69a7#%23
- 磁盘顺序读写效率高于内存随机读写,消息持久化能力即写操作,写操作是直接追加到一个segment文件的末尾,可以做到以时间复杂度为O(1)的方式提供消息持久化能力
- https://queue.acm.org/detail.cfm?id=1563874
- Apache Kafka – A High Throughput Distributed Messaging System的观点
- 无需在内存里维护大量的数据,Kafka不需要担心GC的问题
- 另外Kafka直接通过sendfile系统调用避免了内核态和用户态之间切换以及不必要的数据复制。
1.顺序写入
因为硬盘是机械结构,每次读写都会寻址->写入,其中寻址是一个“机械动作”,它是最耗时的。所以硬盘最“讨厌”随机I/O,最喜欢顺序I/O。为了提高读写硬盘的速度,Kafka就是使用顺序I/O。如果一个topic建立多个分区那么每个parathion都是一个文文件,收到消息后Kafka会把数据插入到文件末尾。
2. Memory Mapped Files(内存映射文件)
64位操作系统中一般可以表示20G的数据文件,它的工作原理是直接利用操作系统的Page来实现文件到物理内存的直接映射。完成映射之后你对物理内存的操作会被同步到硬盘上。
3. Kafka高效文件存储设计特点
Kafka把topic中一个parition大文件分成多个小文件段,通过多个小文件段,就容易定期清除或删除已经消费完文件,减少磁盘占用。
通过索引信息可以快速定位message和确定response的最大大小。通过index元数据全部映射到memory(内存映射文件),可以避免segment file的IO磁盘操作。通过索引文件稀疏存储,可以大幅降低index文件元数据占用空间大小。
kafka中的topic为什么要进行分区?
为了性能考虑,如果topic内的消息只存于一个broker,那这个broker会成为瓶颈,无法做到水平扩展。所以把topic内的数据分布到整个集群就是一个自然而然的设计方式。Partition的引入就是解决水平扩展问题的一个方案。这样,producer可以将数据发送给多个broker上的多个partition,consumer也可以并行从多个broker上的不同paritition上读数据,实现了水平扩展
如果一个Topic对应一个文件,那这个文件所在的机器I/O将会成为这个Topic的性能瓶颈,而有了Partition后,不同的消息可以并行写入不同broker的不同Partition里,极大的提高了吞吐率。
在partition中如何通过offset查找message
例如读取offset=368776的message,需要通过下面2个步骤查找。
- 第一步查找segment file 上述图2为例,其中00000000000000000000.index表示最开始的文件,起始偏移量(offset)为0.第二个文件00000000000000368769.index的消息量起始偏移量为368770 = 368769 + 1.同样,第三个文件00000000000000737337.index的起始偏移量为737338=737337 + 1,其他后续文件依次类推,以起始偏移量命名并排序这些文件,只要根据offset 二分查找文件列表,就可以快速定位到具体文件。 当offset=368776时定位到00000000000000368769.index|log
- 第二步通过segment file查找message 通过第一步定位到segment file,当offset=368776时,依次定位到00000000000000368769.index的元数据物理位置和00000000000000368769.log的物理偏移地址,然后再通过00000000000000368769.log顺序查找直到offset=368776为止。
为什么要引入segment?
如果不引入segment,一个partition直接对应一个文件(应该说两个文件,一个数据文件,一个索引文件),那这个文件会一直增大。同时,在做data purge时,需要把文件的前面部分给删除,不符合kafka对文件的顺序写优化设计方案。引入segment后,每次做data purge,只需要把旧的segment整个文件删除即可,保证了每个segment的顺序写
其他
对于传统的message queue而言,一般会删除已经被消费的消息,而Kafka集群会保留所有的消息,无论其被消费与否。当然,因为磁盘限制,不可能永久保留所有数据(实际上也没必要),因此Kafka提供两种策略删除旧数据。一是基于时间,二是基于Partition文件大小。
Kafka会为每一个Consumer Group保留一些metadata信息——当前消费的消息的position,也即offset。这个offset由Consumer控制。正常情况下Consumer会在消费完一条消息后递增该offset。当然,Consumer也可将offset设成一个较小的值,重新消费一些消息。因为offet由Consumer控制,所以Kafka broker是无状态的,它不需要标记哪些消息被哪些消费过,也不需要通过broker去保证同一个Consumer Group只有一个Consumer能消费某一条消息,因此也就不需要锁机制,这也为Kafka的高吞吐率提供了有力保障。
参考:http://www.jasongj.com/2015/03/10/KafkaColumn1/
https://blog.csdn.net/thewayma/article/details/4287170