Ceph 读写路径源代码分析(1)

本文主要分析了Ceph读写的关键路径上的一些函数的处理和实现,一些比较细节的函数,有待进一步分析和研究。

接收请求

首先,读写请求都是从ms_fast_dispatch,开始, 它是接收读写消息message的入口,就从这里开始分析读写路径。这部分函数的处理是在网络的回调函数里处理的,所有理论上应该尽可能的简单处理,之后交给后面的OpWQ线程池来主要处理。

OSD::ms_fast_dispatch

void OSD::ms_fast_dispatch(Message *m)

  1. 首先检查service是否已经停止了,如果停止了,就直接返回
  2. 调用函数op_tracker.create_request把Message消息转换成OpRequest, 数据结构OpRequest包装了Message,并添加了一些相关的信息,在后面用到的时候,会介绍到。
  3. 获取nextmap, 应该是最新的osdmap, 和 session

dispatch_op_fast

  1. 检查is_stopping,有必要每次都检查吗?
  2. 首先调用函数op_required_epoch(op), 从OpRequest中获取 msg带的epoch,比较msg的 epoch 的比较msg_epoch > osdmap->get_epoch(), 如果 msg 带的epoch 大于osd 最新的epoch,则调用更新自己的epoch,首先检查该请求的链接是否存在,如果不存在,就直接返回
  3. 更加消息类型,调用相应的类型的处理函数

OSD::handle_op

  1. 首先调用op_is_discardable,检查该op是否可以discard
  2. 构建share_map结构体,获取client_session, 从client_session获取last_sent_epoch,调用函数service.should_share_map,来设置share_map.should_send ,主要用于如果epoch不一致,需要通知对方更新最新的epoch,这里和dispatch_op_fast的处理区别是,上次是更新自己,这里是通知对方更新。
    需要注意是,client 和 osd 的 epoch 不一致,并不影响读写,只要 epoch的变化不影响本次读写的PG的OSD list的变化。
  3. 从消息里获取_pgid,从_pg_id里获取pool
  4. 调用osdmap->raw_pg_to_pg,最终调用pg_pool_t::raw_pg_to_pg函数,对pg做了调整
  5. 调用osdmap->get_primary_shard函数,由pg_t 转换为spg_t,spgt_t比pg_t多了一个shard_id_t shard, 也就是osd在osd 列表中的序号,这个对于replicatePG无意义,一般设置为NO_SHARD,对于ErasureCodePG,各个chunk的序号比较重要,关系到数据的恢复。get_primary_shard获取EC的primay osd 的shard id
  6. 调用函数get_pg_or_queue_for_pg, 通过pgid, 获取PG类指针,如果获取成功,就调用enqueue_op处理请求
  7. 否则,做一些错误检查

总结,本函数主要检查了epoch是否share,其次最主要的是获取读写请求相关的PG类。

PG::queue_op

void PG::queue_op(OpRequestRef& op)

  1. 加map_lock 锁,该锁保护waiting_for_map列表,判断waiting_for_map列表不为空,就把当前op 加入该列表,直接return。waiting_for_map列表不为空,说明有操作在等待osd map 的更新,说明当前osd map 不信任,不能继续当前的处理。
  2. 函数op_must_wait_for_map,判断当前的epch 大于 op 的epoch, 则必须加入waiting_for_map等待,这里的osdmap的epoch的判断,是一个PG层的epoch的判断。和前面的判断不在一个层次,这里是需要等待的。当op的epoch 大于 pg的epoch,需要等待更新pg当前的epoch
  3. 加入osd的op_wq处理队列里

总结,本函数做在PG类里,做PG 层面的相关检查,如果ok,就加入osd的op_wq继续处理。

OSD 的op_wq处理

以下操作都是被op_wq的线程池调用,做相应的处理。op_wq是一个ShardedWQ,具体的实现这里不详细分析,代码比较简单,本文着重分析读写流程。

OSD::dequeue_op

