首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >全闪存储为何首选柔性数组 [0] 与 iovec作为 黄金组合

全闪存储为何首选柔性数组 [0] 与 iovec作为 黄金组合

作者头像
早起的鸟儿有虫吃
发布2026-05-15 10:42:02
发布2026-05-15 10:42:02
730
举报

struct iovec 你了解多少

代码语言:javascript
复制

https://codebrowser.dev/glibc/glibc/misc/bits/types/struct_iovec.h.html
struct iovec
  {
    void *iov_base;	/* Pointer to data.  */
    size_t iov_len;	/* Length of data.  */
  };

你知道的:

struct iovec 是和 readvwritevsendmsg` 等系统调用深度绑定的标准接口。

struct iovec 是**用户态与内核态之间高效传递 I/O 内存布局结构

特点1:iovec 零拷贝方式 (一次 writev 调用)

使用标准 iovec,内核可以直接从你不同内存区域取数据,拼成网络包发出去, 完全不需要中间拷贝。 如果自定,就必须先拷贝到一个大缓冲区,再传给 write,多了一次开销

代码语言:javascript
复制
struct iovec iov[2];// 描述 headeriov[0].iov_base = &header;iov[0].iov_len  = sizeof(header);// 描述 bodyiov[1].iov_base = body_ptr;iov[1].iov_len  = body_len;// 一次调用,内核直接从这两个分散的地址取数据发出去ssize_t n = writev(fd, iov, 2); 

对比理解:为什么使用 memcpy

它真正的优势,是把N个分散的内存片段,、 在逻辑上集结成一个完整的数据包进行一次性传递, 从而免除了应用层手动组装数据的内存和时间开销。 这就是分层的艺术。

传统方式(用户态拷贝): text

内存区域A (req_header) ─┐ ├─→ malloc一块大内存 ─→ memcpy拼凑整齐 ─→ 内核再拷贝这个大内存 内存区域B (param_array) ─┘

这里是两次拷贝。第一次是用户态自己拼凑,第二次才是内核拷贝。

iovec 方式(内核聚合并拷贝)

text

内存区域A (req_header) ─┐ ├─→ 直接告诉内核这两块的位置 ─→ 内核自己拷贝到目标 内存区域B (param_array) ─┘

内核直接从用户态的分散内存中拷贝数据。省掉了用户态 memcpy 的那次开销

这里的零拷贝”指的是免除了应用层进行数据归并的拷贝

特点2:谁负责iovec内存申请?

自己不申请内存吗?

不申请

它只负责指和量。它指向的内存必须由用户代码提前分配。

大小怎么确定 大小是完全由上层业务逻辑决定的。

iovec 只是 本地进程内的内存描述符

有效范围:当前用户进程 ↔ 本地内核

无效范围:跨进程、跨节点、跨控制器、跨机器

特点3:iovec 引用外部内存,如何做线程间内存隔离防篡改?

无锁全闪架构下,绝对不通过「锁」防止修改

唯一正确方案:让外部内存的「所有权」只属于当前线程 / 协程,绝不共享给其他线程;

SPDK + HyperTunnel 协程 天生支持线程绑核、协程不跨线程

1

所有内存(包括给 iovec 用的外部内存)都从线程本地内存池分配

2

每个 CPU 核 / 线程有独立的内存池,其他线程无法访问、无法修改

3

iovec 只在当前线程的协程间传递,完全隔离外部线程

✅ 效果:其他线程连内存地址都拿不到,不可能修改

✅ 性能:零开销,完美匹配无锁架构

通过协程绑核实现不跨线程,通过线程专属内存实现隔离,通过即时拷贝杜绝篡改,单线程协程天然无锁安全。

系统设计: 聪明你想到了

各自业务处理函数 都是在协程执行的,一般不会加锁处理,内存地址无法访问问题

提前分配和释放各自req ack 就是更好 利用内存结构

特点4:指针大小永远是固定的4,如何校验iov_len长度?

基础补充:指针和数组区别 还有更好的定义方式吗?

Snap param[0] 是 C 语言的柔性数组, 专门用来实现「固定头 + 变长体」的连续内存请求, 它不是指针,只是标记了结构体末尾的变长数据位置,

它不是指针:结构体本身不会为 param 分配任何内存空间,sizeof(xx) 的结果里不包含 param 的大小。

它是一个「占位标记」:表示这个结构体的内存后面,可以直接跟着任意数量的 Snap 元素,整个请求是一块连续的内存。

指针和数组的区别:主要在 sizeof 行为、内存分配、以及作为函数参数时的退化

举例说明:

代码语言:javascript
复制
typedef struct { int x, y; } SnapParam; //定义一个就结构typedef struct {    int snapNr; //整个BatchReq大小    SnapParam param[0];//不占用任何空间,必针还节省} BatchReq;int main() {    int nr = 3;    BatchReq *p = malloc(sizeof(BatchReq) + sizeof(SnapParam) * nr);//为什么多申请        //固定头部 (BatchReq) | 柔性数组数据 (nr 个 SnapParam) |    p->snapNr = nr;    // 访问元素    p->param[0].x = 10;   // 第一个    p->param[1].x = 20;   // 第二个    (p->param+2)->x = 30; // 第三种写法    free(p);    return 0;}

柔性数组 param[0] 只是个占位符,不占空间。

多申请的内存 = 存放变长数组的真实数据。

整块连续内存 = 最适合 iovec 一次 IO 传输

代码语言:javascript
复制
typedef struct {
    int snapNr;         // 固定成员
    // 其他固定字段...
    SnapParam param[0]; // 柔性数组:0大小,不占结构体空间!
} BatchReq;
// 分配内存
BatchReq *p = malloc(
    sizeof(BatchReq)      // 1. 给【固定头部】分配内存
    +                     // +
    sizeof(SnapParam) * nr // 2. 额外给【柔性数组】分配内存(你问的"多申请")
);
//这里没有list vector这样结构

为什么这些场景都用柔性数组?(核心优势)

1

内存连续 → 完美适配 iovec、网络传输、存储 IO

2

一次分配 / 释放 → 高性能、无碎片、无泄漏

3

不浪费空间 → 结构体大小固定,变长数据按需申请

4

无指针开销 → 跨线程 / 跨节点 / 跨进程更安全

为什么不用 vector/list?

1

底层存储是纯 C 开发,没有 C++ STL

2

iovec / 网络 / NVMe 必须要连续裸内存

3

柔性数组 = 协议原生格式,直接收发;STL 封装结构无法跨节点

4

高性能无锁架构,拒绝 STL 任何额外开销

分布式文件系统、全闪存储、SPDK、内核模块 全部使用纯 C 开发

vector / listC++ STL 容器,底层依赖 C++ 编译器、异常机制、模板、运行时库

底层系统禁用 C++ STL(不稳定、不可控、性能不可控) → 从技术选型上就直接排除

性能层面:亿级 IOPS 不允许 STL 有任何开销

你的全闪架构追求:纳秒级、无锁、零开销

柔性数组:纯裸内存,0 开销

vector/list:

内部有边界检查、内存分配、迭代器

线程不安全,加锁会毁掉性能

动态扩容会产生内存拷贝

→ 高性能存储 绝对容忍不了

内存管理:底层必须手动控制,不能交给 STL

柔性数组:一次 malloc,一次 free

配合 SPDK 内存池、用户态内存池完美使用

vector 自动管理内存,你无法控制物理地址、锁、分配方式

内存必须连续!(iovec / 网络 / 存储硬件 强制要求)

你的核心组件:

iovec 只认连续内存

网络跨节点传输必须连续裸数据

RDMA / NVMe SSD 只接受连续物理内存

对比:

柔性数组 [0]:结构体 + 数据 = 一整块连续内存

list(链表):内存完全分散,根本无法用于 IO / 网络

vector:虽然内存连续,但它是封装对象,不是裸数据布局

案例

全闪存储 - NVMe/SPDK 批量 IO 请求

用途:全闪存储用户态 IO 栈,批量读写磁盘 LBA 地址

适配:SPDK、无锁、协程、连续物理内存

KV 数据库 - 批量键值对操作

用途:数据库批量 Put/Get 请求,网络协议报文

优势:无碎片、一次序列化、高性

分布式存储中 柔性数组[0] 搭配 iovec 的核心优势

1. 内存天然连续,完美适配 iovec 模型

柔性数组实现协议头+变长数据一整块连续内存,只需一个 iovec 条目就能描述完整报文; 无需拆分多段零散缓冲区,适配 readv/writev、SPDK、RDMA 分散聚合IO,极简高效。

2. 无二级指针,跨节点/跨进程通信绝对安全

不用结构体内嵌指针,整块都是裸内存二进制布局; 跨节点网络传输、用户态↔内核态拷贝时,不存在指针地址失效、野指针、跨地址空间非法访问问题,无需序列化适配。

3. 内存管理极简,高并发无碎片

整体一次 malloc 申请、一次 free 释放; 适配分布式存储内存池、协程调度模型,无内存泄漏、无零散内存碎片,支撑亿级IOPS高并发。

4. 适配无锁协程架构,规避线程篡改风险

整块内存自包含,不依赖外部零散buffer; 在单线程协程无锁模型下,无需共享外部指针、不用加锁,从布局上杜绝多线程篡改、内存悬空问题。

5. 协议与内存布局合一,省去编解码开销

柔性数组结构体布局直接等于网络报文/磁盘物理格式; 直接把内存地址交给 iovec 即可收发落盘,无需手动拼接缓冲区、无需额外序列化/反序列化,节省CPU开销。

6. 原生支持零拷贝,匹配全闪硬件低时延

连续内存可直接被 SPDK/RDMA 锁定物理页,支持零拷贝IO; iovec + 柔性数组组合,省去数据二次拷贝,适配全闪存储纳秒级时延设计。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-05-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 后端开发成长指南 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • struct iovec 你了解多少
    • 特点3:iovec 引用外部内存,如何做线程间内存隔离防篡改?
    • 特点4:指针大小永远是固定的4,如何校验iov_len长度?
    • 为什么这些场景都用柔性数组?(核心优势)
  • 内存必须连续!(iovec / 网络 / 存储硬件 强制要求)
  • 案例
  • 分布式存储中 柔性数组[0] 搭配 iovec 的核心优势
    • 1. 内存天然连续,完美适配 iovec 模型
    • 2. 无二级指针,跨节点/跨进程通信绝对安全
    • 3. 内存管理极简,高并发无碎片
    • 4. 适配无锁协程架构,规避线程篡改风险
    • 5. 协议与内存布局合一,省去编解码开销
    • 6. 原生支持零拷贝,匹配全闪硬件低时延
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档