This commit is contained in:
unknown
2026-02-04 20:27:13 +08:00
commit 3b042d1dad
9410 changed files with 1488147 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
package schedulingconfigs
// CandidateInterface 候选对象接口
type CandidateInterface interface {
// CandidateWeight 权重
CandidateWeight() uint
// CandidateCodes 代号
CandidateCodes() []string
}

View File

@@ -0,0 +1,39 @@
package schedulingconfigs
import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/maps"
)
// SchedulingInterface 调度算法接口
type SchedulingInterface interface {
// HasCandidates 是否有候选对象
HasCandidates() bool
// Add 添加候选对象
Add(candidate ...CandidateInterface)
// Start 启动
Start()
// Next 查找下一个候选对象
Next(call *shared.RequestCall) CandidateInterface
// Summary 获取简要信息
Summary() maps.Map
}
// Scheduling 调度算法基础类
type Scheduling struct {
Candidates []CandidateInterface
}
// HasCandidates 判断是否有候选对象
func (this *Scheduling) HasCandidates() bool {
return len(this.Candidates) > 0
}
// Add 添加候选对象
func (this *Scheduling) Add(candidate ...CandidateInterface) {
this.Candidates = append(this.Candidates, candidate...)
}

View File

@@ -0,0 +1,50 @@
package schedulingconfigs
import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/rands"
"hash/crc32"
)
// HashScheduling Hash调度算法
type HashScheduling struct {
Scheduling
count int
}
// Start 启动
func (this *HashScheduling) Start() {
this.count = len(this.Candidates)
}
// Next 获取下一个候选对象
func (this *HashScheduling) Next(call *shared.RequestCall) CandidateInterface {
if this.count == 0 {
return nil
}
if call == nil || call.Options == nil {
return this.Candidates[rands.Int(0, this.count-1)]
}
key := call.Options.GetString("key")
if call.Formatter != nil {
key = call.Formatter(key)
}
sum := crc32.ChecksumIEEE([]byte(key))
return this.Candidates[sum%uint32(this.count)]
}
// Summary 获取简要信息
func (this *HashScheduling) Summary() maps.Map {
return maps.Map{
"code": "hash",
"name": "Hash算法",
"description": "根据自定义的键值的Hash值分配源站",
"networks": []string{"http"},
}
}

View File

@@ -0,0 +1,45 @@
package schedulingconfigs
import (
"fmt"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"math/rand"
"testing"
"time"
)
func TestHashScheduling_Next(t *testing.T) {
s := &HashScheduling{}
s.Add(&TestCandidate{
Name: "a",
Weight: 10,
})
s.Add(&TestCandidate{
Name: "b",
Weight: 10,
})
s.Add(&TestCandidate{
Name: "c",
Weight: 10,
})
s.Add(&TestCandidate{
Name: "d",
Weight: 30,
})
s.Start()
hits := map[string]uint{}
for _, c := range s.Candidates {
hits[c.(*TestCandidate).Name] = 0
}
rand.Seed(time.Now().UnixNano())
for i := 0; i < 1000000; i++ {
call := shared.NewRequestCall()
call.Options["key"] = "192.168.1." + fmt.Sprintf("%d", rand.Int())
c := s.Next(call)
hits[c.(*TestCandidate).Name]++
}
t.Log(hits)
}

View File

@@ -0,0 +1,78 @@
package schedulingconfigs
import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/maps"
"math"
"math/rand"
"time"
)
// RandomScheduling 随机调度算法
type RandomScheduling struct {
Scheduling
array []CandidateInterface
count uint // 实际总的服务器数
}
// Start 启动
func (this *RandomScheduling) Start() {
sumWeight := uint(0)
for _, c := range this.Candidates {
weight := c.CandidateWeight()
if weight == 0 {
weight = 1
} else if weight > 10000 {
weight = 10000
}
sumWeight += weight
}
if sumWeight == 0 {
return
}
for _, c := range this.Candidates {
weight := c.CandidateWeight()
if weight == 0 {
weight = 1
} else if weight > 10000 {
weight = 10000
}
count := uint(0)
if sumWeight <= 1000 {
count = weight
} else {
count = uint(math.Round(float64(weight*10000) / float64(sumWeight))) // 1% 产生 100个数据最多支持10000个服务器
}
for i := uint(0); i < count; i++ {
this.array = append(this.array, c)
}
this.count += count
}
rand.Seed(time.Now().UnixNano())
}
// Next 获取下一个候选对象
func (this *RandomScheduling) Next(call *shared.RequestCall) CandidateInterface {
if this.count == 0 {
return nil
}
if this.count == 1 {
return this.array[0]
}
index := rand.Int() % int(this.count)
return this.array[index]
}
// Summary 获取简要信息
func (this *RandomScheduling) Summary() maps.Map {
return maps.Map{
"code": "random",
"name": "Random随机算法",
"description": "根据权重设置随机分配源站",
"networks": []string{"http", "tcp", "udp", "unix"},
}
}

