PG的Peering过程是十分复杂的,相关的代码实现也相当冗长。这里我们从侧面出发,介绍一下PG源代码实现中的一些重要字段,弄清其含义及用途之后也有利于我们理解PG的Peering操作:
下面我们分别介绍这些字段。
1. PG重要字段分析
1.1 PG.primary
在如下几种情况下,会计算PG的primary,并调用函数PG::init_primary_up_acting()来初始化primary、up set、acting set信息:
-
OSD启动时,加载已存在的PG
-
创建新的PG
-
PG分裂产生新的PG
-
PG进行Peering操作
下面我们对这些情况进行介绍:
1.1.1 OSD启动加载已存在PG
在OSD启动时,会调用如下函数来加载PG:
上面我们看到会调用OSDMap::pg_to_up_acting_osds()来计算当前PG的up osds以及acting osds。我们必须
使用acting set来进行数据的映射;但有些用户也会发现up set也是很有用的,通过对比acting set与up set就可以知道pg_temp是什么。
注:OSD启动时,并不是直接去monitor拿最新的osdmap,而是从superblock中读取上一次所保存的osdmap。
下面我们来看_pg_to_up_acting_osds()函数的实现:
从上面我们可以看到,对于PG的映射有多个步骤:
1) _pg_to_osds()
_pg_to_osds()函数的实现比较简单,其从crushmap中获取PG所映射到的OSD。这里注意,从crushmap获取到到的映射与OSD的运行状态无关(与权重有关,当osd out出去之后,其权重变为了0,在进行crush->do_rule()时就不会再映射到该OSD上了)。之后调用_remove_nonexistent_osds()函数移除在该OSDMap下不存在的OSD。
_pg_to_osds()函数返回了最原始的PG到OSD的映射,并且第一个即为primary。
2) _raw_to_up_osds()
通过_pg_to_osds()函数,我们获取到了原始的PG到OSD的映射关系。而_raw_to_up_osds()函数主要是移除了在该OSDMap下处于down状态的OSD,即获取到了原始的up osds,并且将第一个设置为up primary。
3) _apply_primary_affinity()
此函数主要是处理primary亲和性的设置,这里不做介绍(默认不做设置)。
4) _get_temp_osds()
_get_temp_osds()用于获取指定OSDMap下,指定PG所对应的temp_pg所映射到的OSD。
5)完成PG的up set、acting set的映射
我们再回到_pg_to_up_acting_osds()函数的最后,如果当前没有temp_pg,那么该PG的acting set即为up set。
注: 在PG::init_primary_up_acting()函数中,将acting primary设置给了PG::primary。
1.1.2 PG进行Peering操作
在PG进行peering操作过程中,也会导致重新计算primary、acting set以及up set:
从上面我们看到当有新的OSDMap产生时,在OSD::advance_pg()函数中又计算出了新的upset与acting set。
那具体是在什么地方会导致up set与acting set不一致呢?我们来看peering过程中的GetLog事件:
在上面会调用choose_acting()函数,此时就会导致up set与acting set不一致。下面我们来看这个函数:
上面我们看到会调用calc_replicated_acting()来计算我们所期望的acting set,即want_acting。如果want_acting不等于up的话,那么采用want来申请temp pg。申请temp pg会导致产生新的osdmap,因此会触发新的peering操作,从而在OSD::advance_pg()中调用pg_to_up_acting_osds()完成新的acting set的映射。
因此,这里我们可以总结为:up set是直接根据crushmap算出来的,与权威日志等没有任何关系;而acting set是我们真正读写数据时所依赖的一个osd序列,该序列的第一个osd作为primary来负责数据读写,因此必须拥有权威日志。
1.2 pg_whoami
包含PG的shard信息,以及所在的OSD信息:
对于ReplicatedPG来说,其shard值为shard_id_t::NO_SHARD;对于EC类型的PG来说,由于对数据的读写与PG副本的顺序相关,因此需要shard来指定。
这里我们先来看一下PG的创建:
从上面我们看到对于ReplicatedPG,其直接调用spg_t(pgid)构造函数来创建spg_t对象。
1.3 up_primary
通常是up set中的第一个osd为up_primary。
1.4 up/acting
这里的up是指PG的up set; acting是指PG的acting set。通常情况下,这两个集合是相同的,只有当系统出现异常时,当前的acting set并不能满足正常的读写请求,那么就会在peering时申请产生pg_temp,此时生成的新的acting set就会与up不一样。
关于up set与acting set,我们在前面已经讲述,因此这里不再赘述。
1.5 want_acting
want_acting用于保存我们所期望的的acting set集合,只在pg_temp过程中,该字段有效。下面我们来简单看代码:
1.6 actingbackfill
actingbackfill在整个代码中只有一个地方进行赋值,我们来看一下:
对acting_backfill的计算分为如下几个步骤:
1) acting_backfill的第一个元素是拥有权威日志的OSD
从上面我们可以看到,如果当前up_primary拥有全量数据,那么就优先选择up_primary;否则选择拥有权威日志的osd作为acting_backfill的第一个元素。
2) 从upset中选择
从上面我们可以看到,对于upset中的osd,不管是否与权威日志有重叠,都会被加入到acting_backfill中。
3) 从acting set中选择
从上面我们可以看到,对于acting set中的元素,其需要同时满足如下两个条件才会被加入到acting_backfill中:
-
last_backfill已经完成
-
与权威日志有重叠
4) 从all_info中选择
从上面我们可以看到,对于all_info中的元素,其需要同时满足如下两个条件才会被加入到acting_backfill中:
-
last_backfill已经完成
-
与权威日志有重叠
这里all_info
是怎么来的?我们来简单看一下获取peer_info信息的流程:
从上面可以看出,peer_info主要来源于上面三处。而这些peer_info又是由于在peering过程中调用GetInfo来进行触发的,现在我们来看:
从上面我们看到peer_info是来源于prior_set,这就引出PG::build_prior()函数,我们会在相关章节进行详细介绍。
1.7 actingset
actingset其实就是pg.acting,只是其用std::set方式来进行存储,在init_primary_up_acting()函数中进行设置:
1.8 upset
upset其实就是pg.up,只是其用std::set方式来进行存储,在init_primary_acting()函数中进行设置:
1.9 last_update_ondisk
我们在代码中搜寻last_update_ondisk
,看到对其的修改主要有如下两个地方:
下面我们来分析:
1) PG::activate()
PG::activate()会在PG peering完成时被调用,这里将last_update_ondisk设置为了info.last_update。
注: peering完成时,对于PG primary,其info.last_update是否就等于info.last_complete?
2) PG::repop_all_committed()
PG::repop_all_committed()函数会在对象的三个副本的日志都写完成时被调用。通过该函数我们看到,repop->v赋值给了last_update_ondisk。现在我们来看这中间的过程:
从上面我们可以看到,其实就是将ctx->at_version赋值给了repop->v。
也就是说,当PG::repop_all_committed()被回调,说明该OpContext对应的事务日志已经提交到硬盘,此时会直接将last_update_ondisk设置为ctx->at_version。
疑问:此处如何保证按顺序提交的事务,其响应也是按顺序返回的呢?比如事务A、事务B按顺序提交,但是事务A响应丢失,从而导致ReplicatedPG::repop_all_committed()中last_update_ondisk是一个更新的ctx->at_version。
如能保证PG每个节点所成功提交的事务都是连续的,似乎也没有问题。代码中是如何保证???
3)总结
last_update_ondisk记录的是PG所有副本均完成写日志操作的最新对象版本号。
1.10 last_complete_ondisk
我们在代码中搜寻last_update_ondisk
,看到对其的修改主要有如下两个地方:
下面我们来分析:
1) ReplicatedPG::repop_all_committed()
对于repop_all_committed()函数,其会在对象日志写入完成时回调。我们看到在该函数中,其将repop->pg_local_last_complete赋值给了last_complete_ondisk:
由上面可见,repop_all_committed()函数中记录的last_complete_ondisk可能并不是当前最新写入的事务的version值,而可能是一个更老的info.last_complete(注:猜测主要原因可能是本PG还可能在进行backfilling操作,因此last_complete_ondisk会是一个更老的位置)
2) update_last_complete_ondisk()
对于sub_op_modify_commit()函数,其会在PG的非主副本日志写入完成时被调用:
从上面我们可以看到,对于PG的非主副本,当写日志操作完成时触发回调sub_op_modify_commit(),从而更新last_complete_ondisk,其更新的值同样为当前PGInfo的last_complete值。
3) 总结
last_complete_ondisk用于记录PG所有副本均完成写对象到硬盘的版本号,其是通过pginfo.last_complete来得到的。在后面的章节中,我们会分析PGInfo在整个PG生命周期中的变化。
[参看]