void OSD::dequeue_op(PGRef pg, OpRequestRef op ,ThreadPool::TPHandle &handle)

  1. 检查是否op->send_map_update,如果需要更新,调用函数service.share_map 通知对方更新osdmap信息。在操作OSD::handle_op里,只是设置了share_map的标记,并设置在op->send_map_update,在这里才真正的发消息。
  2. 调用 pg->do_request(op, handle) 处理 请求

总结:这里一开始,做share_map操作,接下来在PG里调do_request来处理。

ReplicatedPG::do_request

void ReplicatedPG::do_request( OpRequestRef& op, ThreadPool::TPHandle &handle)

  1. 调用函数can_discard_request检查op是否可以discard,具体可查找该函数源代码。
  2. 检查flushes_in_progress,如果还有flush操作,则把op加入waiting_for_peered队列,等待。
  3. 如果PG还没有peered,检查pgbackend能否处理该请求,否则加入waiting_for_peered队列,等待pg完成peered后再处理。
  4. 检查pgbacnehd能否处理该请求,如果能处理,就返回
  5. 如果是CEPH_MSG_OSD_OP, 检查该PG的状态,如果处于非active或者reply状态,则把请求添加到waiting_for_active等待队列,检查如果该pool是CachePool,而该操作没有带CEPH_FEATURE_OSD_CACHEPOOL的feature标志,返回错误EOPNOTSUPP
  6. 根据消息的类型,调用相应的处理函数来处理

总结,本函数开始进入相关的ReplicatedPG来处理,首先就是检查PG的状态是否正常

ReplicatedPG::do_op

这个函数是比较复杂,也是比较长的,必须要理解snap相关的一些概念。
void ReplicatedPG::do_op(OpRequestRef& op)

  1. 消息解包m->finish_decode
  2. 调用osd->osd->init_op_flags 初始化 op->rmw_flags, 函数init_op_flags根据flag 来设置rmw_flags
  3. 如果是读操作,并且有CEPH_OSD_FLAG_BALANCE_READS或者CEPH_OSD_FLAG_LOCALIZE_READS标志,说明主从副本都可以读。检查本osd是否是该pg的primay或者replica ; 否则,本osd必须是该pg的primay
  4. 如果是includes_pg_op操作,调用pg_op_must_wait检查该操作是否需要等待,如果需要等待,加入waiting_for_all_missing队列,如果不需要等待,调用do_pg_op处理pg相关的操作
  5. 调用op_has_sufficient_caps检查权限
  6. 检查对象的名字是否超长
  7. 检查操作的客户端是否在blacklist中
  8. 检查是磁盘空间否full
  9. 检查如果是写操作,如果是snap,返回-EINVAL,快照不允许写操作。如果写操作带的数据大于osd_max_write_size(如果设置),直接返回-OSD_WRITETOOBIG错误

    以上, 检查操作PG和osd是否一致,基本的操作相关的参数的检查

  10. 构建要访问对象的head对象(需要主要的时,这是head对象,不是要访问的对象, 任何操作都需要head对象)

  11. 如果是顺序写,调用函数scrubber.write_blocked_by_scrub检查head对象,如果正在进行scrub操作,就加入waiting_for_active等待scrub操作完成再处理。
  12. 检查head对象是否missing:也就是处于缺失状态,需要恢复,调用函数wait_for_unreadable_object加入相应的队列等待
  13. 如果是顺序写,检查是否head对象是否is_degraded_or_backfilling_object, 也就是正在recover状态,都需要调用wait_for_degraded_object加入相应的队列等待
  14. 是否blocked by snap,objects_blocked_on_degraded_snap队列里保存head对象,这些head对象在rollback到某一个snap对象时,snap对象处于缺失状态,,就必须等待恢复,此时head对象不能写操作。objects_blocked_on_snap_promotion里的对象表示head对象rollback时,该对象在Cache pool层没有,需要Data pool层获取。
    这里最好了解一下snapshot的相关的知识和Cache Tier相关的知识。

    以上 10,11,12,13, 14 构建并检查head对象的状态

  15. 如果是 顺序写操作,检查Cache 层是否Full

  16. 检查snapdir对象,是否missing等状态
  17. 检查snapdir对象, 如果是写操作,就-EINVAL。只有读操作,才访问snapdir对象
  18. 检查是否是dup/replay
  19. 构建对象oid,这才是实际要操作的对象,可能是snapshot 也可能是head
  20. 调用函数检查maybe_await_blocked_snapset是否被block by obc,ObjectContext设置为blocked状态,该object 有可能正在flush,或者copy(由于Cache Tier),暂时不能写,需要等待
  21. 调用函数find_object_context 获取object_context,如果获取成功,需要检查oid的状态

  22. 如果hit_set不为空,就需要设置hit_set. hitset, hit_set 和 agent_state都是Cache tier的机制,hit_set记录cache是否命中,暂时不深入分析。

  23. 如果设置了agent_state, 就处理Cache 相关的信息
  24. 获取object_locator
  25. 检查obc is_blocked or blocked by
  26. 获取src_obc,一个Op带的多个OSDOp操作中,需要src oid, 例如rados_clone_range中,需要src_obj
  27. 如果是snapdir,需要所有的clone的objectContext。 如果是snapdir操作,就需要所有的clone对象,就构建所有的clone对象的objectContext,并把它加入的src_obs中
  28. 创建opContext
  29. 调用execute_ctx(ctx);

