MetaQ和kafka的消息读写方式
零拷贝
我们知道,我们在写数据的时候并不是直接写入到磁盘中去的,而是写入到pageCache中去的,pageCache的主要作用是减少磁盘的I/O操作。
在磁盘写入的时候会写入到pageCache中去的,然后pageCache中可以将一些小的写入合并成一个大的写入,再进行异步刷盘。当然我们也可以使用fsync进行强制刷盘,强制刷盘会影响写入性能。一般为了保证消息的可靠性,我们是会采用多副本来存储消息,而不是采用同步刷盘。
读取消息的时候如果在pageCache中有命中则直接返回,如果在pageCache中无法命中则会产生缺页中断,需要从磁盘中加载数据到缓存中,然后返回数据。并且根据局部性原理,在读数据的时候也会进行预读,把该也相邻的磁盘快读入到页缓存中去。
mmap
由于我们读取数据的时候,需要将数据从磁盘拷贝到pageCache中,但是由于pageCache属于内核空间,用户空间无法访问,所以还需要将数据从内核空间拷贝到用户空间。
所以数据需要两次拷贝应用程序才能够访问的到,我们可以通过mmap来减少数据从内核态到用户态的拷贝。通过将程序虚拟页面映射到页缓存中,这样就不需要将数据从内核态拷贝到用户态,也可以避免产生重复数据。也不必要再通过调用read和write方法对文件进行读写,而是通过映射地址和偏移量来直接操作pageCache。
sendfile
下面我们来看下常规的发送文件的过程中,从磁盘读取消息到发送文件的过程是怎么样的。
DMA Copy是指不需要CPU接入,可以直接读写系统内存,类似显卡、网卡和磁盘都是用到DMA,然而像上下文切换的话就需要有CPU接入。
下面我们看看如果采用mmap发送文件之后的流程是怎么样的。
可以看到上下文切换的次数没有变化,但是数据少拷贝了一份,这个和我们上面说到的mmap所能够达到的效果是一样的。
上图中,sendfile采用一次系统调用就完成了发送数据的需求,相比于read+write或者说mmap+writ来
说上下文切换次数变少了,但是数据还是有冗余的。在linux2.4中采用 sendfile+带[分散-收集]DMA。真正实现了无冗余的功能。
上面就是我们说的零拷贝,在Java中是通过FileChannal.transferTo()调用的,底层是通过sendfile实现。
MetaQ和kafka的读写对比
目前kafka支持sendfile的消息读写方式,MetaQ支持mmap的消息读写方式,另外MetaQ还支持sendfile的消息写方式。
默认情况下,MetaQ不论是在CommitLog或者ComsumerQueue中都是采用mmap来实现消息读写的。在发送消息的时候,通常情况下会将数据拷贝到堆内存中去,然后再塞到响应体中进行发送。当然我们也可以通过参数配置不经过堆,通过mapedBuffer直接发送到SocketBuffer。
当消费消息的时候,严格来说对于CommitLog的读取是随机的,因为CommitLog的消息是混合进行存储的,但是从整体上面来看,消息还是会从CommitLog上顺序读取的,先读取旧数据,然后再读取新数据。消息存进去之后很快就会被消费,这个时候消息还是存放在pageCache中的,所以我们是不需要读取磁盘的。
同时pageCache会定时刷盘,但是刷盘的时机是不可控制的,所以会出现swap等现象。mmap也只是做了映射,如果真正去取数据的时候不在内存中,也会产生缺页中断,需要加载数据到内存中,这个时候也会有一些延时。MetaQ采用文件预分配和文件预热来解决pageCache的不确定性。
kafka的消息写入对于单分区来说是顺序写入的,由上文可知,kafka是有.index索引文件和.log数据文件构成的。其中.index索引文件是采用mmap进行读写的,这对于本地读写索引文件则可以提高读取效率,而.log数据文件则采用sendfile的零拷贝进行实现的。对于消息的写入来说,采用mmap其实并没有什么用,因为消息都是从网络中获取的,采用sendfile来发送消息比mmap+write更能提高效率,因为在内存中少了一个SocketBuffer的拷贝。
另外通过网上研究测试表明,如果读取的数据小于4kb的时候,使用mmap的性能效率比sendfile高,当读取数据大于4kb的时候,sendfile的效率比mmap高;对于写数据,如果写入的数据包小于64kb的时候,mmap的性能效率比sendfile高,当写入数据包大于64kb的时候sendfile的效率比mmap高。
由于kafka主要是用于日志传输,处理海量数据,对于数据的正确度要求不是很高,并且在发送消息的时候一般会进行消息的汇聚,然后批量发送消息,所以整体上来说kafka的读写数据量会比较大,这个时候使用sendfile能够获取更高的性能,而MetaQ主要是用来针对阿里的复杂应用场景,对于数据的可靠性、数据的实时性要求会比较高,由于对数据的实时性要求较高,一般不会进行汇聚批量发送消息,所以读写数据量不会很大,这个时候使用mmap可以获得更好的性能,当如果发现写入的数据量比较大时,也可以切换为sendfile进行写入。