ceph分布式存储系统Monitor源码分析总结Word文档格式.docx
《ceph分布式存储系统Monitor源码分析总结Word文档格式.docx》由会员分享,可在线阅读,更多相关《ceph分布式存储系统Monitor源码分析总结Word文档格式.docx(13页珍藏版)》请在冰豆网上搜索。
(5)PG:
全称PlacementGrouops,是一个逻辑的概念,一个PG包含多个OSD。
引入PG这一层其实是为了更好的分配数据和定位数据。
Ceph存储集群中主要的服务有Monitor,OSD,MDS。
其通信框架如下图所示:
图1.1ceph通信框架图
CephAsync模型里,一个Worker类对应一个工作线程和一个事件中心EventCenter。
每个socket对应的AsyncConnection在创建时根据负载均衡绑定到对应的Worker中,以后
都由该Worker处理该AsyncConnection上的所有的读写事件。
本文主要是在阅读Monitor源码中的一些总结,后续可供其他新入职员工阅读参考。
2.类关系
3.启动顺序
一般程序分为两种。
一种是可执行程序,一种是对外提供接口服务的库程序。
对于代码阅读中首先弄明白当前代码是可执行程序还是库程序。
对于monitor,核心业务在libmaon.a中,以静态库的形式提供,通过icfs-mon可执行程序进行调用。
icfs-mon则是monitor的可执行程序。
对于可执行程序的源码阅读,首先要找到main函数,一般main函数的都不大,会在其中执行基本的初始化操作,icfs-mon的main函数就在icfs_mon.cc中。
icfs_min.ccmain函数中主要进行启动参数处理,配置文件加载,数据库加载,创建消息实例,创建monitor实例,monitor初始化等,基本流程如下如图所示。
对于库程序源码主要从对外提供的头文件入手,一般也会存在初始化,注册接口等。
图3.2mon启动顺序
4.基础业务
Monitor的主要任务就是维护集群视图(各种map)的一致性,在维护一致性的时候使用了Paxos协议,并将其实例化到数据库中,方便后续的访问和使用。
维护视图一致性的过程中在monitor内部主要进行的流程大概有bootstrap,数据同据(sync),选举,数据收集,议案下发。
正常流程的议案流程一般是在monitor处理完update请求后触发。
在icfs-mon.cc的main函数中创建monitor后随即进行预初始化(preinit)和初始化(init)操作。
preinit阶段主要初始化了paxos和各个paxosservice以及health_monitor。
Init阶段初始化timer定时器、将monitor添加到dispatcher列表中并进行bootstrap()。
从bootstrap()开始也就进入了Monitor的选举流程。
以下结合代码简单进行介绍。
3.1bootstrap
每次monitor启动时都会按照monmap中的服务器地址去连接其他monitor,并同步数据,bootstrap的第一个目的就是从其他服务拉缺失的paxoslog或者全量复制数据库,其次是在必要时形成多数派建立一个paxos集群或者加入到已有的多数派中。
Bootstrap中具体要做的几件事情:
(1)开始时设置monitor的状态为STATA_PROBING;
(2)然后判断是否设置了mon_compact_on_bootstrap参数,如果设置了,就执行compact操作,对monitor的store进行压缩;
(3)如果集群只有一个monitor,则该monitor直接胜出;
(4)根据mon_probe_timeout重置probe_timeout事件的时间,如果在超时时间内没有取消该事件(handle_prope_reply后会取消,因为已经进行了响应),则进行超时事件处理;
(5)如果monitor在monmap中,则将其将如到outside_quorum集合中;
(6)根据monmap,向其他peer一一发送MMonProbe消息;
对端的monitor收到probe的消息后进行handle_probe_probe处理:
(1)如果missingfeature,发送OP_MISSING_FEATURES的信息,结束;
(2)若message的发送方的版本比自己新,无法通过paxos算法部分做数据修复,需要重新bootstrap()从对方主动拉数据;
(3)正常流程,汇报目前的paxos状态,last_commit,first_commit信息,发送OP_REPLY消息给发送方;
(4)如果发现了一个peer,那么extra_probe_peers.insert(m->
get_source_addr()),想不出什么场景?
发送探测包的monitor节点在最大超时时间内收到OP_REPLY,会调用handle_probe_reply函数进行下一步处理。
如果未收到replay响应包执行超时处理。
(1)先判断当前monitor所处的状态如果是Probing或者Electing,则直接退出;
(2)比对对方的monmap和自己monmap的epoch版本,如果自己的monmap版本低,则更新自己的map,然后重新进入bootstrap()阶段;
(3)如果当前Monitor处于synchronizing阶段,则直接返回;
(4)比对彼此的paxos的版本,如果对方的paxos版本较低,否则判断是否需要进行data的sync。
这里有两种情况,如果自己的paxos版本是比对方的paxos_first_version纪录的版本低,则会进行sync操作。
如果自己paxos的版本和对方的版本相差太远超过了设置的参数paxos_max_join_drift的值,也会先进行数据的sync而不会触发重新的选举操作;
(5)如果从返回的消息中判断已经有一个quorum存在了,自己也在monmap中并且自己的ip地址不为空,说明自己已经是集群的一员了,则直接发起一个选举。
否则,会请求加入这个quorum;
(6)如果没有现成的quorum,并且自己在monmap中,则把peer添加到outside_quorum的集合中。
如果此时outside_quorum中的成员大于等于monmap->
size()/2+1时,开始选举,否则返回,等待条件满足。
注意:
sync_last_committed_floor和last_commited的区别,一般情况下两者是相等的,sync_last_committed_floor是在last_committed丢失或者获取异常等极端情况下的一种保护措施,防止从旧的数据进行同步。
3.2sync
对于sync(数据同步)操作前面已经进行了介绍,数据同步分为两种场景:
一种是在自己的paxos版本是比对方的paxos_first_version纪录的版本低,进行全量数据同步,一种是自己paxos的版本和对方的版本相差超过了设置的参数paxos_max_join_drift,也会进行数据的sync,此时进行的是部分数据同步。
如上图所示,消息OP_GET_COOKIE_FULL和OP_GET_COOKIE_RECENT分别代表全量同步和增量同步,如果是增量同步只需给对端携带last_committed。
对端的handle_sync_get_cookie负责生成cookie信息,handle_sync_get_cookie函数对请求同步方来讲只能触发一次,也就是说对于cookie对数据同步是唯一的,后续一步步数据同步都使用该cookie(数据提供方通过cookie来识别是mon)。
通过OP_COOKIE消息将cookie应答给数据同步请求方。
消息OP_GET_COOKIE_FULL/OP_GET_COOKIE_RECENT,OP_COOKIE进行的是数据同步的准备步骤,后续的OP_GET_CHUNK,OP_CHUNK,OP_LAST_CHUNK才是真正的数据同步。
该步骤相对简单,通过与阅读源码能够理解其内部实现,简单描述下分块同步的逻辑,以下是数据分块的源码。
intleft=g_conf->
mon_sync_max_payload_size;
while(sp.last_committed<
paxos->
get_version()&
&
left>
0){
bufferlistbl;
sp.last_committed++;
interr=store->
get(paxos->
get_name(),sp.last_committed,bl);
assert(err==0);
tx->
put(paxos->
left-=bl.length();
dout(20)<
<
__func__<
"
includingpaxosstate"
<
sp.last_committed
dendl;
}
reply->
last_committed=sp.last_committed;
if(sp.full&
sp.synchronizer->
get_chunk_tx(tx,left);
sp.last_key=sp.synchronizer->
get_last_key();
last_key=sp.last_key;
if((sp.full&
has_next_chunk())||
sp.last_committed<
get_version()){
dout(10)<
chunk,throughversion"
key"
sp.last_key<
}else{
lastchunk,throughversion"
op=MMonSync:
:
OP_LAST_CHUNK;
assert(g_conf->
mon_sync_provider_kill_at!
=3);
//cleanupourlocalstate
sync_providers.erase(sp.cookie);
(1)intleft=g_conf->
获取每次进行数据同步的最大数据大小,在单次数据同步前获取;
(2)while(sp.last_committed<
0);
判断同步请求方paxos版本号是否小于自己的paxos版本号且left是否大于0,如果不满足则说明本次数据已经同步结束,造成同步结束的原因可能是版本已经一致(最后一块数据)也可能是数据块的大小超过g_conf->
mon_sync_max_payload_size;
(3)在上述while循环中进行同步数据块的组装,每次循环中数据同步方的paxos版本号(sp.last_committed)进行递增,单次同步剩余数据的大小(left)进行递减,直到不能满足循环条件退出循环;
(4)if(sp.full&
0)说明是全量同步,且left大于0,代表是最后一块数据,进行数据组装;
(5)if((sp.full&
sp.last_committed<
get_version()){条件满足说明是同步
还没有结束,同步请求方数据同步后还会继续发送OP_CHUNK消息进行下一块数据的获取,否则发送OP_LAST_CHUNK消息,同步数据方进行sync_finish
3.3选举
在boostrap数据多数派和数据同步完成后,可能会进行选举流程,选举的作用是重新确定leader,并形成新的多数派,在多数派中进行后续的数据同步,以下简单介绍下选举的流程。
首先是在mon中调用start_start()函数:
(1)如果Paxos正在STATE_WRITING或者STATE_WRITING_PREVIOUS状态,则等待paxos的更新完成;
(2)重置monitor中的服务,包括probetimeout事件、停止时间检查(montimeskew的检查)、health检查事件、scrub事件等,并且restartpaxos以及所有的paxosservice服务;
(3)设置自己进入STATE_ELECTING状态,调用elector的call_election()。
紧接着调用Elector:
call_election():
(1)从Monstore中读出mon的election_epoch存储在epoch中,更新epoch的值使其变为奇数,表明进入了选举cycle。
epoch为偶数,表明已经形成了稳定的quorum。
(init()函数);
(2)把自己加入到acked_memap中,并设置electing_me为true,希望大家选自己当leader,并向monmap中的成员发送MMonElection:
OP_PROPOSE消息。
Monmap成员中的其他mon经过一系列消息处理后进入Elector:
handle_propose进行propose的消息处理:
(1)首先确保收到消息的epoch版本是处于选举的版本(奇数)并且满足对feature的要求;
(2)接着判断将自己的选举epoch设置为和消息中包含的epoch的值;
(3)最后比对rank值,如果自己的rank值更小,则自己不ack此次选举,而是重新发起一轮选举;
(4)如果自己的rank值更大,则进入Elector:
defer()流程,发送MMonElection:
OP_ACK消息,ack该轮选举。
如果自己已经应答了其他的mon的propose消息,且其他的mon的rank大于等于本次收到mon的rank值,也发送MMonElection:
发起选举的mon收到ACK消息后进入Elector:
handle_ack处理,handle_ack的处理比较简单,将ACK自己的peer加入到acked_me这个map中,进入选举逻辑需要满足如下条件:
acked_me.size()>
=(mon->
monmap->
size()-mon->
off_mon_rank.size())&
(acked_me.size()-stop_acked_me.size())>
(mon->
size()/2)
off_mon_rank中记录的是因为网络原因连不上的mon,stop_acked_me记录的即将异常停止的mon。
此时已经选举出了leader,leader进入Elector:
victory():
(1)将acked_me中的成员加入到quorum中,并且将electionepoch的值加一使其变成偶数,标志选举过程结束;
(2)向quorum中的所有成员发送MMonElection:
OP_VICTORY,消息通知大家选举结束,附带quorum;
并告诉monitor自己选举成功。
设置自己的状态为STATE_LEADER,清空outside_quorum中的成员,调用paxos->
leader_init()初始化paxos,以及所有的paxos_service服务。
在paxos的初始化中会设置paxos的状态为STATE_RECOVERING,并且调用Paxos:
collect()函数,同步mon之间的数据,这个在后面数据同步章节介绍;
(3)向stop_mon中的成员发送选举成功的消息(应该是尝试发送)
各个peon收到MMonElection:
OP_VICTORY消息之后进入Elector:
handle_victory(),处理也比较简单:
(1)lectionepoch设置成消息中的epoch值;
(2)进入Monitor:
lose_election(),设置自己的状态为STATE_PEON,调用peon_init初始化paxos以及相关的paxosservice,更新logger信息;
下图是选举过程的中主要函数调用图。
3.4数据收集
此时可能有人会存在一个疑问,在选举之前不是已经进行了sync数据同步了吗?
为什么在选举结束后还要进行数据同步呢?
我们可以翻到同步数据的章节在回忆以下,上述所说的同步分为两种,一种是全量一种是增量,全量和增量的同步时需要满足一定的条件才会触发,如果没有触发全量同步,各个paxos间的版本差又在paxos_max_join_drift之内,中间可能会存在小版本数据的差异,那就需要在选举结束后在进行一下同步来解决这种小版本差的同步问题。
Leader触发paxos:
collect():
(1)设置STATE_RECOVERING状态;
(2)初始化uncommitted_v,uncommitted_pn,uncommitted_value,accepted_pn等值;
(3)携带last_committed,last_committed,accepted_pn向peon发送OP_COLLECT消息;
(4)设置数据收集超时事件。
Peon收到OP_COLLECT,进入函数paxos:
handle_collect(MonOpRequestRefop)开始处理:
(2)如果自己的last_commited+1小于leader的first_committed,说明自己的版本远远落后leader,需要通过bootstrap主动同步;
(3)生成OP_LAST消息实例,如果自己的accepted_pn小于leader的accepted_pn,则接受并DB入库;
(4)如果leader的last_committed小于自己的,说明leader由数据缺失,通过share_state将缺失的数据补全;
如果存在未提交的数据,也进行数据不全,向leader发送OP_LAST消息。
leader调用paxos:
handle_last(MonOpRequestRefop),进行数据的恢复主要流程有:
(1)检测是否有peon节点的数据比自己新(store_state),并用need_refresh记录是否需要更新内存数据;
(2)检测peon的数据是否比自己的旧,如果旧向该peon发送OP_COMMIT消息让其同步数据;
(3)按照collect更新后,检测自己的pn是否为集群中的最大值,如果不是,更新pn为所集群中的最大值,重新collect;
(4)如果peon已经接受了自己的pn,则进行接受peon的数目进行自增:
num_last++,
如果peon存在更新(或者跟自己相同)的未提交议案,则将peon的未提交信息同步更新自己(更新uncommitted_v,uncommitted_pn,uncmmitted_value);
(5)如果接受自己pn的peon等于quorum成员个数,在存在未提交议案的情况下进进行议案下发(下节描述);
(6)如果没有未提交的议案,直接进入extend_lease(),然后刷新数据,开启新的议案周期。
数据同步的流程图和代码调用图如下所示。
3.5议案下发
议案的propose主要在PaxosService的各种服务更新map,collect后有未提交的数据,Paxos以及PaxosService对数据做trim时进行触发。
Map更新后进行议案propose的流程图如下:
leader接收到修改请求的流程代码调用流程如下:
leader调用paxos:
begin():
(1)申请DB事务,存储pending_v,pending_pn,uncommitted_value等相关值,并进行DB存储,如果是单节点直接走commmit流程;
(2)向quorum成员发送OP_BEGIN消息,消息体中包含未提交的新值,last_committed以及accepted_pn;
(3)设置OP_BEGIN应答超时事件;
peon调用Paxos:
handle_begin(MonOpRequestRefop)响应提案:
(1)设置当前状态为STATE_UPDATING,存储数据,并进行事务提交;
(2)发送OP_ACCEPT给leader;
Leader调用Paxos:
handle_accept(MonOpRequestRefop)处理accept应答:
(1)对accept->
pn与accepted_pn以及accept->
last_committed和last_committed进行检测;
(2)如果所有的monitor全部accept,执行commit_start();
Paxos:
commit_start中主要完成last_committed的本地存储,后端DB存储完成后设置回调commit_finish,并取消accept的接受超时事件。
commit_finish中主要完成以下几步操作:
(1)更新内存中的内存中的last_committed(last_committed++),刷新first_committed(从DB中读出);
(2)通知quorum中其他peon成员提交完成,发送OP_COMMIT,包括议案提交的内容,accepted_pn和last_committed;
(3)设置状态为STATE_REFRESH,调用paxos:
do_refresh()进行所有map的内存更新(增量数据的同步);
(4)commit_propose函数触发等待commit完成的