From 62ca2efd40ff7789e1ccefd4bea8af8d78bb6bc8 Mon Sep 17 00:00:00 2001 From: liubaohai Date: Tue, 3 Dec 2019 22:17:39 +0800 Subject: [PATCH 1/6] epaxos execution learning --- dev/rust/epaxos_exec.md | 89 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 dev/rust/epaxos_exec.md diff --git a/dev/rust/epaxos_exec.md b/dev/rust/epaxos_exec.md new file mode 100644 index 0000000..a0d8ddc --- /dev/null +++ b/dev/rust/epaxos_exec.md @@ -0,0 +1,89 @@ +# instance + +- 每个`instance`需要保存它所依赖其它`Replica`上的那个`instance` + +- 比如三个`Replica`, 每个`instance`定义一个数组`Deps[3]` + +- `Dep[0]`表示依赖`Replica 0`上的的`instance` + +- `Dep[1]`表示依赖`Replica 1`上的的`instance` + +- `Dep[2]`表示依赖`Replica 2`上的的`instance` + + +# 看下执行过程 + +例如下面是一个instance的依赖图 + +``` +instance1---------->instance3---------->instance5 + ^ | | + | | | + | | | + | | | + | V V +instance2<----------instance4---------->instance6 +``` + +实际上执行的过程就是有向图强连通分量的`Tarjan`算法 + +- `Tarjan`需要用到两个数组,DFN[u]为节点u搜索的次序编号,Low(u)为u或u的子树能够追溯到的最早的栈中节点的次序号 + +- 从instance1开始dfs遍历, 比如顺序是`instance1->instance3->instance5->instance6`,保存到栈中, 源代码里面每个`instance` + 定义了两个变量`Index`(=0表示这个节点没有被访问过),`LowLink`,这里对应下面的DFN和LOW + +``` +----------- +|instance6|->DFN:4 LOW:4 +----------- +|instance5|->DFN:3 LOW:3 +----------- +|instance3|->DFN:2 LOW:2 +----------- +|instance1|->DFN:1 LOW:1 +----------- +``` + +- 搜索到`instance6`发现没有边可搜索,这个时候退栈发现`DFN:4==LOW:4`且`instance5`没有可搜索边, + 说明`instance6`是一个强连通分量,这个时候去`execution instance6` + +- 同理`instance5`也是一个强连通分量,执行完`instance6`去执行`instance5`,这个时候栈如下 + +``` +----------- +|instance3|->DFN:2 LOW:2 +----------- +|instance1|->DFN:1 LOW:1 +----------- +``` + +- 退栈到`instance3`,继续搜索`instance4`并加入栈 + +- 继续搜索`instance2`,由于`instance2`有边指向`instance1`,`instance1`还在栈里面,这个时候`instance2`的LOW取`instance1` + 的LOW,也就是1,栈如下 + +``` +----------- +|instance2|->DFN:6 LOW:1 +----------- +|instance4|->DFN:5 LOW:5 +----------- +|instance3|->DFN:2 LOW:2 +----------- +|instance1|->DFN:1 LOW:1 +----------- +``` + +- 这个时候,所有`instance`已经搜索完成,开始出栈,直到找到DFN[idx]=LOW[idx],也就是强连通分量的根,这里就是`instance1`, + 栈里面的元素集合就是一个强连通分量,这里是`instance1 instance2 instance3 instance4` + +- 每个`instance`有一个`seq`,通过它对强连通分量里面的`instance`进行排序,按照这个顺序去执行这个`instance` + +# 其它实现细节 + +- 比如`instance1(Replica1)---->instance10(Replica2)`,每个`Replica`保存了其`execution`后最大的`instance id`,这个时候 + 执行`instance10`需要把小于这个`instance id`的其它`instance`执行。也可以这样理解,`instance1(Replica1)`依赖`Replica2` + 上`instanceid <= 10`的所有`instance` + +- `execution`线程会给每一个没有达到`committed`状态的`instance`设置一个超时时间,到达超时时间还没有达到`committed`状态, + 会触发`instance`的恢复流程 From 90fc2311bd749db9099607ece426ea93d59bfe35 Mon Sep 17 00:00:00 2001 From: liubaohai Date: Tue, 3 Dec 2019 22:30:51 +0800 Subject: [PATCH 2/6] refine --- dev/rust/epaxos_exec.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/dev/rust/epaxos_exec.md b/dev/rust/epaxos_exec.md index a0d8ddc..8433698 100644 --- a/dev/rust/epaxos_exec.md +++ b/dev/rust/epaxos_exec.md @@ -74,8 +74,21 @@ instance2<----------instance4---------->instance6 ----------- ``` -- 这个时候,所有`instance`已经搜索完成,开始出栈,直到找到DFN[idx]=LOW[idx],也就是强连通分量的根,这里就是`instance1`, - 栈里面的元素集合就是一个强连通分量,这里是`instance1 instance2 instance3 instance4` +- 这个时候,所有`instance`已经搜索完成,开始出栈,出栈会修改LOW的值,取看到的最小值,直到找到DFN[idx]=LOW[idx], + 也就是强连通分量的根,这里就是`instance1`,栈里面的元素集合就是一个强连通分量,这里是 + `instance1 instance2 instance3 instance4`,LOW修改后如下 + +``` +----------- +|instance2|->DFN:6 LOW:1 +----------- +|instance4|->DFN:5 LOW:1 +----------- +|instance3|->DFN:2 LOW:1 +----------- +|instance1|->DFN:1 LOW:1 +----------- +``` - 每个`instance`有一个`seq`,通过它对强连通分量里面的`instance`进行排序,按照这个顺序去执行这个`instance` From 674638441ff7af56b6f63dc0267b03d4b4e96ee5 Mon Sep 17 00:00:00 2001 From: liubaohai Date: Tue, 3 Dec 2019 22:31:34 +0800 Subject: [PATCH 3/6] refine --- dev/{rust => }/epaxos_exec.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename dev/{rust => }/epaxos_exec.md (100%) diff --git a/dev/rust/epaxos_exec.md b/dev/epaxos_exec.md similarity index 100% rename from dev/rust/epaxos_exec.md rename to dev/epaxos_exec.md From 485002964f4cf1535981c7bd45ad4cebde1c14a2 Mon Sep 17 00:00:00 2001 From: liubaohai Date: Tue, 3 Dec 2019 22:45:30 +0800 Subject: [PATCH 4/6] refine --- dev/epaxos_exec.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/dev/epaxos_exec.md b/dev/epaxos_exec.md index 8433698..a14e939 100644 --- a/dev/epaxos_exec.md +++ b/dev/epaxos_exec.md @@ -27,10 +27,10 @@ instance2<----------instance4---------->instance6 实际上执行的过程就是有向图强连通分量的`Tarjan`算法 -- `Tarjan`需要用到两个数组,DFN[u]为节点u搜索的次序编号,Low(u)为u或u的子树能够追溯到的最早的栈中节点的次序号 +- `Tarjan`需要用到两个数组,`DFN[u]`为节点u搜索的次序编号,`LOW(u)`为u或u的子树能够追溯到的最早的栈中节点的次序号 - 从instance1开始dfs遍历, 比如顺序是`instance1->instance3->instance5->instance6`,保存到栈中, 源代码里面每个`instance` - 定义了两个变量`Index`(=0表示这个节点没有被访问过),`LowLink`,这里对应下面的DFN和LOW + 定义了两个变量`Index`(=0表示这个节点没有被访问过),`LowLink`,这里对应下面的`DFN`和`LOW` ``` ----------- @@ -59,8 +59,8 @@ instance2<----------instance4---------->instance6 - 退栈到`instance3`,继续搜索`instance4`并加入栈 -- 继续搜索`instance2`,由于`instance2`有边指向`instance1`,`instance1`还在栈里面,这个时候`instance2`的LOW取`instance1` - 的LOW,也就是1,栈如下 +- 继续搜索`instance2`,由于`instance2`有边指向`instance1`,`instance1`还在栈里面,这个时候`instance2`的`LOW`取`instance1` + 的`LOW`,也就是1,栈如下 ``` ----------- @@ -74,9 +74,7 @@ instance2<----------instance4---------->instance6 ----------- ``` -- 这个时候,所有`instance`已经搜索完成,开始出栈,出栈会修改LOW的值,取看到的最小值,直到找到DFN[idx]=LOW[idx], - 也就是强连通分量的根,这里就是`instance1`,栈里面的元素集合就是一个强连通分量,这里是 - `instance1 instance2 instance3 instance4`,LOW修改后如下 +- 这个时候,所有`instance`已经搜索完成,开始出栈,出栈会修改`LOW`的值,取看到的最小值,如下 ``` ----------- @@ -90,13 +88,16 @@ instance2<----------instance4---------->instance6 ----------- ``` +- 找到一个`DFN[idx]=LOW[idx]`,也就是强连通分量的根,这里就是`instance1`,栈里面的元素集合就是一个强连通分量,这里是 + `instance1 instance2 instance3 instance4` + - 每个`instance`有一个`seq`,通过它对强连通分量里面的`instance`进行排序,按照这个顺序去执行这个`instance` # 其它实现细节 - 比如`instance1(Replica1)---->instance10(Replica2)`,每个`Replica`保存了其`execution`后最大的`instance id`,这个时候 - 执行`instance10`需要把小于这个`instance id`的其它`instance`执行。也可以这样理解,`instance1(Replica1)`依赖`Replica2` + 执行`instance10`需要把小于等于这个`instance id`的其它`instance`执行。也可以这样理解,`instance1(Replica1)`依赖`Replica2` 上`instanceid <= 10`的所有`instance` -- `execution`线程会给每一个没有达到`committed`状态的`instance`设置一个超时时间,到达超时时间还没有达到`committed`状态, - 会触发`instance`的恢复流程 +- `execution`线程会给当前`Replica`上最小的没有`committed`的`active instance`设置一个超时时间,到达超时时间还没有达到 + `committed`状态,会触发`instance`的恢复流程 From 7c02bf01936e19247e88d814045785f56092684a Mon Sep 17 00:00:00 2001 From: liubaohai Date: Tue, 3 Dec 2019 22:51:04 +0800 Subject: [PATCH 5/6] refine --- dev/epaxos_exec.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/epaxos_exec.md b/dev/epaxos_exec.md index a14e939..5445935 100644 --- a/dev/epaxos_exec.md +++ b/dev/epaxos_exec.md @@ -44,8 +44,8 @@ instance2<----------instance4---------->instance6 ----------- ``` -- 搜索到`instance6`发现没有边可搜索,这个时候退栈发现`DFN:4==LOW:4`且`instance5`没有可搜索边, - 说明`instance6`是一个强连通分量,这个时候去`execution instance6` +- 搜索到`instance6`发现没有边可搜索,这个时候退栈发现`DFN:4==LOW:4`,说明`instance6`是一个强连通分量,这个时候去 + `execution instance6` - 同理`instance5`也是一个强连通分量,执行完`instance6`去执行`instance5`,这个时候栈如下 From e861bab3f73ce4ac827b71f62a7603a9daf446ec Mon Sep 17 00:00:00 2001 From: liubaohai Date: Wed, 4 Dec 2019 13:38:14 +0800 Subject: [PATCH 6/6] review change --- dev/epaxos_exec.md | 113 ++++++++++++++++++++++++++------------------- 1 file changed, 66 insertions(+), 47 deletions(-) diff --git a/dev/epaxos_exec.md b/dev/epaxos_exec.md index 5445935..1281a93 100644 --- a/dev/epaxos_exec.md +++ b/dev/epaxos_exec.md @@ -1,103 +1,122 @@ -# instance +# Instance -- 每个`instance`需要保存它所依赖其它`Replica`上的那个`instance` +这里仅列出`execution`需要用到的变量 -- 比如三个`Replica`, 每个`instance`定义一个数组`Deps[3]` +```go +type Instance struct { + Deps [ReplicaCount]int32 + Index int + Lowlink int + Seq int +} +``` + +- `Deps`:表示当前`instance`所依赖的其它`Replica`上的`instance id` + + - `Deps[0]`表示依赖`Replica 0`上的的`instance id` + + - `Deps[1]`表示依赖`Replica 1`上的的`instance id` -- `Dep[0]`表示依赖`Replica 0`上的的`instance` + - `Deps[2]`表示依赖`Replica 2`上的的`instance id` -- `Dep[1]`表示依赖`Replica 1`上的的`instance` +- `Index`:用于使用`Tarjan`算法寻找强连通分量的节点搜索次序编号,`=0`表示此节点没有被访问过 -- `Dep[2]`表示依赖`Replica 2`上的的`instance` +- `Lowlink`:`Tarjan`算法中节点或节点的子树能够追溯到的最早的栈中节点的次序号 +- `Seq`:用于强连通分量定序 # 看下执行过程 -例如下面是一个instance的依赖图 +例如下面是一个`ins`的依赖图 ``` -instance1---------->instance3---------->instance5 - ^ | | - | | | - | | | - | | | - | V V -instance2<----------instance4---------->instance6 +ins1---------->ins3---------->ins5 + ^ | | + | | | + | | | + | | | + | V V +ins2<----------ins4---------->ins6 ``` -实际上执行的过程就是有向图强连通分量的`Tarjan`算法 +采用`Tarjan`算法搜索图中的强连通分量`SCC:Strongly Connected Components`,该算法有两个重要的数组 -- `Tarjan`需要用到两个数组,`DFN[u]`为节点u搜索的次序编号,`LOW(u)`为u或u的子树能够追溯到的最早的栈中节点的次序号 +- DFN[]:全称`Depth First Number`,表示节点被搜索到的次序编号,对应`struct Instance`中`Index` -- 从instance1开始dfs遍历, 比如顺序是`instance1->instance3->instance5->instance6`,保存到栈中, 源代码里面每个`instance` - 定义了两个变量`Index`(=0表示这个节点没有被访问过),`LowLink`,这里对应下面的`DFN`和`LOW` +- LOW[]:表示节点或者节点的子树能够追溯到的最早的栈中节点的次序编号,对应`struct Instance`中`Lowlink` + +下面来展示一下执行过程,这里使用`DFN`(`struct Instance`中`Index`),`LOW`(`struct Instance`中`Lowlink`) + +- 从`ins1`开始`DFS`(`Depth First Search`)遍历, 比如顺序是`ins1->ins3->ins5->ins6`,依次入栈,如下 ``` ----------- -|instance6|->DFN:4 LOW:4 +| ins6 |->DFN:4 LOW:4 ----------- -|instance5|->DFN:3 LOW:3 +| ins5 |->DFN:3 LOW:3 ----------- -|instance3|->DFN:2 LOW:2 +| ins3 |->DFN:2 LOW:2 ----------- -|instance1|->DFN:1 LOW:1 +| ins1 |->DFN:1 LOW:1 ----------- ``` -- 搜索到`instance6`发现没有边可搜索,这个时候退栈发现`DFN:4==LOW:4`,说明`instance6`是一个强连通分量,这个时候去 - `execution instance6` +- 搜索到`ins6`发现没有边可搜索,这个时候退栈发现`DFN:4==LOW:4`,说明`ins6`是一个强连通分量,这个时候去`exec ins6` -- 同理`instance5`也是一个强连通分量,执行完`instance6`去执行`instance5`,这个时候栈如下 +- 同理`ins5`也是一个强连通分量,`exec ins5`之后,这个时候栈如下 ``` ----------- -|instance3|->DFN:2 LOW:2 +| ins3 |->DFN:2 LOW:2 ----------- -|instance1|->DFN:1 LOW:1 +| ins1 |->DFN:1 LOW:1 ----------- ``` -- 退栈到`instance3`,继续搜索`instance4`并加入栈 +- 出栈到`ins3`,继续搜索`ins4`并加入栈 -- 继续搜索`instance2`,由于`instance2`有边指向`instance1`,`instance1`还在栈里面,这个时候`instance2`的`LOW`取`instance1` - 的`LOW`,也就是1,栈如下 +- 继续搜索`ins2`,由于`ins2`有边指向`ins1`,`ins1`还在栈里面,这个时候`ins2`的`LOW`取`ins1`的`LOW`,也就是`1`,栈如下 ``` ----------- -|instance2|->DFN:6 LOW:1 +| ins2 |->DFN:6 LOW:1 ----------- -|instance4|->DFN:5 LOW:5 +| ins4 |->DFN:5 LOW:5 ----------- -|instance3|->DFN:2 LOW:2 +| ins3 |->DFN:2 LOW:2 ----------- -|instance1|->DFN:1 LOW:1 +| ins1 |->DFN:1 LOW:1 ----------- ``` -- 这个时候,所有`instance`已经搜索完成,开始出栈,出栈会修改`LOW`的值,取看到的最小值,如下 +- 这个时候,所有节点已经搜索完成,开始出栈,出栈会修改`LOW`的值,取看到的最小值,如下 ``` ----------- -|instance2|->DFN:6 LOW:1 +| ins2 |->DFN:6 LOW:1 ----------- -|instance4|->DFN:5 LOW:1 +| ins4 |->DFN:5 LOW:1 ----------- -|instance3|->DFN:2 LOW:1 +| ins3 |->DFN:2 LOW:1 ----------- -|instance1|->DFN:1 LOW:1 +| ins1 |->DFN:1 LOW:1 ----------- ``` -- 找到一个`DFN[idx]=LOW[idx]`,也就是强连通分量的根,这里就是`instance1`,栈里面的元素集合就是一个强连通分量,这里是 - `instance1 instance2 instance3 instance4` +- 我们需要找到一个节点`DFN=LOW`,也就是这个强连通分量的根,上述例子中就是`ins1`,栈里面的元素集合就是一个强连通分量,这里是 + `ins1 ins2 ins3 ins4` -- 每个`instance`有一个`seq`,通过它对强连通分量里面的`instance`进行排序,按照这个顺序去执行这个`instance` +# 强连通分量定序 + +- 强连通分量中节点是相互可达的,也就是相互依赖,但是我们需要保证强连通分量中的`ins`执行顺序在每个`Replica`中保持一致,采用的 + 方式是通过`ins`中`Seq`进行排序,排序的结果也就是`ins`的执行顺序 # 其它实现细节 -- 比如`instance1(Replica1)---->instance10(Replica2)`,每个`Replica`保存了其`execution`后最大的`instance id`,这个时候 - 执行`instance10`需要把小于等于这个`instance id`的其它`instance`执行。也可以这样理解,`instance1(Replica1)`依赖`Replica2` - 上`instanceid <= 10`的所有`instance` +- 每个`Replica`保存了其`execution`后最大的`ins id`,定义为`ExecedUpTo` + +- 比如`ins1(Replica1)->ins10(Replica2)`,实际上`Replica2`需要`exec`的是`[ExecedUpTo+1, ins10]`,也可以这样理解 + `ins1(Replica1)`依赖`Replica2`上`ins id`在`[ExecedUpTo+1, ins10]`中的所有`ins` -- `execution`线程会给当前`Replica`上最小的没有`committed`的`active instance`设置一个超时时间,到达超时时间还没有达到 - `committed`状态,会触发`instance`的恢复流程 +- `execution`线程会给当前`Replica`上最小的没有`committed`的`active ins`设置一个超时时间,到达超时时间还没有达到 + `committed`状态,会触发`ins`的恢复流程