(原)mds删除目录和文件的操作
先从处理目录和文件删除请求的操作函数开始:
Server::handle_client_unlink(MDRequestRef& mdr)
1,遍历目录路径
int r = mdcache->path_traverse(mdr, cf, refpath, &trace, &in, MDS_TRAVERSE_FORWARD);//遍历路径
CDentry *dn = trace.back();//返回请求的文件路径的目录项
2,在strays的下面为当前节点inode创建一个dentry的目录项straydn
straydn = prepare_stray_dentry(mdr, dnl->get_inode());
3,_unlink_local中会将dn下的inode相关信息关联到straydn中,同时删除掉dn下的inode关联关系,并提交mdlog日志
_unlink_local(mdr, dn, straydn);
看一下Server::_unlink_local()函数实现:
//dn代表的是之前的正常的目录项,straydn代表的是在straydir目录下的为将要删除的dn新建dentry目录项
void Server::_unlink_local(MDRequestRef& mdr, CDentry *dn, CDentry *straydn)
{
//生成一个新的unlink_local日志事件
EUpdate *le = new EUpdate(mdlog, "unlink_local");
//将inode设置到straydn目录项中,进行关联
straydn->push_projected_linkage(in);
//相当于移除掉dentry中的CInode对象,解除关系,这里主要是将dentry的projected设置为空,
//等待日志下刷以后,才会将projected中的对象更新到linkage变量上
dn->push_projected_linkage();
//提交日志,等待日志下刷flush完成后会回调_unlink_local_finish,
//这个回调会做很多的后期处理的事情
journal_and_reply(mdr, 0, dn, le, new C_MDS_unlink_local_finish(this, mdr, dn, straydn));
}
再接着看等mdlog日志flush之后,会回调C_MDS_unlink_local_finish,转而执行下面这个函数
void Server::_unlink_local_finish(MDRequestRef& mdr,CDentry *dn, CDentry *straydn,version_t dnpv)
{
//彻底解除之前关联linkage和dentry之间的关系,并将dn从mdcache中的lru移除,插入到bottom_lru中
// unlink main dentry
dn->get_dir()->unlink_inode(dn);
//将dentry和projected_linkage关联上,我们可以知道,在这一步之前projected_linkage已经通过push_projected_linkage设置为了空
dn->pop_projected_linkage();
mdcache->send_dentry_unlink(dn, straydn, mdr);//发送unlink消息给其他的mds
//提示MDCache这个dentry是一个可能有资格被清除的stray
mdcache->notify_stray(straydn);
//这个函数会通过mdcache内部对象stray_manager来评估,然后判断文件是否能够最终被删除 ===> stray_manager.eval_stray(dn);
//一系列的函数调用:
//stray_manager.eval_stray(dn); --》 StrayManager::eval_stray(CDentry *dn) --》enqueue(dn, false); --》enqueue(dn, trunc); --》 purge(dn);
//接下来具体看下purge(dn)函数里面做的事情
}
接下来具体看下purge(dn)函数里面做的事情:
//这个函数实现将dn目录进行清除,当然清楚的过程也是涉及到一大堆操作
StrayManager::purge(CDentry *dn)
{
CDentry::linkage_t *dnl = dn->get_projected_linkage();
CInode *in = dnl->get_inode();
PurgeItem item;
item.ino = in->inode.ino;
item.stamp = ceph_clock_now();
if (in->is_dir()) {
item.action = PurgeItem::PURGE_DIR;
item.fragtree = in->dirfragtree;
} else {
item.action = PurgeItem::PURGE_FILE;
uint64_t to = 0;
if (in->is_file()) {
to = in->inode.get_max_size();
to = std::max(in->inode.size, to);
// when truncating a file, the filer does not delete stripe objects that are
// truncated to zero. so we need to purge stripe objects up to the max size
// the file has ever been.
to = std::max(in->inode.max_size_ever, to);
}
auto pi = in->get_projected_inode();
item.size = to;
item.layout = pi->layout;
item.old_pools.clear();
for (const auto &p : pi->old_pools)
item.old_pools.insert(p);
item.snapc = *snapc;
}
//purge_queue管理日志journaler,所以这里的push会将item先写入purge_queue下的journaler日志里
//同时也会去日志里读取内容,然后对读取的日志item对象,进行osd上inode的元数据删除
purge_queue.push(item, new C_IO_PurgeStrayPurged(this, dn, false));
}
具体看下 purge_queue.push()干的事情
void PurgeQueue::push(const PurgeItem &pi, Context *completion)
{
bufferlist bl;
encode(pi, bl);
journaler.append_entry(bl);//写日志缓存
//等purge的日志flush以后,就会回调completion---对应上面的--》C_IO_PurgeStrayPurged
journaler.wait_for_flush(completion);
// Maybe go ahead and do something with it right away
//从日志中读取数据,并根据读取到的日志内容执行purge,这里并不对写日志内容的本身进行trim,而是执行日志记录的内容
bool could_consume = _consume();
}
note: 对于_consume()中间干的事情,我们先在这里放一放,等会在详细分析。
当PurgeItem被PurgeQueue下的journaler日志对象flush以后,就会回调C_IO_PurgeStrayPurged对象的finish函数,而finish函数会直接调用:
void StrayManager::_purge_stray_purged(CDentry *dn, bool only_head)
{
//提交一个事件到mdlog日志中,
EUpdate *le = new EUpdate(mds->mdlog, "purge_stray");
mds->mdlog->start_entry(le);
//然后等mdlog以后,就会回调C_PurgeStrayLogged
mds->mdlog->submit_entry(le, new C_PurgeStrayLogged(this, dn, pdv,mds->mdlog->get_current_segment()));
}
看看回调C_PurgeStrayLogged中的事情:
StrayManager::_purge_stray_logged(CDentry *dn, version_t pdv, LogSegment *ls)
{
CInode *in = dn->get_linkage()->get_inode();
inodeno_t ino = in->ino();
if (in->is_dirty())
in->mark_clean();
mds->mdcache->remove_inode(in);//从mdcache中删除节点
}
bool PurgeQueue::_consume()
{
// The journaler is readable: consume an entry
bufferlist bl;
bool readable = journaler.try_read_entry(bl);//从日志里面读取数据
ceph_assert(readable); // we checked earlier
dout(20) << " decoding entry" << dendl;
PurgeItem item;
auto q = bl.cbegin();
try {
decode(item, q);
} catch (const buffer::error &err) {
derr << "Decode error at read_pos=0x" << std::hex
<< journaler.get_read_pos() << dendl;
_go_readonly(EIO);
}
dout(20) << " executing item (" << item.ino << ")" << dendl;
//对item指向的对象执行osd上数据的删除,这里并不trim日志内容本身,日志本身的内容trim需要等待更新日志头信息内容的时候,才去trim日志本身写的osd内容
_execute_item(item, journaler.get_read_pos());
}
PurgeQueue::_consume()函数里面干的最重要的一件事便是从去读取之前写入purge日志内容中的item对象,然后根据item对象的内容,执行osd元数据的真正删除。
接下来看下_execute_item(item, journaler.get_read_pos());函数实现:
void PurgeQueue::_execute_item(const PurgeItem &item,uint64_t expire_to)
{
in_flight[expire_to] = item;//记录下在执行过程中的item对象
...
//对osd上的inode元数据进行删除
C_GatherBuilder gather(cct);
if (item.action == PurgeItem::PURGE_FILE) {
} else if (item.action == PurgeItem::PURGE_DIR) {
} else if (item.action == PurgeItem::TRUNCATE_FILE) {
} else {
return;
}
//设置osd删除掉元数据信息以后,完成后的回调函数
gather.set_finisher(new C_OnFinisher(
new FunctionContext([this, expire_to](int r){
//主要是删除in_flight这个map中的对象,同时更新日志对象的过期位置set_expire_pos
_execute_item_complete(expire_to);
//如果有的话,接着继续调用消耗函数
_consume();
// Have we gone idle? If so, do an extra write_head now instead of
// waiting for next flush after journaler_write_head_interval.
// Also do this periodically even if not idle, so that the persisted
// expire_pos doesn't fall too far behind our progress when consuming
// a very long queue.
//只有空闲的时候,或者到了更新文件头的时间了,才更新日志头的内容
if (in_flight.empty() || journaler.write_head_needed()) {
journaler.write_head(nullptr);//对于日志文件本身的trim是在写完日志头以后,有一个trim的函数调用过程
}
}
}
从上面的调用过程可以看的出来,通过这种回调方式,当完成一个item数据的purge以后,会不断的通过循环回调_consume()来实现数据的读取和消耗,当数据消耗完毕以后,或者日志头文件更新时间过了的时候,就会调用journaler.write_head(nullptr);更新日志数据头信息,在这个函数里面,当写完head数据以后,会回调日志的trim函数对日志进行一次trim清理,防止日志数据积累过大导致一些问题的产生。