Ceph源码笔记- CephFS-Client-Mds

 (1)Client

 (1-a) create

fuse_ll_create
    Client->ll_create
       Client->_create

 

      

 (1-b) open

复制代码
fuse_ll_open
   fuse_ll_req_prepare
   CephFuse->Handle->client->ll_open
   Client->ll_open
      Client->_open
         CEPH_MDS_OP_OPEN
         Client->make_request
            Client->choose_target_mds
                Inode->hash_dentry_name
            Client->send_request
复制代码

 

 (1-c) read

复制代码
fuse_ll_read
    Client->ll_read
       Client->_read
           bjectCacher->_readx
               ObjectCacher->bh_read
       Client->_read_async
           ObjectCacher->file_read
               ObjectCacher->prepare_read
               ObjectCacher->readx
复制代码

 

 

 (1-d) write

fuse_ll_write

    Client->ll_write

       Client->_write

              Client->get_caps

              // 分支A:缓存  :Client->objectcacher

              Client->objectcacher

              ObjectCacher->file_write

                     ObjectCacher->writex

                         ObjectCacher->_wait_for_write       

              //  分支B:直接写透

              Client->_flush_range

              Filer->write_trunc 

 (2)MDS

 (2-a) 多活mds中,分发给某一个mds处理

Server->dispatch_client_request

  CEPH_MDS_OP_LOOKUPINO

  CEPH_MDS_OP_LOOKUPNAME

  CEPH_MDS_OP_LOOKUPPARENT

  Server->handle_client_lookup_ino

      MDCache->open_ino

          MDCache->do_open_ino

             MDCache->do_open_ino_peer

                 MDCache->mds->send_message_mds

          C_MDS_LookupIno2

 

 (2-b) mds的处理:输入路径,返回 Inode

  

复制代码
CEPH_MDS_OP_OPEN:
      Server->handle_client_openc     
               // 遍历mdcache获得inode和dentry
              MDCache->path_traverse


              // 分支A:本节点Cache中不存在,去其他mds节点找找
               MDCache->find_ino_peers
               MDSRank->get_nodeid

               // 分支B:如果Cache中存在该文件的iNode就直接打开
               Server->handle_client_open
复制代码

 

 

 (2-c)  分发给某一个mds处理Openc选择合适的poolid

