基于DOAS文件系统(DFS后端)暴露的SPDK块设备

整体架构

daos_spdk_bdev_arch

步骤

  1. 编译daos, 记录daos安装目录, 比如/opt/daos, 启动daos_agent, daos_engine, daos_server

  2. 编译spdk

    git clone https://github.com/spdk/spdk.git

    git submodule update –init

    ./configure –with-daos #指定daos include目录和lib目录

    make -j 16

  3. 启动spdk nvmf_tgt sudo HUGE_EVEN_ALLOC=yes scripts/setup.sh

    sudo ./build/bin/nvmf_tgt -m [21,22,23,24]

  4. 创建传输层

    sudo ./scripts/rpc.py nvmf_create_transport -t TCP -u 2097152 -i 2097152

  5. 创建块设备: ./scripts/rpc.py bdev_daos_create daosdev0 test-pool test-cont 64 4096 # 4K * 64 = 256KB -> |4K|4K|…|

  6. 创建子系统: ./scripts/rpc.py nvmf_create_subsystem $subsystem -a -s SPDK0000000000000$i -d SPDK_Virtual_Controller_$i

  7. 子系统添加命名空间: ./scripts/rpc.py nvmf_subsystem_add_ns $subsystem disk$i

  8. 添加监听: scripts/rpc.py nvmf_subsystem_add_listener $subsystem -t tcp -a ${BIND_IP} -s 4420

  9. nvme客户端连接: nvme connect-all -t tcp -a 172.31.91.61 -s 4420, 得到块设备后, 格式化即可使用, 如: mkfs.ext4 -F -O mmp /dev/nvme1n1

在设计方面,此 bdev 是一个名为 bdev 本身的文件,位于 DAOS POSIX 容器中,每个 io 通道使用 daos 事件队列。 每个 io 通道都有一个事件队列来支撑最佳 IO 吞吐量。 该实现使用每个设备通道的独立池和容器连接以获得最佳 IO 吞吐量

关键函数

bdev_daos_create

​ bdev_get_daos_engine 初始化daos引擎

​ bdev_daos_io_channel_create_cb 用创建通道的方式检测容器连通性, 如果在通道创建过程中,由于参数不正确而发生错误,例如: 池/容器名称错误,或其他一些内部 DAOS 错误(如达到 CART 上下文限制),bdev_daos_io_channel_create_cb() 会发出有关此类错误的信号,但是,spdk_io_device_register() 不会将它们考虑在内。 设备创建成功,返回成功的 RPC 响应并将 bdev 留在 bdev 列表中,但它完全无法使用且不可修改, 尝试在创建通道的时候连接到DAOS容器,所以在这里模拟创建一个通道,这样我们就可以在创建DAOS bdev的时候返回一个失败,而不是等到第一个通道创建的时候才发现,留下不可用的bdev注册

​ spdk_io_device_register 将不透明的 io_device 上下文注册为 I/O 设备。 I/O设备注册后,可以使用spdk_get_io_channel()函数返回I/O通道

​ spdk_bdev_register 注册一个新的 bdev。 必须从 SPDK APP线程调用此函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
大致流程
./scripts/rpc.py bdev_daos_create daosdev0 test-pool test-cont 64 4096  -> from spdk.rpc.client -> 4K * 64 = 256KB -> |4K|4K|...|
SPDK_RPC_REGISTER("bdev_daos_create", rpc_bdev_daos_create, SPDK_RPC_RUNTIME)
rpc_bdev_daos_create -> module/bdev/daos/bdev_daos_rpc.c
  create_bdev_daos
    block_size % 512 512对齐
    daos->disk.fn_table = &daos_fn_table -> static const struct spdk_bdev_fn_table daos_fn_table -> 提交IO请求 -> bdev_daos_submit_request -> ... -> dfs_write ->  src/client/dfs/dfs.c -> daos_array_write
    bdev_get_daos_engine
      daos_init()
    bdev_daos_io_channel_create_cb
      spdk_call_unaffinitized(_bdev_daos_io_channel_create_cb, ch)
        rte_thread_get_affinity(&orig_cpuset)
        spdk_unaffinitize_thread() 移除cpu亲和性
          CPU_ZERO(&new_cpuset)
        _bdev_daos_io_channel_create_cb
          bdev_get_daos_engine
          daos_pool_connect
          daos_cont_open
          dfs_mount(ch->pool, ch->cont, O_RDWR, &ch->dfs)
          dfs_open
          daos_eq_create
        rte_thread_set_affinity(&orig_cpuset) cpu亲和性设回去
      ch->poller = SPDK_POLLER_REGISTER(bdev_daos_channel_poll, ch, 0)
    bdev_daos_io_channel_destroy_cb
      spdk_poller_unregister
      daos_eq_destroy
      dfs_release
      dfs_umount
      daos_cont_close
      daos_pool_disconnect
      bdev_daos_put_engine
    spdk_io_device_register(daos, bdev_daos_io_channel_create_cb, bdev_daos_io_channel_destroy_cb -> lib/thread/thread.c
      thread = spdk_get_thread() 此外,在注册 io 设备之前将 UT 更改为 set_thread()
      dev->create_cb = create_cb = bdev_daos_io_channel_create_cb
      dev->destroy_cb = destroy_cb
      tmp = RB_INSERT(io_device_tree, &g_io_devices, dev) -> io_device_tree_RB_INSERT(&g_io_devices, dev) -> IO设备对象插入红黑树(g_io_devices全局红黑树头) -> 利用宏生成红黑树插入函数: #define RB_GENERATE_INSERT -> name##_RB_INSERT
    spdk_bdev_register -> spdk_bdev_register(struct spdk_bdev *bdev)
      bdev_register(bdev) -> lib/bdev/bdev.c -> bdev_register(struct spdk_bdev *bdev)
        spdk_bdev_get_memory_domains 获取给定 bdev 使用的 SPDK 内存域。 如果 bdev 报告它使用内存域,这意味着它可以使用位于这些内存域中的数据缓冲区。 用户可以调用此函数并将 domains 设置为 NULL 并将 array_size 设置为 0 以获取 bdev 使用的内存域数
        spdk_bdev_is_md_separate
        bdev_alloc_io_stat
        bdev_name_add
        spdk_bdev_get_buf_align
        spdk_io_device_register
      bdev_open
      bdev_examine
      spdk_bdev_wait_for_examine(bdev_register_finished, desc)
    *bdev = &(daos->disk)

关键数据结构

struct bdev_daos *daos;

struct bdev_daos_io_channel ch = {};

注意点

默认容器类型为: OC_SX (oclass SX 保证IOPS优先, 该参数用于数据冗余和保护)

思考

  1. daos bdev优点, 支持rdma和全闪nvme介质, 将daos后端存储能力通过通用的块暴露给应用
  2. 编程思想: 用最少的成本, 尽快返回RPC错误并退出; 分层解耦思想; 独立通道(优先级通道); 用户层尽量开箱即用,复用原来的接口, 降低学习成本

参考

https://spdk.io/doc/bdev.html

https://github.com/ssbandjl/spdk/commit/2e283fcb67a8ee1d9b4f470f17bec57bbe3adad5

https://docs.daos.io/v2.3/user/blockdev/

https://www.cnblogs.com/whl320124/articles/10064186.html

https://rootw.github.io/2018/05/SPDK-subsys-bdev/

Author 晓兵

首发链接: https://blog.csdn.net/ssbandjl

博客: https://logread.cn | https://blog.csdn.net/ssbandjl

weixin: ssbandjl

公众号: 云原生云

云原生云