大家虽然看过那么多有关PAXOS算法的理论和论文,但仍然云里雾里,原因就是没有实践。这里咱们不讲理论,直接用实际开发来通俗讲解PAXOS算法的应用。
提案(proposal)是PAXOS算法一个重要的组成部分。
先来看一下用于在分布式节点间传递提案(accept_req)的数据结构:
struct accept_req {
node_id_t node_id;
view_stamp msg_vs;
view_stamp req_canbe_exed;
};
struct view_stamp {
view_id_t view_id;
req_id_t req_id;
};
typedef uint32_t view_id_t;
对应它,我们还需要在节点上维护几个变量:
highest_committed_vs
highest_seen_vs
highest_to_commit_vs
我们的分布式系统使用的是三个节点,其中一个是leader,其余两个是secondary node。
highest_seen_vs表示该节点目前接受到的请求中提案号最大的。
在leader上,highest_seen_vs用于产生一个请求的提案号(也就是上面提到的view_stamp这个数据结构):
view_stamp next = get_next_view_stamp(comp);
view_stamp_inc(comp->highest_seen_vs);
leader每从client接受一个请求(准确地说,应该是一个socket operation,包括connect, send, close三种),就将next与这个请求绑定,按照到来的顺序依次加1。leader节点上的highest_seen_vs可以代表目前已接受请求中编号最大的那个。
在secondary节点上,highest_seen_vs的更新要根据来自leader的提案(一开始提到的accept_req)。这个提案代表一个请求,所以它包含这个请求的提案编号 msg_vs。当secondary节点接收到这个提案后,会将提案的msg_vs和这个节点上维护的highest_seen_vs进行比较,如果前者比后者大,就将后者更新为前者:
// update highest seen request
if(view_stamp_comp(&msg->msg_vs,comp->highest_seen_vs)>0){
*(comp->highest_seen_vs) = msg->msg_vs;
}
再回到leader上,看看这个提案是如何构造的。一个提案代表一个请求。通过上面的介绍,我们已经知道提案中的msg_vs就是leader分配给这个提案的编号,那req_canbe_exed代表什么呢?先介绍一下highest_to_commit_vs:
highest_to_commit_vs表示该节点即将要提交的请求。
提交请求指的是这个请求已经获得三个节点大多数(两个或三个节点)认可,可以把它提交给真正的服务器进行处理。这是PAXOS用来将一个请求在三个节点间达成一致的方式。
highest_committed_vs 表示已经提交的请求中最大编号。
也就是说,请求可以分成三种类型:已被接收但未被认可,已认可但未被提交,已提交。
再回到leader上的req_canbe_exed,在leader构造一个请求的提案时,有:
msg->req_canbe_exed.view_id = comp->highest_to_commit_vs->view_id;
msg->req_canbe_exed.req_id = comp->highest_to_commit_vs->req_id;
可见req_canbe_exed传达的是leader节点上即将要提交的请求的提案号。当这个提案发给secondary节点时,secondary节点会把该提案中的req_canbe_exed与自身的highest_to_commit_vs进行比较,如果前者大,就将后者更新为前者:
if(view_stamp_comp(&msg->req_canbe_exed,
comp->highest_to_commit_vs)>0){
// 如果前者大于后者
*(comp->highest_to_commit_vs) = msg->req_canbe_exed;
SYS_LOG(comp,"Now Node %d Can Execute Request %u : %u .\n",
comp->node_id,
comp->highest_to_commit_vs->view_id,
comp->highest_to_commit_vs->req_id);
}
可见,leader会和secondary节点时刻保持状态一致:leader更新了highest_seen_vs和highest_to_commit_vs,会通过发送给secondary节点的提案(accept_req)告诉secondary该更新了。
还有一个概念,先看一下figure:
还剩下msg_vs到highest_committed_vs的箭头。下面将涉及PAXOS的核心思想。
提案到达secondary节点后,该节点发现这个提案的编号小于或等于该节点已经提交的最大编号highest_committed_vs。这说明,这个新到的提案所代表的请求在该节点上已经被提交给真正服务器去处理了,此时该节点会忽略这个请求:
if(view_stamp_comp(&msg->msg_vs,comp->highest_committed_vs)<=0){
// we have committed the operation, safely ignore it
SYS_LOG(comp, "I‘ve already committed the operation.
I‘ll ignore this one.\n");
goto handle_accept_req_exit;
}
问题就来了,
问题一:什么情况下会令secondary节点忽略来自leader的提案?
问题二:为什么一个提案要获得大多数认可才能提交?