【Kafka】第四篇-Kafka为什么这么快?

Kafka消息的文件剖析

通过前面的学习,我们已经知道一个topic下可能有多个partition(至少有一个partition),消息是发送到partition上的,partition是存储在物理磁盘上的,其保存的路径是server.properties文件中配置的路径(默认是/tmp/kafka-logs),可以修改:

# A comma separated list of directories under which to store log files
log.dirs=/usr/local/kafka_2.13-2.5.0/logs/kafka-logs

我们剖析一下这几个文件;
在这里插入图片描述

  • kafka通过分段的方式将Log分为多个LogSegment,LogSegment不是物理存在的而是一个逻辑上的概念,一个 LogSegment对应磁盘上的一个日志文件和一个索引文件,其中日志文件存储消息,索引文件存储消息的索引;

  • 当kafka producer不断发送消息,就会引起partition文件的无限膨胀变成一个巨大的消息文件,不利于消息文件的维护以及消息的消费,所以kafka以 segment为单位把partition进行拆分,将每个巨型partition文件平均分配到多个大小默认是1G的segment数据文件中(每个segment文件中的消息不一定相等),这样方便消息文件的维护以及消息的消费,提高磁盘的利用率;
    Segment文件的大小可以设置,默认是1G,当我们把这个值调小以后,可以看到partition消息日志文件被拆分成多个文件的效果:

# The maximum size of a log segment file. When this size is reached a new log segment will be created.
log.segment.bytes=1073741824
  • Segment文件由两大部分构成,分别为index file 和log file,这两个文件成对出现,后缀".index"和".log"分别表示segment索引文件和segment数据文件;
    第一个segment文件名从0开始,后续每个segment文件名为上一个segment文件最后一条消息的offset值进行递增,数值为20位数字字符长度,没有数字时用0填充;
    通过如下命令查看消息日志内容:
./kafka-run-class.sh kafka.tools.DumpLogSegments --files /usr/local/kafka_2.13-2.5.0/logs/kafka-logs/test-0/00000000000000000000.log --print-data-log

segment中index和log的对应关系

为了提高查找消息的性能,每一个日志文件添加两个索引索引文件:OffsetIndex 和TimeIndex,分别对应 *.index以及 *.timeindex;
查看索引内容 :

./kafka-run-class.sh kafka.tools.DumpLogSegments --files /usr/local/kafka_2.13-2.5.0/logs/kafka-logs/test-0/00000000000000000000.index  --print-data-log
  • Index文件中存储了消息的索引以及物理偏移量,log文件存储了消息的内容,index文件的元数据对应log文件中message的物理偏移地址;
    比如图index文件中元数据3,497,依次在log文件中表示第3个message(在全局partiton表示第368772个message)以及该消息的物理偏移地址为497;

在partition中如何通过offset查找message

比如读取offset=368776的message,需要通过下面两个步骤查找;

  • 第一步查找index文件,其中00000000000000000000.index表示最开始的文件,起始偏移量(offset)为0,第二个文件00000000000000368769.index的消息量起始偏移量为368770=368769 + 1,第三个文件00000000000000737337.index的起始偏移量为737338=737337 + 1,其他后续文件依次类推,以起始偏移量命名并排序这些文件,根据offset 二分查找文件列表,就可以快速定位到具体index文件,当offset=368776时定位到00000000000000368769.index|log;
  • 第二步通过第一步定位到的index file,当offset=368776时(相当于文件368769+7),依次定位到00000000000000368769.index的元数据7,但7不存在,则定位到?(6,1407),然后定位到00000000000000368769.log的物理偏移地址1407,该1407的offset是368775,然后再通过00000000000000368769.log顺序往下查找直到offset=368776为止;

消息日志清除策略

日志的清理策略有两种:

  1. 根据消息的保留时间,当消息在 kafka 中保存的时间超过了指定的时间,就会触发清理过程;
  2. 根据topic存储的数据大小,当topic所占的日志文件大小大于一定的阀值,则可以开始删除最旧的消息,kafka会启动一个后台线程,定期检查是否存在可以删除的消息;
    通过log.retention.bytes和log.retention.hours这两个参数来设置,当其中任意一个达到要求,都会执行删除,默认的保留时间是7天;

消息日志压缩策略

Kafka提供了“日志压缩(Log Compaction)”功能,通过该功能可以有效减少日志文件的大小,缓解磁盘紧张的情况,比如有一些重复的key,服务端会在后台启动Cleaner 线程池,定期将相同的key进行合并,只保留最新的value值;
在这里插入图片描述

