(原)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中删除节点
}

现在回过头来分析_consume()中间干的事情:

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清理,防止日志数据积累过大导致一些问题的产生。

 

 

posted @ 2021-12-23 15:22  lihaiping  阅读(595)  评论(0编辑  收藏  举报