View File

@@ -0,0 +1,79 @@
package schedulingconfigs
import (
"sync"
"testing"
)
type TestCandidate struct {
Name string
Weight uint
}
func (this *TestCandidate) CandidateWeight() uint {
return this.Weight
}
func (this *TestCandidate) CandidateCodes() []string {
return []string{this.Name}
}
func TestRandomScheduling_Next(t *testing.T) {
s := &RandomScheduling{}
s.Add(&TestCandidate{
Name: "a",
Weight: 10,
})
s.Add(&TestCandidate{
Name: "b",
Weight: 10,
})
s.Add(&TestCandidate{
Name: "c",
Weight: 10,
})
s.Add(&TestCandidate{
Name: "d",
Weight: 30,
})
s.Start()
/**for _, c := range s.array {
t.Log(c.(*TestCandidate).Name, ":", c.CandidateWeight())
}**/
hits := map[string]uint{}
for _, c := range s.array {
hits[c.(*TestCandidate).Name] = 0
}
t.Log("count:", s.count, "array length:", len(s.array))
var locker sync.Mutex
var wg = sync.WaitGroup{}
wg.Add(100 * 10000)
for i := 0; i < 100*10000; i++ {
go func() {
defer wg.Done()
c := s.Next(nil)
locker.Lock()
defer locker.Unlock()
hits[c.(*TestCandidate).Name]++
}()
}
wg.Wait()
t.Log(hits)
}
func TestRandomScheduling_NextZero(t *testing.T) {
s := &RandomScheduling{}
s.Add(&TestCandidate{
Name: "a",
Weight: 0,
})
s.Start()
t.Log(s.Next(nil))
}

View File

@@ -0,0 +1,79 @@
package schedulingconfigs
import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
"sync"
)
// RoundRobinScheduling 轮询调度算法
type RoundRobinScheduling struct {
Scheduling
rawWeights []uint
currentWeights []uint
count uint
index uint
locker sync.Mutex
}
// Start 启动
func (this *RoundRobinScheduling) Start() {
lists.Sort(this.Candidates, func(i int, j int) bool {
c1 := this.Candidates[i]
c2 := this.Candidates[j]
return c1.CandidateWeight() > c2.CandidateWeight()
})
for _, c := range this.Candidates {
weight := c.CandidateWeight()
if weight == 0 {
weight = 1
} else if weight > 10000 {
weight = 10000
}
this.rawWeights = append(this.rawWeights, weight)
}
this.currentWeights = append([]uint{}, this.rawWeights...)
this.count = uint(len(this.Candidates))
}
// Next 获取下一个候选对象
func (this *RoundRobinScheduling) Next(call *shared.RequestCall) CandidateInterface {
if this.count == 0 {
return nil
}
this.locker.Lock()
defer this.locker.Unlock()
if this.index > this.count-1 {
this.index = 0
}
var weight = this.currentWeights[this.index]
// 已经一轮了,则重置状态
if weight == 0 {
if this.currentWeights[0] == 0 {
this.currentWeights = append([]uint{}, this.rawWeights...)
}
this.index = 0
}
c := this.Candidates[this.index]
this.currentWeights[this.index]--
this.index++
return c
}
// Summary 获取简要信息
func (this *RoundRobinScheduling) Summary() maps.Map {
return maps.Map{
"code": "roundRobin",
"name": "RoundRobin轮询算法",
"description": "根据权重,依次分配源站",
"networks": []string{"http", "tcp", "udp", "unix"},
}
}