副本分配及数据同步

如何知道各个分区中对应的 leader 是谁呢?
可以查看zookeeper的节点数据:

/brokers/topics/topicName/partitions/0/stat
/brokers/topics/topicName/partitions/1/stat
/brokers/topics/topicName/partitions/2/stat

state值表示当前分区的 leader 是哪个broker-id;

kafka副本数据同步

既然有副本机制,就一定涉及到数据同步,那么数据是如何同步的?
Kafka分区下有可能有很多个副本(replica),从而进一步实现高可用,副本根据角色的不同可分为3类:

leader副本:

  • 响应clients端读写请求的副本;

follower副本:

  • 备份leader副本中的数据;

ISR副本:

  • 包含了leader副本和所有与leader副本保持同步的follower副本;
    写请求首先由 Leader 副本处理,之后 follower 副本会从 leader 上拉取写入的消息,这个过程会有一定的延迟,导致 follower 副本中保存的消息略少于 leader 副本,但是只要没有超出阈值都可以容忍,但是如果一个 follower 副本出现异常,比如宕机、网络断开等原因长时间没有同步到消息,那这个时候,leader就会把它踢出去,kafka 通过ISR集合来维护一个分区副本信息;
    所以ISR 表示目前“可用且消息量与leader相差不多的副本集合,它是整个副本集合的一个子集”,
ISR 集合中的副本必须满足两个条件:
  1. 副本所在节点必须维持着与zookeeper的连接;
  2. 副本最后一条消息的offset与leader副本的最后一条消息的offset之间的 差值不能超过指定的阈值 (replica.lag.time.max.ms),默认是10秒;
    replica.lag.time.max.ms:如果该follower在此时间间隔内一直没有追上过 leader的所有消息,则该follower就会被剔除ISR列表;

Kafka 副本同步涉及到两个重要的属性:LEO 和 HW;

LEO:
  • 即日志末端位移(log end offset),记录该副本底层日志(log)中下一条消息的位移值,注意是下一条消息,也就是说,如果LEO=10,那么表示该副本保存了位移值范围是[0, 9]的10条消息;
HW:
  • (High Water mark),即高水位值,对于同一个副本对象而言,其HW值不会大于LEO值,小于等于HW值的所有消息都被认为是“已备份”的(replicated);
副本数据的同步

- HW(High Watermark)
俗称高水位,它标识了一个特定的消息偏移量(offset),消费者只能拉取到这个offset之前的消息;
下图表示一个日志文件,这个日志文件中只有9条消息,第一条消息的offset(LogStartOffset)为0,最后一条消息的offset为8,offset为9的消息使用虚线表示的,代表下一条待写入的消息,日志文件的HW为6,表示消费者只能拉取offset在 0 到 5 之间的消息,offset为6的消息对消费者而言是不可见的;
在这里插入图片描述

- LEO?(Log End Offset)

  • 标识当前日志文件中下一条待写入的消息的offset,上图中offset为9的位置即为当前日志文件的LEO,LEO 的大小相当于当前日志分区中最后一条消息的offset值加1,分区 ISR 集合中的每个副本都会维护自身的 LEO ,而 ISR 集合中最小的 LEO 即为分区的 HW,对消费者而言只能消费HW之前的消息;
ISR、HW、LEO的关系

假设某分区的 ISR 集合中有 3 个副本,即一个 leader 副本和 2 个 follower 副本,此时分区的 LEO 和 HW 都分别为 3,消息3和消息4从生产者出发之后先被存入leader副本;
在这里插入图片描述
在这里插入图片描述

在消息被写入leader副本之后,follower副本会发送拉取请求来拉取消息3和消息4进行消息同步;
如下图:在同步过程中不同的副本同步的效率不尽相同,在某一时刻follower1完全跟上了leader副本而follower2只同步了消息3,如此leader副本的LEO为5,follower1的LEO为5,follower2的LEO为4,那么当前分区的HW取最小值4,此时消费者可以消费到offset 0至3之间的消息;
在这里插入图片描述

