4.Kafka消息处理
更多干货
- 分布式实战(干货)
- spring cloud 实战(干货)
- mybatis 实战(干货)
- spring boot 实战(干货)
- React 入门实战(干货)
- 构建中小型互联网企业架构(干货)
- python 学习持续更新
- ElasticSearch 笔记
概述
- Kafka消息组织原理
- Kafka消息检索原理
一、磁盘重认识
1、当需要从磁盘读取数据时,要确定读的数据在哪个磁道,哪个扇区:
首先必须找到柱面,即磁头需要移动对准相应磁道,这个过程叫做寻道,所耗费时间叫做寻道时间;
然后目标扇区旋转到磁头下,这个过程耗费的时间叫做旋转时间;
2、一次访盘请求(读/写)完成过程由三个动作组成
- 寻道(时间):磁头移动定位到指定磁道;
- 旋转延迟(时间):等待指定扇区从磁头下旋转经过;
- 数据传输(时间):数据在磁盘、内存与网络之间的实际传输
由于存储介质的特性,磁盘本身存取就比主存慢,再加上机械运动耗费,磁盘的存取速度往往是主存的几百分之一甚至几千分支一
3、怎么样才能提高磁盘的读写效率呢
根据数据的局部性原理 ,有以下两种方法
预读或者提前读;
合并写——多个逻辑上的写操作合并成一个大的物理写操作中;
即采用磁盘顺序读写(不需要寻道时间,只需很少的旋转时间)。实验结果:在一个6 7200rpm SATA RAID-5 的磁盘阵列上线性写的速度大概是300M/秒,但是随机写的速度只有50K/秒,两者相差将近10000倍。
二、Kafka消息的写入原理
Kafka topic信息
Kafka 消息文件存储
Kafka消息删除原理
从最久的日志段开始删除(按日志段为单位进行删除),然后逐步向前推进,直到某个日志段不满足条件为止,删除条件:
- 满足给定条件predicate(配置项log.retention.{ms,minutes,hours}和log.retention.bytes指定);
- 不能是当前激活日志段;
- 大小不能小于日志段的最小大小(配置项log.segment.bytes配置)要删除的是否是所有日志段,如果是的话直接调用roll方法进行切分,因为Kafka至少要保留一个日志段;
recovery-point-offset-checkpoint:表示已经刷写到磁盘的记录
flush刷盘机制
熟悉Linux操作系统原理的都知道,当我们把数据写入到文件系统之后,数据其实在操作系统的page cache里面,并没有刷到磁盘上去。如果此时操作系统挂了,其实数据就丢了。
一方面,应用程序可以调用fsync这个系统调用来强制刷盘;另一方面,操作系统有后台线程,定期刷盘。
如果应用程序每写入1次数据,都调用一次fsync,那性能损耗就很大,所以一般都会在性能和可靠性之间进行权衡。因为对应一个应用来说,虽然应用挂了,只要操作系统不挂,数据就不会丢。
另外, kafka是多副本的,当你配置了同步复制之后。多个副本的数据都在page cache里面,出现多个副本同时挂掉的概率比1个副本挂掉,概率就小很多了。
对于kafka来说,也提供了相关的配置参数,来让你在性能与可靠性之间权衡:
log.flush.interval.messages //多少条消息,刷盘1次
log.flush.interval.ms //割多长时间,刷盘1次
log.flush.scheduler.interval.ms //周期性的刷盘,缺省3000,即3s。
默认前2个参数都没有设置,也就是只第3个参数起作用,即3s钟刷盘1次。
三、Kafka消息的segment file的组成和物理结构
Kafka消息检索过程
以读取offset=368776的message为例,需要通过下面2个步骤查找:
- 第一步查找segment file;
以上图为例,其中00000000000000000000.index表示最开始的文件,起始偏移量(offset)为0.第二个文件00000000000000368769.index的消息量起始偏移量为368770 = 368769 + 1。只要根据offset二分查找文件列表,就可以快速定位到具体文件。当offset=368776时定位到00000000000000368769.index|log
- 第二步通过segment file查找message;
算出368776-368770=6,取00000000000000368769.index文件第三项(6,1407),得出从00000000000000368769.log文件头偏移1407字节读取一条消息即可
每个topic_partition对应一个目录
假设有一个topic叫my_topic,3个partition,分别为my_topic_0, my_topic_1, my_topic_2。其中前2个parition存在1台机器A,另外一个paritition存在另1台机器B上(这里A, B都是指对应partition的leader机器)。
则机器A上,就有2个对应的目录my_topic_0, my_topic_1,也就是每个目录对应一个topic_partition,目录名字就是topic_partition名字。
而目录中,日志文件按照大小或者时间回滚,文件名字为00000000.kafka, 1200000.kakfa。。。
文件名字是上1个log文件中,最后1条消息的offset。这里的1200000.kafka,就是说,在这个文件之前,有1200000条消息。
同时,Kafka中有一个配置参数
所有的topic_partition目录也就存放在这个里面。
文件offset作为Message ID
正如上面所说,kafka没有额外用类似uuid的方式,为每条消息生成一个唯一的message id,而是直接用该message在文件中的offset,作为该message的id。
这里有一个关键点:这里的offset并不是消息在文件中的物理位置,而是一个顺序递增的逻辑编号。从0开始,每append一条消息,offset加1。
index文件
上面说了,每个消息通过一个顺序递增的逻辑编号offset作为其message Id。但消息都是变长的,那怎么通过其offset找到消息所在的文件位置呢?
比如要找offset = 95的消息,最朴素的办法就是:按顺序扫描所有文件,从第0条记录开始读起,然后读到offset = 95 所在的消息位置。但这个办法效率太低。
Kafka引入了index文件。每1个.kakfa文件,都有一个相对应的.index文件,2者文件名字完全一样
具体根据offset查找文件位置,分为3步:
- 第1步:把所有.kakfa的文件名字排序,通过2分查找,得到所在的.kakfa文件
- 第2步:到对应的.index文件里面,再2分查找,找到对应的条目,也就是offset到position的映射。
- 第3步:拿到这个position,就直接定位到.kakfa文件中相应的位置。然后从拿个位置开始,顺序扫描,就能得到实际所在position。
这里.index文件的目的,就是为了加快查找速度。即使没有这个文件,第1步拿到了.kakfa文件,你从头扫描,也能找到位置。
所以这个的.index存的是一个稀疏索引,每隔一个范围,存储1条消息的(offset, position)的对应关系。
变长消息存储
我们知道,kafka的消息是变长的。对于变长记录的存储,一般都是在记录的最前面,用固定的几个字节(比如4个字节),来存储记录的长度。
读的时候,先读取这固定的4个字节,获得记录长度,再根据长度读取后面内容。
多线程写同一个Log文件
前面说到,网络模型是1+N+M,也就意味着M个worker线程可能写同1个log文件