libpaxos阅读笔记
来源:互联网 发布:linux cp命令详解 编辑:程序博客网 时间:2024/05/16 12:22
这篇阅读笔记是在大概1年前写的,是对libpaxos源码的一些注释吧。其中我的师弟yy和dm(不知道他们愿不愿意我署他们的名字,暂用缩写代替吧)对其做了一些修改,还有川大的hs老师,师兄(姐)yc,yz所做的工作,在此感谢。
鉴于个人学识有限,对paxos算法的理解也有限,有任何问题,请留言。废话就不说了,下面是笔记的全文:
libpaxos阅读笔记
libpaxos是paxos算法的一个库实现,它几乎完全是按照<<paxos made simple>>来写的,在阅读程序前,最好先看看<<paxosmade simple>>。
proposer.c
此文件实现proposer。
在此文件中用到的数据结构
typedefstruct proposer_record_t {
intiid;/*实例id*/
intballot;/*proposal number*/
status_flagstatus;/*此proposal的状态*/
intpromise_count;/*回应了preparerequest的acceptor的个数*/
promise_msg*promises[N_OF_ACCEPTORS];/*保存所有acceptor对preparerequest的回应*/
promise_msg*reserved_promise;/*如果acceptor回应的preparerespond中已经有达成共识的value,这在voidcheck_ready(proposer_record*rec)中将其保存在这个结构中*/
}proposer_record; /*这个结构体用于保存所有proposer提出但还没有达成决议的提案*/
typedefstruct pending_promise_t {
intfrom_iid;/*一次发送的所有preparerequest的起始实例id*/
intto_iid;/*一次发送的所有preparerequest的结束实例id*/
longunsigned timer;/*为这些preparerequest设置的起始的时间,这是一个绝对时间用于超时判断*/
structpending_promise_t* next;
}pending_promise;
staticpending_promise*p1_pending_list_head;/*未决的proposalrequest链表*/
staticpending_promise*p1_pending_list_tail;
typedefstruct client_value_wrapper_t {
int value_size;
void* value;/*客户端发来的value*/
structclient_value_wrapper_t* next;
}client_value_wrapper;/*用于保存客户端发来的value,在paxos中即为提案的内容*/
使用一个有头结点和尾节点的链表来保存客户端发送的value:
staticclient_value_wrapper*client_list_head;
staticclient_value_wrapper*client_list_tail;
staticintclient_list_size=0;
staticclient_value_wrapper*p2_pending_accept;/*用来保存p2a提交的value*/
staticlongunsignedp2_pending_timer;/*p2a的时间*/
staticproposer_recordproposer_array[PROPOSER_ARRAY_SIZE];/*记录开始但还没有完成的paxos实例及相关信息*/
staticint highest_open; /*proposer当前所发起过的最高的实例id*/
staticint current_iid;/*当前proposer所能看到的已经形成决议了的最高的实例id*/
staticint prop_id;/*用户指定,用于计算proposalnumber,由两个宏来计算
proposalnumber,它们是BALLOT_NEW,BALLOT_NEXT,通过简单的增加一个间隔MAX_PROPOSERS*/
proposer执行过程简介
proposer的执行由proposer_init函数开始,此函数通过参数传入一个整数,用于赋值给prop_id,每个proposer传入的参数都必须不一样,以便能使每个proposer永远不会产生相同的proposalnumber。此函数将创建3个线程----(1)、proposer_loop、(2)、timeout_checks_loop、(3)、delivery_check_loop。下面将分别介绍各个线程的作用。
1.2.1、proposer_loop
此线程的作用是等待acceptor对preparerequest的回应,如果返回的不是preparerequest的回应,则直接返回不做处理,(实际上acceptor不会给peoposer发送对preparerequest回应之外的其它消息,libpaxos中Proposer和Learner要做到同一个进程中【注:delivery_check_loop线程】,由于Acceptor不会对p2a的消息进行回应,Proposer判断p2a的决议是否达成只有通过Learner学习到的消息来判断)。回应的消息格式为:
proposer_loop函数通过handle_promise_batch函数分析上面收到的消息,并依次处理消息的内容(由handle_promise函数处理,并且以下所述都是对于一个promise_msg,一个实例id来说的),如果收到的某个promise_msg对象是本proposer正在等待的回应,则在proposer_array数组中记录此回应,并且判断是否达到大多数条件(inthandle_promise(promise_msg*pmsg,intacceptor_id,proposer_record*rec)的返回值为1的时候就表示收到了大多数的回应了),如果大多数条件满足,就调用check_ready函数,此函数根据之前收到的所有promise_msg对象,为proposer_array数组的对应项设置一个标志,根据不同的情况,标志可能有两种:(1)p1_ready---此标志表示收到的所有promise_msg中,没有包含任何value(如果value是字符串的话,value=“”),这样proposer在发送acceptrequest时从客户端发来的请求中选择一个value;(2)、p2_reserved----此标志表示收到的所有promise_msg中,至少包含一个value(①如果有多个value的话,那么这些value一定是一样的。这句话将在后面证明),这样的话proposer在发送acceptrequest时将选择这个值作为value。(参考<<paxosmadesimple>>的Phase2)。这样在periodic_phase2_check函数中,根据这两个标志,就可以选择不同的值来发送acceptrequest。acceptrequest的发送和preparerequest不太一样:acceptrequest是一个一个实例发送的,其它的acceptrequest将在其它两个线程中完成。并且根据条件:p1_ready_count+p1_pending_count<(PROPOSER_PREEXEC_WIN_SIZE/2)来prepare_new_instances();
发送的消息格式如下所示:
+-------------------------+-----------------+-----------------+-------------------+
+prepare_batch_msg+prepare_msg+prepare_msg+……….+
+-------------------------+-----------------+-----------------+-------------------+
在prepare_new_instances()中除了发送上面格式的消息,还会调用initialize_proposer_rec()函数在proposer_array数组中对每个实例进行记录和调用add_to_p1_pending_list()函数将发送的preparerequest保存到p1_pending_list_head与p1_pending_list_tail形成的链表中。
1.2.2、timeout_checks_loop
此线程的主要作用是:(1)、检查preparerequest和acceptrequest是否超时,并对超时做相应的处理;(2)、检查preparerequest的发送窗口是否低于一定的限度值,如果是,就再发起一些preparerequest。算法开始阶段的preparerequest就是在这里完成的。
此线程依次调用3个函数:check_p1_pending,check_p2_pending,prepare_new_instances,他们的作用分别是:检查preparerequest是否超时,检查acceptrequest是否超时,发送新的preparerequest。在check_p1_pending函数中,依次检查p1_pending_list_head链表,如果某次preparerequest(s)没有超时,就不用再检查链表后面的项了,因为链表中所有的项都是以时间为顺序来排列的;如果某次preparerequest(s)超时了,则调用check_expired_p1_range函数来将这些preparerequest重新发送出去(发送之前要增加proposalnumber,调用BALLOT_NEXT宏来实现proposalnumber的增加)。在check_p1_pending函数完成之后,根据p2_pending_accept= NULL?来判断是否调用check_p2_pending函数,这样判断的原因如下:(1)、acceptrequest是一个一个发送的;(2)、acceptrequest发送成功之后,此acceptrequest所发送的value的信息会保存在p2_pending_accept变量所指的对象中;(3)、如果本proposer所提出的提议在规定的时间内最终成为决议的话,p2_pending_accept的值将被赋为NULL。在check_p2_pending函数中将首先判断正在等待的对acceptrequest的“回应”(姑且这样,实际上除了对preparerequest的回应之外,其它任何角色都不会对proposer所发出的任何消息做回应)是否超时,如果超时的话,check_p2_pending函数首先重新将acceptrequest所发出的value插入到client_list_head链表,以便在下次paxos的执行实例中再次对此value提出提议;之后再发起一次paxos实例,此次实例的preparerequest只有一个。最后在timeout_checks_loop中,还要根据preparerequest的窗口是否低于一定的限度来决定是否调用prepare_new_instances函数,从而发起新的paxos实例。
1.2.3、delivery_check_loop
此线程的主要作用是根据learner中提供的信息,检查本proposer本次所提出的proposal(acceptrequest)是否被大多数的节点所accept,成为决议。
此线程将主要依次调用learner_get_next_value_wrapper、verify_accepted_value、periodic_phase2_check这3个函数。learner_get_next_value_wrapper在learner.c文件中,他将当前(1)形成了决议的,(2)instanceid为最小的决议,返回的变量类型为lrn_value_wrapper,设其返回值为lmsg(list_head与list_tail形成的链表中的头结点)。则lmsg->iid= current_id必然成立。verify_accepted_value函数首先检查lmsg->iid=current_id是否成立,如果不成立,则表示发生了致命错误,直接结束程序;接着函数增加current_id,原因如下:不管是不是本proposer所提出的提案成为决议,总之current_id所对应的实例id已经被人占用了,永远不可能再在此实例id上达成决议。然后又调用remove_from_pending_if_matches函数,此函数此决议的value在本机的client_list_head中是否存在如果不存在就直接使verify_accepted_value函数返回(超时操作线程会在合适的时候对此value重新发起paxos实例),如果存在就再判断此决议是不是本proposer正在等待的决议,如果是的话,在一些清理操作后就从verify_accepted_value函数返回;反之,调用resubmit_pending_accept函数将p2_pending_accept插入到client_list_head链表中。最后线程将调用periodic_phase2_check函数,根据rec->status的值决定是否发送acceptrequest。
对前面的证明
1.3.1、①如果有多个value的话,那么这些value一定是一样的。
证明:
设这两个value分别是v1,v2(v1!=v2),acceptors中大多数acceptor的集合为A= {a1,a2,……}。即在之前已经有2个不同的proposer对同一个实例id发出了acceptrequest,且所发出的value分别为v1,v2,由于proposer要发出acceptrequest,必须要满足回应的acceptor的集合a∈A,b∈A,且a!=b,而这显然不满足paxos。
acceptor.c
此文件实现了Acceptor。
2.1、相关变量及数据结构
typedefstruct acceptor_record_t {
int iid; //实例id
int ballot;//proposal number
int value_ballot;//如果此acceptor在之前已经acceptor过一个value,则此成员为此value的proposalnumber
int value_size;//对同一个实例id,accept过的的value的大小
char* value;//对同一个实例id,accept过的的value
}acceptor_record;//保存每次的acceptrequest
staticint acceptor_id;//当前acceptor的id,不同的acceptor的id不同
staticacceptor_recordacceptor_array[ACCEPTOR_ARRAY_SIZE];//保存一定数量的实例id不同的acceptrequest
staticchar send_buffer[MC_MSG_MAX_DATA_SIZE];//发送对preparerequest的回应的buffer
2.2、acceptor的执行过程
acceptor只有1个线程,通过一个switch语句来处理----处理的消息有PAXOS_PREPARE
PAXOS_ACCEPT、PAXOS_LSYNC,他们分别对应与处理preparerequest、acceptrequest和learner发起的“同步请求”,acceptor对于收到的PAXOS_PREPARE和PAXOS_ACCEPT都要在disk(BDB)上做记录,记录时以实例id为key。
PAXOS_PREPARE:由于proposer可以同时发送多个preparerequest,所以acceptor在处理preparerequest时是以实例id来区分的(由handle_prepare函数处理):(1)、当收到的preparerequest的实例id比acceptor记录的实例id(在acceptor_array数组中)大时,表示这是新的paxos实例,直接给proposer回应;(2)、当收到的preparerequest的实例id比acceptor记录的实例id(在acceptor_array数组中)的小时,则说明此preparerequest的实例id在之前已经收到过,但是在acceptor_array数组中的记录已经被新的拥有不同实例id的preparerequest所覆盖,而先前的preparerequest则被保存在disk上,所以直接查询BDB,并比较proposalnumber(ballot),如果此次的proposalnumber大,就给proposer一个回应;(3)、当收到的preparerequest的实例id与acceptor_array数组中对应项的实例id相同时,则直接比较proposalnumber,如果刚刚收到的preparerequest的实例id大的话,就回应,否则不做任何处理。
PAXOS_ACCEPT(由handle_accept函数处理):对于PAXOS_ACCEPT的处理与PAXOS_PREPARE的差不多,只是发送的是消息是向learner发送learn消息,而不是向proposer发送preparerequest的回应。
PAXOS_LSYNC(由handle_lsync函数处理):此消息是由learner发出的,消息格式如下:
若有一个新节点启动,当他发现其它节点的实例id比自己的current_id大时,即说明此节点需要追赶其它先启动的节点,此时learner就会发出此消息。对于此消息的处理如下:
设收到的PAXOS_LSYNC消息中的任意一个实例id的值为instance_id,handle_lsync函数首先根据instance_id查询acceptor_array数组,设查询的结果为rec。分两种情况:(1)ifinstance_id =rec->iid,则说明这个决议在acceptor_array数组中还没有其它的paxos实例所覆盖,直接给learner发送learn消息;(2)、ifinstance_id <rec->iid,则说明实例id为instance_id的决议已经不在acceptor_array数组中,则从BDB中将其取出,然后给learner发送learn消息。
learner.c
3.1、相关变量及数据结构
typedefstruct learner_record_t {
int iid;//实例id
learn_msg* learns[N_OF_ACCEPTORS];//接收到的由acceptor发送来的learn消息
int final_value_ballot;//如果达成了决议,则此值为发起此提案的proposalnumber
char* final_value;//如果达成了决议,则此值为决议的value
int final_value_size;//决议的value的大小
}learner_record;//对于同一个实例id,在达成决议之前(value=NULL)每收到一个learnmessage都要保存在这个结构体中。
staticlrn_value_wrapper*list_head;
staticlrn_value_wrapper*list_tail;
/*以上两个变量为一个链表的头和尾,这个链表的作用是保存所有的决议*/
staticint highest_enqueued = -1;//在达成决议的实例中,没有gap的最高的实例id
staticint highest_closed = -1;//达成了决议的最高的实例id
staticint highest_seen;//此learner所看到过的最高的实例id
staticlearner_recordlearner_array[LEARNER_ARRAY_SIZE];//不同实例id的learner_record
//保存在这里
staticint majority = -1;//大多数的acceptor的个数。
3.2、learner的执行过程
learner在启动后一共会初始化2个线程:learner_loop和retransmission_loop。leaner_loop用于等待acceptor的learnmessage并处理这些消息;retransmission_loop用于定时的检查gap并向acceptor发送重传这些gap的决议的请求。
3.2.1、learner_loop
此线程阻塞等待learnmessage,当有learnmessage来时,调用handle_learn函数进行处理,handle_learn函数的根据实际情况将收到的learnmessage保存在learner_array的对应项中,在保存之前需要做一些判断以决定是否保存这个learnmessage,判断的条目有下面的一些:if(lmsg->iid>= highest_enqueued + LEARNER_ARRAY_SIZE)---此实例id是否过大;if(lmsg->iid<= highest_enqueued)---此learnmessage中的实例id已经成为一个决议;if(rec->iid!= lmsg->iid)----此learnmessage是否是此learner收到的此实例id的第一个learnmessage等等。在更新完learner_array后,调用check_majority函数检查是否收到了大多数的acceptor发来的learnmessage,如果是,则关闭此提案(设置对应的learner_record对象中所有final成员的值),并使check_majority函数返回1;反之不做任何处理,并返回0;最后,如果check_majority返回1,learner_loop线程,将判断if(lmsg->iid ==highest_enqueued+1),若满足此条件,则调用enqueue_values函数将此决议的value插入到以list_head为头的链表的尾部。做以上判断的原因是防止插入链表中出现gap而出现错误。
proposer将定时的调用learner_get_next_value函数从此链表中取出值
3.2.2、retransmission_loop
线程定时的检查决议中是否有gap产生(highest_seen>highest_enqueued?),如果有的话就调用ask_retransmission函数,请求acceptor重传产生gap的实例id和其对应的value。
- libpaxos阅读笔记
- libpaxos分析
- libpaxos分析心得
- 阅读笔记
- 阅读笔记
- 阅读笔记
- 阅读笔记
- 阅读笔记
- 项目管理阅读笔记
- ACE阅读笔记
- ICE阅读笔记
- XMPP RFC阅读笔记
- 阅读笔记 2006-06
- linux驱动阅读笔记
- 阅读笔记 2006-08
- OpenOBEX代码阅读笔记
- tinyOS programming 阅读笔记
- 阅读笔记 2006-01
- Winform换肤原理
- android基础--国际化
- JAVA 入门: 手工编译并运行JAVA小程序
- C语言字符串格式化处理 sscanf函数
- 计算机精品学习资料大放送
- libpaxos阅读笔记
- C# 将数据导出到Excel汇总
- android基础--屏幕适配
- ViewState 与 静态变量的 区别
- android基础---样式和主题(style&theme)
- android基础--动画效果
- android基础--屏幕切换动画
- PHP 生成文字及图片水印
- 一步步构建大型网站架构