前言
在ceph monitor 运行过程中,需要选举leader,所有的ceph monitor中只会有一个节点会被选举成leader,其他的节点为peon。
后续所有的update相关的操作,通过leader 发出Paxos propose完成,如果是peon节点收到更新请求,那么会转发到leader,让leader代为执行。
注意ceph中的monitor leader的选举算法,并不是paxos算法,ceph讨了一个巧,它利用了节点在monmap中的rank值,人为地制造了地位不平等,rand最小的节点获胜,从而简单快速地达到选举的目的。
发起
其实整个选举过程,我们可以以start_election为起点研究,那么何时会调用start_election函数呢?
- 节点调用bootstrap函数引导启动,接着会probing,查询其他monitor信息(有可能需要同步数据),完成后发起选举
- 节点收到选举消息MMonElection,如果节点自己已经处于quorum或自己的编号更小,也会重新发起选举
- 节点收到quorum enter/exit命令
第三种情况是一个测试用的工具,我们可以忽略不提。很多种情况都会导致某个节点调用bootstrap,比如某个ceph-mon重新启动,比如上篇lease中提到的两种情形。
如果发生了mon 选举,我们从ceph.log中可以看到如下的内容:
2017-08-27 17:33:43.144946 mon.1 172.1.1.197:6789/0 152076 : cluster [INF] mon.hymwq calling new monitor election
2017-08-27 17:33:43.151244 mon.0 172.1.1.196:6789/0 282354 : cluster [INF] mon.nhgfb calling new monitor election
注意,当集群节点个数比较多的时候,在同一时间点我们可能看到一条到多条上述信息,这是为何?这要深入了解mon的选举过程
选举过程
整个选举过程,在Elector类中实现。此类之中实现了一个election_epoch:
root@scaler02:~# ceph quorum_status |json_pp
{
"quorum_leader_name" : "skmif",
"monmap" : {
"mons" : [
{
"name" : "skmif",
"addr" : "10.10.1.1:6789/0",
"rank" : 0
},
{
"name" : "vqdtz",
"addr" : "10.10.1.2:6789/0",
"rank" : 1
},
{
"name" : "lzhsg",
"addr" : "10.10.1.3:6789/0",
"rank" : 2
}
],
"created" : "2017-08-29 09:27:11.587301",
"epoch" : 3,
"modified" : "2017-08-29 09:28:05.098478",
"fsid" : "6e74645a-9894-4d7b-9e94-9f4b9596d59f"
},
"quorum_names" : [
"skmif",
"vqdtz"
],
"quorum" : [
0,
1
],
"election_epoch" : 24
}
当这个election_epoch为偶数的时候,表示处于稳定状态,为奇数的时候,表示还在选举过程中,mon leader的宝座还在竞争,鹿死谁手尚未可知。
下面梳理下流程。
void Monitor::start_election()
{
/*这条日志我们一般看不到*/
dout(10) << "start_election" << dendl;
wait_for_paxos_write();
_reset();
state = STATE_ELECTING;
logger->inc(l_mon_num_elections);
logger->inc(l_mon_election_call);
cancel_probe_timeout();
clog->info() << "mon." << name << " calling new monitor election\n";
elector.call_election();
}
当我们从ceph.log中看到如下的打印的时候,表示选举已经开始,某一时间段内第一条打印,是发起选举的mon。它率先觉察到异常,调用了bootstrap,最终走到了start_election。
2017-08-27 17:33:43.144946 mon.1 172.1.1.197:6789/0 152076 : cluster [INF] mon.hymwq calling new monitor election
如果我们打印更多debug信息的时候我们可能看到如下的流程:
因为10秒内没有收到延长租约的消息,最终触发了election,有PEON发起,调用了bootstrap
2017-09-02 15:17:13.687831 7fe3d3c5a700 1 mon.vqdtz@1(peon).paxos(paxos updating c 1051189..1051804) lease_timeout -- calling new election
2017-09-02 15:17:13.687849 7fe3d3c5a700 10 mon.vqdtz@1(peon) e3 bootstrap
2017-09-02 15:17:13.687856 7fe3d3c5a700 10 mon.vqdtz@1(peon) e3 sync_reset_requester
2017-09-02 15:17:13.687859 7fe3d3c5a700 10 mon.vqdtz@1(peon) e3 unregister_cluster_logger
2017-09-02 15:17:13.687865 7fe3d3c5a700 10 mon.vqdtz@1(peon) e3 cancel_probe_timeout (none scheduled)
2017-09-02 15:17:13.687869 7fe3d3c5a700 10 mon.vqdtz@1(probing) e3 _reset
2017-09-02 15:17:13.687871 7fe3d3c5a700 10 mon.vqdtz@1(probing) e3 cancel_probe_timeout (none scheduled)
2017-09-02 15:17:13.687873 7fe3d3c5a700 10 mon.vqdtz@1(probing) e3 timecheck_finish
2017-09-02 15:17:13.687882 7fe3d3c5a700 10 mon.vqdtz@1(probing) e3 scrub_reset
....
此处的cancel_probe_timeout即函数中cancel_probe_timeout()语句
2017-09-02 15:17:13.688846 7fe3d3459700 10 mon.vqdtz@1(electing) e3 cancel_probe_timeout (none scheduled)
2017-09-02 15:17:13.688875 7fe3d3459700 5 mon.vqdtz@1(electing).elector(42) start -- can i be leader?
2017-09-02 15:17:13.688915 7fe3d3459700 1 mon.vqdtz@1(electing).elector(42) init, last seen epoch 42
2017-09-02 15:17:13.688919 7fe3d3459700 10 mon.vqdtz@1(electing).elector(42) bump_epoch 42 to 43
2017-09-02 15:17:13.690523 7fe3d3459700 10 mon.vqdtz@1(electing) e3 join_election
2017-09-02 15:17:13.690540 7fe3d3459700 10 mon.vqdtz@1(electing) e3 _reset
2017-09-02 15:17:13.690543 7fe3d3459700 10 mon.vqdtz@1(electing) e3 cancel_probe_timeout (none scheduled)
2017-09-02 15:17:13.690545 7fe3d3459700 10 mon.vqdtz@1(electing) e3 timecheck_finish
2017-09-02 15:17:13.690549 7fe3d3459700 10 mon.vqdtz@1(electing) e3 scrub_reset
从 elector.call_election()开始,就开始调用elector类定义的方法,开始选举。
void call_election() {
start();
}
void Elector::start()
{
if (!participating) {
dout(0) << "not starting new election -- not participating" << dendl;
return;
}
dout(5) << "start -- can i be leader?" << dendl;
acked_me.clear();
classic_mons.clear();
init();
/*从稳定态进入选举态,需要将版本号从偶数往上抬,抬成奇数*/
if (epoch % 2 == 0)
bump_epoch(epoch+1); // odd == election cycle
start_stamp = ceph_clock_now(g_ceph_context);
electing_me = true;
acked_me[mon->rank] = CEPH_FEATURES_ALL;
leader_acked = -1;
/*向每一个成员广播消息,提议开始重新选举*/
for (unsigned i=0; i<mon->monmap->size(); ++i) {
if ((int)i == mon->rank) continue;
Message *m = new MMonElection(MMonElection::OP_PROPOSE, epoch, mon->monmap);
mon->messenger->send_message(m, mon->monmap->get_inst(i));
}
reset_timer();
}
当其他成员收到OP_PROPOSE的消息时,就知道了,需要开始新一轮的选举了。其他的成员收到消息之后,反应可以分成三种:
- 赞成
- 给其他所有成员发消息,选我
- 不理
因为到底谁当选leader,取决于rank的大小,rank小者胜,因此收到消息之后,会比对rank,来决定做什么事情。
如果选举发起方的rank比自身的rank大
天子宁有种乎,兵强马壮者为之耳!如果从未收到过更强者(rank更小者)发来的选举请求,调用start_election,给所有成员发消息,让他们选自己为mon leader。这里面有一种场景,即连续两个弱者要求当leader,这时候,第一次的时候,如果已经调用了start_election,要求大家选自己,就没有必要重新再发一次选自己当leader的请求了。
if (mon->rank < from) {
// i would win over them.
if (leader_acked >= 0) { // we already acked someone
/*自己曾经认过怂,消息来源的mon还不如自己,直接不理,
*来源的mon太弱,是不可能被自己承认的*/
assert(leader_acked < from); // and they still win, of course
dout(5) << "no, we already acked " << leader_acked << dendl;
} else {
/*注意,electing_me记录了自己是否发出过选我为leader的请求
*如果先后收到两个弱小者发来的选举请求,处理第一个的时候,本节点已经发出了选自己当leader的请求,
*当第二个弱者消息到来的时候,没必要再发送选自己当leader的请求*/
if (!electing_me) {
mon->start_election();
}
}
} else {
// they would win over me
if (leader_acked < 0 || // haven't acked anyone yet, or
leader_acked > from || // they would win over who you did ack, or
leader_acked == from) { // this is the guy we're already deferring to
defer(from);
} else {
// ignore them!
dout(5) << "no, we already acked " << leader_acked << dendl;
}
}
这里面还有另外一种情况,即更强者曾经来过,自己曾经认过怂,承认过别人更强,那么这种情况下,采用的是不理的策略,自己都认了怂,这个消息的来源mon还不如自己,肯定是不能承认其leader的地位。
如果选举发起方的rank比自身的rank小
如果消息的来源mon,rank比自己小,要强于自己,发出选举倡议,让大家选自己是不可能了,只剩两种可能,要么承认它,要么不理它。
if (leader_acked < 0 || /*从未承认过别人,从未认过怂*/
leader_acked > from || /*虽然承认过别人,认过怂,无奈这次来的更强大,所以还是得认怂,承认它*/
leader_acked == from) {
defer(from); /*defer函数的作用是认可对方可以当leader*/
}else {
/*曾经认可过更强者,不可能向不够强的mon发送认可,不理*/
dout(5) << "no, we already acked " << leader_acked << dendl;
}
从上面可以看出,一个节点根据时序的不同,可能调用defer多次,承认多个mon当leader的请求。
如果曾经认可过更强的mon,当处于中间水平的mon到来的时候,自己是不可能再向该mon发送认可的回应。
通过上面的讨论可以看出,只有rank最小者,即最强者才有可能搜集到最多的承认。
void Elector::defer(int who)
{
dout(5) << "defer to " << who << dendl;
if (electing_me) {
/*注意,认怂就要清零,哪怕曾竖起过大旗,要求别人选自己*/
acked_me.clear();
classic_mons.clear();
electing_me = false;
}
// ack them
leader_acked = who;
ack_stamp = ceph_clock_now(g_ceph_context);
/*发送OP_ACK承认对方可以当leader*/
MMonElection *m = new MMonElection(MMonElection::OP_ACK, epoch, mon->monmap);
m->sharing_bl = mon->get_supported_commands_bl();
mon->messenger->send_message(m, mon->monmap->get_inst(who));
// set a timer
reset_timer(1.0); // give the leader some extra time to declare victory
}
处理OP_ACK 消息
收到这个OP_ACK消息,表示对方认可自己当leader的请求,我们来看下如何处理:
void Elector::handle_ack(MMonElection *m)
{
dout(5) << "handle_ack from " << m->get_source() << dendl;
int from = m->get_source().num();
assert(m->epoch % 2 == 1); // election
if (m->epoch > epoch) {
dout(5) << "woah, that's a newer epoch, i must have rebooted. bumping and re-starting!" << dendl;
bump_epoch(m->epoch);
start();
m->put();
return;
}
assert(m->epoch == epoch);
uint64_t required_features = mon->get_required_features();
if ((required_features ^ m->get_connection()->get_features()) &
required_features) {
dout(5) << " ignoring ack from mon" << from
<< " without required features" << dendl;
return;
}
if (electing_me) {
// thanks
/*搜集到一枚承认,记录在acked_me*/
acked_me[from] = m->get_connection()->get_features();
if (!m->sharing_bl.length())
classic_mons.insert(from);
dout(5) << " so far i have " << acked_me << dendl;
/*如果所有成员都承认了自己的leader地位,那么宣布获胜,调用victory*/
if (acked_me.size() == mon->monmap->size()) {
// if yes, shortcut to election finish
victory();
}
} else {
// ignore, i'm deferring already.
assert(leader_acked >= 0);
}
m->put();
}
注意,如果所有的人都承认自己leader地位,那么可以宣布获胜。但是有些情况下,无法等到所有的回应。比如某个ceph-mon进程已经不在了,是不可能得到其承认的。为了防止出现这种情况下,在通知其他节点选自己的start函数设置了定时器“
void Elector::start()
{
...
reset_timer();
}
void Elector::reset_timer(double plus)
{
// set the timer
cancel_timer();
expire_event = new C_ElectionExpire(this);
mon->timer.add_event_after(g_conf->mon_lease + plus,
expire_event);
}
void Elector::expire()
{
dout(5) << "election timer expired" << dendl;
/*注意,超过半数,就能宣布获胜
*注意,如果认过怂,electing_me就变成false了,你就不可能宣布胜利了*/
if (electing_me &&
acked_me.size() > (unsigned)(mon->monmap->size() / 2)) {
// i win
victory();
} else {
// whoever i deferred to didn't declare victory quickly enough.
if (mon->has_ever_joined)
start();
else
mon->bootstrap();
}
}
如果自己获胜的话,会给其他节点发送OP_VICTORY消息,告诉别的节点,自己已经当选leader了。
void Elector::victory()
{
/*选举过程完成,需要抬election_epoch,变成偶数*/
assert(epoch % 2 == 1); // election
bump_epoch(epoch+1); // is over!
// tell everyone!
for (set<int>::iterator p = quorum.begin();
p != quorum.end();
++p) {
if (*p == mon->rank) continue;
MMonElection *m = new MMonElection(MMonElection::OP_VICTORY, epoch, mon->monmap);
m->quorum = quorum;
m->quorum_features = features;
m->sharing_bl = *cmds_bl;
mon->messenger->send_message(m, mon->monmap->get_inst(*p));
}
// tell monitor
mon->win_election(epoch, quorum, features, cmds, cmdsize, ©_classic_mons);
}
而竞选的失败者,在handle_victory函数中处理OP_VICTORY消息:
void Elector::handle_victory(MMonElection *m)
{
...
bump_epoch(m->epoch) ;
// they win
mon->lose_election(epoch, m->quorum, from, m->quorum_features);
...
}
注意,胜利者通过win_election重新初始化,成为Leader,而失败者,通过lost_election重新初始化,变成PEON