View File

@@ -0,0 +1,101 @@
package schedulingconfigs
import "testing"
func TestRoundRobinScheduling_Next(t *testing.T) {
s := &RoundRobinScheduling{}
s.Add(&TestCandidate{
Name: "a",
Weight: 5,
})
s.Add(&TestCandidate{
Name: "b",
Weight: 10,
})
s.Add(&TestCandidate{
Name: "c",
Weight: 20,
})
s.Add(&TestCandidate{
Name: "d",
Weight: 30,
})
s.Start()
for _, c := range s.Candidates {
t.Log(c.(*TestCandidate).Name, c.CandidateWeight())
}
t.Log(s.currentWeights)
for i := 0; i < 100; i++ {
t.Log("===", "round", i, "===")
t.Log(s.Next(nil))
t.Log(s.currentWeights)
t.Log(s.rawWeights)
}
}
func TestRoundRobinScheduling_Two(t *testing.T) {
s := &RoundRobinScheduling{}
s.Add(&TestCandidate{
Name: "a",
Weight: 10,
})
s.Add(&TestCandidate{
Name: "b",
Weight: 10,
})
s.Start()
for _, c := range s.Candidates {
t.Log(c.(*TestCandidate).Name, c.CandidateWeight())
}
t.Log(s.currentWeights)
for i := 0; i < 100; i++ {
t.Log("===", "round", i, "===")
t.Log(s.Next(nil))
t.Log(s.currentWeights)
t.Log(s.rawWeights)
}
}
func TestRoundRobinScheduling_NextPerformance(t *testing.T) {
s := &RoundRobinScheduling{}
s.Add(&TestCandidate{
Name: "a",
Weight: 1,
})
s.Add(&TestCandidate{
Name: "b",
Weight: 2,
})
s.Add(&TestCandidate{
Name: "c",
Weight: 3,
})
s.Add(&TestCandidate{
Name: "d",
Weight: 6,
})
s.Start()
for _, c := range s.Candidates {
t.Log(c.(*TestCandidate).Name, c.CandidateWeight())
}
t.Log(s.currentWeights)
hits := map[string]uint{}
for _, c := range s.Candidates {
hits[c.(*TestCandidate).Name] = 0
}
for i := 0; i < 100*10000; i++ {
c := s.Next(nil)
hits[c.(*TestCandidate).Name]++
}
t.Log(hits)
}

View File

@@ -0,0 +1,112 @@
package schedulingconfigs
import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/rands"
"math/rand"
"net/http"
"time"
)
// StickyScheduling Sticky调度算法
type StickyScheduling struct {
Scheduling
count int
mapping map[string]CandidateInterface // code => candidate
}
// Start 启动
func (this *StickyScheduling) Start() {
this.mapping = map[string]CandidateInterface{}
for _, c := range this.Candidates {
for _, code := range c.CandidateCodes() {
this.mapping[code] = c
}
}
this.count = len(this.Candidates)
rand.Seed(time.Now().UnixNano())
}
// Next 获取下一个候选对象
func (this *StickyScheduling) Next(call *shared.RequestCall) CandidateInterface {
if this.count == 0 {
return nil
}
if call == nil || call.Options == nil {
return this.Candidates[rands.Int(0, this.count-1)]
}
typeCode := call.Options.GetString("type")
param := call.Options.GetString("param")
if call.Request == nil {
return this.Candidates[rand.Int()%this.count]
}
code := ""
if typeCode == "cookie" {
cookie, err := call.Request.Cookie(param)
if err == nil {
code = cookie.Value
}
} else if typeCode == "header" {
code = call.Request.Header.Get(param)
} else if typeCode == "argument" {
code = call.Request.URL.Query().Get(param)
}
matched := false
var c CandidateInterface = nil
defer func() {
if !matched && c != nil {
codes := c.CandidateCodes()
if len(codes) == 0 {
return
}
if typeCode == "cookie" {
call.AddResponseCall(func(resp http.ResponseWriter) {
http.SetCookie(resp, &http.Cookie{
Name: param,
Value: codes[0],
Path: "/",
Expires: time.Now().AddDate(0, 1, 0),
})
})
} else {
call.AddResponseCall(func(resp http.ResponseWriter) {
resp.Header().Set(param, codes[0])
})
}
}
}()
if len(code) == 0 {
c = this.Candidates[rand.Int()%this.count]
return c
}
found := false
c, found = this.mapping[code]
if !found {
c = this.Candidates[rand.Int()%this.count]
return c
}
matched = true
return c
}
// Summary 获取简要信息
func (this *StickyScheduling) Summary() maps.Map {
return maps.Map{
"code": "sticky",
"name": "Sticky算法",
"description": "利用Cookie、URL参数或者HTTP Header来指定源站",
"networks": []string{"http"},
}
}

