- 在MDS源码剖析-2 消息分发中,已知客户端目录相关操作分发至Server.cc的dispatch_client_request进行处理。主要包含以下请求
void Server::dispatch_client_request(MDRequestRef& mdr) {
...
switch (req->get_op()) {
// 根据inode NO.(ino) 查找inode
case CEPH_MDS_OP_LOOKUPHASH:
case CEPH_MDS_OP_LOOKUPINO:
handle_client_lookup_ino(mdr, false, false);
break;
// 根据inode NO.(ino) 查找父节点
case CEPH_MDS_OP_LOOKUPPARENT:
handle_client_lookup_ino(mdr, true, false);
break;
// 根据inode NO.(ino) 查找inode及Dentry
case CEPH_MDS_OP_LOOKUPNAME:
handle_client_lookup_ino(mdr, false, true);
break;
// 根据path查找inode和dentry
case CEPH_MDS_OP_LOOKUP:
handle_client_getattr(mdr, true);
break;
// 根据path查找inode
case CEPH_MDS_OP_GETATTR:
handle_client_getattr(mdr, false);
break;
// 设置/更新文件attr
case CEPH_MDS_OP_SETATTR:
handle_client_setattr(mdr);
break;
// 设置/更新文件layout
case CEPH_MDS_OP_SETLAYOUT:
handle_client_setlayout(mdr);
break;
// 设置/更新目录layout
case CEPH_MDS_OP_SETDIRLAYOUT:
handle_client_setdirlayout(mdr);
break;
// 设置文件xattr k-v pair
case CEPH_MDS_OP_SETXATTR:
handle_client_setxattr(mdr);
break;
// 删除文件xattr
case CEPH_MDS_OP_RMXATTR:
handle_client_removexattr(mdr);
break;
// 分段ls dir
case CEPH_MDS_OP_READDIR:
handle_client_readdir(mdr);
break;
// 文件加xlock
case CEPH_MDS_OP_SETFILELOCK:
handle_client_file_setlock(mdr);
break;
// 文件加rdlock
case CEPH_MDS_OP_GETFILELOCK:
handle_client_file_readlock(mdr);
break;
// 创建并open文件
case CEPH_MDS_OP_CREATE:
if (mdr->has_completed)
handle_client_open(mdr); // already created.. just open
else
handle_client_openc(mdr);
break;
// open文件
case CEPH_MDS_OP_OPEN:
handle_client_open(mdr);
break;
// 创建文件
case CEPH_MDS_OP_MKNOD:
handle_client_mknod(mdr);
break;
// 创建link
case CEPH_MDS_OP_LINK:
handle_client_link(mdr);
break;
// 删除link/目录
case CEPH_MDS_OP_UNLINK:
case CEPH_MDS_OP_RMDIR:
handle_client_unlink(mdr);
break;
// 重命名
case CEPH_MDS_OP_RENAME:
handle_client_rename(mdr);
break;
// 创建目录
case CEPH_MDS_OP_MKDIR:
handle_client_mkdir(mdr);
break;
// 创建符号(软)链接
case CEPH_MDS_OP_SYMLINK:
handle_client_symlink(mdr);
break;
// 查询快照列表
case CEPH_MDS_OP_LSSNAP:
handle_client_lssnap(mdr);
break;
// 创建快照
case CEPH_MDS_OP_MKSNAP:
handle_client_mksnap(mdr);
break;
// 删除快照
case CEPH_MDS_OP_RMSNAP:
handle_client_rmsnap(mdr);
break;
// 快照重命名
case CEPH_MDS_OP_RENAMESNAP:
handle_client_renamesnap(mdr);
break;
default:
dout(1) << " unknown client op " << req->get_op() << dendl;
respond_to_request(mdr, -EOPNOTSUPP);
}
- 以下两篇博文对cephfs各种基本概念和锁有比较深入的阐述,建议先进行阅读,重点要理解CDir、CDentry、和CInode结构的关联关系
CEPHFS内部实现 概念篇
CEPH MDS锁实现介绍
简要类图如下:
inode-dentry-dir.png
LOOKUP inode/parent/name,基于ino
涉及CEPH_MDS_OP_LOOKUPHASH
、CEPH_MDS_OP_LOOKUPINO
、CEPH_MDS_OP_LOOKUPPARENT
、CEPH_MDS_OP_LOOKUPNAME
四种消息
均由Server::handle_client_lookup_ino(MDRequestRef& mdr, bool want_parent, bool want_dentry)
函数处理
源码:src/mds/Server.cc
void Server::handle_client_lookup_ino(MDRequestRef& mdr,
bool want_parent, bool want_dentry)
{
MClientRequest *req = mdr->client_request;
//尝试从mdcache的inodemap中查找
inodeno_t ino = req->get_filepath().get_ino();
CInode *in = mdcache->get_inode(ino);
...
if (!in) {
//若mdcache中未找到,则调用open_ino进行异步加载,本次处理结束
//加载完成后通过C_MDS_LookupIno2回调,执行三种可能的路径
//1. 不存在,返回-ESTALE;2.存在且属于本rank,重新分发mdr至此函数处理;3.存在但属于其它rank,forward给dest rank
mdcache->open_ino(ino, (int64_t)-1, new C_MDS_LookupIno2(this, mdr), false);
return;
}
// 若路径中存在快照,则递归打开各级snaprealm,以确保拿到最新的快照信息
if (mdr && in->snaprealm && !in->snaprealm->is_open() &&
!in->snaprealm->open_parents(new C_MDS_RetryRequest(mdcache, mdr))) {
return;
}
// check for nothing (not read or write); this still applies the
// path check.
if (!check_access(mdr, in, 0))
return;
//对于want_parent || want_dentry情形,pin dn并表明需要读锁
CDentry *dn = in->get_projected_parent_dn();
CInode *diri = dn ? dn->get_dir()->inode : NULL;
set<SimpleLock*> rdlocks;
if (dn && (want_parent || want_dentry)) {
mdr->pin(dn);
rdlocks.insert(&dn->lock);
}
// 对于非loner客户端,可能还需要对authlock和xattrlock 加读锁
unsigned mask = req->head.args.getattr.mask;
if (mask) {
Capability *cap = in->get_client_cap(mdr->get_client());
int issued = 0;
if (cap && (mdr->snapid == CEPH_NOSNAP || mdr->snapid <= cap->client_follows))
issued = cap->issued();
// permission bits, ACL/security xattrs
if ((mask & CEPH_CAP_AUTH_SHARED) && (issued & CEPH_CAP_AUTH_EXCL) == 0)
rdlocks.insert(&in->authlock);
if ((mask & CEPH_CAP_XATTR_SHARED) && (issued & CEPH_CAP_XATTR_EXCL) == 0)
rdlocks.insert(&in->xattrlock);
mdr->getattr_caps = mask;
//加读锁
if (!rdlocks.empty()) {
set<SimpleLock*> wrlocks, xlocks;
if (!mds->locker->acquire_locks(mdr, rdlocks, wrlocks, xlocks))
return;
if (diri != NULL) {
// need read access to directory inode
if (!check_access(mdr, diri, MAY_READ))
return;
}
}
// lookup_parent的情况,mdr->tracei返回为dn->get_dir()->inode,也就是父节点的inode
if (want_parent) {
if (in->is_base()) {
respond_to_request(mdr, -EINVAL);
return;
}
if (!diri || diri->is_stray()) {
respond_to_request(mdr, -ESTALE);
return;
}
dout(10) << "reply to lookup_parent " << *in << dendl;
mdr->tracei = diri;
respond_to_request(mdr, 0);
} else {
if (want_dentry) {
inodeno_t dirino = req->get_filepath2().get_ino();
if (!diri || (dirino != inodeno_t() && diri->ino() != dirino)) {
respond_to_request(mdr, -ENOENT);
return;
}
dout(10) << "reply to lookup_name " << *in << dendl;
} else
dout(10) << "reply to lookup_ino " << *in << dendl;
// 非lookup_parent的情况,mdr->tracei返回为当前节点的inode
mdr->tracei = in;
if (want_dentry)
mdr->tracedn = dn; //lookup_name的情况还需额外返回dentry
respond_to_request(mdr, 0);
}
}
open_ino细节分析
mdcache->open_ino
, 加载inode
由于此时还不知道ino对应的rank,默认情况下open_ino_info_t的check_peers为true,所以在此lookup场景,调用过程为:
-
open_ino
->do_open_ino
->do_open_ino_peer
->mds->send_message_mds(new MMDSOpenIno(info.tid, ino, pa), peer)
此过程将向第一个非自己且不在checked列表的mds发起openino请求,此mds暂时标记为checking - 对端的mds接收到
MMDSOpenIno
消息后,由MDCache::handle_open_ino
处理。先调用get_inode
从本地缓存inodemap中查找,如果没有找到,则根据消息中传来的祖先信息m->ancestors
调用open_ino_traverse_dir
进行查找和加载,在基于ino的lookup情形,mds无从知道祖先信息,所以此步骤如果inodemap中不存在,则本MDS节点将查找失败。 - 对端MDS将返回
MMDSOpenInoReply
消息,若查找到,将返回inode及其祖先节点inode信息,若没有找到将返回错误码,返回消息由MDCache::handle_open_ino_reply
进行处理 -
handle_open_ino_reply
中,将对端mds rank加入checked列表,将checking置空。若对端返回了错误码,则重新执行do_open_ino
,这将重新在另一个MDS开展上述查找逻辑;若对端返回了inode,则执行open_ino_finish
流程 -
open_ino_finish
会将open_ino_info_t
从opening_inodes
中移除,并执行open_ino
函数注册的回调C_MDS_LookupIno2
, 回调会执行三种可能的路径:1. 不存在,返回-ESTALE;2.存在且属于本rank,重新分发mdr至Server::handle_client_lookup_ino
处理;3.存在但属于其它rank,forward给dest rank,由其它MDS的Server::handle_client_lookup_ino
处理 - 综上,由于请求中提供的可使用的信息太少,基于ino查找inode是一个可能要跨多个MDS经历多次网络消息来回的过程,若在所有MDS的cache中都不存在,将会查找失败。这是一个非常低效的操作
LOOKUP,基于path
涉及CEPH_MDS_OP_LOOKUP
消息
由函数Server::handle_client_getattr(MDRequestRef& mdr, bool is_lookup)
处理
源码文件src/mds/Server.cc
void Server::handle_client_getattr(MDRequestRef& mdr, bool is_lookup)
{
MClientRequest *req = mdr->client_request;
set<SimpleLock*> rdlocks, wrlocks, xlocks;
//确保path非空
if (req->get_filepath().depth() == 0 && is_lookup) {
// refpath can't be empty for lookup but it can for
// getattr (we do getattr with empty refpath for mount of '/')
respond_to_request(mdr, -EINVAL);
return;
}
...
// 遍历path准备读锁,返回Inode
CInode *ref = rdlock_path_pin_ref(mdr, 0, rdlocks, want_auth, false, NULL,
!is_lookup);
//若inode未找到,直接返回,在rdlock_path_pin_ref中会完成向客户端的response
if (!ref) return;
//inode找到的情况,对于非loner客户端,可能还需要对linklock、authlock、filelock和xattrlock 加读锁
...
// 加锁
if (!mds->locker->acquire_locks(mdr, rdlocks, wrlocks, xlocks))
return;
if (!check_access(mdr, ref, MAY_READ))
return;
...
// 返回inode
mdr->tracei = ref;
if (is_lookup)
// 返回dentry
mdr->tracedn = mdr->dn[0].back();
respond_to_request(mdr, 0);
}
-
rdlock_path_pin_ref
是过程中最关键的函数,其实现逻辑如下
CInode* Server::rdlock_path_pin_ref(MDRequestRef& mdr, int n,
set<SimpleLock*> &rdlocks,
bool want_auth,
bool no_want_auth, /* for readdir, who doesn't want auth _even_if_ it's
a snapped dir */
file_layout_t **layout,
bool no_lookup) // true if we cannot return a null dentry lease
{
const filepath& refpath = n ? mdr->get_filepath2() : mdr->get_filepath();
...
// 遍历mdcache获得inode和dentry
int r = mdcache->path_traverse(mdr, NULL, NULL, refpath, &mdr->dn[n], &mdr->in[n], MDS_TRAVERSE_FORWARD);
if (r > 0)
return NULL; // delayed
if (r < 0) { // error
if (r == -ENOENT && n == 0 && !mdr->dn[n].empty()) {
if (!no_lookup) {
mdr->tracedn = mdr->dn[n].back();
}
respond_to_request(mdr, r);
} else if (r == -ESTALE) {
dout(10) << "FAIL on ESTALE but attempting recovery" << dendl;
// C_MDS_TryFindInode回调,若仍然返回-ESTALE则直接向客户端返回错误码;
// 若find_ino_peers成功,则重新分发请求至handle_client_getattr处理
MDSInternalContextBase *c = new C_MDS_TryFindInode(this, mdr);
//类似于do_open_inode,循环所有MDS RANK,_do_find_ino_peer
mdcache->find_ino_peers(refpath.get_ino(), c);
} else {
dout(10) << "FAIL on error " << r << dendl;
respond_to_request(mdr, r);
}
return 0;
}
CInode *ref = mdr->in[n];
dout(10) << "ref is " << *ref << dendl;
// fw to inode auth?
if (mdr->snapid != CEPH_NOSNAP && !no_want_auth)
want_auth = true;
if (want_auth) {
// lookup请求的want_auth为false
...
}
for (int i=0; i<(int)mdr->dn[n].size(); i++)
rdlocks.insert(&mdr->dn[n][i]->lock);
if (layout)
mds->locker->include_snap_rdlocks_wlayout(rdlocks, ref, layout);
else
mds->locker->include_snap_rdlocks(rdlocks, ref);
// set and pin ref
mdr->pin(ref);
return ref;
}
- 在
mdcache->path_traverse
返回-ESTALE
的情况下,lookup过程会产生跨MDS的find inode请求,下面对mdcache->path_traverse
的过程做进一步分析
源码文件src/mds/MDCache.cc
// 函数返回大于0的值,说明需要等待,将由C_MDS_RetryRequest进行重试,_get_waiter将返回此回调
// 返回小于0值,表示错误
int MDCache::path_traverse(MDRequestRef& mdr, Message *req, MDSInternalContextBase *fin, // who
const filepath& path, // path
vector<CDentry*> *pdnvec, // result dn
CInode **pin, // result inode
int onfail) // == MDS_TRAVERSE_FORWARD
{
bool discover = (onfail == MDS_TRAVERSE_DISCOVER); // false
bool null_okay = (onfail == MDS_TRAVERSE_DISCOVERXLOCK); //false
bool forward = (onfail == MDS_TRAVERSE_FORWARD); //true
...
CInode *cur = get_inode(path.get_ino());
// MDSDIR的机制还没完全弄清楚
if (cur == NULL) {
if (MDS_INO_IS_MDSDIR(path.get_ino()))
open_foreign_mdsdir(path.get_ino(), _get_waiter(mdr, req, fin));
else {
/*对于一般的目录,若本地cache中找不到其inode,会返回-ESTALE,
这样在上层函数`rdlock_path_pin_ref`中就会向其它MDS进行inode查询*/
return -ESTALE;
}
return 1;
}
if (cur->state_test(CInode::STATE_PURGING))
return -ESTALE;
// make sure snaprealm are open...
if (mdr && cur->snaprealm && !cur->snaprealm->is_open() &&
!cur->snaprealm->open_parents(_get_waiter(mdr, req, fin))) {
return 1;
}
// 清空pdnvec,设置返回的inode,接下来要组装祖先dentry列表,通过pdnvec返回
if (pdnvec)
pdnvec->clear();
if (pin)
*pin = cur;
//开始path逐级遍历
unsigned depth = 0;
while (depth < path.depth()) {
...
//跳过snapdir并获得snapid
...
// open dir
frag_t fg = cur->pick_dirfrag(path[depth]);
CDir *curdir = cur->get_dirfrag(fg);
if (!curdir) {
if (cur->is_auth()) {
// parent dir frozen_dir?
...
curdir = cur->get_or_open_dirfrag(this, fg);
} else {
// discover?
// dir不属于本rank,向inode的auth_rank发起discover请求
discover_path(cur, snapid, path.postfixpath(depth), _get_waiter(mdr, req, fin),
null_okay);
// 触发异步重试,然后回到这里继续后续流程
return 1;
}
}
// 至此,本级的CDir一定从本节点或其它MDS获取到了
assert(curdir);
...
// dentry
CDentry *dn = curdir->lookup(path[depth], snapid);
CDentry::linkage_t *dnl = dn ? dn->get_projected_linkage() : 0;
// null and last_bit and xlocked by me?
...
//等待别的mds释放dn独占锁并重试
...
// dn还未与inode关联的情况
if (dnl && dnl->is_null()) {
if (dn可读 || 被自己客户端锁定)
...
return -ENOENT;
} else {
// 可能是别的客户端已经锁定正在操作,则等待后重试
dn->lock.add_waiter(SimpleLock::WAIT_RD, _get_waiter(mdr, req, fin));
return 1;
}
}
// dn已经与inode关联的情况
if (dnl && !dnl->is_null()) {
CInode *in = dnl->get_inode();
if (!in) {
// linkage中只有remote_ino没有inode的情况
assert(dnl->is_remote());
in = get_inode(dnl->get_remote_ino());
// 先从本地缓存加载 remote_ino对应的inode,若没有,走open_ino流程加载inode
...
}
// 至此找到dn对应的inode
cur = in;
...
// add to trace, continue.
touch_inode(cur);
if (pdnvec)
pdnvec->push_back(dn);
if (pin)
*pin = cur;
depth++;
continue;
}
// dentry都不存在的情况
if (curdir->is_auth()) {
// dir属于本rank的情况
if (curdir->is_complete() ||
(snapid == CEPH_NOSNAP &&
curdir->has_bloom() &&
!curdir->is_in_bloom(path[depth]))){
// dir处于complete状态,或者通过布隆过滤器确认dn不存在
...
return -ENOENT;
} else {
...
// dir处于非complete状态,重新fetch加载,然后重试
touch_inode(cur);
curdir->fetch(_get_waiter(mdr, req, fin), path[depth]);
if (mds->logger) mds->logger->inc(l_mds_traverse_dir_fetch);
return 1;
}
} else {
// dir不属于本rank的情况
mds_authority_t dauth = curdir->authority();
...
if (forward) { // forward在此lookup情形下为true,所以会做转发
// forward
if (curdir->is_ambiguous_auth()) {
// dir的auth mds未知的情况,等待其auth明确后重试
curdir->add_waiter(CDir::WAIT_SINGLEAUTH, _get_waiter(mdr, req, fin));
return 1;
}
//转发请求到auth mds,返回2触发上层调用进入等待重试逻辑
request_forward(mdr, dauth.first);
return 2;
}
}
}
...
return 0;
}
简单总结下path_traverse过程:
- 首先找到本级的CInode,若本地不存在就远程加载
- 然后逐级向上查找,先找到CInode的父CDir,然后通过CDir lookup CDentry,然后通过CDentry的linkage找到其CInode,过程中若出现dir\dentry\inode在本地找不到的情况,则进行远程加载
- 所有的远程加载过程均为异步重试模式,保障mds的工作线程能得到及时的释放
- 直至路径上到root的所有层级节点的inode和dentry都找全以后,将各祖先节点的完整CDentry结构通过参数
pdnvec
返回,当前节点的inode通过参数pin
返回
与CEPH_MDS_OP_LOOKUPINO
、CEPH_MDS_OP_LOOKUPPARENT
、CEPH_MDS_OP_LOOKUPNAME
等消息的处理相比,CEPH_MDS_OP_LOOKUP
的处理中,除了查询本节点inode和dentry以外,还需加载所有祖先节点的dentry,对于目录层次深的情况,其代价更大,需要依赖各级缓存来减少此类lookup操作
GETATTR
涉及消息CEPH_MDS_OP_GETATTR
由函数Server::handle_client_getattr(MDRequestRef& mdr, bool is_lookup)
处理
源码文件src/mds/Server.cc
其实现与前述CEPH_MDS_OP_LOOKUP
消息使用相同的处理函数,区别在于is_lookup
参数,此处为false
,而 lookup时为true
相对lookup实现上有如下差异:
- getattr允许不传入
refpath
,不传时,将获取/
的inode返回 - 不返回tracedn,也就是祖先节点的dn,但
path_traverse
过程仍然要做