void Server::handle_client_openc(MDRequestRef& mdr)
{
  MClientRequest *req = mdr->client_request;
  client_t client = mdr->get_client();

  // 将flag转为cmode,由于是CREATE操作,此处的cmode为CEPH_FILE_MODE_WR(写)、或者CEPH_FILE_MODE_RDWR(读+写)
  int cmode = ceph_flags_to_mode(req->head.args.open.flags);
  ...

  bool excl = req->head.args.open.flags & CEPH_O_EXCL;
  // 客户端未指定O_EXCL标记,代表着:若文件存在则直接打开,若不存在则创建后打开
  // 此处还做了额外的处理:若文件存在但状态STALE,则查找最新的inode信息再重试(C_MDS_TryFindInode);
  // 异常情况下,除非为不存在(-ENOENT),否则直接返回
  if (!excl) {
    // path_traverse 为逐级向上查找过程,加载路径上所有节点的inode和dentry
    // 在MDS源码分析-3一文中有详细阐述
    int r = mdcache->path_traverse(mdr, NULL, NULL, req->get_filepath(),
                                   &mdr->dn[0], NULL, MDS_TRAVERSE_FORWARD);
    if (r > 0) return;
    if (r == 0) { 
      // 文件存在,则直接打开,与上述的OPEN处理流程一致
      handle_client_open(mdr);
      return;
    }     
    if (r < 0 && r != -ENOENT) {
      if (r == -ESTALE) {
        // STALE状态,则重新加载最新inode
        MDSInternalContextBase *c = new C_MDS_TryFindInode(this, mdr);
        mdcache->find_ino_peers(req->get_filepath().get_ino(), c);
      } else {
        dout(10) << "FAIL on error " << r << dendl;
        respond_to_request(mdr, r);
      }    
      return;
    }
  }
  set<SimpleLock*> rdlocks, wrlocks, xlocks;
  file_layout_t *dir_layout = NULL;
  // 准备读锁和互斥锁
  // 从父目录开始,路径上的目录全部需要读锁
  // 由于此流程为新创建文件,inode还未生成,所以创建一个null dentry,并将null dentry加入互斥锁列表xlocks
  // 此处返回的是一个null dentry,即还未关联inode的dentry,注意并不是一个nullptr
  CDentry *dn = rdlock_path_xlock_dentry(mdr, 0, rdlocks, wrlocks, xlocks,
                                         !excl, false, false, &dir_layout);
  if (!dn) return;
  if (mdr->snapid != CEPH_NOSNAP) {
    // 不允许在snap目录下做创建操作,此检查按理应该前置,可以避免一些无谓的开销
    respond_to_request(mdr, -EROFS);
    return;
  }
  // 设置layout,若父目录有layout则使用,否则使用默认的layout
  file_layout_t layout;
  ...
  const auto default_layout = layout;
  // 读取请求中可能携带的layout参数,进行转换,更新layout参数
  ...
  // 若layout发生改变,说明客户端在创建的同时还要设置定制化的layout
  if (default_layout != layout) {
    access |= MAY_SET_VXATTR;
  }
  // 对layout设置的正确性做检查,主要有以下规则
  // stripe unit要小于或等于object size,确保一个object至少能容纳一个stripe
  // object size必须为stripe unit的整数倍,这很自然
  // stripe unit必须为64K的整数倍,这与OSD bluestore的min_alloc_size(HDD的默认值为64K)保持对齐,
  // 才能保证在rados的存储空间不被浪费,同时得到最好的rados性能
  // 另外,pool设置必须是mdsmap中注册过的data pool之一
  if (!layout.is_valid()) {
    dout(10) << " invalid initial file layout" << dendl;
    respond_to_request(mdr, -EINVAL);
    return;
  }
  if (!mds->mdsmap->is_data_pool(layout.pool_id)) {
    dout(10) << " invalid data pool " << layout.pool_id << dendl;
    respond_to_request(mdr, -EINVAL);
    return;
  }
  // 额外增加父节点的authlock到rdlocks,然后进行各种锁的加锁操作
  // 自此开始进入被锁保护的操作流程
  CDir *dir = dn->get_dir();
  CInode *diri = dir->get_inode();
  rdlocks.insert(&diri->authlock);
  if (!mds->locker->acquire_locks(mdr, rdlocks, wrlocks, xlocks))
    return;

  // 父目录的操作权限检查
  if (!check_access(mdr, diri, access))
    return;
  // 检查dir分片的条目数是否达到限制,如果达到限制,请求会失败,需要在目录分裂之后才能创建新文件
  if (!check_fragment_space(mdr, dir))
    return;

  CDentry::linkage_t *dnl = dn->get_projected_linkage();
  // 确保关联的inode不存在,因为dn是新创建的null dentry,此时还未关联inode
  if (!dnl->is_null()) {
    ...
    respond_to_request(mdr, -EEXIST);
    return;
  }

  // 创建一个新的inode,使用给定的ino
  // 此处的req->head.ino为之前为session预分配的ino之一
  // 绝大多数情况下,新创建的inode会直接使用传入的ino,但在session处于opening状态时,会不信任预分配的ino
  // 此种特殊情况下,函数内会重新从inotable重新分配ino
  // 还可能触发新一轮的ino预分配
  // 最后新分配的inode会从prealloc状态转到used,并加入到mdcache中
  CInode *in = prepare_new_inode(mdr, dn->get_dir(), inodeno_t(req->head.ino),
                                 req->head.args.open.mode | S_IFREG, &layout);
  assert(in);

  // 将inode与此前的null dentry关联,dn就不再是null dentry
  dn->push_projected_linkage(in);
  ...

  // 为客户端接下来的写操作准备caps
  Capability *cap = mds->locker->issue_new_caps(in, cmode, mdr->session, realm, req->is_replay());
  in->authlock.set_state(LOCK_EXCL);
  in->xattrlock.set_state(LOCK_EXCL);

  if (cap && (cmode & CEPH_FILE_MODE_WR)) {
    in->inode.client_ranges[client].range.first = 0;
    in->inode.client_ranges[client].range.last = in->inode.get_layout_size_increment();
    in->inode.client_ranges[client].follows = follows;
    cap->mark_clientwriteable();
  }

  // 开始准备mdlog,记录新文件的创建操作,并将mdcache中的父目录标记为dirty
  mdr->ls = mdlog->get_current_segment();
  EUpdate *le = new EUpdate(mdlog, "openc");
  mdlog->start_entry(le);
  le->metablob.add_client_req(req->get_reqid(), req->get_oldest_client_tid());
  journal_allocated_inos(mdr, &le->metablob);
  mdcache->predirty_journal_parents(mdr, &le->metablob, in, dn->get_dir(), PREDIRTY_PRIMARY|PREDIRTY_DIR, 1);
  le->metablob.add_primary_dentry(dn, in, true, true, true);

  // make sure this inode gets into the journal
  le->metablob.add_opened_ino(in->ino());
  LogSegment *ls = mds->mdlog->get_current_segment();
  ls->open_files.push_back(&in->item_open_file);

  // 准备openc完成的回调,它会在journal submit_entry成功,待flusher下刷log以后被调用
  // 回调中会将新的inode标记为dirty,将dentry放入mdcache中,执行balancer->hit_inode,最后返回客户端消息
  C_MDS_openc_finish *fin = new C_MDS_openc_finish(this, mdr, dn, in, follows);

  if (mdr->client_request->get_connection()->has_feature(CEPH_FEATURE_REPLY_CREATE_INODE)) {
    dout(10) << "adding ino to reply to indicate inode was created" << dendl;
    // add the file created flag onto the reply if create_flags features is supported
    ::encode(in->inode.ino, mdr->reply_extra_bl);
  }
  // 执行log提交journal的流程,提交前会做一次early reply,此reply返回unsafe状态
  // 然后会提交log到journal queue,等待异步回刷,异步回刷以后,会调用上面的回调C_MDS_openc_finish->finish()
  journal_and_reply(mdr, in, dn, le, fin);

  // 本次openc操作可能导致dir分片的条目数超标,可能触发目录分裂,目录的分裂、合并过程以后专门介绍
  mds->balancer->maybe_fragment(dir, false);
}
posted @   乌鸦嘴-raven  阅读(1324)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示