Skip to content

Commit 2faecc1

Browse files
tiancheng91ginuerzh
authored andcommitted
feat: support node sort by tcp ping latency
1 parent fd57e80 commit 2faecc1

File tree

4 files changed

+127
-8
lines changed

4 files changed

+127
-8
lines changed

cmd/gost/peer.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,16 @@ import (
1313
)
1414

1515
type peerConfig struct {
16-
Strategy string `json:"strategy"`
17-
MaxFails int `json:"max_fails"`
18-
FailTimeout time.Duration
19-
period time.Duration // the period for live reloading
20-
Nodes []string `json:"nodes"`
21-
group *gost.NodeGroup
22-
baseNodes []gost.Node
23-
stopped chan struct{}
16+
Strategy string `json:"strategy"`
17+
MaxFails int `json:"max_fails"`
18+
FastestCount int `json:"fastest_count"` // topN fastest node count
19+
FailTimeout time.Duration
20+
period time.Duration // the period for live reloading
21+
22+
Nodes []string `json:"nodes"`
23+
group *gost.NodeGroup
24+
baseNodes []gost.Node
25+
stopped chan struct{}
2426
}
2527

2628
func newPeerConfig() *peerConfig {
@@ -51,6 +53,7 @@ func (cfg *peerConfig) Reload(r io.Reader) error {
5153
FailTimeout: cfg.FailTimeout,
5254
},
5355
&gost.InvalidFilter{},
56+
gost.NewFastestFilter(0, cfg.FastestCount),
5457
),
5558
gost.WithStrategy(gost.NewStrategy(cfg.Strategy)),
5659
)
@@ -125,6 +128,8 @@ func (cfg *peerConfig) parse(r io.Reader) error {
125128
cfg.Strategy = ss[1]
126129
case "max_fails":
127130
cfg.MaxFails, _ = strconv.Atoi(ss[1])
131+
case "fastest_count":
132+
cfg.FastestCount, _ = strconv.Atoi(ss[1])
128133
case "fail_timeout":
129134
cfg.FailTimeout, _ = time.ParseDuration(ss[1])
130135
case "reload":

cmd/gost/route.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ func (r *route) parseChain() (*gost.Chain, error) {
6666
FailTimeout: nodes[0].GetDuration("fail_timeout"),
6767
},
6868
&gost.InvalidFilter{},
69+
gost.NewFastestFilter(0, nodes[0].GetInt("fastest_count")),
6970
),
7071
gost.WithStrategy(gost.NewStrategy(nodes[0].Get("strategy"))),
7172
)

selector.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ import (
44
"errors"
55
"math/rand"
66
"net"
7+
"sort"
78
"strconv"
89
"sync"
910
"sync/atomic"
1011
"time"
12+
13+
"github.com/go-log/log"
1114
)
1215

1316
var (
@@ -205,6 +208,92 @@ func (f *FailFilter) String() string {
205208
return "fail"
206209
}
207210

211+
// FastestFilter filter the fastest node
212+
type FastestFilter struct {
213+
mu sync.Mutex
214+
215+
pinger *net.Dialer
216+
pingResult map[int]int
217+
pingResultTTL map[int]int64
218+
219+
topCount int
220+
}
221+
222+
func NewFastestFilter(pingTimeOut int, topCount int) *FastestFilter {
223+
if pingTimeOut == 0 {
224+
pingTimeOut = 3000 // 3s
225+
}
226+
return &FastestFilter{
227+
mu: sync.Mutex{},
228+
pinger: &net.Dialer{Timeout: time.Millisecond * time.Duration(pingTimeOut)},
229+
pingResult: make(map[int]int, 0),
230+
pingResultTTL: make(map[int]int64, 0),
231+
topCount: topCount,
232+
}
233+
}
234+
235+
func (f *FastestFilter) Filter(nodes []Node) []Node {
236+
// disabled
237+
if f.topCount == 0 {
238+
return nodes
239+
}
240+
241+
// get latency with ttl cache
242+
now := time.Now().Unix()
243+
r := rand.New(rand.NewSource(time.Now().UnixNano()))
244+
245+
var getNodeLatency = func(node Node) int {
246+
if f.pingResultTTL[node.ID] < now {
247+
f.mu.Lock()
248+
f.pingResultTTL[node.ID] = now + 5 // tmp
249+
defer f.mu.Unlock()
250+
251+
// get latency
252+
go func(node Node) {
253+
latency := f.doTcpPing(node.Addr)
254+
ttl := 300 - int64(60*r.Float64())
255+
256+
f.mu.Lock()
257+
f.pingResult[node.ID] = latency
258+
f.pingResultTTL[node.ID] = now + ttl
259+
defer f.mu.Unlock()
260+
}(node)
261+
}
262+
return f.pingResult[node.ID]
263+
}
264+
265+
// sort
266+
sort.Slice(nodes, func(i, j int) bool {
267+
return getNodeLatency(nodes[i]) < getNodeLatency(nodes[j])
268+
})
269+
270+
// split
271+
if len(nodes) <= f.topCount {
272+
return nodes
273+
}
274+
275+
return nodes[0:f.topCount]
276+
}
277+
278+
func (f *FastestFilter) String() string {
279+
return "fastest"
280+
}
281+
282+
// doTcpPing
283+
func (f *FastestFilter) doTcpPing(address string) int {
284+
start := time.Now()
285+
conn, err := f.pinger.Dial("tcp", address)
286+
elapsed := time.Since(start)
287+
288+
if err == nil {
289+
_ = conn.Close()
290+
}
291+
292+
latency := int(elapsed.Milliseconds())
293+
log.Logf("pingDoTCP: %s, latency: %d", address, latency)
294+
return latency
295+
}
296+
208297
// InvalidFilter filters the invalid node.
209298
// A node is invalid if its port is invalid (negative or zero value).
210299
type InvalidFilter struct{}

selector_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,30 @@ func TestFailFilter(t *testing.T) {
127127
}
128128
}
129129

130+
func TestFastestFilter(t *testing.T) {
131+
nodes := []Node{
132+
Node{ID: 1, marker: &failMarker{}, Addr: "1.0.0.1:80"},
133+
Node{ID: 2, marker: &failMarker{}, Addr: "1.0.0.2:80"},
134+
Node{ID: 3, marker: &failMarker{}, Addr: "1.0.0.3:80"},
135+
}
136+
filter := NewFastestFilter(0, 2)
137+
138+
var print = func(nodes []Node) []string {
139+
var rows []string
140+
for _, node := range nodes {
141+
rows = append(rows, node.Addr)
142+
}
143+
return rows
144+
}
145+
146+
result1 := filter.Filter(nodes)
147+
t.Logf("result 1: %+v", print(result1))
148+
149+
time.Sleep(time.Second)
150+
result2 := filter.Filter(nodes)
151+
t.Logf("result 2: %+v", print(result2))
152+
}
153+
130154
func TestSelector(t *testing.T) {
131155
nodes := []Node{
132156
Node{ID: 1, marker: &failMarker{}},

0 commit comments

Comments
 (0)