monitor选主主要包括前期准备和选主两部分
选主前期准备
发送探测消息
任何一个monitor节点都可以发起选举,选举的入口函数是bootstrap
bootstrap
cancel_probe_timeout();
if (probe_timeout_event)
timer.cancel_event(probe_timeout_event);
probe_timeout_event = NULL;
// monitor状态改变
state = STATE_PROBING;
_reset();
for (unsigned i = 0; i < monmap->size(); i++)
if ((int)i != rank)
messenger->send_message(new MMonProbe(monmap->fsid, MMonProbe::OP_PROBE, name, has_ever_joined), monmap->get_inst(i));
for (set<entity_addr_t>::iterator p = extra_probe_peers.begin(); p != extra_probe_peers.end();
if (*p != messenger->get_myaddr())
messenger->send_message(new MMonProbe(monmap->fsid, MMonProbe::OP_PROBE, name, has_ever_joined), i);
bootstrap会向monmap和extra_probe_peers中的节点发送OP_PROBE消息,其中extra_probe_peers表示那些用户认为已经添加成功,实际上集群中所有的节点并不全都获悉该节点的加入。比如集群有节点A(主)、B,用户向A发送命令添加C,此时A向B同步最新monmap的时候故障,则B并不知道C的加入,C节点启动后,会向monmap中的B节点发送OP_PROBE消息,B节点发现C节点不在自己的monmap中,所以将其加入extra_probe_peers。
回应探测消息
MMonProbe *r;
r = new MMonProbe(monmap->fsid, MMonProbe::OP_REPLY, name, has_ever_joined);
r->name = name;
r->quorum = quorum;
monmap->encode(r->monmap_bl, m->get_connection()->get_features());
r->paxos_first_version = paxos->get_first_committed();
r->paxos_last_version = paxos->get_version();
m->get_connection()->send_message(r);
处理回应
monmap->encode(mybl, m->get_connection()->get_features());
// 对方的monmap和自己的不相同
if (!mybl.contents_equal(m->monmap_bl))
newmap->decode(m->monmap_bl);
/*
满足以下条件,自己就会更新monmap,并重新bootstrap
(1) 对方参与过一轮完整的选举。
(2) 自己没有参与过一轮完整的选举。
(3) 对方的monmap版本比自己更新。
这常常发生在一个新的monitor节点加入时。
*/
if (m->has_ever_joined && (newmap->get_epoch() > monmap->get_epoch() !has_ever_joined))
monmap->decode(m->monmap_bl);
bootstrap();
return;
// 如果当前monitor不处于STATE_PROBING阶段,就忽略这个ACK。比如在真正选举的时候,有ACK到达,就忽略这个消息,这也说明可能会忽略具有更新日志节点的ACK,导致本节点的日志不是最新的。因此,在真正选举阶段仍然会对比自己和monmap中其它节点的日志版本,如果有必要会再次进行恢复。
if (!is_probing())
return;
// 如果当前monitor处于STATE_SYNCHRONIZING阶段,也忽略这个消息。因此,monitor一次只能恢复根据一个其它节点恢复日志。
if (is_synchronizing())
return;
// sync_last_committed_floor是上次同步的开始版本,如果sync_last_committed_floor大于对方的最新版本,就不需要同步了
if (m->paxos_last_version >= sync_last_committed_floor)
// 自己的paxos最新确认版本落后于对方记录的最老确认版本,需要全量更新,这一般在一个新monitor加入时会出现
if (paxos->get_version() < m->paxos_first_version && m->paxos_first_version > 1)
cancel_probe_timeout();
sync_start(other, true);
if (sync_full)
auto t(std::make_shared<MonitorDBStore::Transaction>());
t->put("mon_sync", "in_sync", 1);
sync_last_committed_floor = std::max(sync_last_committed_floor, paxos->get_version());
t->put("mon_sync", "last_committed_floor", sync_last_committed_floor);
MMonSync *m = new MMonSync(sync_full ? MMonSync::OP_GET_COOKIE_FULL : MMonSync::OP_GET_COOKIE_RECENT);
// 如果是增量更新,就记录本节点的最新确认版本
if (!sync_full)
m->last_committed = paxos->get_version();
messenger->send_message(m, sync_provider);
return;
// 自己的最新确认版本落后对方最新确认版本部分,增量更新
if (paxos->get_version() + g_conf->paxos_max_join_drift < m->paxos_last_version) {
cancel_probe_timeout();
sync_start(other, false);
return;
/*
到这里就说明不需要同步数据,如果对方的quorum不为空,则说明对方处于选举结束的时间段,
要不是你要求选举,我才不会理你。
*/
// Monitor::_reset会清quorum
if (m->quorum.size())
// 如果monmap包含本节点,则开启选举,这一般发生在一个节点下线后立马又上线,或者新加入的节点同步完成。
if (monmap->contains(name) && !monmap->get_addr(name).is_blank_ip())
start_election();
else
// 发生在用户不实现调用mon add命令添加一个节点,就启动一个节点?
messenger->send_message(new MMonJoin(monmap->fsid, name, messenger->get_myaddr()), monmap->get_inst(*m->quorum.begin()));
else
// 比如全部节点都重启,则会进入这一步
if (monmap->contains(m->name))
outside_quorum.insert(m->name);
else
return;
// 要想重新选举,得确定能和过半的节点通信
unsigned need = monmap->size() / 2 + 1;
if (outside_quorum.size() >= need)
if (outside_quorum.count(name))
start_election();
可以看到PROBE的目的就是为了探测自己的信息是否和集群中的其它节点有偏差,如果有就需要更新。