Kafka 日志存储 — 磁盘存储
Kafka 依赖与磁盘来存储和缓存消息,采用文件追加的方式来写入消息。顺序写盘的速度快于随机写内存。
1 磁盘存储
除顺序写入外,Kafka中大量使用了页缓存、零拷贝等技术来进一步提升吞吐性能。
1.1 页缓存
页缓存是操作系统实现的一种磁盘缓存,以此来减少对磁盘I/O的操作。
图 进程读取及写入数据流程
1.1.1 刷盘
将脏页写入到磁盘的过程叫做刷盘。
vm.dirty_background_ratio | 当脏页数量达到系统内存的百分之多少之后就会触发刷盘(异步)。默认值为10。 |
vm.dirty_ratio | 当脏页数量达到系统内存的百分之多少之后就立马进行刷盘(同步)。默认值为20。 |
表 Linux 控制刷盘时机的参数
Kafka同样提供了同步刷盘及间断性强制刷盘的功能,但是不建议使用,刷盘任务应交由操作系统去调配。
1.1.2 未使用进程内的缓存
进程会在其内部缓存处理所需的数据,然后这些数据有可能还缓存在页缓存中,因此同一份数据有可能被缓存了两次。
在Java中,对象的内存开销非常大,通常会是真实数据大小的几倍,空间使用率低下。Java的垃圾回收会随着堆内数据的增多而变得越来越慢。
使用页面缓存可以省去进程内部的缓存消耗,同时通过紧凑的字节码来代替使用对象的方式来节省更多的空间.
此外,即使Kafka服务重启,页缓存还是会保持有效,而进程内的缓存却需要重建。
1.1.3 swap 分区
当物理内存(非磁盘,而是相对于虚拟内存)不够时,Linux会使用磁盘的一部分作为swap分区,将非活跃的进程调入swap分区,来把内存空出来给活跃的进程。
通过vm.swappiness参数来进行调节,其上限为100,表示积极使用swap分区,并把内存上的数据及时地搬运到swap分区中,下限为0,表示任何情况下都不要发生交换。
对于Kafka而言,尽量避免这种内存交换,否则会对它的各方面性能产生很大的负面影响。建议将这个参数值设置为1。
1.2 磁盘I/O流程
图 应用与磁盘I/O操作流程
写操作:用户调用fwrite把数据写入C库标准IObuffer后就返回,写操作通常是异步操作。C库不会立即将数据刷新到磁盘,会将多次小数据量相邻写操作先缓存起来合并,最终调用write一次性写入页缓存。内核的pdflush线程不停检测脏页,判断是否要写到磁盘,如果是,则发起磁盘I/O请求。
读操作:用户调用fread到C库IObuffer中读取数据,如果成功则返回,否则页缓存读取数据,如果成功则返回,否则发起磁盘I/O请求,读取后将数据缓存到页缓存和C库的IObuffer,并返回。读操作是同步操作。
I/O请求:通用块层根据I/O请求构造一个或多个bio(block I/O,阻塞式I/O模型)结构并交给调度层。调度层将bio结构进行排序和合并组织成队列:将一个或多个进程的读操作合并到一起读,将一个或多个进程的写操作合并到一起写,尽可能变随机为顺序,读必须优先满足。
1.2.1 Linux的I/O调度策略
算法 | 介绍 | 优缺点 |
NOOP | No Operation,无操作调度器。采用FIFO(先进先出)策略,按照I/O请求到达的顺序进行处理。在这基础上,会尝试合并相邻(物理地址相邻)的I/O请求: 当一个新的I/O请求到达时,会检查这个请求是否可以和队列种某个请求合并(减少磁盘转动次数)。 | 优点:低延迟、高吞吐量、简单高效。 缺点:请求饥饿问题、磁盘寻道开销大、缺乏适应性。 |
CFQ | Completely Fair Queuing,完全公平排队。默认的调度算法。为每个进程单独创建一个队列来管理该进程所产生的请求。根据进程的权重和时间片来调度这些请求。并且将I/O请求按照地址进行排序。 | 优点:公平性,确保每个进程都能获得公平的I/O带宽。较少了磁盘寻道开销。 缺点:处理大量小I/O请求时有延迟;对优先级高的请求响应不够迅速。 |
DEADLINE | 最后期限。在CFQ基础上,解决了I/O请求“饿死”的情况。除了CFQ本身的I/O队列,还分别为读写提供了FIFO队列。读FIFO队列的最大等待时间为500ms,写FIFO队列的最大等待时间为5s。FIFO队列的优先级要比CFQ队列高。 | 优点:实时性强、公平性、灵活性。 缺点:复杂度高、资源开销更大 |
ANTICIPATORY | 预期的。上面的算法考虑的焦点在于满足零散I/O请求上,对于连续的I/O请求(比如顺序读),并没有做优化。ANTICIPATORY在DEADLINE的基础上,为每个读I/O都设置了6ms的等待时间窗口,在等待期间OS收到了相邻位置的读请求,则合并请求并处理。 | 优点:通过预测和合并来减少磁盘的寻道次数和旋转延迟。 缺点:如果非连续读请求较少,则延迟会更长。 |
表 Linux 的I/O调度策略
请求饥饿:在I/O调度过程种,某些I/O请求由于调度算法的不当处理(无法公平、有效地处理所有请求)而长时间得不到服务,导致请求被“饿死”的现象(长时间等待)。
1.3 零拷贝
将数据直接从磁盘复制到网卡设备中,而不需要经过应用程序。
例如:将磁盘中的图片展示给用户。
正常流程为,将磁盘中的数据复制出来放到内存buf中,然后将这个buf通过套接字(Socket)传输给用户。这个过程中,经历了4次复制。
图 非零拷贝技术的复制过程
图 内核模式下零拷贝技术的复制过程
零拷贝技术通过DMA(Direct Memory Access)技术将文件内容复制到内核模式下的Read Buffer中,然后直接传递到网卡设备。这是在内核模式下实现了零拷贝。