聊聊lustre中的fid和fld

2022-08-17 12:57:46 浏览数 (1)

IO访问流程概览

  • lustre 客户端提供linux kernel中的vfslustre server质检的接口层。lustre客户端是由不同的服务组成,每个服务层提供特定的lustre服务。比如MDC提供访问一个或者多个MDS的服务,OSC提供访问lustre集群中的OST.
  • MGC用来管理管理整个集群配置的客户端,每个MGC都会和MGS进行通信获取集群的最新的配置变更。MDC是lustre集群中MDS的客户端,每个MDC连接到MDS在客户端测提供目录或者文件的元数据管理操作服务。
  • LMVlogical metadata volume简称,它用来聚合多个MDC呈现单一元数据命名空间给客户端,提供透明方式访问所有的MDT。比如文件系统的目录树被分割为多个子树分别存储在多个MDT上,但是对于客户端看到的是统一一致的命名空间。
  • LOVlogical object volume简称聚合多个OSC提供透明访问集群内多个OST.比如数据设置的stripe_count=4,stripe_size=1M,,lustre 客户端内部在挂载的时候会初始化LOV,对应也会初始化4个OSC,每个OSC对应访问集群内的一个OST。集群内有4个OST,文件大小为5M,这时候数据写入的时候,文件stripe后,每个OSC请求OST写入这个文件中stripe_size=1m的分片数据。当访问读取数据的时候,每个OSC请求OST拿到数据,然后经过LOV层进行聚合,提供完整文件的数据。

数据查找和创建访问流程

  • 当在lustre客户端查找一个文件时候,lustre客户端打包rpc请求发送给MDS获取lock(目前有两种锁分别是read lock with lookup-intent锁和write lock with create intent锁).MDS响应客户端返回了三个非常核心重要的数据,分别是a lock、这个文件的所有的metadata attributes、这个文件的file layout extented attribute(EA)file layout是当前集群中包含该文件分片数据的OST列表和布局访问模式(用来描述数据分片对象如何在多个ost上分布),基于这个信息lustre客户端直接可以访问OST获取文件数据。
  • 当文件被创建的时候,lustre客户端请求MDS,MDS根据文件的layout发送rpc请求连接后端的OST,当OST完成了数据对象的创建后,返回FID(一个或者多个)MDS完成这个文件的对象创建。FID是一个128个字节的文件标识。FID是lustre客户端用来文件的唯一标识。每个OST上文件分片对象都是有唯一的FID于此同时每个数据分片在MDT上都会对应该这个文件的FID
  • 当执行挂载lustre客户端的时候,发起mount命令,lustre客户端内部的MGC服务连接到MGS获取整个文件系统的配置信息,挂载整个root目录树

MDT上扩展属性

  • lustre中MDT上对于文件的metadata是存储在mdt上文件的inode上的,而对于lustre 文件系统fid是作为文件或者数据对象的唯一标识。那么客户端请求创建一个文件,在MDT上会有创建fid,这个fid也会对应一个MDT上的inode.这inode上会存储客户端请求创建文件的inode,这个文件布局信息以及跨哪些OST都是以扩展属性保存在这个inode上的.MDT上扩展属性的layout eatrusted.link.定义的value是struct struct lov_mds_md_v3
代码语言:javascript复制
// mds获取到ost上的fid信息的结构
struct ost_id {
	union {
		struct {
			__u64	oi_id;
			__u64	oi_seq;
		} oi;
		struct lu_fid oi_fid;
	};
} __attribute__((packed));

#define lov_ost_data lov_ost_data_v1

// 定义mds上文件stripe的数据分片的信息
struct lov_ost_data_v1 {          /* per-stripe data structure (little-endian)*/
	struct ost_id l_ost_oi;	  /* OST object ID */
	__u32 l_ost_gen;          /* generation of this l_ost_idx */
	__u32 l_ost_idx;          /* OST index in LOV (lov_tgt_desc->tgts) */
};

// mdt 上的文件元数据对应的fid,fid对应的inode存储在inode上的扩展属性的trusted.link的值
struct lov_mds_md_v3 {            /* LOV EA mds/wire data (little-endian) */
	__u32 lmm_magic;          /* magic number = LOV_MAGIC_V3 */
	__u32 lmm_pattern;        /* LOV_PATTERN_RAID0, LOV_PATTERN_RAID1 */
	struct ost_id	lmm_oi;	  /* LOV object ID */
	__u32 lmm_stripe_size;    /* size of stripe in bytes */
	/* lmm_stripe_count used to be __u32 */
	__u16 lmm_stripe_count;   /* num stripes in use for this object */
	__u16 lmm_layout_gen;     /* layout generation number */
	char  lmm_pool_name[LOV_MAXPOOLNAME   1]; /* must be 32bit aligned */
	struct lov_ost_data_v1 lmm_objects[0]; /* per-stripe data */
};

