Kafka作为一款分布式的消息队列,是如何做到百万级TPS呢?,用了哪些黑科技才能做到如此这般独孤求败呢?
1 页缓存
将磁盘的数据缓存到内存中,把对磁盘的访问变为对内存的访问
kafka在写数据的时候,会先将数据写入到页缓存,满足一定条件后刷写到磁盘上,可以保证更高的读写性能。
1.1 页缓存-读
在启用页缓存做读取的情况下,会先查看对应的页,是否在页缓存中,如果在(命中),那么直接读取并返回即可,避免了对磁盘的访问;
如果没有命中,则向磁盘发起读请求,并将读取的数据存入页缓存,之后将数据返回给进程。
1.2 页缓存-写
若一个进程需要将数据写入到磁盘,会检查对应的数据是否在页缓存中,如果不存在,会先在页缓存添加相应的页,然后将数据写入对应的页;这时候对应的页就变成了脏页;
脏页达到一定的条件之后就会被操作系统写入到磁盘。Linux系统中有几个参数可以指定脏页达到什么条件进行磁盘刷写:
- vm.dirty_background_ratio:脏页数量达到内存数量的百分之多少后,触发pdflush/flush/kdmflush等后台进程进行刷写,默认为10,不会阻塞其他IO
- vm_dirty_ratio:脏页数量达到内存百分之多少,强制进行磁盘刷写,所有的新IO都会被阻塞,知道脏页刷写完毕,默认为20
- vm.dirty_expire_centisecs 指定脏数据能存活的时间。在这里它的值是30秒。当 pdflush/flush/kdmflush 在运行的时候,他们会检查是否有数据超过这个时限,如果有则会把它异步地写到磁盘中。毕竟数据在内存里待太久也会有丢失风险。
- vm.dirty_writeback_centisecs 指定多长时间 pdflush/flush/kdmflush 这些进程会唤醒一次,然后检查是否有缓存需要清理。
虽然页缓存的刷写由操作系统来控制,但是Kafka自身也提供了同步刷盘及间断性强制刷盘(fsync)的功能,可以显著提高消息的可靠性,防止宕机造成的页缓存没有及时写到磁盘造成的数据丢失。具体的参数有:
- log.flush.interval.messages:消息达到多少条时将数据flush到磁盘,默认10000
- log.flush.interval.ms:每隔多长时间,强制进行一次磁盘flush,默认null
不过建议刷盘任务还是由操作系统来做,消息的可靠性应该由多分本机制来保证,而不是影响性能的同步刷盘操作。
1.3 为什么要用页缓存
为什么要使用页缓存,而不是在JVM内维护一个缓存呢?主要原因有以下几点:
- 相同的数据,页缓存在全局只会存储一份,JVM维护可能不同进程被缓存了多次;
- Java内存密度太低,一个boolean类型,本来一个比特就能表示的,Java需要16字节(8字节对象头,8字节信息(有7字节是用于内存对齐))。
- JVM垃圾回收慢,且堆越大越慢
- Jvm重启,页缓存依旧存在
- 页缓存和文件之间的一致性有操作系统来保障,简化代码逻辑,同时比进程内维护更加安全有效
1.4 关于Swap
内存交换本身是为了再不足的时候将数据临时写入到磁盘,避免影响服务。但它的作用和我们使用页缓存的初衷背道而驰,应当尽量避免这种内存交换,可以设置vm.swappiness=1来防止内存耗尽中止某些进程,也可以最大限度的限制对Kafka的影响
2 零拷贝
通过零拷贝技术,可以去掉那些没有必要的数据复制操作,同时减少上下文的切换次数
设想一种情况,需要将服务器磁盘的一个文件展示给用户。
正常的做法是:
- 调用read(),将文件A的内容复制到内核态模式的ReaderBuffer中;
- CPU控制将内核模式数据复制到用户模式中;
- 调用write(),将用户模式下的内容复制到内核模式下的SocketBuffer中;
- 将内核模式下的SocketBuffer的数据复制到网卡设备中进行发送;
整个过程,一共进行了4次复制,内核和用户模式的上下文切换也是4次;
采用零拷贝
如果不拷贝到用户控件,有内核模式的ReaderBuffer直接拷贝到SocketBuffer是否可行呢?
其实是可行的,但这依次有3次复制。直到引入DMA技术,才将拷贝减少到2次:
- 使用DMA技术将文件内容复制都内核的ReaderBuffer中
- DMA将数据从内核的ReaderBuffer直接发送到网卡设备
整个过程,进行了2次复制,上下文切换也是2次。针对内核而言,数据在内核模式下实现了零拷贝。
DMA(Direct Memroy Access,直接内存读取)
DMA就是为了解决批量数据的IO问题,允许不同速度的硬件沟通。不需要依赖CPU来进行复制,只需要CPU初始化这个传输动作,而传输本身是有DMA控制器来实现和完成的,无需CPU直接控制传输,也没有终端处理方式那样保留现场和恢复现场的过程,通过硬件为RAM和IO设备开辟一条直接传输数据的通道,是的CPU效率大大提升。
如果不使用DMA,CPU需要从来源把数据复制到暂存器,再将其写会新的地方,这个时间内,CPU无法处理其他工作。
零拷贝使用的场景有:
- 数据较大,读写较慢,追求速度
- 内存不足,不能加载太大数据
- 宽带不足,存在其他线程大量的IO操作,导致宽带不足
3 磁盘顺序读写
磁盘的顺序写,要比内存随机写都要快
在磁盘上顺序读写,不需要移动磁盘臂来寻找数据位置,并且操作系统对于线性读写也做了很多优化,比如预读(read-ahead)技术,提前将一个比较大的磁盘读入内存和后写(write-behind),将很多小的逻辑写操作后合并组成一个大的物理写操作。
参考: