一、实验环境
系统:Windows10
编译软件:Visual Studio 2022
语言:C
二、内容
假设由5台服务器Ai(i=1,2..5)组成集群,每份数据在5台服务器中各保留一个副本。当客户端C1和C2同时修改存储在集群中的同一个数据时,由于网络修改延迟的存在无法保证两个数据的请求到达每台服务器的先后顺序。其次,可能会出现C1的请求先到达A1服务器,而C2的请求先到达A2服务器的情况。这种情况下,在A1和A2服务器上对数据的修改顺序不同,而修改顺序的不同直接导致了这两个副本数据的不一致。要保证修改保持副本一致只要保证在每台服务器上对数据的操作顺序相同。修改操作的顺序相同的最简单方法是采用PrimarySecondary模式,所有对数据修改的操作都先发送到Primary主机,然后由
Primary主机分发给Secondary主机,此时操作的顺序由Primary主机决定。但这种方法存在单点故障问题。请使用PAXOS协议,设计一个数据更新方案,保证每台服务器数据的一致性。
方案原理如下:
在 Paxos 中,决策的过程分为两个主要阶段:准备阶段和提议阶段。这两个阶段确保了服务器和客户能够达成一致,即使消息丢失或部分人掉线也能保证一致性。
阶段 1:准备阶段(Prepare Phase)
- 提议者发起提案
C1是提议者,C1想要发起修改集群中数据的提议,C1需要先和其他客户端C2、C3、C4、C5确认是否可以发起这个提案。
C1给C2、C3、C4、C5发了条消息表示想要提个提案,编号为 num,C2、C3、C4、C5没有接受过编号更大的提案C1就可以继续提。这个编号用来区分不同的提案,确保每次只处理最新的提案。 - 接受者回应
C2、C3、C4、C5是接受者,分别检查自己是否接受过编号更大的提案。假设都没有接受过更大的提案,C2、C3、C4、C5回复并允许C1提议。Paxos 协议只需要大多数人同意就可以继续下去。C1可以进入下一阶段。
阶段 2:提议阶段(Accept Phase)
-
提议者正式提出提案
C1得到了大多数的回应,于是正式发出提案:“C1提议修改A1服务器的数据,提案编号为 num。”
接受者再次回应
C2、C3、C4、C5收到提案后,检查提案的编号,并确认这个编号是之前同意的编号,于是回复:“同意修改A1服务器的数据”
由于C2、C3、C4、C5已经同意(大多数原则),C1的提案就算通过了。 -
最终结果通知
C1已经得到了大多数的同意,于是他给所有人发消息:“我在修改数据”,此时C1获得了锁,其他用户C2、C3、C4、C5没有获得锁,所以不能修改服务器的数据。
即使有的用户没有回复,他们也能收到最终的结果,知道要C1要修改数据了。
通过这两个阶段,Paxos 确保了大多数人的一致意见,即使部分人没能及时参与讨论,也不会影响结果。
三、代码
#include <stdio.h>
#include <pthread.h>
#define ACCEPTORS_NUM 5
#define MAJORITY (ACCEPTORS_NUM/2 + 1)
typedef struct
{
int promised_id;
int accepted_id;
int accepted_value;
}Acceptor;
Acceptor acceptors[ACCEPTORS_NUM];
pthread_mutex_t lock[ACCEPTORS_NUM];//5把锁
void Init_Acceptor()
{
for (int i = 0; i < ACCEPTORS_NUM; i++)
{
acceptors[i].promised_id = -1;
acceptors[i].accepted_id = -1;
acceptors[i].accepted_value = -1;
pthread_mutex_init(&lock[i], NULL);
}
}
//Prepare
int Prepare(int acceptor_index, int proposal_id)
{
pthread_mutex_lock(&lock[acceptor_index]);//获得锁
if (proposal_id > acceptors[acceptor_index].promised_id)
{
acceptors[acceptor_index].promised_id = proposal_id;
pthread_mutex_unlock(&lock[acceptor_index]);//释放锁
return 1;//获得提议许可
}
pthread_mutex_unlock(&lock[acceptor_index]);//释放锁
return 0;//未获得提议许可
}
//Accept
int Accept(int acceptor_index, int proposal_id,int value)
{
pthread_mutex_lock(&lock[acceptor_index]);//获得锁
if (proposal_id >= acceptors[acceptor_index].promised_id)
{
acceptors[acceptor_index].promised_id = proposal_id;
acceptors[acceptor_index].accepted_value = value;
pthread_mutex_unlock(&lock[acceptor_index]);//释放锁
return 1;//提议成功
}
pthread_mutex_unlock(&lock[acceptor_index]);//释放锁
return 0;//提议失败
}
//Proposer
void* Proposer(void* args)
{
int proposal_id = *(int*)args;
int value = proposal_id * 10;//假设每个提议的值为编号的10倍
printf("Proposer value : %d with proposal ID:%d\n",value,proposal_id);
int promise_count = 0;
//准备阶段,向所有接收者Acceptors发Prepare请求
for (int i = 0; i < ACCEPTORS_NUM; i++)
{
if (Prepare(i, proposal_id)) {
promise_count++;
}
}
if (promise_count >= MAJORITY)
{
int accept_count = 0;
for (int i = 0; i < ACCEPTORS_NUM; i++)
{
if (Accept(i, proposal_id,value)) {
accept_count++;
}
}
if (accept_count >= MAJORITY) {
printf("Proposal ID:%d accepted with value %d \n",proposal_id,value);
}
else
{
printf("Proposal ID:%d failed in accept\n", proposal_id);
}
}
else
{
printf("Proposal ID:%d failed in prepare\n", proposal_id, value);
}
return NULL;
}
int main()
{
Init_Acceptor();
pthread_t proposer1, proposer2;
int proposer1_id = 1;
int proposer2_id = 2;
pthread_create(&proposer1, NULL, Proposer, &proposer1_id);
pthread_create(&proposer2, NULL, Proposer, &proposer2_id);
pthread_join(proposer1, NULL);
pthread_join(proposer2, NULL);
return 0;
}
四、运行结果