Ceph Monitor源码机制分析

0 前言
最近终于有点时间可以看看Ceph的代码了,接下来准备就Ceph monitor这个Ceph集群中最重要的组件进行深入的分析。


1 Monitor的作用
Monitor在Ceph集群中扮演着管理者的角色,维护了整个集群的状态(抽象成几张map,包括osdmap、monmap、mdsmap、auth、log等),保证集群的相关组件在同一时刻能够达成一致,相当于集群中的领导层。之所以说是相关而不是所有的主要是因为OSD map的更新采用了类似于灰度发布的机制,这会导致在一个时刻集群中所有OSD或者Client所持有的OSDmap的版本可能是不一致的。总结一句话就是monitor是负责收集集群信息、更新集群信息以及发布集群信息的。如果只有一个monitor那么,这件事情会轻松的多,集群信息的增、删、改、查都有这个monitor完成。但作为一个分布式存储解决方案,规避任何的单点故障都是一个必备条件,所以在使用Ceph的生产环境中也会部署多个Monitor。单点问题解决了,但Monitor多了之后相应的集群数据管理也就复杂了,引入了许多新的问题,比如:集群数据存在哪里?数据到底有谁更新?其他组件从哪里读取信息?多个monitor之间如何进行数据同步等?所以在一个标准的Ceph环境中,Monitor做的事情可以抽象成以下两点:

管好自己,其实也就解决多个monitor之间如何协同工作,比如谁负责更新数据,怎么更新?monitor之间怎么同步数据?谁负责发布数据?如何确保monitor的健康问题?
管好集群信息,其实也就解决存储哪些数据?数据怎么存储?存储在哪里?怎么保证存储数据的正确性?
如何抽象成以上两点呢?还得从代码入手,看一下monitor的代码目录结构:

当然,以上源码文件的划分也不是完全分开的,彼此之间仍然有一些关联关系存在,比如选举这个过程实际上还会涉及monitor之间的数据同步也就是数据管理中的更新。数据更新,需要通过数据存储模块完成持久化。Monitor管理的数据中OSDMap和MDSMap这两部分没有放在monitor目录下,而是分别放在osd和mds目录下的,所以这里用红色字体将他们标记了出来。

接下来的几篇文章主要围绕以上两个大面详解monitor中所涉及的相关技术。

2 Monitor的初始化
Monitor的启动过程,相对比较简单,具体过程参见ceph_mon.cc这个源码文件。大概可以分为以下几部分:

介绍ceph_mon命令能够处理的参数以及使用方法
根据配置文件指定的mon_data目录创建名为store的MonitorDBStore实例并且打开数据目录。判断当前数据目录的使用情况是否超过报警限制。并且读出store的magic number确保store是正常的。
mon第一次启动时,会执行mkfs操作构建monmap,之后的启动从store中读出monmap,并从中获取mon的ip地址以后Messengerbind使用以及mon的rank值。所以如果第一次mon配置错误,后续修改mon的配置文件,重新再启动mon是不会生效的。
创建一个Monitor数据通信的Messenger,并且设置messager的policy以及throttler。
创建并初始化Monitor实例mon。初始化分为两个阶段preinit()和init(),在preinit()阶段主要初始化了paxos和各个paxosservice以及health_monitor,在init()阶段主要是初始化timer定时器、将monitor添加到dispatcher列表中并进行bootstrap()。从bootstrap()开始也就进入了Monitor的选举流程,这个会在下一节详细介绍。
Monitor进程只创建了一个Messenger,也就意味着它只有一个dispatch_queue和一个dispatcher线程,所有的请求都会排队。另外,Monitor还会初始化一个timer,其会创建一个线程用来处理所有的消息超时event,包括probe、propose、lease等消息,所以这些消息也是串行处理的。这事Monitor中两个真正做事的线程。所以当你在集群中执行命令半天不返回时,八成是因为Monitor的dispatch队列堵有消息排队了,而根本原因可能是Monitor store数据更新缓慢造成的,这有可能是磁盘有问题,也有可能是LevelDB/RocksDB有大量冗余数据导致读取缓慢。

3 Monitor的选举机制
Monitor要做的事情很明确了,就是管理、维护和发布集群的状态信息,但是为了避免单点故障或者性能热点问题,一般使用多个Monitor来做这一件事情,也就是管理层有多个成员。集群的正常运行,首先需要管理层达成一致,达成一致就需要有一个能拍板的monitor(leader),大家都听它的就行了。所以要达成一致核心问题就是在众多monitor中选出那个能拍板的monitor。Ceph解决这个问题的方法很简单,有点类似于领导人的选举,即有资格的monitor先形成一个quorum(委员会),然后委员会的成员在quorum这个范围内选出一个leader,集群状态信息的更新以及quorum成员的维护就有这个leader负责。Leader的选取规则也比较简单,每个monitor在初始化的时候都会根据它的IP地址被赋予一个rank值,当选举leader时,rank值最小的monitor胜出当选leader。当quorum成员发生变化时(增加或者减少),都会触发重新选举流程,再选出一个leader。