FID文件唯一标识

  • lustre中的不仅仅是stripe文件对象,包括目录的entries、内部配置文件,这些数据都是以对象形式在lustre中存储的。为了标识整个lustre文件系统中每个对象的唯一性,lustre为每个对象提供一个FID作为唯一标识。FID是128位长度长度,包括了三个部分64位的序列号、32位的对象ID、32位的版本号FID的定义如下:
代码语言:javascript复制
struct lu_fid {
	// 申请到的序列号
	__u64 f_seq;
	// 对象的ID
	__u32 f_oid;
	// 版本号
	__u32 f_ver;
} __attribute__((packed));

// 从fid中获取客户端查看的inode
static inline __u64 fid_flatten(const struct lu_fid *fid)
{
	__u64 ino;
	__u64 seq;

	if (fid_is_igif(fid)) {
		ino = lu_igif_ino(fid);
		return ino;
	}

	seq = fid_seq(fid);

	ino = (seq << 24)   ((seq >> 24) & 0xffffff0000ULL)   fid_oid(fid);

	return ino ?: fid_oid(fid);
}
  • Lustre文件系统控制序列号,客户端来申请。lustre中的sequence controller服务是在MDT0上运行,每个MDTOST运行一个sequence server服务。lustr整套服务启动后运行在MDT或者OST上的sequence serverMDT0上的sequence controller进行通信获取一个sequence的范围;每个FID客户端连接到MDT或者OST运行的sequence server服务获取唯一的sequence.
  • lustre客户端创建新的对象的时候,客户端会为这个对象申请在FID中的sequence,是由后端的storage targets中的sequence server授权给客户端的sequence range,客户端基于这个sequence range生成对象FID中的sequenceFID中的sequence number和object id是每个客户端维护的sequence number的计数器和object id的自增序列。如果客户端超出了有MDT或者OST授权sequence range,这时候会再次请求storage target(MDT或者OST)申请一个新的sequence range.
  • 新文件(文件大小> stripe_size)的创建,客户端会使用来自MDT授权的的sequence number来构造新文件的FID,此时的FID是用来标识MDT上这个文件的元数据对象。如果lustre集群有多个OST,这些OST存储文件的实际数据,这种情况下MDS充当FID申请客户端,向OST申请sequence number构造FID来标识每个OST上数据对象。lustre中定义sequence服务端和客户端类型。具体参照如下
代码语言:javascript复制
/* seq client type */
// sequence 服务的客户端类型
enum lu_cli_type {
	// 负责申请mdt上的Sequence 客户端类型
	LUSTRE_SEQ_METADATA = 1,
	// 负责处理ost上数据对象的 sequence客户端类型
	LUSTRE_SEQ_DATA
};

// sequence 服务端类型
enum lu_mgr_type {
		// 
        LUSTRE_SEQ_SERVER,
        LUSTRE_SEQ_CONTROLLER
};

// 运行于mdt上的sequence 服务
static int mdt_seq_init(const struct lu_env *env, struct mdt_device *mdt)
{
	/* init sequence controller server(MDT0) */
	if (ss->ss_node_id == 0) {
		// 运行mdt0上的sequence controller,该controller负责分配seq的范围给sequence server
		rc = seq_server_init(env, ss->ss_control_seq, mdt->mdt_bottom,
				     mdt_obd_name(mdt), LUSTRE_SEQ_CONTROLLER,
				     ss);
		if (rc)
			GOTO(out_seq_fini, rc);
	}
	// 运行其他mdt(包括mdt0)上的sequence server服务,负责给fid客户端分配sequnce来构建fid
	rc = seq_server_init(env, ss->ss_server_seq, mdt->mdt_bottom,
			     mdt_obd_name(mdt), LUSTRE_SEQ_SERVER, ss);
	if (rc)
		GOTO(out_seq_fini, rc);

	// 初始化连接sequence controller的客户端,主要是和mdt0通信,获取sequence range
	rc = mdt_seq_init_cli(env, mdt);
	EXIT;
out_seq_fini:
	if (rc)
		mdt_seq_fini(env, mdt);

	return rc;
}

// 运行于ost上的sequnce服务
int ofd_fid_init(const struct lu_env *env, struct ofd_device *ofd)
{
	// OST上启动一个的Seqence server服务
	rc = seq_server_init(env, ss->ss_server_seq, ofd->ofd_osd, obd_name,
			     LUSTRE_SEQ_SERVER, ss);
	if (rc) {
		CERROR("%s: seq server init error: rc = %dn", obd_name, rc);
		GOTO(out_server, rc);
	}

	// 客户端的初始化
	seq_client_init(ss->ss_client_seq, NULL, LUSTRE_SEQ_DATA,
			name, NULL);
	rc = seq_server_set_cli(env, ss->ss_server_seq, ss->ss_client_seq);
out_name:
	OBD_FREE(name, len);

	return rc;
}
  • 每个ost或者mdt上根据seq_server_handle请求的类型来判断是请求mdt0上的sequence controller还是 非mdt0和ost上的sequence server.
