零拷贝及其应用 | 青训营

55 阅读5分钟
  1. 传统文件传输过程
    • linux传输文件的过程
      • 4次用户态与内核态的上下文切换
        • 系统调用 write read
        • 区分用户空间和内核空间
          • 隔离了操作系统程序和应用程序
          • 保证了操作系统的稳定性
      • 4次数据拷贝(CPU拷贝)
        • 为什么内核空间不直接使用用户空间的数据
          • 因为内核不能信任任何用户空间的指针(需要进行数据检查)
            • 为什么不直接进行检查呢(不进行拷贝)
            • 因为检查的过程用户可能修改源数据
            • 检查的时候不让用户修改不就行了?加锁?(我的想法)
          • 稳定性和效率的平衡
      • CPU数据拷贝方式 操作系统优化
        • IO 轮询(轮询磁盘数据是否准备好)
          • 实现简单
          • 占用大量CPU时间 效率低下
        • IO 中断 (准备好了发送IO中断信号)
          • 对比轮询 一定程度释放了CPU资源
          • 大数据量会 CPU会反复中断(中断开销也比较大)
        • DMA(Direct Memory Access)传输 (处理内核缓冲区)
          • CPU只需要处理一次中断
          • 彻底减少了一次CPU拷贝
        • 上述拷贝步骤read 都需要从磁盘到内核缓冲区 内核缓冲区到用户缓冲区
  2. 零拷贝的实现方式
    • 零拷贝定义 并不是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 + write440
read + write + DMA422
mmap + witte412
sendfile212
sendfile + gatherDMA202
splice202
  1. go语言的实践 代码
  2. 零拷贝的应用
    • 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的一些解释