零拷贝及其应用 | 青训营
- 传统文件传输过程
- linux传输文件的过程
- 4次用户态与内核态的上下文切换
- 系统调用 write read
- 区分用户空间和内核空间
- 隔离了操作系统程序和应用程序
- 保证了操作系统的稳定性
- 4次数据拷贝(CPU拷贝)
- 为什么内核空间不直接使用用户空间的数据
- 因为内核不能信任任何用户空间的指针(需要进行数据检查)
- 为什么不直接进行检查呢(不进行拷贝)
- 因为检查的过程用户可能修改源数据
- 检查的时候不让用户修改不就行了?加锁?(我的想法)
- 稳定性和效率的平衡
- CPU数据拷贝方式 操作系统优化
- IO 轮询(轮询磁盘数据是否准备好)
- IO 中断 (准备好了发送IO中断信号)
- 对比轮询 一定程度释放了CPU资源
- 大数据量会 CPU会反复中断(中断开销也比较大)
- DMA(Direct Memory Access)传输 (处理内核缓冲区)
- CPU只需要处理一次中断
- 彻底减少了一次CPU拷贝
- 上述拷贝步骤read 都需要从磁盘到内核缓冲区 内核缓冲区到用户缓冲区
- 零拷贝的实现方式
- 零拷贝定义 并不是0次拷贝数据
- 减少用户空间和内核空间之间的CPU拷贝次数
- 减少上下文切换次数
- mmap + write
- mmap映射内核缓冲区到用户缓冲区 直接操作指针 不再拷贝(又减少一次)
- 仍然需要四次上下文切换 mmap 两次 write 两次 两次cpu拷贝
- 适用大文件 不适应小文件(会碎片化)
- sendfile
- CPU直接将内核缓存区的文件 拷贝到socket缓存区
- 只有一个内核空间的CPU拷贝 和sendfile()的两次上下文切换
- sendfile + gather --没有使用CPU进行拷贝
- socket缓存区 文件描述符fd 和 偏移量 offset 存入到socket缓冲区
- 减少一次内核的CPU拷贝 网卡使用DMA根据上述信息去内核缓冲区拷贝
- 去除了所有的CPU拷贝 只有sendfile 的两次上下文信息
- 缺点: 需要硬件支持 gather
- splice 系统调用
- 在内核缓存区和socket缓冲区建立管道 内部通过管道进行数据拷贝
| 拷贝方式 | 上下文切换数 | CPU拷贝数 | DMA拷贝数 | 用户进程是否可以修改数据 |
|---|
| read + write | 4 | 4 | 0 | 是 |
| read + write + DMA | 4 | 2 | 2 | 是 |
| mmap + witte | 4 | 1 | 2 | 是 |
| sendfile | 2 | 1 | 2 | 否 |
| sendfile + gatherDMA | 2 | 0 | 2 | 否 |
| splice | 2 | 0 | 2 | 否 |
- go语言的实践 代码
- 零拷贝的应用
- Kafka 吞吐量大概百万级 海量数据应用场景
- producer -> broker -> consumer
- disk就是指磁盘 机械硬盘 SSD固态硬盘 memory内存
- disk 花时间的主要三部分 寻道时间 旋转延时 数据传输
- 优化点1:保证顺序读写 --解决单次IO的性能
- 顺序读写在上述几种存储介质中 大于随机读写的性能
- producer 每次回追加写入到partition(逻辑概念 对于segment)
- consumer每次消费的时候 根据offset进行顺序读写
- 批量刷盘 减少磁盘IO的次数
- 优化点2:页缓存技术 --减少磁盘IO的次数
- 利用linux的page cache技术
- 异步落盘 减少磁盘IO次数
- 通过Replication 机制去解决数据丢失的问题
- 但还是存在丢失的可能 当broker要持久化数据时(写入)断电
- 优化点3:零拷贝之mmap --减少数据拷贝的次数
- 稀疏索引(sparse index) 这里clickhouse也使用了稀疏索引
- 稠密索引(dense index)和稀疏索引其实就是空间和时间的trade-off
- 数据量巨大时 为每条数据建立索引也耗费大量空间
- 索引文件的size都不大,因此很容易将它们做内存映射(mmap) 减少一次数据拷贝
- clickshouse MergeTree 索引通过order by字段指定 为稀疏索引 这篇读过 导师推荐 跳表索引也读过 不使用主键用跳表索引
- 通过mmap 去读写刚刚的稀疏索引文件
- 为什么不用mmap直接读取日志文件
- 内存映射的文件是有限制的 内存肯定小(Memory-mapped files)
- 稀疏索引文件 比较小
- 优化点4:零拷贝之sendfile
- consumer 不需要对数据进行修改 采样零拷贝方式
- sendfile() 直接从page cache 中 到socket缓冲区
- 若不在page cache中 去磁盘读取 (欲读的操作)
- 如果生产消费的速度差不多 基本上可以从page cache 中读取 磁盘访问少
- RocketMQ 10万级别吞吐 业务中用到的
- producer -> broker -> consumer
- broker 对于一台服务器 存储topic topic也可以分片存储在不同的broker中
- message queue 用于存储消息的物理地址
- 每个topic的消息地址 存储在多个message queue
- kafka适用日志类的传输 海量数据
- RocketMQ 可靠性强 功能性支持 适用电商应该是 严格有序
- 优化点1 : mmap
- consumer queue commit log 都使用了 mmap
- rocketMQ 文件使用定长存储
- mmap 存在缺页中断问题 通过文件预热解决
- 为什么RocketMQ不适用sendfile 机制
- 因为sendfile不支持用户进程修改
- 无法支持RocketMQ 提供的一些功能
- Q&A
- Q:通过使用mmap进行文件读写,的读写速度和内存速度一致吗 还是和磁盘速度一致
- A:通过使用mmap进行文件读写,读取速度和写入速度通常会接近内存的速度,而不是磁盘的速度。
- RocketMQ的一些解释