整个Monitor的选举过程也是Monitor根据以下状态机进行状态变化的过程:

即Monitor在启动之后,会根据monmap发现其他的monitor并获取其他monitor的monmap、paxos版本等信息,然后酌情syncronize数据并触发quorum范围内的选举,选举之后monitor要么成为Leader要么成为Peon,直到服务停掉。

 

这么说还是有点笼统,让我们用代码说话吧,当然你得学会看代码。选举的入口函数是Monitor::start_election(),查一下调用这个函数的代码,不难看出会在以下三种情况发生时,调用它:

Monitor::handle_probe_reply(), monitor进行bootstrap时,首先会向monmap中所有的成员发送MMonProbe消息,然后在收到peer返回的probereply时,会根据返回的quorum信息以及paxos版本来判定是否需要发起选举。
Elector::handle_propose(),这个是在收到别的monitor发过来的选举请求消息时,会根据情况触发重新选举。
Monitor::do_admin_command()和Monitor::handle_command(),这两个属于通过ceph命令或者mon的admin socket执行quorumenter和quorum exit触发的选举操作。
这里详细介绍一下在第一种情况下触发的选举,并以此梳理一下整个选举过程。

  Monitor::bootstrap(),monitor进程的启动逻辑,具体做了以下几件事:

开始时设置monitor的状态为STATA_PROBING
然后判断是否设置了mon_compact_on_bootstrap参数,如果设置了,就执行compact操作,对monitor的store进行压缩
如果集群只有一个monitor,则该monitor直接胜出
根据mon_probe_timeout重置probe_timeout事件的时间
如果monitor在monmap中,则将其将如到outside_quorum集合中。
根据monmap,向其他peer一一发送MMonProbe消息。 
Monitor收到消息,经过dispatch逻辑之后进入Monitor::dispatch_op(),解析出来是MSG_MON_PROBE类型的消息,进而进入Monitor::handle_probe(),然后进入Monitor::handle_probe_reply进行处理。

  Monitor::handle_probe_reply(),在这里主要做了以下几件事。

先判断当前monitor所处的状态如果是Probing或者Electing,则直接退出。
比对对方的monmap和自己monmap的epoch版本,如果自己的monmap版本低,则更新自己的map,然后重新进入bootstrap()阶段。
如果当前Monitor处于synchronizing阶段,则直接返回
比对彼此的paxos的版本,如果对方的paxos版本较低,否则判断是否需要进行data的sync。这里有两种情况,如果自己的paxos版本是比对方的paxos_first_version纪录的版本低,则会进行sync操作。如果自己paxos的版本和对方的版本相差太远超过了设置的参数paxos_max_join_drift的值,也会先进行数据的sync而不会触发重新的选举操作。
如果从返回的消息中判断已经有一个quorum存在了,自己也在monmap中摒弃自己的ip地址不为空,则直接发起一个选举。否则,会请求加入这个quorum。
如果没有现成的quorum,并且自己在monmap中,则把peer添加到outside_quorum的集合中。如果此时outside_quorum中的成员大于等于monmap->size() / 2 + 1时,开始选举,否则返回,等待条件满足。 
  Monitor::start_election (),在这里主要做了以下几件事。

如果Paxos正在STATE_WRITING或者STATE_WRITING_PREVIOUS状态,则等待paxos的更新完成。
重置monitor中的服务,包括probe timeout事件、停止时间检查(mon time skew的检查)、health检查事件、scrub事件等,并且restart paxos以及所有的paxos service服务。
设置自己进入STATE_ELECTING状态,并增加l_mon_num_elections和l_mon_election_call这些统计数据。
调用elector的call_election()。 
  Monitor::start_election (),在这里主要做了以下几件事。

从Mon store中读出mon的election_epoch存储在epoch中,更新epoch的值使其变为奇数,表明进入了选举cycle。epoch为偶数,表明已经形成了稳定的quorum。
把自己加入到acked_me map中,并设置electing_me为true,希望大家选自己当leader。
向monmap中的成员发送MMonElection::OP_PROPOSE消息。 
  其它的Monitor收到消息后,经过dispatch逻辑,即Monitor:: ms_dispatch() --> Monitor::_ms_dispatch() --> Monitor::dispatch_op()--> Elector::dispatch(),之后进入消息处理流程。

Elector::handle_propose(),首先确保收到消息的epoch版本是处于选举的版本(奇数)并且满足对feature的要求。接着判断将自己的选举epoch设置为和消息中包含的epoch的值。最后比对rank值,如果自己的rank值更小,则自己不ack此次选举,而是重新发起一轮选举。如果自己的rank值更大,则进入Elector::defer()流程,发送MMonElection::OP_ACK消息,ack该轮选举。
  发起选举的Monitor收到ACK消息之后,进入处理流程:

