这是一个有点绕口的标题。我们先来拆分一下,里面包含了三个概念:命名空间,非对称访问,以及多路径。多路径(multipath),可以针对单点故障提供容错能力,还可以为I/O流量提供负载平衡。当多路径驱动程序检测到活动路径的I/O错误时,它会将流量从故障路径转移到设备指定的辅助路径。当首选路径恢复正常时,可以将控制权返回到首选路径。本文主要围绕一个测试用例来介绍这些功能在SPDK中的支持和实现。
一
先看几个名词
1►
非对称命名空间访问 (ANA)
共享命名空间(namespace)可以借助于多PCIe端口或NVMe over Fabrics等网络端口,通过NVMe存储控制器(controller)被访问。如果,controller的namespace访问特征受到NVM subsystem内部配置的影响,那么就会发生非对称访问(Asymmetric Namespace Access , 简称ANA),反之称为对称访问。
ANA的状态,有Optimized(最优访问)和Non-Optimized(非最优访问),还有Inaccessible(不可访问)等,SPDK暂不支持Persistent Loss(持久消失)。
图1.1展示了一个非对称访问的例子,这是一个包含双端口NVMe SSD的subsystem,呈现了两个独立的域。NVMe namespace B (图中NS B)和namespace C (图中NS C)都包含在controller2所在的域里。也就是说,controller 2提供对namespace B和namespace C的最优访问,而controller 1不能。
图1.1 controller 2提供对namespace B的最优访问
为了让controller 1提供对namespace B最优访问,NVM subsystem可以重新配置或可以自动执行内部配置动作来改变,如图1.2所示。controller 2提供对namespace C的最优访问,而controller 1提供对namespace B的最优访问。
图1.2 controller 1提供对namespace B的最优访问
2►
多路径I / O和命名空间共享
根据NVMe协议,多路径I/O指的是主机和命名空间之间,两个或多个完全独立的PCIe路径。命名空间共享,是指两个或多个主机使用不同的NVMe控制器,访问公共共享命名空间的能力,如图1.3所示。(注:多路径I/O和命名空间共享都需要NVM系统包含两个或多个控制器。)
图1.3:具有两个控制器和两个端口的NVM子系统
3►
bpftrace脚本
bpftrace.sh 是一个帮助脚本,有助于针对正在运行的SPDK 应用程序,运行 bpftrace 脚本。它使用了数据包筛选器(Berkeley Packet Filter,简称BPF),所以要求SPDK在配置编译选项时使能userspace DTrace探针,./configure --with-usdt。
这是一个典型的用法:
scripts/bpftrace.sh `pid of spdk_tgt` scripts/bpf/nvmf.bt
通过nvmf.bt的筛选,SPDK 应用程序将打印出有关 NVMF子系统和轮询组信息状态转换的信息,如下所示:
2110.935735: nvmf_tgt reached state NONE
2110.954316: nvmf_tgt reached state CREATE_TARGET
2110.967905: nvmf_tgt reached state CREATE_POLL_GROUPS
2111.235982: nvmf_tgt reached state START_SUBSYSTEMS
2189.921492: nqn.2016-06.io.spdk:cnode1 change state from INACTIVE to ACTIVE start
2189.952508: nqn.2016-06.io.spdk:cnode1 on thread 2 state to ACTIVE start
2189.959125: nqn.2016-06.io.spdk:cnode1 on thread 2 state to ACTIVE done
2190.005832: nqn.2016-06.io.spdk:cnode1 change state from INACTIVE to ACTIVE done
关于SPDK BPF的详细使用方法,请参考之前的微信文章:SPDK的BPF Tracing
二
ANA multipath的验证
SPDK新增了支持ANA multipath验证的实例。在这个例子中,我们首先在target和host之间建立两条I/O路径(含两个端口),然后改变其中一个端口的ANA state。通过bpfrace脚本产生实时的与NVMF相关的I/O路径直方图,进而验证当前port是否是active port,确定活动的I/O路径。
图2.1显示了I/O路径的基本框架,本例的单网卡双端口环境比较简单,仅作为验证需要。如果用户环境允许的话可以用两个物理网卡测试。需要说明的是,测试脚本是建立在RPC交互命令之上,涉及到subsystem、transport等与NVMF相关的一些名词,关于这些名词的说明请参考附录。接下来看下具体的步骤。
图2.1:ANA multipath框架示例
1►
启动target server
我们在target端运行nvmf_tgt,然后创建nvmf tcp传输、侦听tcp端口的nvmf子系统,以及作为命名空间添加到nvmf子系统的malloc bdev。
build/bin/nvmf_tgt -i 0 -e 0xFFFF -m 0x3 &
rpc.py nvmf_create_transport -t tcp -o -u 8192
rpc.py bdev_malloc_create 64 512 -b Malloc0
rpc.py nvmf_create_subsystem nqn.2016-06.io.spdk:cnode1 -a -s SPDK1 -r -m 2
rpc.py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 Malloc0
还是在target端,添加两个listener,
rpc.py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t tcp -a 10.0.0.2 -s 4420
rpc.py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t tcp -a 10.0.0.2 -s 4421
并设置ANA state,选择4421端口所在的路径为最优路径,并打印。
rpc.py nvmf_subsystem_listener_set_ana_state nqn.2016-06.io.spdk:cnode1 -t tcp -a 10.0.0.2 -s 4420 -n non_optimized
rpc.py nvmf_subsystem_listener_set_ana_state nqn.2016-06.io.spdk:cnode1 -t tcp -a 10.0.0.2 -s 4421 -n optimized
rpc.py nvmf_subsystem_get_listeners nqn.2016-06.io.spdk:cnode1 | jq '.[] | .ana_states[0]'
配置好的ana_states数组显示如下:
{
"ana_group": 1,
"ana_state": "optimized"
}
{
"ana_group": 1,
"ana_state": "non_optimized"
}
2►
启动host端
这时我们在host端启动一个app(比如bdevperf)来接收RPC 命令,
test/bdev/bdevperf/bdevperf -m 0x4 -z -q 128 -o 4096 -w verify -t 60 &
同样在host端创建NVMe控制器,设置multipath行为类型(failover或multipath)。回看图2.1,host端看到的Nvme0就是target端的Malloc0。
rpc.py bdev_nvme_attach_controller -b Nvme0 -t tcp -a 10.0.0.2 -s 4420 -f ipv4 -n nqn.2016-06.io.spdk:cnode1 -l -1 -o 10
rpc.py bdev_nvme_attach_controller -b Nvme0 -t tcp -a 10.0.0.2 -s 4421 -f ipv4 -n nqn.2016-06.io.spdk:cnode1 -x multipath -l -1 -o 10
回到target端,尝试改变4421端口listener的ANA state。
rpc.py nvmf_subsystem_listener_set_ana_state nqn.2016-06.io.spdk:cnode1 -t tcp -a 10.0.0.2-s 4420 -n non_optimized
rpc.py nvmf_subsystem_listener_set_ana_state nqn.2016-06.io.spdk:cnode1 -t tcp -a 10.0.0.2 -s 4421 -n inaccessible
再在host端运行bdevperf脚本,设置时长2分钟。
test/bdev/bdevperf/bdevperf.py -t 120 perform_tests
在target端使用bpftrace.sh脚本产生NVMF路径相关的trace日志文件。
scripts/bpftrace.sh “nvmf_tgt的进程号” scripts/bpf/nvmf_path.bt &> trace.txt
打印trace.txt获得I/O路径直方图。
Attaching 4 probes...
@path[10.0.0.2, 4421]: 20764
@path[10.0.0.2, 4421]: 21053
@path[10.0.0.2, 4421]: 21008
在target端查看当前活动的端口,是不是trace文件里记录的端口,这里应是4421。
rpc.py nvmf_subsystem_get_listeners nqn.2016-06.io.spdk:cnode1 |
jq -r '.[] | select (.ana_states[0].ana_state=="optimized")'
图2.2:两个端口ANA state的变化过程
当断开port2的listener,我们会看到SPDK会选择仅存的non_optimized 状态的端口作为I/O路径。
rpc.py nvmf_subsystem_remove_listener nqn.2016-06.io.spdk:cnode1 -t tcp -a 10.0.0.2 -s 4421
再重连port2的listener,并恢复端口的optimized 状态,active I/O路径为又会回到port2 。
rpc.py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t tcp -a 10.0.0.2 -s 4421
rpc.py nvmf_subsystem_listener_set_ana_state nqn.2016-06.io.spdk:cnode1 -t tcp -a 10.0.0.2 -s 4421 -n optimized
到这里,这个实例算是演示结束了。以上命令和步骤会整理到
3►
multipath和failover
为了弄明白multipath和failover的区别,我们来看下SPDK代码里RPC命令bdev_nvme_attach_controller是怎么处理这两个参数的。先看这3个参数,
-x 设置多路径行为 (disable关闭, failover故障切换, multipath多路径)
-l 删除ctrlr之前,等待ctrlr重新连接的时间,-1表示无限次重试。0表示没有重新连接重试。
-o 延迟重新连接重试的时间。
rpc.py bdev_nvme_attach_controller -b Nvme0 -t tcp -a 10.0.0.2 -s 4420 -f ipv4 -n nqn.2016-06.io.spdk:cnode1 -l -1 -o 10 -----①
rpc.py bdev_nvme_attach_controller -b Nvme0 -t tcp -a 10.0.0.2 -s 4421 -f ipv4 -n nqn.2016-06.io.spdk:cnode1 -x multipath -l -1 -o 10 -----②
再回看上面示例里的命令,首先由rpc_bdev_nvme_attach_controller( ) 函数解析multipath参数,然后由bdev_nvme_create( )函数创建这个控制器。
1. int bdev_nvme_create(…)
2. /* 处理multipath和failover参数的回调函数不一样 */
3. if (nvme_bdev_ctrlr_get_by_name(base_name) == NULL || multipath)
4. attach_cb = connect_attach_cb;
5. else
6. attach_cb = connect_set_failover_cb;
connect_attach_cb( )会调用nvme_bdev_ctrlr_create( ),
1. static int nvme_bdev_ctrlr_create(…)
2. nbdev_ctrlr = nvme_bdev_ctrlr_get_by_name(name); /* 获得Nvme0控制器指针*/
3. if (nbdev_ctrlr != NULL)
4. /* 如果已经存在,检查是否支持multipath,对应上面第②条命令 */
5. bdev_nvme_check_multipath(nbdev_ctrlr, ctrlr);
6. else
7. /* 如果是新的NVMe控制器,则将该控制器入队,对应上面第①命令 */
8. TAILQ_INSERT_TAIL(&g_nvme_bdev_ctrlrs, nbdev_ctrlr, tailq);
9. /* 把指定的控制器添加到现有控制器队列 */
10. nvme_ctrlr->nbdev_ctrlr = nbdev_ctrlr;
11. TAILQ_INSERT_TAIL(&nbdev_ctrlr->ctrlrs, nvme_ctrlr, tailq);
而在failover的情况下,connect_set_failover_cb则会调用bdev_nvme_add_secondary_trid( ),将辅助路径添加到现有nvme_ctrlr路径队列用以进行故障切换,也就是一次入队操作。
1. static int bdev_nvme_add_secondary_trid(…)
2. TAILQ_FOREACH(tmp_trid, &nvme_ctrlr->trids, link)
3. if (tmp_trid->is_failed && tmp_trid != nvme_ctrlr->active_path_id)
4. TAILQ_INSERT_BEFORE(tmp_trid, new_trid, link);
在检查它是否可以访问与主路径相同的命名空间后,它将断开连接,直到发生故障转移。值得注意的是,SPDK目前不支持transport类型为PCIe的故障切换,只支持同一transport类型,和同一NQN的故障切换。
4►
listener和ANA
关于添加、删除listener和更改ANA状态的RPC命令,都是通过操作码来分配到不同分支来处理的。
1. static void nvmf_rpc_listen_paused()
2. if (ctx->op == NVMF_RPC_LISTEN_ADD)
3. spdk_nvmf_subsystem_add_listener(ctx->subsystem, &ctx->trid, nvmf_rpc_subsystem_listen, ctx);
4. else if (ctx->op == NVMF_RPC_LISTEN_REMOVE)
5. spdk_nvmf_subsystem_remove_listener(subsystem, &ctx->trid);
6. else if (ctx->op == NVMF_RPC_LISTEN_SET_ANA_STATE)
7. nvmf_subsystem_set_ana_state(subsystem, &ctx->trid, ctx->ana_state, ctx->anagrpid, nvmf_rpc_set_ana_state_done, ctx);
RPC命令nvmf_subsystem_add_listener:首先找到对应的target和subsystem,初始化ana_state,默认为OPTIMIZED,接下来更新发现日志,继续侦听。
1. void spdk_nvmf_subsystem_add_listener(…)
2. for (i = 0; i < subsystem->max_nsid; i++)
3. listener->ana_state[i] = SPDK_NVME_ANA_OPTIMIZED_STATE;
4. TAILQ_INSERT_HEAD(&listener->subsystem->listeners, listener, link);
5. nvmf_update_discovery_log(listener->subsystem->tgt, NULL);
6. nvmf_rpc_listen_resumed(…);
RPC命令nvmf_subsystem_remove_listener:找到相对应的控制器,把它的listener指针置空,然后删除subsystem的这个listener结点,最后在每一个I/O 通道上通知更新poll group(附录表A)。
RPC命令nvmf_subsystem_listener_set_ana_state:通过nvmf_subsystem_set_ana_state( )函数,先找到指定的listener,改完ANA state以后,再在每一个I/O 通道上通知更新poll group。实际上是先断开listener,设置好再重连的过程。
►
附录
SPDK NVMe-oF名词 |
解释 |
target |
具有关联命名空间的子系统的集合,以及传输集和关联的网络连接。 |
subsystem |
NVMe-oF 子系统,包含命名空间和控制器,并执行访问控制。 |
namespace(ns) |
NVMe-oF 命名空间,是指块设备层。 |
transport |
网络结构传输方式,有RDMA,TCP等。 |
poll group |
作为一个单元轮询的网络连接集合的抽象。 |
listener |
侦听器,被target接受的新连接网络地址。 |
NQN |
代表主机系统(启动器)。 |
ANA state |
ANA 状态是每个侦听器和每个子系统的,并存储在子系统listener中。 |
表A:SPDK NVMe-oF名称解释
►
参考资料
1. http://www.spdk.io/doc/usdt.md
2. NVM Express base specification revision v1.4
3. NVM Express over Fabrics revision v1.1
转载须知
DPDK与SPDK开源社区
公众号文章转载声明
推荐阅读
SPDK的BPF Tracing
TADK v22.03 Release
手把手教你让英特尔®E810飞起来
DPDK Release 22.03
点点“赞”和“在看”,给我充点儿电吧~