forked from ginuerzh/gost
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathselector.go
147 lines (125 loc) · 3.06 KB
/
selector.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
package gost
import (
"errors"
"math/rand"
"sync"
"sync/atomic"
"time"
)
var (
// ErrNoneAvailable indicates there is no node available
ErrNoneAvailable = errors.New("none available")
)
// NodeSelector as a mechanism to pick nodes and mark their status.
type NodeSelector interface {
Select(nodes []Node, opts ...SelectOption) (Node, error)
}
type defaultSelector struct {
}
func (s *defaultSelector) Select(nodes []Node, opts ...SelectOption) (Node, error) {
sopts := SelectOptions{
Strategy: &RoundStrategy{},
}
for _, opt := range opts {
opt(&sopts)
}
for _, filter := range sopts.Filters {
nodes = filter.Filter(nodes)
}
if len(nodes) == 0 {
return Node{}, ErrNoneAvailable
}
return sopts.Strategy.Apply(nodes), nil
}
// SelectOption used when making a select call
type SelectOption func(*SelectOptions)
// SelectOptions is the options for node selection
type SelectOptions struct {
Filters []Filter
Strategy Strategy
}
// WithFilter adds a filter function to the list of filters
// used during the Select call.
func WithFilter(f ...Filter) SelectOption {
return func(o *SelectOptions) {
o.Filters = append(o.Filters, f...)
}
}
// WithStrategy sets the selector strategy
func WithStrategy(s Strategy) SelectOption {
return func(o *SelectOptions) {
o.Strategy = s
}
}
// Strategy is a selection strategy e.g random, round robin
type Strategy interface {
Apply([]Node) Node
String() string
}
// RoundStrategy is a strategy for node selector
type RoundStrategy struct {
count uint64
}
// Apply applies the round robin strategy for the nodes
func (s *RoundStrategy) Apply(nodes []Node) Node {
if len(nodes) == 0 {
return Node{}
}
old := atomic.LoadUint64(&s.count)
atomic.AddUint64(&s.count, 1)
return nodes[int(old%uint64(len(nodes)))]
}
func (s *RoundStrategy) String() string {
return "round"
}
// RandomStrategy is a strategy for node selector
type RandomStrategy struct {
Seed int64
rand *rand.Rand
once sync.Once
}
// Apply applies the random strategy for the nodes
func (s *RandomStrategy) Apply(nodes []Node) Node {
s.once.Do(func() {
seed := s.Seed
if seed == 0 {
seed = time.Now().UnixNano()
}
s.rand = rand.New(rand.NewSource(seed))
})
if len(nodes) == 0 {
return Node{}
}
return nodes[s.rand.Int()%len(nodes)]
}
func (s *RandomStrategy) String() string {
return "random"
}
// Filter is used to filter a node during the selection process
type Filter interface {
Filter([]Node) []Node
String() string
}
// FailFilter filters the dead node.
// A node is marked as dead if its failed count is greater than MaxFails.
type FailFilter struct {
MaxFails int
FailTimeout time.Duration
}
// Filter filters nodes.
func (f *FailFilter) Filter(nodes []Node) []Node {
if len(nodes) <= 1 || f.MaxFails <= 0 {
return nodes
}
nl := []Node{}
for i := range nodes {
if atomic.LoadUint32(&nodes[i].failCount) < uint32(f.MaxFails) ||
time.Since(time.Unix(atomic.LoadInt64(&nodes[i].failTime), 0)) >= f.FailTimeout {
nl = append(nl, nodes[i].Clone())
}
}
return nl
}
func (f *FailFilter) String() string {
return "fail"
}