总之,do_op主要检查相关的对象的状态是否正常,并获取ObjectContext,OpContext相关的上下文信息

ReplicatedPG::execute_ctx

  1. 创建一个新的事务 ctx->op_t = pgbackend->get_transaction()
  2. 如果是写操作, 更新ctx->snapc
  3. 如果是read,加各种ondisk_read_lock 所
  4. 调用prepare_transaction
  5. 调用calc_trim_to,计算需要trim的pg log的 版本
  6. 调用函数 issue_repop发起replicat op操作:
  7. 调用函数eval_repop,检查各个副本已经reply,做相应的操作

总结,execute_ctx 执行操作,把相关的操作打包成事务,并没有真正修改文件系统的数据,后面的操作都是在主从副本上应用已经打包好的日志。

ReplicatedPG::get_object_context

获取对象的object context

  1. 从object_contexts的lur缓存中查找对象对应的ObjectContextRef
  2. 如果查找就返回,如果没有查找到,就需要创建

ReplicatedPG::calc_trim_to

  1. 计算target,设置为最小的日志保存数量cct->_conf->osd_min_pg_log_entries,如果pg处于degraded,或者正在修复的状态,设置为最大target = cct->_conf->osd_max_pg_log_entries (10000)
  2. 如果min_last_complete_ondisk不为空,且不等于pg_trim_to, pg_log的size大于target,计算pg_log的前 num_to_trim 个日志的最小的 版本

peer_last_complete_ondisk 保存了各个osd在该pg上的最后成功完成操作的版本,函数update_peer_last_complete_ondisk在每次sub_op_modify_reply完成后调用,用于更新该值
函数calc_min_last_complete_ondisk计算 min_last_complete_ondisk,在每次函数eval_repop中完成操作调用计算。

ReplicatedPG::prepare_transaction

把相关的操作打包,包括比较复杂的 snapshot的处理

ReplicatedPG::issue_repop

  1. 首先更新peer_info的相关信息,pinfo.last_update和pinfo.last_complete,如果pinfo.last_complete == pinfo.last_update,说明该peer的状态处于clean状态,没有需要recover的对象,就同时更新二者,否则只更新pinfo.last_update
  2. 加各种ondisk_write_lock
  3. 调用 repop->ctx->apply_pending_attrs()
  4. 检查如果是 EC,是否可以rollback
  5. 设置回调Context,调用函数 pgbackend->submit_transaction
  6. 解ondisk_read_unlock

PGBackend 的处理

PGBackent的处理,就是把打包好的transaction,分发到各个从osd上应用,对与EarasreCode,就实现就是ECBackend,也是主chunk向各个分片chunk分发数据。PGBacend的设计,相当于增加了一层后端存储相关的。

ReplicatedBackend::submit_transaction

  1. 调用issure_op
  2. 并调用parent->queue_transactions,修改自己,也就是主osd

ReplicatedBackend::issue_op

把请求发送到pg的各个从osd

你可能感兴趣的:(ceph)