2019独角兽企业重金招聘Python工程师标准>>>
Ceph集群在系统扩容时触发rebalance的机制分析
一、概述。
Ceph系统扩容无非就是向集群中添加新的存储节点来扩充当前Ceph集群的容量。当集群中有新的存储节点加入后,整个Ceph集群中的OSDs节点数目就会发生变化(即:OSDMap发生变化),CRUSHMap也会发生变化(比如:新加入一个机柜到Ceph集群)。因此当前集群中的部分PGs就会根据最新的CRUSHMap和OSDMap重新计算primary和replicas节点,使得新加入集群的OSDs能够处理PGs的主副本,进而使得Ceph集群中的数据能够均匀的分布在整个Ceph集群中。
二、rebalance触发机制分析。
1、OSDMap变化解析。
当有新的OSD节点加入到Ceph集群时,需要从OSD的main()函数入手分析OSD是如何加入到Ceph集群中也就是说OSD节点如何添加到OSDMap中的。Ceph源代码调用流程如下:
ceph_osd.cc
|__OSD::init()
|__OSD::start_boot()
|__MonClient::get_version()向Monitor发送MMonGetVersion消息来获取当前OSDMap的最新和最旧的版本号。调用该函数是传入一个回调函数结构体C_OSD_GetVersion,在获取到OSDMap最新和最旧的版本号时调用该结构体中的void finish()方法,在该方法中调用OSD::_maybe_boot()函数做进一步处理。
OSD::_maybe_boot()
|__OSD::_send_boot()创建MOSDBoot消息且将该消息发送给Monitor节点。
Monitor节点上负责接收和处理Message的函数是Monitor::ms_dispatch()函数,该函数获取从其它节点上发送过来的消息。Monitor::ms_dispatch()函数处理流程如下:
Monitor::ms_dispatch()
|__Monitor::_ms_dispatch()
|__Monitor::dispatch_op()该函数是处理消息的路由函数,根据消息类型决定具体的处理函数。对于MOSDBoot消息由PaxosService::dispatch()函数做进一步处理。在该函数处理过程中会依次调用PaxosService::preprocess_query()方法和PaxosService::prepare_update()方法,这两个方法在PaxosService类中是虚函数。由于Monitor节点上负责处理OSDMap的类是OSDMonitor类,该类继承自PaxosService类,在OSDMonitor类中实现了具体处理流程。由于OSD节点是新加入的因此当前OSDMap上没有该节点的信息,因此OSDMonitor::preprocess_query()函数返回false,需要调用OSDMonitor::prepare_update()函数做进一步处理。
Monitor::dispatch_op()
|__PaxosService::dispatch()
|__OSDMonitor::preprocess_query()
|__OSDMonitor::prepare_update()
|__OSDMonitor::prepare_boot()在该函数中创建一个OSDMap的pending_inc结构,之后调用wait_for_finished_proposal()函数来申请一个提案,在Monitor集群中达成一致,在该提案达成一致后回调C_Booted::_finish()函数,该函数进一步调用OSDMonitor::_booted()函数。OSDMonitor::_booted()函数调用send_lastest()函数将当前最新的OSDMap发送给新加入的OSD节点。
2、OSD节点处理OSDMap过程解析。
OSD节点处理Monitor节点发送过来的OSDMap消息的处理流程如下:
OSD::_dispatch()
|__OSD::handle_osd_map()
|__OSD::consume_map()
|__PG::queue_null()
|__PG::queue_peering_event()
OSD::_dispatch()函数是消息处理的路由函数,根据消息类型调用具体的处理函数。对于处理Monitor节点发送过来的OSDMap消息,则由handle_osd_map()函数进行处理。在handle_osd_map()函数中首先对OSDMap消息进行解析且得到OSDMap且保存,之后调用consume_map()做进一步处理。在consume_map()函数中遍历该OSD节点上已有的PGs且统计出primary/replicas/stray的数量,其次唤醒等待OSDMap的PGs,最后遍历当前OSD节点上所有PGs且调用PG::queue_null()函数将OSD节点上所有PGs添加到peering队列中。
对于新加入的OSD节点来说,由于其上没有PGs,因此新加入的OSD节点上并没有PGs加入到peering队列,所以新加入的OSD节点暂时没有后续的操作。
由于OSDMap有变化,因此Monitor节点会把最新的OSDMap发送给Ceph集群中所有的OSDs节点,使得集群中所有OSDs节点都拿到最新的OSDMap。根据上面的分析我们知道当前集群中的OSDs节点上会有PGs,因此当前集群上有PGs的OSDs节点都会将所有PGs添加到peering队列中,进而导致PG状态机的变化,使得PG从peering状态逐步转换到active+clean状态。
3、PG状态机过程解析。
PG处于peering状态时需要经过GetInfo/GetLog/GetMissing三个状态,只有这三个状态都能顺利通过后,当前PG状态变为active状态之后进行recovery和backfill最终变成active+clean状态。若这三个状态中任意状态无法正常完成则会等待并一直处于该状态最终变成inactive状态。
GetInfo状态:
GetInfo::GetInfo()
|__PG::generate_past_interval()
|__PG::build_prior()
|__GetInfo::get_infos()
|__GetInfo::react(const MNotifyRec&)
|__PG::proc_replica_info()
|__post_event(GotInfo())
PG::generate_past_interval():生成past_interval结构:past_interval中保存PG副本发生变化时,从上次PG稳定的epoch开始到PG副本发生变化前的epoch的集合。在past_interval中start_epoch到end_epoch过程中该PG的副本所在的OSDs没有发生变化;
PG::build_prior():生成past_interval结构:past_interval中保存PG副本发生变化时,从上次PG稳定的epoch开始到PG副本发生变化前的epoch的集合。在past_interval中start_epoch到end_epoch过程中该PG的副本所在的OSDs没有发生变化;
GetInfo::get_infos():向prior_set中的OSD节点发送获取Info信息;
GetInfo::react(const MNotifyRec&):处理OSD节点回复的Info信息,在该函数中更新peer_info结构。当prior_set集合中所有OSD节点都回复Info消息后,发送GotInfo消息,之后进入到GetLog状态;
GetLog状态:
GetLog::GetLog()
|__PG::choose_acting()
|__PG::find_best_info()
|__PG::calc_replicated_acting()
|__context
|__GetLog::react(const GotLog&)
|__PG::proc_master_log()
|__merge_log()
|__PGLog::merge_log()
|__更新peer_info和peer_missing结构
|__transit
PG::find_best_info():找出最具权威的OSD节点。在所有的peer_info以及本pg_info中找到最具权威的OSD。最具权威OSD节点的原则是last_update最新或者log tail最长或者当前primary OSD;
PG::calc_replicated_acting():找到合适的acting集合。acting集合包括:want/acting_backfill/backfill三个集合。
A)want:最具权威OSD节点/所有up且信息完整的OSD节点/所有acting且信息完整的OSD节点/所有peer_info且信息完整的OSD节点(want_acting)
B)acting_backfill:最具权威OSD节点/所有up且信息完整的OSD节点/所有acting且信息完整的OSD节点/所有peer_info且信息完整的OSD节点(actingbackfill)
C)backfill:up但信息不完整的OSD节点(backfill_targets)
context
PG::proc_master_log():处理权威OSD节点发送回来的Log信息:最具权威OSD节点发送过来的Log消息包括info/log/missing信息,之后调用merge_log()函数将最具权威OSD节点的log信息合并到本地且找到本PG缺失的pg_log(保存到pg_log->missing中),另外有一部分本地PG缺失的objects保存到pg_log->divergent_priors中。更新最具权威OSD节点的Info信息,即更新peer_info结构;合并history信息;更新权威OSD节点的peer_missing结构;
transit
GetMissing状态:
GetMissing::GetMissing()
|__context
|__GetMissing::react(const MLogRec&)
|__PG::proc_replica_log()
|__PGLog::proc_replica_log()
|__更新peer_info和peer_missing结构
|__post_event(Activate())
GetMissing():在actingbackfill集合中找到有效的节点(peer_info.last_update > pg_log.tail && peer_info.last_backfill != hobject_t && peer_info.last_update != pg->info.last_update)且向actingbackfill中有效的OSDs发送获取log+missing信息(LOG或FULLLOG);
GetMissing::react(const MLogRec&):接收log+missing信息的处理函数,处理其它OSDs发送过来的log+missing信息,之后修剪oinfo以及omssing信息,丢弃那些不完整不可用的log。整理接收到的oinfo到peer_info中,omissing到peer_missing中;
post_event(Activate()):发送Activate事件到PG状态机,Peering状态收到Activate事件后迁移到Active状态;
Active状态:
Active::Active()
|__PG::Activate()
|__遍历pg->actingbackfill将所有replicas节点插入到pg->blocked_by集合
PG::Activate()处理流程如下:
1)判断是否需要进行replay操作。执行replay操作的原因是在past_interval中有OSDs节点出现Crashed的情况(比较past_interval中的参数和past_interval.acting[]中的osd_info结构中的参数),否则不会执行replay。执行replay操作时,将当前PG插入到OSD节点上的replay_queue队列中。replay操作由OSD节点上的tick()定时器函数触发,在tick()函数中调用OSD::check_replay_queue()函数,该函数从replay_queue队列中取出待执行replay操作的PG,之后调用PG::replay_queued_ops()函数,将该PG插入到OSD->op_wq队列中;
2)更新当前PG info.last_epoch_started为当前osdmap的epoch值;
3)注册Activate完成回调函数,在回调函数中若不是primary则向primary发送Info消息通知primary此replica完成Activate,若是primary则判断是否所有的replicas都回复消息,若是则发送AllReplicasActivated消息;
4)对于本地有missing信息则更新pg_log->complete_to字段;
以下都是primary PG的处理。
5)遍历actingbackfill列表,创建MOSDPGLOG消息,以peer_info信息、pg_log信息以及past_interval信息填充MOSDPGLOG消息,最终将MOSDPGLOG消息发送给actingbackfill列表中指定的OSD。在指定的OSD上执行merge_log()操作即可;
6)遍历actingbackfill列表,对于primary来说将pg_log.missing填充到missing_loc.needs_recovery_map结构,对于replicas来说将peer_missing[]填充到missing_loc.needs_recovery_map结构;
7)对于需要进行recovery操作来说(判断标准是primary/replicas存在missing的对象),遍历missing_loc.needs_recovery_map中需要recovery的对象,检查该对象是否在primary/actingbackfill/peer_missing上能被找到,若能被找到则更新missing_loc结构保存需要recovery的对象和该对象所在OSD节点的映射关系。设置当前PG的状态为PG_STATE_DEGRADED;
8)调用osd->queue_for_recovery()进入recovery状态;
9)设置当前状态为PG_STATE_ACTIVATING;
Recovery状态:
OSD::do_recovery()
|__检查执行recovery的条件是否满足
|__ReplicatedPG::start_recovery_ops()
|__ReplicatedPG::recover_replicas()
|__ReplicatedPG::recover_primary()
|__ReplicatedPG::recover_replicas()
|__ReplicatedPG::recover_backfill()
|__根据当前PG不同的状态,发送RequestBackfill()/AllReplicasRecovered()/Backfilled()不同事件
|__PG::discover_all_missing() if recovery操作已经完成但是还有missing对象
OSD::do_recovery():执行recovery操作的线程是OSD::do_recovery()函数,该函数从recovery队列中获取待执行recovery操作的PG,之后根据用户设置的recovery限制条件判断是否执行recovery操作。对于需要执行recovery操作来说,首先调用ReplicatedPG::start_recovery_ops()来执行实际的reovery操作,当recovery操作执行完成后,若后续没有recovery操作且当前仍然还有missing对象没有,则调用PG::discover_all_missing()找到这些missing的对象且通过获取FULLLOG的方式重新寻找这些missing的对象;
ReplicatedPG::start_recovery_ops():该函数执行实际的recovery操作。
1)调用ReplicatedPG::recover_replicas()遍历actingbackfill集合,将actingbackfill集合中missing的对象且primary上已经存在的对象push到actingbackfill集合对应的节点上;
2)调用ReplicatedPG::recover_primary()遍历本地missing的对象且missing_loc中存在该对象和所在OSD的映射关系,则从missing_loc中指定的OSD节点pull missing的对象;
3)调用ReplicatedPG::recover_replicas()遍历actingbackfill集合,将actingbackfill集合中missing的对象且primary上已经存在的对象push到actingbackfill集合对应的节点上;
4)对于需要立即执行backfill操作来说,调用ReplicatedPG::recover_backfill()执行backfill操作;
A)根据last_backfill_started和peer_info[].last_backfill,初始化backfill_info和peer_backfill_info结构。其中backfill_info保存primary中需要进行backfil的对象信息,peer_backfill_info保存peers中需要进行backfill的对象信息;
B)设置backfill_info.begin字段为last_backfill_started,即:最近一次backfill的开始时间;
C)调用ReplicatedPG::update_range()函数更新待执行backfill操作的对象集合。
a)对于BackfillInterval->version < pg_info_t->log_tail来说调用scan_range()函数从BackfillInterval->begin开始读取指定数量的Objects到BackfillInterval->objects结构中,该结构保存待backfill对象及其版本号的映射关系;
b)对于BackfillInterval->version > pg_info_t->log_tail来说,从pg_log中的pg_log.get_log().log->version等于BackfillInterval->version开始一直到pg_log.get_log()末尾,对该pg_log范围内的对象来说若该对象在BackfillInterval->begin和BackfillInterval->end之间的则将该对象及其版本号写入到BackfillInterval->objects中,否则将该对象从BackfillInterval->objects中删除;
c)设置BackfillInterval->version = pg_info_t.last_update;
D)遍历backfill_targets集合,调用peer_backfill_info[osd].trim_to()函数将peer_backfill_info[osd].objects中小于last_backfill_started或peer_info[osd].last_backfill的BackfillInterval从peer_backfill_info[osd].objects中删除;
E)调用backfill_info.trim_to()函数将backfill_info.objects中小于last_backfill_started的BackfillInterval从backfill_info.objects中删除;
F)遍历backfill_targets集合,获取对应peer_backfill_info[osd]信息,对于peer_backfill_info[osd].begin小于backfill_info.begin的OSD,向该OSD发送MOSDPGScan::OP_SCAN_GET_DIGEST消息。replicas节点上ReplicatedPG::do_scan()函数处理该请求,do_scan()函数读取从m->begin开始的指定数量的对象且这些对象的范围信息以及对象ID和版本信息通过MOSDPGScan::OP_SCAN_DIGEST发送回primary。primary节点的ReplicatedPG::do_scan()函数处理MOSDPGScan::OP_SCAN_DIGEST消息,在该处理函数中更新peer_backfill_info[osd]中对应的begin/end/objects;
G)调用earliest_peer_backfill()函数获取到peer_backfill_info中最先需要backfill的对象。若peer_backfill_info中最先需要backfill的对象
H)对于need_ver_targs或missing_targs不为空,则将need_ver_targs和missing_targs中的内容写到to_push集合中;
I)更新last_backfill_started = backfill_info.begin;
J)删除need_ver_targs和keep_ver_targs中对应的peer_backfill_info[]项;
K)遍历to_remove集合,调用send_remove_op()函数向指定的OSD发送MOSDSubOp且op=CEPH_OSD_OP_DELETE,使得指定的OSD节点删除指定的对象;
L)遍历to_push集合,调用prep_backfill_object_push()函数向指定的OSD发送PUSH消息,将对象信息发送给指定的OSD;
M)调用ReplicatedBackend::run_recovery_op()函数执行实际的push操作;
N)遍历backfill_targets集合,若new_last_backfill > peer_info[].last_backfill,则更新peer_info[].last_backfill=new_last_backfill,若new_last_backfill == MAX则向backfill_targets对应的osd发送MOSDPGBackfill::OP_BACKFILL_FINISH消息,否则发送MOSDPGBackfill::OP_BACKFILL_PROGRESS消息,消息的last_backfill=peer_info[].last_backfill。ReplicatedPG::do_backfill()函数处理MOSDPGBackfill消息,当收到OP_BACKFILL_FINISH时发送RecoveryDone事件给PG状态机,之后发送MOSDPGBackfill::OP_BACKFILL_FINISH_ACK消息给primary,primary的ReplicatedPG::do_backfill()函数收到MOSDPGBackfill::OP_BACKFILL_FINISH_ACK消息。对于非primary收到MOSDPGBackfill::OP_BACKFILL_PROGRESS消息后调用pg_info_t->set_last_backfill()更新pg_info_t.last_backfill为消息中提供的m->last_backfill值;
5)对于PG当前状态为PG_STATE_RECOVERING时,若此时仍然需要进行backfill操作(needs_backfill() return true),则发送RequestBackfill()事件,否则发送AllReplicasRecovered()事件。对于PG当前状态不是PG_STATE_RECOVERING则发送Backfilled()事件。对于接收到AllReplicasRecovered()事件或Backfilled()事件的PG,将其状态变更为Recovered。在PG状态机在Active状态下收到AllReplicasActivated()事件后,设置all_replicas_activated=true。当PG状态为Recovered且all_replicas_activated=true,则PG发送GoClean()事件,之后PG状态变更为Clean状态(即:active+clean状态);
PG::discover_all_missing():该函数在执行完recovery操作后,仍然有missing的对象时被调用执行。该函数遍历might_have_unfound集合,检查peer_info[]和peer_missing[]集合中有关might_have_unfound集合变量的信息,确信仍然missing的,则将might_have_unfound集合中确信有missing的项插入到peer_missing_requested集合中,同时向有missing项的OSD节点发送获取FULLLOG消息;