开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第16天,点击查看活动详情
分布式快照算法应用到流式系统中就是确定一个 Global 的 Snapshot,错误处理的时候各个节点根据上一次的 Global Snapshot 来恢复,在系统做 Failure Recovery 的时候非常有用,它更多是一种容错处理。
分布式系统可以先简单由进程和进程之间的channel组成 。分布式系统可以被看作有向图,进程是节点,而进程之间的有向边是chanel,每个节点的入边和出边对应了input channel和 output channel。总之,一个分布式系统的全局状态可以由进程的状态和 channel 中的 message 组成,这正是distributed snapshot需要记录的。
算法假设:
Channel是无限大的FIFO队列
channel收到的message是有序且无重复的
算法目标: 通过记录每个进程的local state和它的input channel中有序的message,也就是说局部快照,全局快照可以通过所有进程的局部快照合并得到。
整体流程:
Initiating a snapshot: 可以由系统中的任意一个进程发起
Propagating a snapshot: 系统中其他进程开始逐个创建 snapshot 的过程
Terminating a snapshot: 算法结束条件
具体流程: Initiating a snapshot
1. 假设P\i发起,它记录自己的进程状态,生成一个marker,这个marker和channel的message不同
2. 发送marker通过output channel发送给其它进程
3. 记录所有input channel收到的信息
Propagating a snapshot 对于进程 Pj 从 input channel Ckj 接收到 marker 信息:
如果 Pj 还没有记录自己的进程状态,则:
- Pj 记录自己的进程状态,同时将 channel Ckj 置为空
- 向 output channel 发送 marker 信息
否则:
- 记录在收到marker之前的channel中收到的所有message
(也就是把channel记录为保留pj状态以来从这个channel接收到的信息集合)
因此这里的 marker 其实是充当一个分隔符,分隔进程做 local snapshot (记录进程状态)的 message。比如 Pj 做完 local snapshot 之后 Ckj 中发送过来的 message 为 [a,b,c,marker,x,y,z] 那么 a, b, c 就是进程 Pk 做 local snapshot 前的数据,Pj 对于这部分数据需要记录下来,比如记录在 log 里面。而 marker 后面 message 正常处理掉就可以了。
Terminating a snapshot 所有的进程都收到 marker 信息并且记录下自己的状态和 channel 的状态(包含的 message)
下面用MPI来模拟Chandy-lamport快照算法(该算法的图解分析可以参照博文:blog.csdn.net/justlpf/art… 以及developer.aliyun.com/article/688…)
任务要求:
- 实现全局状态的快照算法,并监控下列程序:两个进程P和Q用两个通道连成一个环,它们不断地轮转消息m。在任何一个时刻,系统中仅有一份m的拷贝。每个进程的状态是指由它接收到m的次数。P首先发送m。在某一点,P得到消息且它的状态是101。在发送m之后,P启动快照算法,要求记录由快照算法报告的全局状态。
实现代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mpi.h>
typedef struct{
char msg[50];
int marker;
int count;
} Message;
int main(int argc, char** argv) {
MPI_Init(&argc, &argv);
int world_rank, world_size;
MPI_Comm_size(MPI_COMM_WORLD, &world_size);
MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
int limit = 110;
int local_count = 0;
int is_snap = 0;
int is_marked = 0;
Message message;
message.count = 0;
message.marker = 0;
int act = world_size;
Message *msg_recv = (Message*)malloc(sizeof(Message)*10); //假设每个进程本地只能保留10个消息,用于快照恢复
int msg_recv_count_after_marker = 0;
while (message.count < limit) {
if(message.count % 2 == world_rank) {
message.count++;
if(message.count == 103 || message.marker == 1) {
//要么主动local snapshot,要么接收到marker再snapshot,snapshot后发送marker
//假设在这时候有一个进程做了local snapshot并发送了一个marker,由于是轮转发送,所以snap shot只能等到轮到自己发送是才能做
//这时仍然能够发送消息,此时要将marker添加到消息中
act --;
//printf("act of %d is %d at send of clock %d\n", world_rank, act, message.count);
is_snap = 1;
//报告本地状态信息,并将后面到来的消息都保存下来
printf("Process %d has local status %d\n", world_rank, local_count);
printf("%d Begin Snapshot!!! at clock %d\n", world_rank, message.count);
//设置消息
message.marker = 1; //如果是0则表示不是marker,逻辑上在消息上添加了marker
strcpy(message.msg, "HELLO with MARKER!!!");
//发送消息
MPI_Send(&message, sizeof(message), MPI_BYTE, (world_rank + 1) % world_size, 0, MPI_COMM_WORLD);
printf("Process %d send %s to process %d at clock %d\n", world_rank, message.msg, (world_rank + 1) % world_size, message.count);
//destory the message
memset(message.msg, '$', sizeof(message.msg));
printf("Process %d destroyed the message at clock %d\n", world_rank, message.count);
message.marker = 0; //恢复marker
}
else {
strcpy(message.msg, "HELLO");
//正常消息通信,不带有marker,发送marker后也能正常通信
MPI_Send(&message, sizeof(message), MPI_BYTE, (world_rank + 1) % 2, 0, MPI_COMM_WORLD);
printf("Process %d sent %s to %d at clock %d\n", world_rank, message.msg, (world_rank + 1) % 2, message.count);
//destory the message
memset(message.msg, '$', sizeof(message.msg));
printf("Process %d destroyed the message at clock %d\n", world_rank, message.count);
}
}
else {
MPI_Recv(&message, sizeof(message), MPI_BYTE, (world_rank + 1) % 2, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
printf("Process %d received %s from process %d at clock %d\n", world_rank, message.msg, (world_rank + 1) % 2, message.count);
if(message.marker == 1) act --;
// 若果进程是发送完marker后,则接受的信息不会被处理,只是将其保存到本地
if(is_snap == 1) {
//保存消息
//printf("sssssssssssss at %d\n", world_rank);
memcpy(msg_recv + msg_recv_count_after_marker, &message, sizeof(Message));
msg_recv_count_after_marker++;
}
//printf("act of %d is %d at recv of clock %d\n", world_rank, act, message.count);
if(act == 0) {
//说明对面也完成了自己的snapshot并发送了marker
//报告状态信息
printf("%d end snap shot at clock %d !!!\n", world_rank, message.count);
printf("---------------P_%d log----------------\n", world_rank);
for(int i=0; i<msg_recv_count_after_marker; i++)
printf("message %s with clock %d marker %d\n", msg_recv[i].msg, msg_recv[i].count, msg_recv[i].marker);
printf("---------------P_%d log----------------\n", world_rank);
message.marker = 0; //恢复marker,不能在这一轮恢复,只能在下一轮恢复
is_snap = 0;
act = world_size;
}
local_count++;
}
}
MPI_Finalize();
return 0;
}
下面是部分运行结果:
Process 0 destroyed the message at clock 101
Process 0 received HELLO from process 1 at clock 102
Process 0 has local status 51
0 Begin Snapshot!!! at clock 103
Process 0 send HELLO with MARKER!!! to process 1 at clock 103
Process 1 received HELLO with MARKER!!! from process 0 at clock 103
Process 1 has local status 52
1 Begin Snapshot!!! at clock 104
Process 1 send HELLO with MARKER!!! to process 0 at clock 104
Process 1 destroyed the message at clock 104
Process 0 destroyed the message at clock 103
Process 0 received HELLO with MARKER!!! from process 1 at clock 104
0 end snap shot at clock 104 !!!
---------------P_0 log----------------
message HELLO with MARKER!!! with clock 104 marker 1
---------------P_0 log----------------
Process 0 sent HELLO to 1 at clock 105
Process 1 received HELLO from process 0 at clock 105
Process 0 destroyed the message at clock 105
1 end snap shot at clock 105 !!!
---------------P_1 log----------------
message HELLO with clock 105 marker 0
---------------P_1 log----------------
Process 1 sent HELLO to 0 at clock 106
Process 1 destroyed the message at clock 106