-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdirector.go
208 lines (185 loc) · 4.84 KB
/
director.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
package vintage
import (
"crypto/sha256"
"encoding/binary"
"fmt"
"math"
"math/rand"
"sort"
"time"
)
type Director struct {
Type DirectorType
Properties map[string]any
Backends []map[string]any
determinedBackend string
}
type DirectorOption func(d *Director)
func DirectorProperty(key string, value any) DirectorOption {
return func(d *Director) {
d.Properties[key] = value
}
}
func DirectorBackend(opts ...DirectorOption) DirectorOption {
return func(d *Director) {
inner := &Director{
Properties: make(map[string]any),
}
for i := range opts {
opts[i](inner)
}
d.Backends = append(d.Backends, inner.Properties)
}
}
func NewDirector(name string, dType DirectorType, opts ...DirectorOption) *Backend {
d := &Director{
Type: dType,
Properties: make(map[string]any),
Backends: []map[string]any{},
}
for i := range opts {
opts[i](d)
}
return &Backend{
Name: name,
Director: d,
}
}
func (d *Director) Backend(ident RequestIdentity) string {
if d.determinedBackend == "" {
var backend string
switch d.Type {
case Random:
backend = d.random()
case Fallback:
backend = d.fallback()
case Hash:
backend = d.hash(ident)
case Client:
backend = d.client(ident)
case CHash:
backend = d.chash(ident)
}
d.determinedBackend = backend
}
return d.determinedBackend
}
// Elect backend by random.
// NOTE: Fastly Compute does not know that backend is healthy or not,
// so runtime does not consider about quorum weight
func (d *Director) random() string {
lottery := make([]int, 1000)
var last int
for index, v := range d.Backends {
var weight int
if w, ok := v["weight"].(int); ok {
weight = w
}
for i := 0; i < weight; i++ {
lottery[last] = index
last++
if last > len(lottery) {
extend := make([]int, 2000)
for j := 0; j < len(lottery); j++ {
extend[j] = lottery[i]
}
lottery = extend
}
}
}
rand.New(rand.NewSource(time.Now().Unix()))
lottery = lottery[0:last]
backend := d.Backends[lottery[rand.Intn(last)]]
return backend["backend"].(string)
}
// Elect backend by fallback algorithm.
// NOTE: Fastly Compute does not know that backend is healthy or not,
// so runtime always chooses first backend
func (d *Director) fallback() string {
backend := d.Backends[0]
return backend["backend"].(string)
}
// Elect backend by hash algorithm.
// TODO: need basement hash string like req.hash in VCL
func (d *Director) hash(ident RequestIdentity) string {
hash := sha256.Sum256([]byte(ident.Hash))
return d.getBackendByHash(hash[:])
}
// Elect backend by client identity.
// TODO: need basement client ip like client.identity in VCL
func (d *Director) client(ident RequestIdentity) string {
hash := sha256.Sum256([]byte(ident.Client))
return d.getBackendByHash(hash[:])
}
// Elect backend by consistent hash algorithm.
// TODO: need basement hash string like req.hash in VCL
func (d *Director) chash(ident RequestIdentity) string {
var circles []uint32
var maxInt uint32 = 10000
hashTable := make(map[uint32]string)
var seed uint32
if s, ok := d.Properties["seed"].(int); ok {
seed = uint32(s)
}
for _, v := range d.Backends {
backend := v["backend"].(string) // nolint:errcheck
// typically loop three times in order to find suitable ring position
for i := 0; i < 3; i++ {
buf := make([]byte, 4)
binary.BigEndian.PutUint32(buf, seed)
hash := sha256.New()
hash.Write(buf)
hash.Write([]byte(backend))
hash.Write([]byte(fmt.Sprint(i)))
h := hash.Sum(nil)
num := binary.BigEndian.Uint32(h[:8]) % maxInt
hashTable[num] = backend
circles = append(circles, num)
}
}
// sort slice for binary search
sort.Slice(circles, func(i, j int) bool {
return circles[i] < circles[j]
})
var hashKey [32]byte
key := "client"
if v, ok := d.Properties["key"].(string); ok {
key = v
}
switch key {
case "object":
hashKey = sha256.Sum256([]byte(ident.Hash))
default: // client
hashKey = sha256.Sum256([]byte(ident.Client))
}
k := binary.BigEndian.Uint32(hashKey[:8]) % maxInt
index := sort.Search(len(circles), func(i int) bool {
return circles[i] >= k
})
if index == len(circles) {
index = 0
}
return hashTable[circles[index]]
}
func (d *Director) getBackendByHash(hash []byte) string {
var target string // determined backend name
for m := 4; m <= 16; m += 2 {
maxInt := uint64(math.Pow(10, float64(m)))
num := binary.BigEndian.Uint64(hash[:8]) % maxInt
for _, v := range d.Backends {
backend := v["backend"].(string) // nolint:errcheck
bh := sha256.Sum256([]byte(backend))
b := binary.BigEndian.Uint64(bh[:8])
if b%(maxInt*10) >= num && b%(maxInt*10) < num+maxInt {
target = backend
goto DETERMINED
}
}
}
DETERMINED:
// When target is not determined, use first healthy backend
if target == "" {
target = d.Backends[0]["backend"].(string) // nolint:errcheck
}
return target
}