利用Partition实现并行处理

机器间的并行处理
磁盘间的并行处理
一个Partition只能被一个Consumer消费 Partition的个数决定了最大并行度

ISR实现CAP中可用性与数据一致性的动态平衡

由于Leader可移除不能及时与之同步的Follower,故与同步复制相比可避免最慢的Follower拖慢整体速度,也即ISR提高了系统可用性。
ISR中的所有Follower都包含了所有Commit过的消息,而只有Commit过的消息才会被Consumer消费,故从Consumer的角度而言,ISR中的所有Replica都始终处于同步状态,从而与异步复制方案相比提高了数据一致性。
ISR可动态调整,极限情况下,可以只包含Leader,极大提高了可容忍的宕机的Follower的数量。与Majority Quorum方案相比,容忍相同个数的节点失败,所要求的总节点数少了近一半。

顺序写磁盘

每一个Partition其实都是一个文件 ,收到消息后Kafka会把数据插入到文件末尾
Kafka是不会自动删除数据的,它会把所有的数据都保留下来,每个消费者(Consumer)对每个Topic都有一个offset用来表示 读取到了第几条数据
Kakfa提供了两种策略来删除数据。一是基于时间,二是基于partition文件大小

Page Cache

I/O Scheduler会将连续的小块写组装成大块的物理写从而提高性能。
I/O Scheduler会尝试将一些写操作重新按顺序排好,从而减少磁头移动时间。
读操作可以直接在Page Cache内进行。如果消费和生产速度相当,甚至不需要通过物理磁盘交换数据。
如果进程重启,JVM内的Cache会失效,但Page Cache仍然可用

Kafka 重度依赖底层操作系统提供的 PageCache 功能。当上层有写操作时,操作系统只是将数据写入 PageCache,同时标记 Page 属性为 Dirty。当读操作发生时,先从 PageCache 中查找,如果发生缺页才进行磁盘调度,最终返回需要的数据。

实际上 PageCache 是把尽可能多的空闲内存都当做了磁盘缓存来使用。同时如果有其他进程申请内存,回收 PageCache 的代价又很小,所以现代的 OS 都支持 PageCache。使用 PageCache 功能同时可以避免在 JVM 内部缓存数据,JVM 为我们提供了强大的 GC 能力,同时也引入了一些问题不适用与 Kafka 的设计。
如果在 Heap 内管理缓存,JVM 的 GC 线程会频繁扫描 Heap 空间,带来不必要的开销。如果 Heap过大,执行一次 Full GC 对系统的可用性来说将是极大的挑战。
所有在在 JVM 内的对象都不免带有一个 Object Overhead(千万不可小视),内存的有效空间利用率会因此降低。
所有的 In-Process Cache 在 OS 中都有一份同样的 PageCache。所以通过将缓存只放在 PageCache,可以至少让可用缓存空间翻倍。
如果 Kafka 重启,所有的 In-Process Cache 都会失效,而 OS 管理的 PageCache 依然可以继续使用。

sendfile和transferTo实现零拷贝

OS 从硬盘把数据读到内核区的 PageCache。
用户进程把数据从内核区 Copy 到用户区。
然后用户进程再把数据写入到 Socket,数据流入内核区的 Socket Buffer 上。
OS 再把数据从 Buffer 中 Copy 到网卡的 Buffer 上,这样完成一次发送。
这里写图片描述
整个过程共经历两次 Context Switch,四次 System Call。同一份数据在内核 Buffer 与用户 Buffer 之间重复拷贝,效率低下。其中 2、3 两步没有必要,完全可以直接在内核区完成数据拷贝。这也正是Sendfile 所解决的问题,经过 Sendfile 优化后,整个 I/O 过程就变成了下面这个样子。

批处理

send方法并不是立即将消息发送出去,而是通过batch.size和linger.ms控制实际发送频率,从而实现批量发送。

数据压缩

支持将数据压缩后再传输给Broker。除了可以将每条消息单独压缩然后传输外,Kafka还支持在批量发送时,将整个Batch的消息一起压缩后传输。数据压缩的一个基本原理是,重复数据越多压缩效果越好。
因此将整个Batch的数据一起压缩能更大幅度减小数据量,从而更大程度提高网络传输效率。

Broker接收消息后,并不直接解压缩,而是直接将消息以压缩后的形式持久化到磁盘。
Consumer Fetch到数据后再解压缩。因此Kafka的压缩不仅减少了Producer到Broker的网络传输负载,同时也降低了Broker磁盘操作的负载,也降低了Consumer与Broker间的网络传输量,从而极大得提高了传输效率,提高了吞吐量。

高效的序列化

Kafka消息的Key和Payload(或者说Value)的类型可自定义,只需同时提供相应的序列化器和反序列化器即可。因此用户可以通过使用快速且紧凑的序列化-反序列化方式(如Avro,Protocal Buffer)来减少实际网络传输和磁盘存储的数据规模,从而提高吞吐率。这里要注意,如果使用的序列化方法太慢,即使压缩比非常高,最终的效率也不一定高。

Memory Mapped Files

Java NIO,它给我提供了一个mappedbytebuffer类可以用来实现内存映射

内存映射文件 ,在64位操作系统中一般可以表示20G的数据文件,它的工作原理是直接利用操作系统的Page来实现文件到物理内存的直接映射。完成映射之后你对物理内存的操作会被同步到硬盘上(操作系统在适当的时候)。
通过mmap,进程像读写硬盘一样读写内存(当然是虚拟内存)

省去了用户空间到内核空间 复制的开销(调用文件的read会把数据先放到内核空间的内存中,然后再复制到用户空间的内存中)

写到mmap中的数据并没有被真正的写到硬盘,操作系统会在程序主动调用flush的时候才把数据真正的写到硬盘。 Kafka提供了一个参数——producer.type来控制是不是主动flush,如果Kafka写入到mmap之后就立即flush然后再返回Producer叫 同步 (sync);写入mmap之后立即返回Producer不调用flush叫 异步 (async)。