当所有副本都成功写入消息3和消息4之后,整个分区的HW和LEO都变为5,因此消费者可以消费到offset为4的消息了;
在这里插入图片描述

  • 由此可见kafka的复制机制既不是完全的同步复制,也不是单纯的异步复制,事实上,同步复制要求所有能工作的follower副本都复制完,这条消息才会被确认已成功提交,这种复制方式极大的影响了性能,而异步复制方式下,follower副本异步的从leader副本中复制数据,数据只要被leader副本写入就会被认为已经成功提交,在这种情况下,如果follower副本都还没有复制完而落后于leader副本,然后leader副本宕机,则会造成数据丢失;
  • Kafka中每个replica都有HW,leader和follower各自维护更新自己的HW的状态,一条消息只有被ISR里的所有Follower都从Leader复制过去才会被认为已提交,这样就避免了部分数据被写进了Leader,还没来得及被任何 Follower复制就宕机了,而从造成数据丢失(Consumer 无法消费这些数据),而对于Producer而言,它可以选择是否等待消息commit,这可以通过acks来设置,这种机制确保了消息都同步到了ISR,这样使得一条被commit的消息就不会丢失;
  • **kafka权衡了同步和异步的两种策略,采用ISR集合巧妙解决两种方案的缺陷:**当follower副本延迟过高,leader副本则会把该follower副本踢出ISR集合,消息依然可以快速提交,当leader副本所在的broker突然宕机,会优先将 ISR集合中follower副本选举为leader,新leader副本包含了HW之前的全部消息,这样就避免了消息的丢失;

Kafka为什么这么快?

  • 首先 Kafka 每次接收到数据都会往磁盘上去写,那么频繁的往磁盘文件里写数据,在我们的印象中都会觉得写磁盘性能很差;
  • 一般情况下向磁盘写数据确实性能会很差,但是Kafka有极为优秀和出色的设计,就是为了保证数据写入性能;
  • Kafka是以磁盘顺序写的方式来写数据,也就是说仅仅将数据追加到文件的末尾,不是在文件的随机位置来写数据;
  • 普通的机械磁盘如果随机写的话,也就是随便找到文件的某个位置来写数据,确实性能很差,但是如果是追加文件末尾按照顺序的方式来写数据的话,那么这种磁盘顺序写的性能基本上可以与写内存的性能差不多;
  • 假如写入一条数据仅仅耗费0.01毫秒,那么每秒就可以写入10万条数据,所以要保证每秒写入几万甚至几十万条数据的核心点就是尽最大可能提升每条数据写入的性能,这样就可以在单位时间内写入更多的数据量,提升吞吐量;
  • 平时使用Java访问文件既可以是顺序的也可以是随机的,取决于使用的Java API,比如RandomAccessFile和FileInputStream这样的stream类就是两种不同的方式,对Kafka而言,它使用了基于日志结构(log-structured)的数据格式,即每个分区日志只能在尾部追加写入(append),而不允许随机“跳到”某个位置开始写入,故此实现了顺序写入;

Kafka零拷贝技术

  • 前面是消息写入,采用顺序写提升了性能,那么消息消费,kafka也有优秀的设计;
  • 从Kafka里要消费数据,消费的时候实际上就是从Kafka的磁盘文件里读取某条数据然后发送给消费者,kafka中的消费者在读取服务端的数据时,需要将服务端的磁盘文件通过网络读取到消费者进程,网络读取需要经过几种网络节点;
    在这里插入图片描述
传统的通过网络读取文件数据的步骤如下:
  • (1)操作系统将数据从磁盘文件中读取到内核空间的页面缓存;
  • (2)应用程序将数据从内核空间读入用户空间缓冲区;
  • (3)应用程序将读到数据写回内核空间并放入socket缓冲区;
  • (4)操作系统将数据从socket缓冲区复制到网卡接口,此时数据才能通过网络发送;
Kafka优化了这个流程,Kafk使用了“零拷贝技术”:

在这里插入图片描述

零拷贝技术
  • “零拷贝技术”只需将磁盘文件的数据复制到页面缓存中一次,然后将数据从页面缓存直接发送到网络中,避免了重复复制操作;
  • 如果有20个消费者,传统方式下,数据复制次数为4*20=80次,而使用“零拷贝技术”只需要1+20=21次,一次为从磁盘复制到页面缓存,20次表示20个消费者各自读取一次页面缓存;
- 副本分配算法
将全部N Broker和待分配的i个Partition排序.
将第i个Partition分配到第(i mod n)个Broker上.
将第i个Partition的第j个副本分配到第((i + j) mod n)个Broker上.

3个broker  3个patrition  3个副本
第1个partition --> 1 % 3 = 1;  1+1 % 3 = 2   1+2 % 3 = 0
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值