问题:
每个分区有一个为leader,其他都为follower,leader处理partition的所有读写请求,与此同时,follower会被动定期地去复制leader上的数据。 性能受最后一个同步数据的分区界节点决定。
kafka中的副本机制是以分区粒度进行复制的,我们在kafka中创建 topic的时候,都可以设置一个复制因子,这个复制因子决定着分区副本的个数,如果leader 挂掉了,kafka 会把分区主节点failover到其他副本节点,这样就能保证这个分区的消息是可用的。
如果某个分区的Leader挂了,那么其它跟随者将会进行选举产生一个新的leader,之后所有的读写就会转移到这个新的Leader上,在kafka中会在Zookeeper上针对每个Topic维护一个称为ISR(in-sync replica,已同步的副本)的集合,显然还有一些副本没有来得及同步。只有这个ISR列表里面的才有资格成为leader(先使用ISR里面的第一个,如果不行依次类推,因为ISR里面的是同步副本,消息是最完整且各个节点都是一样的)。
每一个partiton是一个目录,一个目录里面被分成多个segment(段)数据文件,segment数据文件由2大部分组成,分别为index file和data file,此2个文件一一对应,成对出现,后缀”.index”和“.log”分别表示为segment索引文件、数据文件.
例如读取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为止。
Kafka中可以将Topic从物理上划分成一个或多个分区(Partition),每个分区在物理上对应一个文件夹,以”topicName_partitionIndex”的命名方式命名,该文件夹下存储这个分区的所有消息(.log)和索引文件(.index),这使得Kafka的吞吐率可以水平扩展。
创建分区:在创建Toptic的时候可以指定分区数量,一般设置成节点的整数倍。
生产消息:根据key和不同的分区策略将数据存到不同的分区。
好处: 可以水平扩展容量。
顺序性:每一个分区的数据是顺序存储的,消费者在消费的时候由于一个toptic可能存在不同的分区上导致最终的数据可能不是有序的。
同一个toptic可以有多个消费者消费,当两个消费者使用同一个group.id时就是一个集群消费,当不是一个group.id时每一个消费者都可以读取该toptic的全部消息。
高吞吐是kafka需要实现的核心目标之一,为此kafka做了以下一些设计:
先了解 内核态、用户态:
从宏观上来看,Linux操作系统的体系架构分为用户态和内核态(或者用户空间和内核)。内核从本质上看是一种软件——控制计算机的硬件资源,并提供上层应用程序运行的环境。用户态即上层应用程序的活动空间,应用程序的执行必须依托于内核提供的资源,包括CPU资源、存储资源、I/O资源等。为了使上层应用能够访问到这些资源,内核必须为上层应用提供访问的接口:即系统调用。
传统的网络数据传输 要经过多次的内核态到用户态 以及数据的拷贝操作,
zero-copy直接从内核缓冲区把数据传输到socket关联的缓冲区来替代传统的方式。
直接通过下面的方法进行channel到channel的数据传输。是直接在内核态进行的,避免拷贝数据导致的内核态和用户态的多次切换。
采用sendfile系统调用之后,数据直接在内核态交换,系统上下文切换减少为2次。根据测试结果,可以提高60%的数据发送性能。
sendfile API完成的。它支持将字节从套接口转移到磁盘,通过内核空间保存副本,并在内核用户之间调用内核。
每一个Partition其实都是一个文件 ,收到消息后Kafka会把数据插入到文件末尾(虚框部分)。
这种方法有一个缺陷—— 没有办法删除数据 ,所以Kafka是不会删除数据的,它会把所有的数据都保留下来,每个消费者(Consumer)对每个Topic都有一个offset用来表示读取到了第几条数据 。
如果不删除硬盘肯定会被撑满,所以Kakfa提供了两种策略来删除数据。一是基于时间,二是基于partition文件大小。具体配置可以参看它的配置文档。
通过mmap,进程像读写硬盘一样读写内存(当然是虚拟机内存),
解除虚拟空间和内存空间的映射,这也是一种读写磁盘文件的方法,也是一种进程共享数据的方法 共享内存
mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。
传统模式下我们从硬盘读取一个文件是这样的,
read先复制到内核空间(read是系统调用,所以用内核空间),然后复制到用户空间(1,2);
send从用户空间重新复制到内核空间(你用的socket是系统调用,所以它也有自己的内核空间),最后发送给网卡(3、4).
Zero Copy中直接从内核空间(DMA的)到内核空间(Socket的),然后发送网卡。
Zero-Copy&Sendfile()Linux 2.1版本内核引入了sendfile函数,用于将文件通过socket传送。sendfile(socket, file, len);该函数通过一次系统调用完成了文件的传送,减少了原来 read/write方式的模式切换。此外更是减少了数据的copy,sendfile的详细过程
通过sendfile传送文件只需要一次系统调用,当调用 sendfile时:1。首先通过DMA copy将数据从磁盘读取到kernel buffer中2。然后通过CPU copy将数据从kernel buffer copy到sokcet buffer中3。最终通过DMA copy将socket buffer中数据copy到网卡buffer中发送sendfile与read/write方式相比,少了 一次模式切换一次CPU copy。但是从上述过程中也可以发现从kernel buffer中将数据copy到socket buffer是没必要的。
总结
Kafka速度的秘诀在于,它把所有的消息都变成一个的文件。通过mmap提高I/O速度,写入数据的时候它是末尾添加所以速度最优;读取数据的时候配合sendfile直接暴力输出
https://www.jianshu.com/p/cf093ccdaea2
kafka 常见问题:
https://blog.csdn.net/yjh314/article/details/77568580
https://blog.csdn.net/caisini_vc/article/details/48007297
https://blog.csdn.net/u013920292/article/details/78815161#commentBox
http://trumandu.github.io/2019/04/13/Kafka%E9%9D%A2%E8%AF%95%E9%A2%98%E4%B8%8E%E7%AD%94%E6%A1%88%E5%85%A8%E5%A5%97%E6%95%B4%E7%90%86/
https://blog.csdn.net/u012562943/article/details/76128183
https://juejin.im/post/5b970f1c5188255c865e00e7
https://binchencoder.github.io/2019/08/28/kafka%E6%98%AF%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E5%87%A0%E5%8D%81%E4%B8%87%E7%9A%84%E9%AB%98%E5%B9%B6%E5%8F%91%E5%86%99%E5%85%A5/?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io
http://kafka.apachecn.org/documentation.html#persistence