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

     

  • 相关阅读:
    nginx相关总结
    nginx 虚拟主机+反向代理+负载均衡
    linux文本查看与搜索
    mysqldump导出数据出现问题
    转载 | 缩小浏览器窗口右边出现空白
    转载 | Sublime Text3 安装以及初次配置
    转载 | Sublime text3 实用快捷键整理
    转载 | SVG向下兼容优雅降级方法
    CSS等分布局方法
    如何用实现文字环绕图片?
  • 原文地址:https://www.cnblogs.com/lihaiping/p/15723704.html
Copyright © 2020-2023  润新知