Kafka核心思想概括
- 所有的消息以“有序日志“的方式存储,生产者将消息发布到末端(可理解为追加),消费者从某个逻辑位按序读取。
【场景一】消息中间件
在选择消息中间件时,我们的主要关注点有:性能、消息的可靠性,顺序性。
1.性能
关于Kafka的高性能,主要是因为它在实现上利用了操作系统一些底层的优化技术,尽管作为写业务代码的程序员,这些底层知识也是需要了解的。
【优化一】零拷贝 mmp sendfile pagecache
这是Kafka在消费者端的优化,我们通过两张图来比较一下传统方式与零拷贝方式的区别:
- 传统方式:
- 零拷贝方式:
-
终极目标:如何让数据不经过用户空间?
-
从图中可看出,零拷贝省略了拷贝到用户缓冲的步骤,通过文件描述符,直接从内核空间将数据复制到网卡接口。
-
并且“零拷贝技术”只用将磁盘文件的数据复制到页面缓存中一次,然后将数据从页面缓存直接发送到网络中(发送给不同的订阅者时,都可以使用同一个页面缓存),避免了重复复制操作。
-
不仅是Kafka,Java的NIO提供的FileChannle,它的transferTo、transferFrom方法也利用了这种在内核区完成数据传输的功能。
-
PageCache
-
PageCache是系统级别的缓存,它把尽可能多的空闲内存当作磁盘缓存使用来进一步提高IO效率,同时当其他进程申请内存,回收PageCache的代价也很小。
-
当上层有写操作时,操作系统只是将数据写入PageCache,同时标记Page属性为Dirty。当读操作发生时,先从PageCache中查找,如果发生缺页才进行磁盘调度,最终返回需要的数据。
-
PageCache同时可以避免在JVM内部缓存数据,避免不必要的GC、以及内存空间占用。对于In-Process Cache,如果Kafka重启,它会失效,而操作系统管理的PageCache依然可以继续使用。
-
对应到Kafka生产和消费消息中:
producer把消息发到broker后,数据并不是直接落入磁盘的,而是先进入PageCache。PageCache中的数据会被内核中的处理线程采用同步或异步的方式写回到磁盘。
Consumer消费消息时,会先从PageCache获取消息,获取不到才回去磁盘读取,并且会预读出一些相邻的块放入PageCache,以方便下一次读取
如果Kafka producer的生产速率与consumer的消费速率相差不大,那么几乎只靠对broker PageCache的读写就能完成整个生产和消费过程,磁盘访问非常少。
【优化二】顺序写入磁盘 sequenceIO
- 随机读写会导致寻址时间延长,从而影响磁盘的读写速度。
- 写入消息时,采用文件追加的方式,并且不允许修改已经写入的消息,于是写入磁盘的方式是顺序写入。我们通常认为的基于磁盘读写性能较差,指的是基于磁盘的随机读写;事实上,基于磁盘的顺序读写,性能接近于内存的随机读写,以下是性能对比图:
- Kafka在将数据持久化到磁盘时,采用只追加的顺序写,有效降低了寻址时间,提高效率。下图展示了Kafka写入数据到partition的方式:
- Kafka不会"直接"删除数据,而是把所有数据保存到磁盘,每个consumer会指定一个offset来记录自己订阅的topic的partition中消费的位置。当然我们可以设置策略来清理数据,比如通过参数log.retention.hours指定过期时间,当达到过期时间时,Kafka会清理数据。
【优化三】内存映射 mmp
- 虚拟映射只支持文件; 在进程 的非堆内存开辟一块内存空间,和OS内核空间的一块内存进行映射, kafka数据写入、是写入这块内存空间,但实际这块内存和OS内核内存有映射,也就是相当于写在内核内存空间了,且这块内核空间、内核直接能够访问到,直接落入磁盘。 这里,我们需要清楚的是:内核缓冲区的数据,flush就能完成落盘。
-
- 概括:用户空间的一段内存区域映射到内核空间,这样,无论是内核空间或用户空间对这段内存区域的修改,都可以直接映射到另一个区域。
-
- 优势:如果内核态和用户态存在大量的数据传输,效率是非常高的。
-
- 为什么会提高效率:概括来讲,传统方式为read()系统调用,进行了两次数据拷贝;内存映射方式为mmap()系统调用,只进行一次数据拷贝
- Kafka提供了一个参数——producer.type来控制是不是主动flush;如果Kafka写入到mmap之后就立即flush然后再返回Producer叫同步(sync);写入mmap之后立即返回Producer不调用flush叫异步(async)。
- Java NIO对文件映射的支持 Java NIO,提供了一个 MappedByteBuffer 类可以用来实现内存映射。 MappedByteBuffer只能通过调用FileChannel的map()取得,再没有其他方式。 FileChannel.map()是抽象方法,具体实现是在 FileChannelImpl.c 可自行查看JDK源码,其map0()方法就是调用了Linux内核的mmap的API。
- Kafka是用mmap作为文件读写方式的,它就是一个文件句柄,所以直接把它传给sendfile;偏移也好解决,用户会自己保持这个offset,每次请求都会发送这个offset。(还记得吗?放在zookeeper中的);数据量更容易解决了,如果消费者想要更快,就全部扔给消费者。如果这样做一般情况下消费者肯定直接就被压死了;所以Kafka提供了的两种方式——Push,我全部扔给你了,你死了不管我的事情;Pull,好吧你告诉我你需要多少个,我给你多少个。
- 使用 MappedByteBuffer类要注意的是:mmap的文件映射,在full gc时才会进行释放。当close时,需要手动清除内存映射文件,可以反射调用sun.misc.Cleaner方法。
【优化四】批量压缩
- 生产者:批量发送消息集
- 消费者:主动拉取数据,同样采用批量拉取的方式
Kafka总结
总的来说Kafka快的原因:
- 1、partition顺序读写,充分利用磁盘特性,这是基础;
- 2、Producer生产的数据持久化到broker,采用mmap文件映射,实现顺序的快速写入;
- 3、Customer从broker读取数据,采用sendfile,将磁盘文件读到OS内核缓冲区后,直接转到socket buffer进行网络发送。
mmap 和 sendfile总结
- 1、都是Linux内核提供、实现零拷贝的API;
- 2、sendfile 是将读到内核空间的数据,转到socket buffer,进行网络发送;
- 3、mmap将磁盘文件映射到内存,支持读和写,对内存的操作会反映在磁盘文件上。
RocketMQ 在消费消息时,使用了 mmap。kafka 使用了 sendFile。
2.可靠性
- Kafka的副本机制是保证其可靠性的核心。
- 关于副本机制,我将它理解为Leader-Follower机制,就是多个服务器中有相同数据的多个副本,并且划分的粒度是分区。很明显,这样的策略就有下面几个问题必须解决:
各副本间如何同步?
- ISR机制:Leader动态维护一个ISR(In-Sync Replica)列表,
- Leader故障,如何选举新的Leader?
要想解决这个问题,就要引出Zookeeper,它是Kafka实现副本机制的前提,关于它的原理且听下回分解,本篇还是从Kafka角度进行分析。在这里我们只需要了解,一些关于Broker、Topics、Partitions的元信息存储在Zookeeper中,Leader发生故障时,从ISR集合中进行选举新的Leader。
request.required.acks来设置数据的可靠性:
分区机制和副本机制知识点:
3.顺序性
- 顺序性保证主要依赖于分区机制 + 偏移量。
- 提到分区,首先就要解释一下相关的概念以及他们之间的关系,个人总结如下几点:
- 服务器(Broker):指一个独立的服务器
- 主题(Topic):消息的逻辑分类,可跨Broker
- 分区(Partition):消息的物理分类,基本的存储单元
- 这里盗一张图阐述上述概念间的关系
- 为什么分区机制可以保证消息的顺序性?
- Kafka可以保证一个分区内消息是有序且不可变的。
- 生产者:Kafka的消息是一个键值对,我们通过设置键值,指定消息被发送到特定主题的特定分区。
- 可以通过设置key,将同一类型的消息,发到同一个分区,就可以保证消息的有序性。
- 消费者:消费者需要通过保存偏移量,来记录自己消费到哪个位置,在0.10版本前,偏移量保存在zk中,后来保存在 __consumeroffsets topic中。
【场景二】流处理
- 在0.10版本后,Kafka内置了流处理框架API——Kafka Streams,一个基于Kafka的流式处理类库,它利用了上述,至此,Kafka也就随之发展成为一个囊括消息系统、存储系统、流处理系统的中央式的流处理平台。
- 与已有的Spark Streaming平台不同的是,Spark Streaming或Flink是一个是一个系统架构,而Kafka Streams属于一个库。Kafka Streams秉承简单的设计原则,优势体现在运维上。同时Kafka Streams保持了上面提到的所有特性。
- 关于二者适合的应用场景,已有大佬给出了结论,就不强行总结了。
- Kafka Streams:适合”Kafka --> Kafka“场景
- Spark Streaming:适合”Kafka --> 数据库”或“Kafka --> 数据科学模型“场景