代码语言:javascript复制
// sequence rpc请求的op type
enum seq_rpc_opc {
        SEQ_QUERY                       = 700,
        SEQ_LAST_OPC,
        SEQ_FIRST_OPC                   = SEQ_QUERY
};

// sequence服务的类型类型
enum seq_op {
        SEQ_ALLOC_SUPER = 0,
        SEQ_ALLOC_META = 1
};


// 请求的查询sequence rpc 的op_type
struct req_msg_field RMF_SEQ_OPC =
        DEFINE_MSGF("seq_query_opc", 0,
                    sizeof(__u32), lustre_swab_generic_32s, NULL);


// 请求sequence range范围
struct req_msg_field RMF_SEQ_RANGE =
        DEFINE_MSGF("seq_query_range", 0,
                    sizeof(struct lu_seq_range),
                    lustre_swab_lu_seq_range, NULL);
EXPORT_SYMBOL(RMF_SEQ_RANGE);

// seq_handler 是sequence处理函数;sequence的处理函数,对于lustre客户端,mdt是自身申请文件数据的fid;于此mdt需要作为fid的客户端rpc连接到其他的ost的sequence server申请sequence,返回fid给mdt
static int seq_handler(struct tgt_session_info *tsi)
{
	// 获取fid客户端网络 rpc请求
	opc = req_capsule_client_get(tsi->tsi_pill, &RMF_SEQ_OPC);
	if (opc) {
		out = req_capsule_server_get(tsi->tsi_pill, &RMF_SEQ_RANGE);
		if (!out)
			RETURN(err_serious(-EPROTO));
		// 获取fid客户端网络 rpc请求
		tmp = req_capsule_client_get(tsi->tsi_pill, &RMF_SEQ_RANGE);

		out->lsr_index = tmp->lsr_index;
		out->lsr_flags = tmp->lsr_flags;
		// 在mdt0上的sequence controller或者非mdt0的和ost上的Sequence server处理sequence请求
		rc = seq_server_handle(site, tsi->tsi_env, *opc, out);
	} else {
		rc = err_serious(-EPROTO);
	}

	RETURN(rc);
}
// 运行在mdt和ost上申请sequence的函数,根据请求类型来决策是申请controller上 sequence范围还server上的sequence 
static int seq_server_handle(struct lu_site *site,
			     const struct lu_env *env,
			     __u32 opc, struct lu_seq_range *out)
{
	int rc;
	struct seq_server_site *ss_site;
	struct dt_device *dev;
	ENTRY;

	ss_site = lu_site2seq(site);

	switch (opc) {

	// 在sequence server上申请sequence 范围
	case SEQ_ALLOC_META:
		rc = seq_server_alloc_meta(ss_site->ss_server_seq, out, env);
		break;

	// 在controller上申请一个sequence 范围
	case SEQ_ALLOC_SUPER:
		rc = seq_server_alloc_super(ss_site->ss_control_seq, out, env);
		break;
	default:
		rc = -EINVAL;
		break;
	}
	RETURN(rc);
}

FID Location Database(FLD)

  • 后端的mdt或者ost永远不会拥有相同的sequence,fld client根据数据分片的fid决定数据分片的位置。数据分片位置的查找是基于fid存储表fld.在lustre中所有的fld都存储在mdt0上,其他的mdt或者ost存储了一个子集。fld client发送查询请求给fld serverfld server响应fid给客户端,同时把fid添加到fld client的缓存中来加速访问。
代码语言:javascript复制
enum fld_op {
	FLD_CREATE = 0,
	FLD_DELETE = 1,
	FLD_LOOKUP = 2,
};


static int seq_server_handle(struct lu_site *site,
			     const struct lu_env *env,
			     __u32 opc, struct lu_seq_range *out)
{

	switch (opc) {
	// 在sequence server上申请sequence 范围
	case SEQ_ALLOC_META:
		rc = seq_server_alloc_meta(ss_site->ss_server_seq, out, env);
		break;

	// 在controller上申请一个sequence 范围
	case SEQ_ALLOC_SUPER:
		rc = seq_server_alloc_super(ss_site->ss_control_seq, out, env);
		break;
	default:
		rc = -EINVAL;
		break;
	}
}

// sequence server申请Sequence
int seq_server_alloc_meta(struct lu_server_seq *seq,
			  struct lu_seq_range *out,
			  const struct lu_env *env)
{
	int rc;
	// 实际的处理函数
	rc = __seq_server_alloc_meta(seq, out, env);
	RETURN(rc);
}

