• kafka之六:为什么Kafka那么快


    转自:  http://mp.weixin.qq.com/s?__biz=MzIxMjAzMDA1MQ==&mid=2648945468&idx=1&sn=b622788361b384e152080b60e5ea69a7#rd

      网上有很多Kafka的测试文章,测试结果通常都是“吊打”其他MQ。感慨它的牛B之余我觉得必要仔细分析一下它如此快速的原因。这篇文章不同于其他介绍Kafka使用或者技术实现的文章,我会重点解释——为什么真快。(当然不是因为它用了Scala!!!!)

    生产者(写入数据)

    生产者(producer)是负责向Kafka提交数据的,我们先分析这一部分。

    Kafka会把收到的消息都写入到硬盘中,它绝对不会丢失数据。为了优化写入速度Kafak采用了两个技术, 顺序写入 和 MMFile 。

    顺序写入

    因为硬盘是机械结构,每次读写都会寻址->写入,其中寻址是一个“机械动作”,它是最耗时的。所以硬盘最“讨厌”随机I/O,最喜欢顺序I/O。为了提高读写硬盘的速度,Kafka就是使用顺序I/O。

    上图就展示了Kafka是如何写入数据的, 每一个Partition其实都是一个文件 ,收到消息后Kafka会把数据插入到文件末尾(虚框部分)。

    这种方法有一个缺陷—— 没有办法删除数据 ,所以Kafka是不会删除数据的,它会把所有的数据都保留下来,每个消费者(Consumer)对每个Topic都有一个offset用来表示 读取到了第几条数据 。

    上图中有两个消费者,Consumer1有两个offset分别对应Partition0、Partition1(假设每一个Topic一个Partition);Consumer2有一个offset对应Partition2。这个offset是由客户端SDK负责保存的,Kafka的Broker完全无视这个东西的存在;一般情况下SDK会把它保存到zookeeper里面。(所以需要给Consumer提供zookeeper的地址)。

    如果不删除硬盘肯定会被撑满,所以Kakfa提供了两种策略来删除数据。一是基于时间,二是基于partition文件大小。具体配置可以参看它的配置文档。

    Memory Mapped Files

    即便是顺序写入硬盘,硬盘的访问速度还是不可能追上内存。所以Kafka的数据并 不是实时的写入硬盘 ,它充分利用了现代操作系统 分页存储 来利用内存提高I/O效率。

    Memory Mapped Files(后面简称mmap)也被翻译成 内存映射文件 ,在64位操作系统中一般可以表示20G的数据文件,它的工作原理是直接利用操作系统的Page来实现文件到物理内存的直接映射。完成映射之后你对物理内存的操作会被同步到硬盘上(操作系统在适当的时候)。

    通过mmap,进程像读写硬盘一样读写内存(当然是虚拟机内存),也不必关心内存的大小有虚拟内存为我们兜底。

    使用这种方式可以获取很大的I/O提升, 省去了用户空间到内核空间 复制的开销(调用文件的read会把数据先放到内核空间的内存中,然后再复制到用户空间的内存中。)也有一个很明显的缺陷——不可靠, 写到mmap中的数据并没有被真正的写到硬盘,操作系统会在程序主动调用flush的时候才把数据真正的写到硬盘。 Kafka提供了一个参数——producer.type来控制是不是主动flush,如果Kafka写入到mmap之后就立即flush然后再返回Producer叫 同步 (sync);写入mmap之后立即返回Producer不调用flush叫 异步 (async)。

    mmap其实是Linux中的一个函数就是用来实现内存映射的,谢谢Java NIO,它给我提供了一个mappedbytebuffer类可以用来实现内存映射(所以是沾了Java的光才可以如此神速和Scala没关系!!)

    消费者(读取数据)

    Kafka使用磁盘文件还想快速?这是我看到Kafka之后的第一个疑问,ZeroMQ完全没有任何服务器节点,也不会使用硬盘,按照道理说它应该比Kafka快。可是实际测试下来它的速度还是被Kafka“吊打”。 “一个用硬盘的比用内存的快”,这绝对违反常识;如果这种事情发生说明——它作弊了。

    没错,Kafka“作弊”。无论是 顺序写入 还是 mmap 其实都是作弊的准备工作。

    如何提高Web Server静态文件的速度

    仔细想一下,一个Web Server传送一个静态文件,如何优化?答案是zero copy。传统模式下我们从硬盘读取一个文件是这样的

    先复制到内核空间(read是系统调用,放到了DMA,所以用内核空间),然后复制到用户空间(1,2);从用户空间重新复制到内核空间(你用的socket是系统调用,所以它也有自己的内核空间),最后发送给网卡(3、4)。

    Zero Copy中直接从内核空间(DMA的)到内核空间(Socket的),然后发送网卡。

    这个技术非常普遍,The C10K problem 里面也有很详细的介绍,Nginx也是用的这种技术,稍微搜一下就能找到很多资料。

    Java的NIO提供了FileChannle,它的transferTo、transferFrom方法就是Zero Copy。

    Kafka是如何耍赖的

    想到了吗?Kafka把所有的消息都存放在一个一个的文件中, 当消费者需要数据的时候Kafka直接把“文件”发送给消费者 。这就是秘诀所在,比如: 10W的消息组合在一起是10MB的数据量,然后Kafka用类似于发文件的方式直接扔出去了,如果消费者和生产者之间的网络非常好(只要网络稍微正常一点10MB根本不是事。。。家里上网都是100Mbps的带宽了),10MB可能只需要1s。所以答案是——10W的TPS,Kafka每秒钟处理了10W条消息。

    可能你说:不可能把整个文件发出去吧?里面还有一些不需要的消息呢?是的,Kafka作为一个“高级作弊分子”自然要把作弊做的有逼格。Zero Copy对应的是sendfile这个函数(以Linux为例),这个函数接受

    • out_fd作为输出(一般及时socket的句柄)

    • in_fd作为输入文件句柄

    • off_t表示in_fd的偏移(从哪里开始读取)

    • size_t表示读取多少个

    没错,Kafka是用mmap作为文件读写方式的,它就是一个文件句柄,所以直接把它传给sendfile;偏移也好解决,用户会自己保持这个offset,每次请求都会发送这个offset。(还记得吗?放在zookeeper中的);数据量更容易解决了,如果消费者想要更快,就全部扔给消费者。如果这样做一般情况下消费者肯定直接就被 压死了 ;所以Kafka提供了的两种方式——Push,我全部扔给你了,你死了不管我的事情;Pull,好吧你告诉我你需要多少个,我给你多少个。

    总结

    Kafka速度的秘诀在于,它把所有的消息都变成一个的文件。通过mmap提高I/O速度,写入数据的时候它是末尾添加所以速度最优;读取数据的时候配合sendfile直接暴力输出。阿里的RocketMQ也是这种模式,只不过是用Java写的。

    单纯的去测试MQ的速度没有任何意义,Kafka这种“暴力”、“流氓”、“无耻”的做法已经脱了MQ的底裤,更像是一个暴力的“数据传送器”。 所以对于一个MQ的评价只以速度论英雄,世界上没人能干的过Kafka,我们设计的时候不能听信网上的流言蜚语——“Kafka最快,大家都在用,所以我们的MQ用Kafka没错”。在这种思想的作用下,你可能根本不会关心“失败者”;而实际上可能这些“失败者”是更适合你业务的MQ。

    Kafka文件存储机制那些事

    分析过程分为以下4个步骤:

    • topic中partition存储分布
    • partiton中文件存储方式
    • partiton中segment文件存储结构
    • 在partition中如何通过offset查找message

    通过上述4过程详细分析,我们就可以清楚认识到kafka文件存储机制的奥秘。

    2.1 topic中partition存储分布

    假设实验环境中Kafka集群只有一个broker,xxx/message-folder为数据文件存储根目录,在Kafka broker中server.properties文件配置(参数log.dirs=xxx/message-folder),例如创建2个topic名称分别为report_push、launch_info, partitions数量都为partitions=4

    存储路径和目录规则为: 
    xxx/message-folder

    这里写图片描述

    在Kafka文件存储中,同一个topic下有多个不同partition,每个partition为一个目录,partiton命名规则为topic名称+有序序号,第一个partiton序号从0开始,序号最大值为partitions数量减1。

    2.2 partiton中文件存储方式

    下面示意图形象说明了partition中文件存储方式: 
    这里写图片描述

    • 每个partion(目录)相当于一个巨型文件被平均分配到多个大小相等segment(段)数据文件中。但每个段segment file消息数量不一定相等,这种特性方便old segment file快速被删除。
    • 每个partiton只需要支持顺序读写就行了,segment文件生命周期由服务端配置参数决定。

    这样做的好处就是能快速删除无用文件,有效提高磁盘利用率。

    2.3 partiton中segment文件存储结构

    读者从2.2节了解到Kafka文件系统partition存储方式,本节深入分析partion中segment file组成和物理结构。

    • segment file组成:由2大部分组成,分别为index file和data file,此2个文件一一对应,成对出现,后缀”.index”和“.log”分别表示为segment索引文件、数据文件.
    • segment文件命名规则:partion全局的第一个segment从0开始,后续每个segment文件名为上一个segment文件最后一条消息的offset值。数值最大为64位long大小,19位数字字符长度,没有数字用0填充。

    下面文件列表是笔者在Kafka broker上做的一个实验,创建一个topicXXX包含1 partition,设置每个segment大小为500MB,并启动producer向Kafka broker写入大量数据,如下图2所示segment文件列表形象说明了上述2个规则:

    这里写图片描述

    以上述图2中一对segment file文件为例,说明segment中index<—->data file对应关系物理结构如下:

    这里写图片描述

    上述图3中索引文件存储大量元数据,数据文件存储大量消息,索引文件中元数据指向对应数据文件中message的物理偏移地址。

    其中以索引文件中元数据3,497为例,依次在数据文件中表示第3个message(在全局partiton表示第368772个message)、以及该消息的物理偏移地址为497。

    从上述图3了解到segment data file由许多message组成,下面详细说明message物理结构如下:

    这里写图片描述

    参数说明

    关键字解释说明
    8 byte offset 在parition(分区)内的每条消息都有一个有序的id号,这个id号被称为偏移(offset),它可以唯一确定每条消息在parition(分区)内的位置。即offset表示partiion的第多少message
    4 byte message size message大小
    4 byte CRC32 用crc32校验message
    1 byte “magic” 表示本次发布Kafka服务程序协议版本号
    1 byte “attributes” 表示为独立版本、或标识压缩类型、或编码类型。
    4 byte key length 表示key的长度,当key为-1时,K byte key字段不填
    K byte key 可选
    value bytes payload 表示实际消息数据。

    2.4 在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为止。

    从上述图3可知这样做的优点,segment index file采取稀疏索引存储方式,它减少索引文件大小,通过mmap可以直接内存操作,稀疏索引为数据文件的每个对应message设置一个元数据指针,它比稠密索引节省了更多的存储空间,但查找起来需要消耗更多的时间。

    3 Kafka文件存储机制–实际运行效果

    实验环境:

    • Kafka集群:由2台虚拟机组成
    • cpu:4核
    • 物理内存:8GB
    • 网卡:千兆网卡
    • jvm heap: 4GB

    这里写图片描述

    从上述图5可以看出,Kafka运行时很少有大量读磁盘的操作,主要是定期批量写磁盘操作,因此操作磁盘很高效。这跟Kafka文件存储中读写message的设计是息息相关的。Kafka中读写message有如下特点:

    写message:

    • 消息从java堆转入page cache(即物理内存)。
    • 由异步线程刷盘,消息从page cache刷入磁盘。

    读message

    • 消息直接从page cache转入socket发送出去。
    • 当从page cache没有找到相应数据时,此时会产生磁盘IO,从磁盘Load消息到page cache,然后直接从socket发出去

    4.总结

    Kafka高效文件存储设计特点:

      • Kafka把topic中一个parition大文件分成多个小文件段,通过多个小文件段,就容易定期清除或删除已经消费完文件,减少磁盘占用。
      • 通过索引信息可以快速定位message和确定response的最大大小。
      • 通过index元数据全部映射到memory,可以避免segment file的IO磁盘操作。
      • 通过索引文件稀疏存储,可以大幅降低index文件元数据占用空间大小。
  • 相关阅读:
    连通域标记
    qt&gdal
    gdal vs2013编译
    java配置
    windows下面安装Python和pip
    mfc operator new”: 没有重载函数接受 3 个参数
    std::min&std::max与mfc冲突问题
    qt中vtk易出现错误
    cmake构建qt工程
    Webstorm补丁
  • 原文地址:https://www.cnblogs.com/duanxz/p/4705164.html
Copyright © 2020-2023  润新知