Feature: support round-robin strategy for load-balance group (#1044)
This commit is contained in:
parent
2cd1b890ce
commit
9a62b1081d
2 changed files with 69 additions and 21 deletions
|
@ -3,6 +3,8 @@ package outboundgroup
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapters/outbound"
|
"github.com/Dreamacro/clash/adapters/outbound"
|
||||||
|
@ -14,11 +16,24 @@ import (
|
||||||
"golang.org/x/net/publicsuffix"
|
"golang.org/x/net/publicsuffix"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type strategyFn = func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy
|
||||||
|
|
||||||
type LoadBalance struct {
|
type LoadBalance struct {
|
||||||
*outbound.Base
|
*outbound.Base
|
||||||
single *singledo.Single
|
single *singledo.Single
|
||||||
maxRetry int
|
|
||||||
providers []provider.ProxyProvider
|
providers []provider.ProxyProvider
|
||||||
|
strategyFn strategyFn
|
||||||
|
}
|
||||||
|
|
||||||
|
var errStrategy = errors.New("unsupported strategy")
|
||||||
|
|
||||||
|
func parseStrategy(config map[string]interface{}) string {
|
||||||
|
if elm, ok := config["strategy"]; ok {
|
||||||
|
if strategy, ok := elm.(string); ok {
|
||||||
|
return strategy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "consistent-hashing"
|
||||||
}
|
}
|
||||||
|
|
||||||
func getKey(metadata *C.Metadata) string {
|
func getKey(metadata *C.Metadata) string {
|
||||||
|
@ -81,11 +96,28 @@ func (lb *LoadBalance) SupportUDP() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy {
|
func strategyRoundRobin() strategyFn {
|
||||||
|
idx := 0
|
||||||
|
return func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy {
|
||||||
|
length := len(proxies)
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
idx = (idx + 1) % length
|
||||||
|
proxy := proxies[idx]
|
||||||
|
if proxy.Alive() {
|
||||||
|
return proxy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return proxies[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func strategyConsistentHashing() strategyFn {
|
||||||
|
maxRetry := 5
|
||||||
|
return func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy {
|
||||||
key := uint64(murmur3.Sum32([]byte(getKey(metadata))))
|
key := uint64(murmur3.Sum32([]byte(getKey(metadata))))
|
||||||
proxies := lb.proxies()
|
|
||||||
buckets := int32(len(proxies))
|
buckets := int32(len(proxies))
|
||||||
for i := 0; i < lb.maxRetry; i, key = i+1, key+1 {
|
for i := 0; i < maxRetry; i, key = i+1, key+1 {
|
||||||
idx := jumpHash(key, buckets)
|
idx := jumpHash(key, buckets)
|
||||||
proxy := proxies[idx]
|
proxy := proxies[idx]
|
||||||
if proxy.Alive() {
|
if proxy.Alive() {
|
||||||
|
@ -95,6 +127,12 @@ func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||||
|
|
||||||
return proxies[0]
|
return proxies[0]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||||
|
proxies := lb.proxies()
|
||||||
|
return lb.strategyFn(proxies, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
func (lb *LoadBalance) proxies() []C.Proxy {
|
func (lb *LoadBalance) proxies() []C.Proxy {
|
||||||
elm, _, _ := lb.single.Do(func() (interface{}, error) {
|
elm, _, _ := lb.single.Do(func() (interface{}, error) {
|
||||||
|
@ -115,11 +153,20 @@ func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLoadBalance(name string, providers []provider.ProxyProvider) *LoadBalance {
|
func NewLoadBalance(name string, providers []provider.ProxyProvider, strategy string) (lb *LoadBalance, err error) {
|
||||||
|
var strategyFn strategyFn
|
||||||
|
switch strategy {
|
||||||
|
case "consistent-hashing":
|
||||||
|
strategyFn = strategyConsistentHashing()
|
||||||
|
case "round-robin":
|
||||||
|
strategyFn = strategyRoundRobin()
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("%w: %s", errStrategy, strategy)
|
||||||
|
}
|
||||||
return &LoadBalance{
|
return &LoadBalance{
|
||||||
Base: outbound.NewBase(name, "", C.LoadBalance, false),
|
Base: outbound.NewBase(name, "", C.LoadBalance, false),
|
||||||
single: singledo.NewSingle(defaultGetProxiesDuration),
|
single: singledo.NewSingle(defaultGetProxiesDuration),
|
||||||
maxRetry: 3,
|
|
||||||
providers: providers,
|
providers: providers,
|
||||||
}
|
strategyFn: strategyFn,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,7 +111,8 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
|
||||||
case "fallback":
|
case "fallback":
|
||||||
group = NewFallback(groupName, providers)
|
group = NewFallback(groupName, providers)
|
||||||
case "load-balance":
|
case "load-balance":
|
||||||
group = NewLoadBalance(groupName, providers)
|
strategy := parseStrategy(config)
|
||||||
|
return NewLoadBalance(groupName, providers, strategy)
|
||||||
case "relay":
|
case "relay":
|
||||||
group = NewRelay(groupName, providers)
|
group = NewRelay(groupName, providers)
|
||||||
default:
|
default:
|
||||||
|
|
Loading…
Reference in a new issue