Kafka【五】Buffer Cache (缓冲区缓存)、Page Cache (页缓存)和零拷贝技术
【1】Buffer Cache (缓冲区缓存)
在Linux操作系统中,Buffer Cache
(缓冲区缓存)是内核用来优化对块设备(如磁盘)读写操作
的一种机制(故而有一种说法叫做块缓存
)。尽管在较新的Linux内核版本中,Buffer Cache
和Page Cache
已经被整合在一起,但在理解历史背景和功能时,了解Buffer Cache
仍然很有帮助。
Buffer Cache 的历史和定义
传统的Buffer Cache
主要用于缓存块设备上的数据块,包括文件系统的元数据和数据本身
。它的主要目的是提高对块设备的读写性能,减少磁盘I/O操作,从而提升系统整体性能。
Buffer Cache 的用途
-
数据缓存:
- 当数据被读取或写入块设备时,
Buffer Cache
会将这些数据暂时存储在内存中,以备后续快速访问。 - 这样做的好处是,当应用程序再次请求相同的数据时,可以直接从
Buffer Cache
中获取,而不需要重新从磁盘读取,从而节省了I/O操作的时间。
- 当数据被读取或写入块设备时,
-
元数据缓存:
Buffer Cache
还会缓存文件系统的元数据,如节点(node),这些元数据包含了文件的基本信息,如文件大小、权限、时间戳等。- 缓存元数据可以加速文件系统的操作,因为文件系统的许多操作都需要访问这些元数据。
Buffer Cache 的实现
-
缓存管理:
- 内核通过一系列的数据结构和算法来管理
Buffer Cache
,确保高效地利用有限的内存资源。 - 内核会根据内存使用情况和缓存策略自动决定哪些数据应该保留在缓存中,哪些数据应该被淘汰。
- 内核通过一系列的数据结构和算法来管理
-
缓存替换策略:
- 内核使用了一定的缓存替换策略来决定何时将数据从
Buffer Cache
中移除。 - 通常采用的是最近最少使用(LRU, Least Recently Used)算法或类似算法,以确保经常使用的数据能够被保留。
- 内核使用了一定的缓存替换策略来决定何时将数据从
Buffer Cache 的示例
假设一个应用程序需要读取一个文件的内容:
-
首次读取:
- 应用程序请求读取文件的一部分。
- 内核检查
Buffer Cache
中是否有请求的数据。 - 如果没有找到,则从磁盘读取数据,并将其放入
Buffer Cache
中。
-
后续读取:
- 下次应用程序再次请求读取同一部分数据时,内核会直接从
Buffer Cache
中获取数据,而不需要访问磁盘。
- 下次应用程序再次请求读取同一部分数据时,内核会直接从
现代Linux内核的变化
在较新的Linux内核版本中,Buffer Cache
和Page Cache
已经被整合为统一的缓存机制,称为Unified Buffer Cache
。这种整合旨在更好地利用内存资源,并提高整体的缓存效率。
统一缓存机制的优点
- 资源共享:统一后的缓存机制可以更好地共享内存资源,避免了传统缓存机制中可能出现的内存碎片问题。
- 性能优化:统一缓存机制可以根据实际情况动态调整缓存策略,从而提高缓存的命中率和整体性能。
- 简化管理:统一缓存机制简化了内核的缓存管理逻辑,使得内核更加简洁高效。
总结
尽管Buffer Cache
作为一个单独的概念在较新的Linux内核版本中已经不再单独存在,但它所代表的缓存机制仍然是提高系统性能的重要手段之一。通过缓存块设备上的数据和元数据,Buffer Cache
能够显著减少磁盘I/O操作,提高文件系统的读写性能。在现代Linux内核中,这种缓存机制已经被整合到统一的缓存机制中,以更好地适应现代计算环境的需求。
【2】Page Cache (页缓存)
页缓存是操作系统实现的一种主要的磁盘缓存,以此用来减少对磁盘I/O的操作。具体来说,就是把磁盘中的数据缓存到内存中,把对磁盘的访问变为对内存的访问
。为了弥补性能上的差异 ,现代操作系统越来越多地将内存作为磁盘缓存,甚至会将所有可用的内存用于磁盘缓存,这样当内存回收时也几乎没有性能损失,所有对于磁盘的读写也将经由统一的缓存。
当一个进程准备读取磁盘上的文件内容时,操作系统会先查看待读取的数据所在的页(page)是否在页缓存(page cache)中,如果存在(命中)则直接返回数据
,从而避免了对物理磁盘I/O操作;如果没有命中,则操作系统会向磁盘发起读取请示并将读取的数据页写入页缓存,之后再将数据返回进程。
同样,如果一个进程需要将数据写入磁盘,那么操作系统也会检测数据对应的页是否在页缓存中,如果不存在,则会先在页缓存中添加相应的页,最后将数据写入对应的页。被修改过后的页也就变成了脏页,操作系统会在合适的时间把脏页中的数据写入磁盘
,以操作数据的一致性。
对一个进程页言,它会在进程内部缓存处理所需的数据,然而这些数据有可能还缓存在操作系统的页缓存中,因此同一份数据有可能被缓存了2次。并且,除非使用Direct I/O的方式,否则页缓存很难被禁止。
Kafka中大量使用了页缓存,这是Kafka实现高吞吐的重要因此之一
。虽然消息都是先被写入页缓存,然后由操作系统负责具体的刷盘任务,但在Kafka中同样提供了同步刷盘及间断性强制刷盘(fsync)的功能,这些功能可以通过log.flush.interval.message、log.flush.interval.ms
等参数来控制。同步刷盘可以提高消息的可靠性,防止由于机器掉电等异常造成处于页缓存而没有及时写入磁盘的消息丢失。不过一般不建议这么做,刷盘任务就应交由操作系统去调配,消息的可靠性应该由多副本机制来保障,而不是由同步刷盘这种严重影响性能的行为来保障。
【3】page cache与buffer cache区别
在Linux操作系统中,page cache
和buffer cache
都是内核用来缓存数据的机制,但它们有不同的目的和工作原理。下面是这两种缓存的具体区别:
Page Cache (页缓存)
- 定义:Page cache 是 Linux 内核用于缓存文件系统数据的一种机制。它主要用来缓存文件的内容,即
文件中的数据块
。 - 用途:Page cache 的主要目的是
提高文件读写操作的性能
。当一个文件被频繁访问时,它的数据会被缓存到内存中,这样后续对该文件的访问就可以直接从内存中读取,而不是每次都从磁盘读取,从而加快了访问速度。 - 位置:Page cache 存储在内存中的页面(page)中,每个页面大小通常是 4KB 或者更大(取决于系统配置)。
- 实现:Page cache 通过虚拟内存系统实现,当文件数据被读取时,内核会将数据映射到虚拟内存的页面上。
- 示例:当应用程序读取一个文件时,内核会检查 page cache 中是否有该文件的数据;如果有,则直接从 page cache 中返回数据;如果没有,则从磁盘读取数据并放入 page cache。
Buffer Cache (缓冲区缓存)
- 定义:Buffer cache 是 Linux 内核用于
缓存块设备上的数据
的一种机制。它主要用于缓存块设备(如磁盘)上的元数据(如节点)和数据块
。 - 用途:Buffer cache 的主要目的是
提高对块设备的读写性能
。它不仅可以缓存文件的数据块,还可以缓存文件系统的元数据(如文件权限、大小等),从而加速文件系统的操作。 - 位置:Buffer cache 存储在
内存中的缓冲区
中,每个缓冲区的大小通常是固定大小,通常是 512 字节到几 KB 不等。 - 实现:Buffer cache 通过
内核的缓冲区管理器
实现,当数据被读取或写入块设备时,内核会将数据放入 buffer cache 中。 - 示例:当应用程序需要访问一个文件的元数据时,内核会检查 buffer cache 中是否有该元数据;如果有,则直接从 buffer cache 中返回数据;如果没有,则从磁盘读取数据并放入 buffer cache。
区别总结
-
缓存的对象:
- Page cache:主要用于缓存文件的内容。
- Buffer cache:主要用于缓存块设备上的数据块和元数据。
-
数据单位:
- Page cache:以页面(page)为单位,通常每个页面大小为 4KB。
- Buffer cache:以缓冲区(buffer)为单位,每个缓冲区的大小较小,通常是 512 字节到几 KB。
-
缓存范围:
- Page cache:专门针对文件系统的缓存,用于加速文件读写操作。
- Buffer cache:不仅缓存文件数据,还缓存文件系统的元数据,用于加速文件系统操作。
-
缓存策略:
- Page cache:侧重于文件数据的缓存。
- Buffer cache:侧重于块设备上的数据缓存,包括文件系统的元数据。
近期变化
值得注意的是,在较新的 Linux 内核版本中,buffer cache 和 page cache 已经合并到了一起,统一称为 generic buffer cache
或者 unified buffer cache
。这意味着现在这两种缓存机制已经整合在一起,共享内存资源,从而更好地利用内存并提高整体性能。
总的来说,page cache 和 buffer cache 虽然在早期有明显的区分,但在现代 Linux 内核中,这两者已经趋于整合,共同服务于提高文件系统和块设备的读写性能。
块设备上的数据块与文件内容
.
在块设备上的数据块确实包含了文件的内容。因此,从某种意义上说,Buffer Cache确实缓存了文件的内容。然而,在传统意义上,Buffer Cache更侧重于缓存元数据,而Page Cache更侧重于缓存文件的内容。
【4】零拷贝
“零拷贝”(Zero-copy)是一种计算机编程技术,旨在减少数据从一个位置传输到另一个位置时的数据复制次数,从而提高性能。零拷贝技术的核心思想是尽可能减少数据在不同内存区域之间的复制操作,尤其是减少数据在用户空间和内核空间之间
的复制。这种方法可以显著提高数据传输的效率,尤其是在涉及大量数据传输的应用中。
零拷贝的背景
在传统的数据传输过程中,数据通常需要在用户空间(应用程序所在的内存区域
)和内核空间(操作系统内核所在的内存区域)
之间多次复制。例如,当应用程序读取磁盘上的数据并通过网络发送时,数据可能会经历以下过程:
- 从磁盘读取数据到内核空间的缓冲区。
- 将数据从内核空间复制到用户空间的缓冲区。
- 将数据从用户空间的缓冲区复制回内核空间的网络发送缓冲区。
- 最终通过网络发送出去。
这种多次复制不仅消耗CPU资源,还增加了系统的延迟。
零拷贝的技术实现
零拷贝技术通过减少这些不必要的复制操作来提高性能。以下是几种常见的零拷贝技术实现:
1. 直接 I/O
- 定义:直接 I/O(Direct I/O)允许应用程序直接从磁盘读取数据到用户空间,绕过内核缓冲区。
- 系统调用:Linux 中的
open()
函数可以使用O_DIRECT
标志打开文件,然后通过read()
和write()
系统调用直接读写数据。 - 优点:减少数据在内核缓冲区和用户空间之间的复制。
2. mmap
- 定义:mmap(memory-mapped file)允许应用程序直接映射文件到内存地址空间,从而可以直接访问文件内容。
- 系统调用:使用
mmap()
系统调用来映射文件到内存。 - 优点:数据可以直接在内存中访问,无需复制到用户空间。
3. sendfile
- 定义:sendfile 系统调用允许数据直接从一个文件描述符传输到另一个文件描述符,而不需要复制到用户空间。
- 系统调用:Linux 中的
sendfile()
系统调用可以将数据从一个文件描述符(如磁盘文件)传输到另一个文件描述符(如网络套接字)。 - 优点:数据可以直接从内核缓冲区传输到网络缓冲区,减少数据复制。
4. splice
- 定义:splice 系统调用允许数据在两个文件描述符之间直接传输,而不需要经过用户空间。
- 系统调用:使用
splice()
系统调用可以将数据从一个文件描述符传输到另一个文件描述符。 - 优点:适用于 Linux 2.6 及以上版本,可以实现高效的数据传输。
零拷贝的应用场景
零拷贝技术广泛应用于需要高效数据传输的应用场景中,包括但不限于:
- 网络服务器:Web 服务器、数据库服务器等需要高效处理大量数据传输的场景。
- 高性能计算:科学计算、大数据处理等需要高速数据传输的应用。
- 嵌入式系统:资源受限的设备中,零拷贝技术可以显著提高数据处理能力。
总结
零拷贝技术通过减少数据在用户空间和内核空间之间的复制次数,提高了数据传输的效率,减少了CPU资源的消耗。在现代操作系统中,通过各种系统调用和技术实现了零拷贝,使得数据传输更为高效。了解和使用零拷贝技术对于提高应用程序的性能具有重要意义。
【5】kafka中的零拷贝
kafka的高性能是多方面协同的结果,包括宏观架构、分布式partition存储、ISR数据同步、以及“无所不用其极”的高效利用磁盘/操作系统特性。其中零拷贝并不是不需要拷贝,通常是说在IO读写过程中减少不必要的拷贝次数。
这里我们要说明是,内核在执行操作时同一时间点只会做一件事,比如Java写文件这个操作,为了提高效率,这个操作是分为3步:
- 第一步java将数据写入自己的缓冲区,
- 第二步java需要写入数据的磁盘页可能就在当前的页缓存(Page Cache)中,所以java需要将自己的缓冲区的数据写入操作系统的页缓存(Page Cache)中。
- 第三步操作系统会在页缓存数据满了后,将数据实际刷写到磁盘文件中。
在这个过程,Java Application数据的写入和页缓存的数据刷写对于操作系统来讲是不一样的,可以简单理解为,页缓存的数据刷写属于内核的内部操作
,而用于启动的应用程序的数据操作属于内核的外部操作
,权限会受到一定的限制。
所以内核在执行不同操作时,就需要将不同的操作环境加载到执行空间中,
- 也就是说,当java想要将数据写入页缓存时,就需要调用用户应用程序的操作,这就是用户态操作。
- 当需要将页缓存数据写入文件时,就需要中断用户用用程序操作,而重新加载内部操作的运行环境,这就是内核态操作。
可以想象,如果存在大量的用户态和内核态的切换操作,IO性能就会急剧下降。所以就存在零拷贝操作,减少用户态和内核态的切换,提高效率。Kafka消费者消费数据以及Follower副本同步数据就采用的是零拷贝技术。
【6】补充:Linux中用户态、内核态、用户空间、内核空间、用户缓冲区、内核缓冲区
在Linux操作系统中,用户态(User Mode)和内核态(Kernel Mode)以及用户空间(User Space)和内核空间(Kernel Space)是操作系统中非常重要的概念。这些概念区分了操作系统中的不同执行环境和内存区域。下面详细解释这些概念及其相互关系:
1. 用户态(User Mode) vs 内核态(Kernel Mode)
-
用户态(User Mode):
- 定义:用户态是指应用程序运行的模式。在这种模式下,程序只能访问受限制的系统资源,并且只能执行一部分系统调用。
- 安全:用户态的设计是为了保护系统的稳定性和安全性,防止应用程序直接访问硬件或执行潜在危险的操作。
- 权限:用户态下的程序没有直接访问硬件的权限,也不能执行敏感的系统调用。
-
内核态(Kernel Mode):
- 定义:内核态是指操作系统内核运行的模式。在这种模式下,内核可以访问所有系统资源,并执行任何操作。
- 权限:内核态下的代码拥有最高权限,可以直接访问硬件、修改内存等。
- 切换:应用程序通过系统调用(如
read
、write
、open
等)从用户态切换到内核态,执行完相应的操作后再切换回用户态。
2. 用户空间(User Space) vs 内核空间(Kernel Space)
-
用户空间(User Space):
- 定义:用户空间是指应用程序运行所在的内存区域。在这个区域内,应用程序只能访问自己分配的内存和其他受保护的资源。
- 权限:用户空间内的程序不能直接访问内核空间的内存或其他资源。
- 隔离:用户空间提供了应用程序之间的隔离,防止一个应用程序错误影响其他应用程序。
-
内核空间(Kernel Space):
- 定义:内核空间是指操作系统内核运行所在的内存区域。在这个区域内,内核可以访问所有的内存资源和硬件。
- 权限:内核空间内的代码拥有最高权限,可以直接访问硬件、修改内存等。
- 资源管理:内核空间负责管理系统的资源,如内存、进程、文件系统等。
3. 用户缓冲区(User Buffer) vs 内核缓冲区(Kernel Buffer)
-
用户缓冲区(User Buffer):
- 定义:用户缓冲区是指应用程序在用户空间中分配的缓冲区,用于临时存储数据。
- 用途:用户缓冲区通常用于应用程序读取或写入数据时的临时存储。
- 访问:内核不能直接访问用户缓冲区中的数据,必须通过特定的机制(如系统调用)进行数据交换。
-
内核缓冲区(Kernel Buffer):
- 定义:内核缓冲区是指内核在内核空间中分配的缓冲区,用于临时存储数据。
- 用途:内核缓冲区通常用于内核内部的数据处理,如文件系统的缓存、网络数据包的处理等。
- 访问:内核可以直接访问内核缓冲区中的数据,而应用程序则不能直接访问内核缓冲区。
总结
- 用户态和内核态区分了程序运行的不同模式,前者受到限制,后者拥有最高权限。
- 用户空间和内核空间区分了内存的不同区域,前者应用程序运行所在,后者内核运行所在。
- 用户缓冲区和内核缓冲区区分了数据存储的不同区域,前者位于用户空间,后者位于内核空间。
通过这些区分,Linux操作系统能够有效地管理资源,保证系统的稳定性和安全性。