View File

@@ -0,0 +1,128 @@
package schedulingconfigs
import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/maps"
"net/http"
"testing"
)
func TestStickyScheduling_NextArgument(t *testing.T) {
s := &StickyScheduling{}
s.Add(&TestCandidate{
Name: "a",
Weight: 1,
})
s.Add(&TestCandidate{
Name: "b",
Weight: 2,
})
s.Add(&TestCandidate{
Name: "c",
Weight: 3,
})
s.Add(&TestCandidate{
Name: "d",
Weight: 6,
})
s.Start()
t.Log(s.mapping)
req, err := http.NewRequest(http.MethodGet, "http://www.example.com/?origin=c", nil)
if err != nil {
t.Fatal(err)
}
options := maps.Map{
"type": "argument",
"param": "origin",
}
call := shared.NewRequestCall()
call.Request = req
call.Options = options
t.Log(s.Next(call))
t.Log(options)
}
func TestStickyScheduling_NextCookie(t *testing.T) {
s := &StickyScheduling{}
s.Add(&TestCandidate{
Name: "a",
Weight: 1,
})
s.Add(&TestCandidate{
Name: "b",
Weight: 2,
})
s.Add(&TestCandidate{
Name: "c",
Weight: 3,
})
s.Add(&TestCandidate{
Name: "d",
Weight: 6,
})
s.Start()
t.Log(s.mapping)
req, err := http.NewRequest(http.MethodGet, "http://www.example.com/?origin=c", nil)
if err != nil {
t.Fatal(err)
}
req.AddCookie(&http.Cookie{
Name: "origin",
Value: "c",
})
options := maps.Map{
"type": "cookie",
"param": "origin",
}
call := shared.NewRequestCall()
call.Request = req
call.Options = options
t.Log(s.Next(call))
t.Log(options)
}
func TestStickyScheduling_NextHeader(t *testing.T) {
s := &StickyScheduling{}
s.Add(&TestCandidate{
Name: "a",
Weight: 1,
})
s.Add(&TestCandidate{
Name: "b",
Weight: 2,
})
s.Add(&TestCandidate{
Name: "c",
Weight: 3,
})
s.Add(&TestCandidate{
Name: "d",
Weight: 6,
})
s.Start()
t.Log(s.mapping)
req, err := http.NewRequest(http.MethodGet, "http://www.example.com/?origin=c", nil)
if err != nil {
t.Fatal(err)
}
req.Header.Set("origin", "c")
options := maps.Map{
"type": "header",
"param": "origin",
}
call := shared.NewRequestCall()
call.Request = req
call.Options = options
t.Log(s.Next(call))
t.Log(options)
}

View File

@@ -0,0 +1,28 @@
package schedulingconfigs
import "github.com/iwind/TeaGo/maps"
// AllSchedulingTypes 所有请求类型
func AllSchedulingTypes() []maps.Map {
types := []maps.Map{}
for _, s := range []SchedulingInterface{
new(RandomScheduling),
new(RoundRobinScheduling),
new(HashScheduling),
new(StickyScheduling),
} {
summary := s.Summary()
summary["instance"] = s
types = append(types, summary)
}
return types
}
func FindSchedulingType(code string) maps.Map {
for _, summary := range AllSchedulingTypes() {
if summary["code"] == code {
return summary
}
}
return nil
}

View File

@@ -0,0 +1,11 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package schedulingconfigs
import "testing"
func TestFindSchedulingType(t *testing.T) {
t.Logf("%p", FindSchedulingType("roundRobin"))
t.Logf("%p", FindSchedulingType("roundRobin"))
t.Logf("%p", FindSchedulingType("roundRobin"))
}