This commit is contained in:
qaq
2025-06-17 12:17:33 +08:00
parent 4a6b2c8e0f
commit 9fa968d07c
648 changed files with 64968 additions and 1621 deletions
+95
View File
@@ -0,0 +1,95 @@
package selector
import (
"context"
"time"
"github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
"github.com/go-gost/core/selector"
)
type failFilter[T any] struct {
maxFails int
failTimeout time.Duration
}
// FailFilter filters the dead objects.
// An object is marked as dead if its failed count is greater than MaxFails.
func FailFilter[T any](maxFails int, timeout time.Duration) selector.Filter[T] {
return &failFilter[T]{
maxFails: maxFails,
failTimeout: timeout,
}
}
// Filter filters dead objects.
func (f *failFilter[T]) Filter(ctx context.Context, vs ...T) []T {
if len(vs) <= 1 {
return vs
}
var l []T
for _, v := range vs {
maxFails := f.maxFails
failTimeout := f.failTimeout
if mi, _ := any(v).(metadata.Metadatable); mi != nil {
if md := mi.Metadata(); md != nil {
if md.IsExists(labelMaxFails) {
maxFails = mdutil.GetInt(md, labelMaxFails)
}
if md.IsExists(labelFailTimeout) {
failTimeout = mdutil.GetDuration(md, labelFailTimeout)
}
}
}
if maxFails <= 0 {
maxFails = 1
}
if failTimeout <= 0 {
failTimeout = DefaultFailTimeout
}
if mi, _ := any(v).(selector.Markable); mi != nil {
if marker := mi.Marker(); marker != nil {
if marker.Count() < int64(maxFails) ||
time.Since(marker.Time()) >= failTimeout {
l = append(l, v)
}
continue
}
}
l = append(l, v)
}
return l
}
type backupFilter[T any] struct{}
// BackupFilter filters the backup objects.
// An object is marked as backup if its metadata has backup flag.
func BackupFilter[T any]() selector.Filter[T] {
return &backupFilter[T]{}
}
// Filter filters backup objects.
func (f *backupFilter[T]) Filter(ctx context.Context, vs ...T) []T {
if len(vs) <= 1 {
return vs
}
var l, backups []T
for _, v := range vs {
if mi, _ := any(v).(metadata.Metadatable); mi != nil {
if mdutil.GetBool(mi.Metadata(), labelBackup) {
backups = append(backups, v)
continue
}
}
l = append(l, v)
}
if len(l) == 0 {
return backups
}
return l
}
+43
View File
@@ -0,0 +1,43 @@
package selector
import (
"context"
"time"
"github.com/go-gost/core/selector"
)
// default options for FailFilter
const (
DefaultMaxFails = 1
DefaultFailTimeout = 10 * time.Second
)
const (
labelWeight = "weight"
labelBackup = "backup"
labelMaxFails = "maxFails"
labelFailTimeout = "failTimeout"
)
type defaultSelector[T any] struct {
strategy selector.Strategy[T]
filters []selector.Filter[T]
}
func NewSelector[T any](strategy selector.Strategy[T], filters ...selector.Filter[T]) selector.Selector[T] {
return &defaultSelector[T]{
filters: filters,
strategy: strategy,
}
}
func (s *defaultSelector[T]) Select(ctx context.Context, vs ...T) (v T) {
for _, filter := range s.filters {
vs = filter.Filter(ctx, vs...)
}
if len(vs) == 0 {
return
}
return s.strategy.Apply(ctx, vs...)
}
+115
View File
@@ -0,0 +1,115 @@
package selector
import (
"context"
"hash/crc32"
"math/rand"
"sync"
"sync/atomic"
"time"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
"github.com/go-gost/core/selector"
ctxvalue "github.com/go-gost/x/ctx"
)
type roundRobinStrategy[T any] struct {
counter uint64
}
// RoundRobinStrategy is a strategy for node selector.
// The node will be selected by round-robin algorithm.
func RoundRobinStrategy[T any]() selector.Strategy[T] {
return &roundRobinStrategy[T]{}
}
func (s *roundRobinStrategy[T]) Apply(ctx context.Context, vs ...T) (v T) {
if len(vs) == 0 {
return
}
n := atomic.AddUint64(&s.counter, 1) - 1
return vs[int(n%uint64(len(vs)))]
}
type randomStrategy[T any] struct {
rw *RandomWeighted[T]
mu sync.Mutex
}
// RandomStrategy is a strategy for node selector.
// The node will be selected randomly.
func RandomStrategy[T any]() selector.Strategy[T] {
return &randomStrategy[T]{
rw: NewRandomWeighted[T](),
}
}
func (s *randomStrategy[T]) Apply(ctx context.Context, vs ...T) (v T) {
if len(vs) == 0 {
return
}
s.mu.Lock()
defer s.mu.Unlock()
s.rw.Reset()
for i := range vs {
weight := 0
if md, _ := any(vs[i]).(metadata.Metadatable); md != nil {
weight = mdutil.GetInt(md.Metadata(), labelWeight)
}
if weight <= 0 {
weight = 1
}
s.rw.Add(vs[i], weight)
}
return s.rw.Next()
}
type fifoStrategy[T any] struct{}
// FIFOStrategy is a strategy for node selector.
// The node will be selected from first to last,
// and will stick to the selected node until it is failed.
func FIFOStrategy[T any]() selector.Strategy[T] {
return &fifoStrategy[T]{}
}
// Apply applies the fifo strategy for the nodes.
func (s *fifoStrategy[T]) Apply(ctx context.Context, vs ...T) (v T) {
if len(vs) == 0 {
return
}
return vs[0]
}
type hashStrategy[T any] struct {
r *rand.Rand
mu sync.Mutex
}
func HashStrategy[T any]() selector.Strategy[T] {
return &hashStrategy[T]{
r: rand.New(rand.NewSource(time.Now().UnixNano())),
}
}
func (s *hashStrategy[T]) Apply(ctx context.Context, vs ...T) (v T) {
if len(vs) == 0 {
return
}
if h := ctxvalue.HashFromContext(ctx); h != nil {
value := uint64(crc32.ChecksumIEEE([]byte(h.Source)))
logger.Default().Tracef("hash %s %d", h.Source, value)
return vs[value%uint64(len(vs))]
}
s.mu.Lock()
defer s.mu.Unlock()
return vs[s.r.Intn(len(vs))]
}
+52
View File
@@ -0,0 +1,52 @@
package selector
import (
"math/rand"
"time"
)
type randomWeightedItem[T any] struct {
item T
weight int
}
type RandomWeighted[T any] struct {
items []*randomWeightedItem[T]
sum int
r *rand.Rand
}
func NewRandomWeighted[T any]() *RandomWeighted[T] {
return &RandomWeighted[T]{
r: rand.New(rand.NewSource(time.Now().UnixNano())),
}
}
func (rw *RandomWeighted[T]) Add(item T, weight int) {
ri := &randomWeightedItem[T]{item: item, weight: weight}
rw.items = append(rw.items, ri)
rw.sum += weight
}
func (rw *RandomWeighted[T]) Next() (v T) {
if len(rw.items) == 0 {
return
}
if rw.sum <= 0 {
return
}
weight := rw.r.Intn(rw.sum) + 1
for _, item := range rw.items {
weight -= item.weight
if weight <= 0 {
return item.item
}
}
return rw.items[len(rw.items)-1].item
}
func (rw *RandomWeighted[T]) Reset() {
rw.items = nil
rw.sum = 0
}