将ACK自己的peer加入到acked_me这个map中,如果acked_me的个数和monmap中成员的个数一样,则表明选举成功,进入victory流程。这里有点需要搞清楚的是在有一个monitor down的情况下,剩余的monitor是如何选举成功的(acked_me的成员肯定和monmap的成员个数不相等)。
  Leader会进入Elector::victory(),具体处理流程如下:

将acked_me中的成员加入到quorum中,并且将election epoch的值加一使其变成偶数,标志选举过程结束。
向quorum中的所有成员发送MMonElection::OP_VICTORY,消息通知大家选举结束。
告诉monitor自己选举成功。 
  Leader进入Monitor::win_election(),具体处理流程如下:

设置自己的状态为STATE_LEADER,清空outside_quorum中的成员。
调用paxos->leader_init()初始化paxos,以及所有的paxos_service服务。在paxos的初始化中会设置paxos的状态为STATE_RECOVERING,并且调用Paxos::collect()函数,同步mon之间的数据,这个会在后面的Paxos数据更新部分介绍。
启动health_monitor服务,目前主要是检查mon存储空间的使用情况。
启动timecheck检查,确保monitor之间的时差不超过mon_clock_drift_allowed,如果超过就会报告mon clockskew。
更新monitor的metadata,其主要纪录了以下信息:
[root@ceph02 ~]#ceph mon metadata ceph02
{
    "arch": "x86_64",
    "cpu": "Intel Xeon E312xx(Sandy Bridge)",
    "distro": "CentOS",
    "distro_codename":"Core",
    "distro_description":"CentOS Linux release 7.1.1503 (Core) ",
    "distro_version":"7.1.1503",
    "hostname": "ceph02",
    "kernel_description": "#1SMP Tue Sep 15 15:05:51 UTC 2015",
    "kernel_version":"3.10.0-229.14.1.el7.x86_64",
    "mem_swap_kb": "0",
    "mem_total_kb":"1884312",
    "os": "Linux"
}
  Peon在收到MMonElection::OP_VICTORY消息之后进入Elector::handle_victory(),具体处理流程如下:

将自己的election epoch设置成消息中的epoch值。
进入Monitor::lose_election(),设置自己的状态为STATE_PEON,调用peon_init初始化paxos以及相关的paxosservice,更新logger信息。
取消自己的expire_event时间,即有参数mon_election_timeout控制的时间。 
至此,Monitor的选举过程就算结束了,但Paxos的状态还没有进入稳态,所以剩下的事情就是Leader来协调quorum中所有成员的数据同步了,这个主要是通过Paxos协议的两阶段提交机制来完成,整个过程相对比较复杂,会在后续数据更新机制中详细进行介绍。为了方便了解整个选举过程,我将主要的逻辑以时序图的形势展现出来,具体详见下图,图中主要以Leader为主线,给出了选举过程涉及的几个主要文件。


————————————————
版权声明:本文为CSDN博主「瞧见风」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/scaleqiao/article/details/52315468
原文链接:https://blog.csdn.net/scaleqiao/article/details/52242345
原文链接:https://blog.csdn.net/scaleqiao/article/details/52231900

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Ceph中,stripe是一种将数据分片存储的概念。当进行文件读取操作时,需要通过一系列的计算来确定数据所在的具体位置。本文以CephFS的文件读取流程为例进行分析。 首先,在文件读取过程中,Ceph会将文件划分为若干个条带(stripe),每个条带由多个对象分片(stripe unit)组成。条带可以看作是逻辑上连续的一维地址空间。 接下来,通过file_to_extent函数将一维坐标转化为三维坐标(objectset,stripeno,stripepos),来确定具体的位置。其中,objectset表示所在的对象集,stripeno表示条带号,stripepos表示条带内的偏移位置。 具体的计算过程如下:假设需要读取的数据的偏移量为offset,每个对象分片的大小为su(stripe unit),每个条带中包含的对象分片数为stripe_count。 首先,计算块号blockno = offset / su,表示数据所在的分片号。 然后,计算条带号stripeno = blockno / stripe_count,表示数据所在的条带号。 接着,计算条带内偏移stripepos = blockno % stripe_count,表示数据在条带内的偏移位置。 接下来,计算对象集号objectsetno = stripeno / stripes_per_object,表示数据所在的对象集号。 最后,计算对象号objectno = objectsetno * stripe_count + stripepos,表示数据所在的对象号。 通过以上计算,可以确定数据在Ceph中的具体位置,从而完成文件读取操作。 需要注意的是,以上分析是基于Ceph版本10.2.2(jewel)进行的,尽管版本跨度较大,但是该部分代码在12.2.10(luminous)版本中仍然比较稳定,基本的框架没有发生变化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值