@@ -19,73 +19,15 @@ timer 的实现在工业界已经是有解的问题了。常见的就是时间
1919
2020最常见的时间堆一般用小顶堆实现,小顶堆其实就是一种特殊的二叉树:
2121
22- ```
23- ┌─────┐
24- │ │
25- │ 5 │
26- └─────┘
27- │
28- │
29- ┌──────────┴──────────┐
30- │ │
31- ▼ ▼
32- ┌─────┐ ┌─────┐
33- │ │ │ │
34- │ 6 │ │ 10 │
35- └─────┘ └─────┘
36- │ │
37- ┌────┴─────┐ ┌────┴─────┐
38- │ │ │ │
39- ▼ ▼ ▼ ▼
40- ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐
41- │ │ │ │ │ │ │ │
42- │ 7 │ │ 6 │ │ 11 │ │ 20 │
43- └─────┘ └─────┘ └─────┘ └─────┘
44- │ │
45- │ │
46- ┌───────┴────┐ └───────┐
47- │ │ │
48- ▼ ▼ ▼
49- ┌─────┐ ┌─────┐ ┌─────┐
50- │ │ │ │ ............... │ │
51- │ 15 │ │ 8 │ │ 30 │
52- └─────┘ └─────┘ └─────┘
53- ```
22+ ![ 二叉堆] ( ../images/ch6-binary_tree.png )
5423
5524小顶堆的好处是什么呢?实际上对于定时器来说,如果堆顶元素比当前的时间还要大,那么说明堆内所有元素都比当前时间大。进而说明这个时刻我们还没有必要对时间堆进行任何处理。所以对于定时 check 来说,时间复杂度是 O(1) 的。
5625
5726当我们发现堆顶的元素 < 当前时间时,那么说明可能已经有一批事件已经开始过期了,这时进行正常的弹出和堆调整操作就好。每一次堆调整的时间复杂度都是 O(LgN)。
5827
5928Go 自身的 timer 就是用时间堆来实现的,不过并没有使用二叉堆,而是使用了扁平一些的四叉堆。在最近的版本中,还加了一些优化,我们先不说优化,先来看看四叉的小顶堆长什么样:
6029
61- ```
62- +-----+
63- | |
64- | 0 |
65- +-----+
66- |
67- |
68- |
69- v
70- +-----+-----+-----+-----+
71- | | | | |
72- | 3 | 2 | 2 | 10 |
73- +-----+-----+-----+-----+
74- | | | |
75- | | | |
76- +----------+ | | | |
77- +----------------+ 4*i+1 +-----------------------+ | | +-----------------------------+
78- | +----------+ +-------------------+ +---+ |
79- | | | |
80- | | | |
81- v | | v
82- +-----+-----+-----+-----+ | | +-----+-----+-----+-----+
83- | | | | | v v | | | | |
84- | 20 | 4 | 5 | 13 | +-----+-----+-----+-----+ +-----+-----+-----+-----+ | 99 | 13 | 11 | 12 |
85- +-----+-----+-----+-----+ | | | | | | | | | | +-----+-----+-----+-----+
86- | 12 | 14 | 15 | 16 | | 3 | 10 | 3 | 3 |
87- +-----+-----+-----+-----+ +-----+-----+-----+-----+
88- ```
30+ ![ 四叉堆] ( ../images/ch6-four-branch-tree.png )
8931
9032小顶堆的性质,父节点比其 4 个子节点都小,子节点之间没有特别的大小关系要求。
9133
@@ -126,59 +68,15 @@ Go 自身的 timer 就是用时间堆来实现的,不过并没有使用二叉
12668
12769我们可以参考 elasticsearch 的设计,每份任务数据都有多个副本,这里假设两副本:
12870
129- ```
130- ┌──────────┐
131- │ node 1 │
132- ├──────────┴────────────────────────┐
133- │ ┏━━━┓ ┌───┐ ┌───┐ ┏━━━┓ │
134- │ ┃ 0 ┃ │ 1 │ │ 3 │ ┃ 4 ┃ │
135- │ ┗━━━┛ └───┘ └───┘ ┗━━━┛ │
136- └───────────────────────────────────┘
137- ┌──────────┐
138- │ node 2 │
139- ├──────────┴────────────────────────┐
140- │ ┌───┐ ┏━━━┓ ┏━━━┓ │
141- │ │ 0 │ ┃ 2 ┃ ┃ 3 ┃ │
142- │ └───┘ ┗━━━┛ ┗━━━┛ │
143- └───────────────────────────────────┘
144- ┌──────────┐
145- │ node 3 │
146- ├──────────┴────────────────────────┐
147- │ ┏━━━┓ ┌───┐ ┌───┐ │
148- │ ┃ 1 ┃ │ 2 │ │ 4 │ │
149- │ ┗━━━┛ └───┘ └───┘ │
150- └───────────────────────────────────┘
151- ```
71+ ![ 数据分布] ( ../images/ch6-data-dist1.png )
15272
15373一份数据虽然有两个持有者,但持有者持有的副本会进行区分,比如持有的是主副本还是非主副本,主副本在图中为摸黑部分,非主副本为正常线条。
15474
15575一个任务只会在持有主副本的节点上被执行。
15676
15777当有机器故障时,任务数据需要进行 rebalance 工作,比如 node 1 挂了:
15878
159- ```
160- ┌──────────┐
161- │ node 1 │
162- ├──────────┴────────────────────────┐
163- │ │
164- │ X X X │
165- │ │
166- └───────────────────────────────────┘
167- ┌──────────┐
168- │ node 2 │
169- ├──────────┴────────────────────────┐
170- │ ┌───┐ ┌───┐ ┏━━━┓ ┏━━━┓ ┏━━━┓ │
171- │ │ 0 │ │ 1 │ ┃ 2 ┃ ┃ 3 ┃ ┃ 4 ┃ │
172- │ └───┘ └───┘ ┗━━━┛ ┗━━━┛ ┗━━━┛ │
173- └───────────────────────────────────┘
174- ┌──────────┐
175- │ node 3 │
176- ├──────────┴────────────────────────┐
177- │ ┏━━━┓ ┏━━━┓ ┌───┐ ┌───┐ ┌───┐ │
178- │ ┃ 0 ┃ ┃ 1 ┃ │ 2 │ │ 3 │ │ 4 │ │
179- │ ┗━━━┛ ┗━━━┛ └───┘ └───┘ └───┘ │
180- └───────────────────────────────────┘
181- ```
79+ ![ 数据分布2] ( ../images/ch6-data-dist2.png )
18280
18381node 1 的数据会被迁移到 node 2 和 node 3 上。
18482
0 commit comments