DAOS系统架构-Data Plane
daos: 2.6.0
1. 概述
Data Plane也称为daos_engine(即I/O Engine)。daos_engine是一个多线程进程,每个daos_engine都有一组target xstream set,主要负责I/O处理。每组中target xstream set的数量由dss_tgt_nr指定。每个target xstream set都包含1个main xstream和一组offload xstream。offload xstream的数量由dss_tgt_offload_xs_nr指定。另外每个daos_engine还有1个system xstream set,负责系统级别的任务处理(比如元数据请求处理)。
2. 模块接口
I/O Engine提供了一种DAOS module接口,该接口允许按照需求加载服务端侧的代码。每个DAOS module实际上都是一个动态库,这些动态库都是由I/O Engine通过dlopen加载的。目前DAOS module有:object、pool、container、management、vos,DAOS module与I/O Engine之间的接口被定义在dss_module数据结构中。
每个DAOS module都应该包含以下信息:
- DAOS module的名字
- DAOS module的ID
- 功能掩码
- DAOS module的初始化函数和结束函数
另外,每个DAOS module还可以进行一些可选的配置:
- 在整个堆栈启动并运行后,调用的清理函数
- CART RPC 句柄
- dRPC 句柄
DAOS module 接口
struct dss_module {
/* Name of the module */
const char *sm_name;
/* Module id see enum daos_module_id */
int sm_mod_id;
/* dRPC handlers, for unix socket comm, last entry must be empty */
struct dss_drpc_handler *sm_drpc_handlers;
......
}
DAOS module 结构定义
struct dss_module mgmt_module = {
.sm_name = "mgmt",
.sm_mod_id = DAOS_MGMT_MODULE,
.sm_ver = DAOS_MGMT_VERSION,
.sm_proto_count = 2,
.sm_init = ds_mgmt_init,
.sm_fini = ds_mgmt_fini,
.sm_setup = ds_mgmt_setup,
.sm_cleanup = ds_mgmt_cleanup,
.sm_proto_fmt = {&mgmt_proto_fmt_v2, &mgmt_proto_fmt_v3},
.sm_cli_count = {MGMT_PROTO_CLI_COUNT, MGMT_PROTO_CLI_COUNT},
.sm_handlers = {mgmt_handlers_v2, mgmt_handlers_v3},
.sm_drpc_handlers = mgmt_drpc_handlers,
};
DAOS module 加载
dss_module_load(const char *modname) {
/* load the dynamic library */
sprintf(name, "lib%s.so", modname);
handle = dlopen(name, RTLD_LAZY | RTLD_GLOBAL);
/* lookup the dss_module structure defining the module interface */
sprintf(name, "%s_module", modname);
smod = (struct dss_module *)dlsym(handle, name);
......
}
3. 线程模型与Argobot集成
I/O Engine是一个使用Argobots进行非阻塞处理的多线程进程。
默认情况下,系统会为每个target创建1个main xstream,0个offload xstream。offload xstream的数量可以通过daos_engine命令行参数进行配置。此外系统还会创建1个额外的xstream,用来处理元数据请求。每个xstream与1个CPU core绑定。main xstream接收来自客户端和其他servers向target发起的请求。另外,一个特殊的ULT会被启动,用来推进网络和NVMe I/O操作。
static int dss_xstreams_init(void) {
/* start system service XS */
for (i = 0; i < dss_sys_xs_nr; i++) {
xs_id = i;
rc = dss_start_xs_id(tags, xs_id);
if (rc)
D_GOTO(out, rc);
tags &= ~DAOS_RDB_TAG;
}
/* start main IO service XS */
for (i = 0; i < dss_tgt_nr; i++) {
xs_id = DSS_MAIN_XS_ID(i);
rc = dss_start_xs_id(DAOS_SERVER_TAG, xs_id);
if (rc)
D_GOTO(out, rc);
}
/* start offload XS if any */
for (i = 0; i < dss_tgt_nr; i++) {
int j;
for (j = 0; j < dss_tgt_offload_xs_nr /
dss_tgt_nr; j++) {
xs_id = DSS_MAIN_XS_ID(i) + j + 1;
rc = dss_start_xs_id(DAOS_OFF_TAG, xs_id);
if (rc)
D_GOTO(out, rc);
}
}
}
4. Thread-local Storage(TLS)
每个xstream会分配私有的存储空间,该存储空间可以通过dss_tls_get函数访问。在每个DAOS module注册时,每个DAOS module都可以指定一个module key以及与该key相关联的一个数据结构,该数据结构将会在TSL中被每个xstream分配。dss_module_key_get函数会获取到该数据结构。
static inline void *
daos_module_key_get(struct daos_thread_local_storage *dtls, struct daos_module_key *key)
{
...
return dtls->dtls_values[key->dmk_index];
}
5. Incast Variable集群
DAOS会使用IV(incast variable)在同一个IV命名空间内的servers之间共享values和statuses。其中该命名空间被组织为树形结构。树的根节点被称之为IV leader,而servers既可以是叶子节点也可以是非叶子节点。每个server维护自己的IV cache。在fetch期间,如果local cache中的数据无法满足请求,则会将请求转发到父节点,直到到达根节点。至于update操作,它会先更新local cache,然后转发到父节点,直到到达根节点,根节点在将更改传播到其他servers。IV命名空间是按照pool划分的,当创建pool时便会创建该IV命名空间,并伴随着pool的销毁而销毁。为了使用IV,每个IV用户都需要在IV命名空间下注册自己以获取一个ID,然后在该IV命名空间下使用这个ID对自己的IV值进行fetch或者update操作。
6. dRPC Server
I/O Engine包含一个dRPC server,该server会监听给定的Unix套接字上的活动。关于dRPC,请参考dRPC。
dRPC server会定期的轮询传入的客户端连接和请求。它可以通过struct drpc_progress_context对象处理多个并发的客户端连接,该结构用于管理struct drpc对象和任何活着的客户端连接。
dRPC server轮询是运行在它自己的User-Level Thread (ULT)中,dRPC socket已经被设置为非阻塞模式,并且轮询的超时时间为0,这使得server可以运行在UTL中而不是它自己的xstream中。这种通道的流量是相对较低的。
static void drpc_listener_run(void *arg) {
struct drpc_progress_context *ctx;
D_ASSERT(arg != NULL);
ctx = (struct drpc_progress_context *)arg;
D_INFO("Starting dRPC listener\n");
set_listener_running(true);
while (is_listener_running()) {
int rc;
/* wait a second */
rc = drpc_progress(ctx, 1000);
if (rc != DER_SUCCESS && rc != -DER_TIMEDOUT) {
D_ERROR("dRPC listener progress error: "DF_RC"\n",
DP_RC(rc));
}
ABT_thread_yield();
}
D_INFO("Closing down dRPC listener\n");
drpc_progress_context_close(ctx);
}
6.1. dRPC Progress
drpc_progress表示dRPC server循环的一个迭代,工作流如下:
- 对监听套接字和客户端连接进行轮询。
- 如果在客户端连接上看到了任何活动:
- 如果由数据传入,则调用drpc_recv来处理传入的数据。
- 如果客户端已断开连接或连接已中断,则释放struct drpc对象,并从drpc_progress_context中移除。
- 如果在监听套接字上看到了任何活动:
- 如果有新的连接接入:调用drpc_accept,并在新的struct drpc对象添加到drpc_progress_context中的客户端连接列表里。
- 如果发生错误了,则向调用者返回
-DER_MISC。它会引起I/O Engine记录此次错误,但不会打断dRPC server循环。
- 如果未检测到任何活动,则向调用者返回
-DER_TIMEDOUT。这存粹是为了调试。实际上,I/O Engine会忽略该错误码,因为没有任何活动实际上并不代表发生错误。
6.2. dRPC Handler Registration
每个DAOS module都可以为一个或多个dRPC module IDs注册一个句柄函数来实现对dRPC消息的处理。
句柄的注册非常简单。在dss_module中,sm_drpc_handlers是一个struct dss_drpc_handler数组,当该字段设置为NULL是,表示没有注册任何句柄。当I/O Engine加载DAOS module时,他将会自动注册所有的dRPC句柄。
dRPC module ID与DAOS module ID是不同的。因为根据功能不同,一个DAOS module可能需要注册多个dRPC module ID。
dRPC server使用drpc_hdlr_process_msg来处理传入的消息。此函数会检查传入的消息的dRPC module ID,然后搜索一个句柄,如果找到了就执行该句柄,并返会Drpc_Response。如果没有找到句柄,它将生成其自身的Drpc_Response,以表示该dRPC module ID并没有被注册。