// sequence server申请Sequence范围
static int __seq_server_alloc_meta(struct lu_server_seq *seq,
				   struct lu_seq_range *out,
				   const struct lu_env *env)
{
	struct lu_seq_range *space = &seq->lss_space;
	int rc = 0;
	// 
	rc = seq_server_check_and_alloc_super(env, seq){
		// 向mdt0申请新的可用的sequence range
		rc = seq_client_alloc_super(seq->lss_cli, env);
		// 插入到本地的sequence server的fld
		rc = fld_insert_entry(env, fld, space);
	}

	// 设置sequence server中的sequence range,并且更新seqence server的fld
	rc = range_alloc_set(env, out, seq);

	RETURN(rc);
}


// mdt0上sequnece请求处理
int seq_server_alloc_super(struct lu_server_seq *seq,
			   struct lu_seq_range *out,
			   const struct lu_env *env)
{

	rc = __seq_server_alloc_super(seq, out, env);

	RETURN(rc);
}



// mdt0上同时运行sequence controller和sequence server,这时候只需要在本地申请sequence range即可,不需要网络请求
static int __seq_server_alloc_super(struct lu_server_seq *seq,
				    struct lu_seq_range *out,
				    const struct lu_env *env)
{
	struct lu_seq_range *space = &seq->lss_space;
	// 本mdt0内申请新的sequence 范围
	range_alloc(out, space, seq->lss_width);
	// 内存对应Sequence的范围,同时更新本地的fld
	rc = seq_store_update(env, seq, out, 1 /* sync */)
	{
		rc = fld_server_create(env, seq->lss_site->ss_server_fld, out,th)
		{
			rc = fld_index_create(env, fld, range, th);
		}
	}

	RETURN(rc);
}
  • lustre中的fid申请请求到了mdt或者ost,是由sequence serverfld_server_lookup函数处理.其逻辑先查找fld cache,如果没有找到再去查找fld数据库,比如lustre客户端创建一个文件,首先是在mdt上会有一个元数据inode对应的fid,存储在mdt端的fld,如果这个文件设置的stripe,那么mdt端会作为fid client请求ostsequence server,返回fid后,mdt持久化到本地的fld中。接下来看看具体的实现
代码语言:javascript复制
// lustre sequence定义了2种类型的操作,一个是查询,一个是读取Sequence
struct tgt_handler fld_handlers[] = {
	TGT_FLD_HDL_VAR(0,	FLD_QUERY,	fld_handle_query),
	TGT_FLD_HDL_VAR(0,	FLD_READ,	fld_handle_read),
};

// sequence查询的请求处理函数
static int fld_handle_query(struct tgt_session_info *tsi)
{
	int	rc;

	ENTRY;

	req_capsule_set(tsi->tsi_pill, &RQF_FLD_QUERY);
	// 处理Sequence的查找工作
	rc = fld_handle_lookup(tsi)
	{
		rc = fld_server_lookup(tsi->tsi_env, fld, in->lsr_start, out)
		{
			// 优先查找fld cache是否存在
			rc = fld_local_lookup(env, fld, seq, range);

			// 如果不存在,则请求mdt0申请一个sequence range
			rc = fld_client_rpc(fld->lsf_control_exp,
				    range, FLD_QUERY, NULL);
			if (rc == 0)
				// 插入到本地fld Cache
				fld_cache_insert(fld->lsf_cache, range);
			}
	}

	RETURN(rc);
}


// sequence读取的请求处理函数
static int fld_handle_read(struct tgt_session_info *tsi)
{
	struct obd_export *exp = tsi->tsi_exp;
	struct lu_site *site = exp->exp_obd->obd_lu_dev->ld_site;
	struct lu_seq_range *in;
	void *data;
	int rc;

	ENTRY;

	// 初始化请求包
	req_capsule_set(tsi->tsi_pill, &RQF_FLD_READ);

	// 获取来自`fld client`的请求
	in = req_capsule_client_get(tsi->tsi_pill, &RMF_FLD_MDFLD);
	if (!in)
		RETURN(err_serious(-EPROTO));

	req_capsule_set_size(tsi->tsi_pill, &RMF_GENERIC_DATA, RCL_SERVER,
			     PAGE_SIZE);
	// 服务端需要解包
	rc = req_capsule_server_pack(tsi->tsi_pill);
	if (unlikely(rc != 0))
		RETURN(err_serious(rc));

	data = req_capsule_server_get(tsi->tsi_pill, &RMF_GENERIC_DATA);

	// 本地从本地fld数据库读取seqeunce
	rc = fld_server_read(tsi->tsi_env, lu_site2seq(site)->ss_server_fld,
			     in, data, PAGE_SIZE);
	RETURN(rc);
}

0 人点赞