目录
kafka高吞吐
一、顺序读写:sequence IO
- 磁盘的特性:快速顺序读写、慢速随机读写。磁盘是典型的IO块设备,每次读写都会经历寻址,其中寻址中寻道是比较耗时的。随机读写会导致寻址时间延长,从而影响磁盘的读写速度;
- Kafka在将数据持久化到磁盘时,采用只追加的顺序写,Kafka会将数据插入到文件末尾,并且Kafka不会"直接"删除数据,而是把所有数据保存到磁盘,每个consumer会指定一个offset来记录自己订阅的topic的partition中消费的位置。我们可以设置策略来清理数据,比如通过参数log.retention.hours指定过期时间,当达到过期时间时,Kafka会清理数据;
二、零拷贝:无第三方存储,通道对拷(java nio的写法)
传统IO: 第一次:将磁盘文件,读取到操作系统内核缓冲区;
第二次:将内核缓冲区的数据,copy到application应用程序的buffer;
第三步:将application应用程序buffer中的数据,copy到socket网络发送缓冲区(属于操作系统内核的缓冲区);
第四次:将socket buffer的数据,copy到网卡,由网卡进行网络传输。
Kafka的数据并不是实时的写入硬盘,它充分利用了现代操作系统分页存储来利用内存提高I/O效率。
mmap:Memory Mapped Files
简单描述其作用就是:将磁盘文件映射到内存, 用户通过修改内存就能修改磁盘文件。
它的工作原理是直接利用操作系统的Page来实现文件到物理内存的直接映射。完成映射之后你对物理内存的操作会被同步到硬盘上(操作系统在适当的时候)。
Linux 2.4+ 内核通过 sendfile 系统调用,提供了零拷贝。磁盘数据通过 DMA 拷贝到内核态 Buffer 后,直接通过 DMA 拷贝到 NIC Buffer(socket buffer),无需 CPU 拷贝。
Java NIO对sendfile的支持就是FileChannel.transferTo()/transferFrom()。
fileChannel.transferTo( position, count, socketChannel)
把磁盘文件读取OS内核缓冲区后的fileChannel,直接转给socketChannel发送;底层就是sendfile。消费者从broker读取数据,就是由此实现。
Producer生产的数据持久化到broker,采用mmap文件映射,实现顺序的快速写入;
Customer从broker读取数据,采用sendfile,将磁盘文件读到OS内核缓冲区后,直接转到socket buffer进行网络发送。
mmap 和 sendfile总结:
1、都是Linux内核提供、实现零拷贝的API;
2、sendfile 是将读到内核空间的数据,转到socket buffer,进行网络发送;
3、mmap将磁盘文件映射到内存,支持读和写,对内存的操作会反映在磁盘文件上。
三、日志分段及消息查找
.index 偏移量索引文件
.log 日志文件
.snapshot 日志快照
.timeindex 时间戳索引文件
leader-epoch-checkpoint 用于副本同步的检查点文件
每个文件的命名是有固定的格式的,文件名长度20位,以该日志中的第一条消息的offset值命名,不够的补0,因此00000000000000000038.log中第一条消息的偏移量为38。
a.日志分段
日志段有当前日志段和过往日志段。Kafka在进行日志分段时,会开辟一个新的文件。触发日志分段主要有以下条件:
当前日志段日志文件大小超过了log.segment.bytes配置的大小
当前日志段中消息的最大时间戳与系统的时间戳差值超过了log.roll.ms配置的毫秒值
当前日志段中消息的最大时间戳与当前系统的时间戳差值超过log.roll.hours配置的小时值,优先级比log.roll.ms低
当前日志段中索引文件与时间戳索引文件超过了log.index.size.max.bytes配置的大小
追加的消息的偏移量与当前日志段中的之间的偏移量差值大于Interger.MAX_VALUE,意思就是因为要追加的消息偏移量不能转换为相对偏移量。原因在于在偏移量索引文件中,消息基于baseoffset的偏移量使用4个字节来表示。
索引文件在做分段的时候首先会固定好索引文件的大小(log.index.size.max.bytes),在新的分段的时候对前一个分段的索引文件进行裁剪,文件的大小才代表实际的数据大小。
b.消息查找
offset查找(.index)
:偏移量索引文件由4字节的相对位移(offset)和4字节的物理地址(postion)组成。
Kafka内部维护了一个ConcurrentSkipListMap来保存在每个日志分段,通过跳跃表方式,定位到具体的日志偏移量索引文件,然后在此文件中,根据二分法来查找不大于需要查找的offset对应的postion,然后在日志文件中从postion处往后遍历,找到offset等于要查找的offset对应的消息。
时间戳查找(.timeindex)
:时间戳索引文件是由8字节的时间戳和4自己的相对偏移量组成。
时间戳查找的时候首先拿要查找的时间戳和每个时间戳索引文件的最后一条记录进行比较,如果最后一条记录的时间戳小于等于0,就和文件修改时间比较,找到不小于查找时间戳的时间索引文件。找到对应的日志段时间戳索引文件以后,二分法查找不大于查找时间戳的offset,再根据此offset进行偏移量文件查找。
偏移量索引文件offset是递增的,但在时间戳索引文件时间戳不是递增的,除非broker段将log.message.timestamp.type参数设置为LogAppendTime,时间戳可以保持单调增长。因为在新版的Kafka Producer中允许客户端设置时间戳,如果log.message.timestamp.type参数的设置为CreateTime,就会造成实际戳索引文件中的乱序。
四、批处理
- 写入时批量提交,减少io次数
五、数据压缩:gzip,snappy,lz4
-
生产者数据压缩,节省网络带宽和Kafka存储成本
-
生产者生成的所有数据的压缩类型,此配置接受标准压缩编解码器(‘gzip’,‘snappy’,‘lz4’)
#生产者生成的所有数据的压缩类型,此配置接受标准压缩编解码器(‘gzip’,‘snappy’,‘lz4’),
#默认值为producer,推荐使用 lz4
#默认情况下消息是不压缩的,此参数可指定采用何种算法压缩消息,可取值:none,snappy,gzip,lz4。#snappy压缩算法由Google研发,这种算法在性能和压缩比取得比较好的平衡;
#相比之下,gzip消耗更多的CPU资源,但是压缩效果也是最好的。通过使用压缩,我们可以节省网络带宽和Kafka存储成本。spring.kafka.producer.compression-type=lz4
PS:如果有写错或者写的不好的地方,欢迎各位大佬在评论区留下宝贵的意见或者建议,敬上!如果这篇博客对您有帮助,希望您可以顺手帮我点个赞!不胜感谢!
原创作者:wsjslient |