Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions dev/epaxos_exec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Instance

这里仅列出`execution`需要用到的变量

```go
type Instance struct {
Deps [ReplicaCount]int32

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

能保证只依赖一个副本上的一个instance吗?
例如:一个read操作可能依赖于另外一个read和write操作

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

你看下最下面的说明,“其它实现细节”,我理解是保存了当前副本最大的那个。执行的时候是需要执行小于等于这个id的所有instance

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deps依赖最大的, 就间接的相当于依赖所有的deps了

Index int
Lowlink int
Seq int
}
```

- `Deps`:表示当前`instance`所依赖的其它`Replica`上的`instance id`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

也包括自己的吧?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

包含

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deps为啥要包括自己呢? 是论文里约定吗? 我不太记得见到这个了.

感觉R1上的inst[j] 依赖 R1上的inst[j]有点奇怪啊. 正常创建这个inst的时候, 应该是inst[j] 依赖inst[j-1].

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

还是说也包含自己的是指包含replica不是instance...

Copy link
Author

@liubaohai liubaohai Dec 12, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

func (r *Replica) updateAttributes(cmds []state.Command, seq int32, deps [DS]int32, replica int32, instance int32) (int32, [DS]int32, bool) {
	changed := false
	for q := 0; q < r.N; q++ {
		if r.Id != replica && int32(q) == replica {
			continue
		}
		for i := 0; i < len(cmds); i++ {
			if d, present := (r.conflicts[q])[cmds[i].K]; present {
				if d > deps[q] {
					deps[q] = d
					if seq <= r.InstanceSpace[q][d].Seq {
						seq = r.InstanceSpace[q][d].Seq + 1
					}
					changed = true
					break
				}
			}
		}
	}
	for i := 0; i < len(cmds); i++ {
		if s, present := r.maxSeqPerKey[cmds[i].K]; present {
			if seq <= s {
				changed = true
				seq = s + 1
			}
		}
	}

	return seq, deps, changed
}

看实现是依赖当前Replica中最大的那个包含相同key的那个instanceid,并不一定是inst[j-1]key有交集的最大的那个,可能是inst[j-5],不会是它自己


- `Deps[0]`表示依赖`Replica 0`上的的`instance id`

- `Deps[1]`表示依赖`Replica 1`上的的`instance id`

- `Deps[2]`表示依赖`Replica 2`上的的`instance id`

- `Index`:用于使用`Tarjan`算法寻找强连通分量的节点搜索次序编号,`=0`表示此节点没有被访问过

- `Lowlink`:`Tarjan`算法中节点或节点的子树能够追溯到的最早的栈中节点的次序号

- `Seq`:用于强连通分量定序

# 看下执行过程

例如下面是一个`ins`的依赖图

```
ins1---------->ins3---------->ins5
^ | |
| | |
| | |
| | |
| V V
ins2<----------ins4---------->ins6
```

采用`Tarjan`算法搜索图中的强连通分量`SCC:Strongly Connected Components`,该算法有两个重要的数组

- DFN[]:全称`Depth First Number`,表示节点被搜索到的次序编号,对应`struct Instance`中`Index`

- LOW[]:表示节点或者节点的子树能够追溯到的最早的栈中节点的次序编号,对应`struct Instance`中`Lowlink`

下面来展示一下执行过程,这里使用`DFN`(`struct Instance`中`Index`),`LOW`(`struct Instance`中`Lowlink`)

- 从`ins1`开始`DFS`(`Depth First Search`)遍历, 比如顺序是`ins1->ins3->ins5->ins6`,依次入栈,如下

```
-----------
| ins6 |->DFN:4 LOW:4
-----------
| ins5 |->DFN:3 LOW:3
-----------
| ins3 |->DFN:2 LOW:2
-----------
| ins1 |->DFN:1 LOW:1
-----------
```

- 搜索到`ins6`发现没有边可搜索,这个时候退栈发现`DFN:4==LOW:4`,说明`ins6`是一个强连通分量,这个时候去`exec ins6`

- 同理`ins5`也是一个强连通分量,`exec ins5`之后,这个时候栈如下

```
-----------
| ins3 |->DFN:2 LOW:2
-----------
| ins1 |->DFN:1 LOW:1
-----------
```

- 出栈到`ins3`,继续搜索`ins4`并加入栈

- 继续搜索`ins2`,由于`ins2`有边指向`ins1`,`ins1`还在栈里面,这个时候`ins2`的`LOW`取`ins1`的`LOW`,也就是`1`,栈如下

```
-----------
| ins2 |->DFN:6 LOW:1
-----------
| ins4 |->DFN:5 LOW:5
-----------
| ins3 |->DFN:2 LOW:2
-----------
| ins1 |->DFN:1 LOW:1
-----------
```

- 这个时候,所有节点已经搜索完成,开始出栈,出栈会修改`LOW`的值,取看到的最小值,如下

```
-----------
| ins2 |->DFN:6 LOW:1
-----------
| ins4 |->DFN:5 LOW:1
-----------
| ins3 |->DFN:2 LOW:1
-----------
| ins1 |->DFN:1 LOW:1
-----------
```

- 我们需要找到一个节点`DFN=LOW`,也就是这个强连通分量的根,上述例子中就是`ins1`,栈里面的元素集合就是一个强连通分量,这里是
`ins1 ins2 ins3 ins4`

# 强连通分量定序

- 强连通分量中节点是相互可达的,也就是相互依赖,但是我们需要保证强连通分量中的`ins`执行顺序在每个`Replica`中保持一致,采用的
方式是通过`ins`中`Seq`进行排序,排序的结果也就是`ins`的执行顺序

# 其它实现细节

- 每个`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 ins`设置一个超时时间,到达超时时间还没有达到
`committed`状态,会触发`ins`的恢复流程