5.8 KiB
5.8 KiB
架构目标
- 通过 LD_PRELOAD hook POSIX 文件操作(open/read/write/pread/pwrite/close/fsync 等),将 MySQL 的数据文件 IO 重定向到 SPDK Blobstore。
- 最大化性能:绕过内核、利用多核并发、低延迟、小块写合并。
- 核心原则:每个 pthread 拥有独立的 SPDK 执行上下文,全局共享底层存储资源。
全局资源(进程级别,唯一一份)
zvfs_t *g_fs:文件系统实例,包含:struct spdk_blob_store *bs:全局 Blobstore(通过 spdk_bs_load/init 创建)。- bdev(Nvme0n1 或 Malloc0,通过 JSON 配置加载)。
- 全局元数据:dirents 数组(zvfs_dirent_t *[])、fd_table(zvfs_file_t *[])、openfd_count。
- 保护全局元数据的锁:pthread_rwlock_t g_meta_lock(读多写少场景)。
- 全局初始化标志:
bool g_mounted、bool g_env_inited。 - pthread_key_t 用于线程本地存储:
g_thread_local_key(带 destructor)。
线程本地资源(每个 pthread 独占一份,通过 TLS 实现)
每个 pthread 拥有以下私有状态,存储在结构体 thread_local_zvfs_t 中:
typedef struct {
struct spdk_thread *thread; // 本线程专属的 SPDK thread
struct spdk_io_channel *channel; // 本线程专属的 IO channel(绑定到 g_fs->bs)
TAILQ_HEAD(, io_ctx) pending_queue; // 本线程的 pending IO 队列,用于 batch poll
// 可选扩展:
// struct dma_buf_pool *dma_pool; // per-thread DMA buf 复用池
// struct page_cache *local_cache; // 如果需要 per-thread cache
} thread_local_zvfs_t;
- 创建时机:lazy(第一次 IO 时调用
get_thread_local())。 - 存储方式:通过
pthread_setspecific(g_thread_local_key, tl)绑定到当前 pthread。 - 销毁时机:pthread 退出时,TLS destructor 自动调用:
- spdk_bs_free_io_channel(channel)
- spdk_thread_exit + poll until exited + spdk_thread_destroy
核心函数:get_thread_local()
thread_local_zvfs_t *get_thread_local(void) {
// 确保 key 已创建(只执行一次)
pthread_once(&g_key_once, init_thread_key);
thread_local_zvfs_t *tl = pthread_getspecific(g_thread_local_key);
if (tl == NULL) {
tl = calloc(1, sizeof(*tl));
tl->thread = spdk_thread_create("zvfs_worker", NULL);
tl->channel = spdk_bs_alloc_io_channel(g_fs->bs);
TAILQ_INIT(&tl->pending_queue);
pthread_setspecific(g_thread_local_key, tl);
}
return tl;
}
工作流程(每个 pthread 独立执行)
-
线程首次进入 IO 操作
- 调用
get_thread_local()→ 创建并绑定 thread + channel。 - 如果 !g_mounted → 调用 zvfs_ensure_mounted()(使用当前 thread 进行 poll 完成 mount)。
- 调用
-
元数据操作(open/unlink/mkdir/rmdir/rename 等)
- 加读锁(g_meta_lock)检查/修改全局 dirents、dirs、fd_table。
- 创建/查找 zvfs_file_t,调用 zvfs_create/zvfs_open(使用当前 thread 同步等待)。
- 分配伪 fd,记录到全局 fd_table。
- 释放锁。
-
读操作(read/pread)
- 获取当前 tl = get_thread_local()。
- spdk_set_thread(tl->thread)。
- 如果小读 + cache hit → 直接 memcpy 返回。
- 否则:创建 io_ctx,加入 tl->pending_queue。
- 调用 spdk_blob_io_read(..., tl->channel, ...)。
- 执行 batch_poll(tl, my_ctx):
- while (!my_ctx->done) spdk_thread_poll(tl->thread, 0, 0);
- 从 dma_buf 拷贝到用户 buf。
-
写操作(write/pwrite)
- 获取 tl。
- spdk_set_thread(tl->thread)。
- 如果小写 → patch per-file page cache(dirty),标记 dirty,返回(延迟写)。
- 如果 cache 满或大写 → flush dirty pages(batch spdk_blob_io_writev,用 tl->channel)。
- 创建 io_ctx → 加入 pending_queue → submit write → batch_poll。
-
fsync
- flush per-file dirty cache(batch writev + spdk_blob_sync_md)。
- 使用当前 tl->thread poll 等待完成。
-
close
- fsync(flush cache)。
- zvfs_close(用当前 tl->thread 同步)。
- 释放 fd(加锁更新全局 fd_table)。
性能关键机制
- 独立 poll:每个 pthread 用自己的 spdk_thread 独立 poll,无跨线程消息。
- batch poll:一个 poll 循环可完成多个 pending IO,提升有效 QD。
- page cache:per-file 4K dirty pages(hashmap),合并小写,减少 write amplification。
- channel per-thread:避免全局 channel 争用,每个线程独立提交 IO。
- 最小全局锁:只在元数据修改时短时加锁(rwlock),IO 操作无锁。
资源所有权总结表
| 资源类型 | 所有权 | 数量 | 创建时机 | 销毁时机 |
|---|---|---|---|---|
| bdev | 全局 | 1 | zvfs_ensure_mounted | zvfs_umount |
| blobstore (bs) | 全局 | 1 | zvfs_ensure_mounted | zvfs_umount |
| zvfs_t / g_fs | 全局 | 1 | zvfs_ensure_mounted | zvfs_umount + free |
| dirents / fd_table | 全局 | 1 | meta_load | zvfs_umount + free |
| spdk_thread | per-pthread | = pthread 数 | 首次 get_thread_local | pthread 退出(destructor) |
| io_channel | per-pthread | = pthread 数 | 首次 get_thread_local | pthread 退出(destructor) |
| pending_queue | per-pthread | = pthread 数 | 首次 get_thread_local | pthread 退出 |
| page cache | per-file | per open fd | open 时 lazy | close 时 flush + free |