1.概述
对于分布式来说最重要的莫过于所有副本数据的一致性。
在monitor节点中,存在着Leader和Peon两种角色。当客户端发出读命令时可以由相应的Peon或者Leader返回。一旦发生修改动作,所有的消息会第一时间发送给Leader节点,然后由Leader节点分发给Peon节点。
paxos算法保证了一次修改操作只能批准一个值,从而保证了分布式系统副本的一致性。
2.paxos转换时机
1.monitor启动时,paxos初始化;
2.monitor进入bootstrap时,paxos的restart ;
3.monitor根据选举结果,paxos对应初始化为leader或peon;
4.monitor异常后,paxos recovery阶段;
5.monitor运行过程中,paxos决议;
1)monitor启动
Monitor::preinit()->Monitor::int_paxos()->Paxos::init()
初始化主要做了什么?
void Paxos::init()
{
//load paxos variables from stable storage
//上次当选leader时产生的PN,下次当选继续产生
last_pn = get_store()->get(get_name(), "last_pn");
//我接受的最后一个PN(来源未知)
accepted_pn = get_store()->get(get_name(), "accepted_pn");
//最后一次commit的paxos id
last_committed = get_store()->get(get_name(), "last_committed");
//日志中最早commit的paxos id(并不一定是第一个)
first_committed = get_store()->get(get_name(), "first_committed");
dout(10) << __func__ << " last_pn: " << last_pn << " accepted_pn: "
<< accepted_pn << " last_committed: " << last_committed
<< " first_committed: " << first_committed << dendl;
dout(10) << "init" << dendl;
assert(is_consistent());
}
接下来简单介绍下last_pn以及fist_committed:
last_pn会在发出paxos决议,以及选举成功paxos初始化为leader时更新,涉及函数Paxos::get_new_proposal_number。
version_t Paxos::get_new_proposal_number(version_t gt)
{
if (last_pn < gt)
last_pn = gt;
...
// 第n次选举后产生的pn值为100*n+rank(leader)
last_pn /= 100;
last_pn++;
last_pn *= 100;
last_pn += (version_t)mon->rank;
...
}
之所以说fist_committed有可能不是第一个,是因为monitor为保证日志文件不至于太大,在每一次paxos执行完commit后会调用finish_round()->trim(),根据日志数量决定是否清理。
void Paxos::finish_round()
{
dout(10) << __func__ << dendl;
assert(mon->is_leader());
// leader在commit完数据后调用该函数,设置状态为active,重新接受提案
state = STATE_ACTIVE;
...
//清理过量日志
if (should_trim()) {
trim();
}
if (is_active() && pending_proposal) {
//表决下一个等待提案,并将状态置为updating
propose_pending();
}
}
first_committed更新时机如下:
bool should_trim() {
int available_versions = get_version() - get_first_committed();
int maximum_versions = g_conf->paxos_min + g_conf->paxos_trim_min;
////12.2.2版本对应的paxos_min500,paxos_trim_min为250
if (trimming || (available_versions <= maximum_versions))
return false;
return true;
}
void Paxos::trim()
{
assert(should_trim());
//12.2.2版本对应的paxos_min,paxos_trim_max为500
version_t end = MIN(get_version() - g_conf->paxos_min,
get_first_committed() + g_conf->paxos_trim_max);
...
dout(10) << "trim to " << end << " (was " << first_committed << ")" << dendl;
MonitorDBStore::TransactionRef t = get_pending_transaction();
...
}
可以看出日志条数会介于500-750之间,而first_commited则是保留下的最早的而非第一个。
2)paxos::restart()会在monitor的bootstrap时执行,主要是重置未处理的提案以及清理timeout的事件。
那么何时进入bootstrap()呢?
a.作为新加入的monitor节点加入集群
b.mon初始化
c.timer线程出现超时
void Paxos::restart()
{
..
dout(10) << "restart -- canceling timeouts" << dendl;
//清空所有的超时事件
cancel_events();
//清理提案的value
new_value.clear();
//如果正在写,先将写完成
if (is_writing() || is_writing_previous()) {
dout(10) << __func__ << " flushing" << dendl;
mon->lock.Unlock();
mon->store->flush();
mon->lock.Lock();
dout(10) << __func__ << " flushed" << dendl;
}
//设置状态为recovering
state = STATE_RECOVERING;
// 重置待决议的提案
pending_proposal.reset();
...
}
3)选举完成时paxos初始化为peon或leader。
A.paxos初始化为peon
void Paxos::peon_init()
{
..
cancel_events();
new_value.clear();//与restart部分流程一致,不一一解释
state = STATE_RECOVERING;
lease_expire = utime_t();
dout(10) << "peon_init -- i am a peon" << dendl;
// 设置leader collect超时,超时重新选举
reset_lease_timeout();
// discard pending transaction
pending_proposal.reset();
..
}
B.paxos初始化为leader
void Paxos::leader_init()
{
cancel_events();
new_value.clear();//与peon的初始化类似
pending_proposal.reset();
//单monitor节点集群直接将状态设置为active,等待propose提案
if (mon->get_quorum().size() == 1) {
state = STATE_ACTIVE;
return;
}
//否则将状态设置为recovering,进入数据恢复状态
state = STATE_RECOVERING;
//发送collect消息,进行数据恢复,该过程会在情况4)中详细描述
collect(0);
}
4)有的人会问,选举之前不是进行过数据同步吗?为什么选举结束,paxos初始化为leader时还需要进一步的数据恢复呢?
原因在于如果monitor之间的paxos版本号的差距在一定的范围内时不进行增量以及全同步,因此这里是非常必要的。
从leader的collect开始就进入了paxos的recover过程
void Paxos::collect(version_t oldpn)
{
// we're recoverying, it seems!(重置状态)
state = STATE_RECOVERING;
assert(mon->is_leader());
// reset the number of lasts received
uncommitted_v = 0;
uncommitted_pn = 0;
uncommitted_value.clear();//清空之前提案的决议
peer_first_committed.clear();
peer_last_committed.clear();
// look for uncommitted value
if (get_store()->exists(get_name(), last_committed+1)) {
version_t v = get_store()->get(get_name(), "pending_v");
version_t pn = get_store()->get(get_name(), "pending_pn");
if (v && pn && v == last_committed + 1) {
uncommitted_pn = pn;
} else {
do