反向代理源站实现使用域名分组

This commit is contained in:
GoEdgeLab
2021-09-20 11:54:21 +08:00
parent a4d8c3a27f
commit 466d5e78fa
18 changed files with 431 additions and 186 deletions

View File

@@ -28,6 +28,8 @@ type OriginConfig struct {
MaxConns int `yaml:"maxConns" json:"maxConns"` // 最大并发连接数
MaxIdleConns int `yaml:"idleConns" json:"idleConns"` // 最大空闲连接数
Domains []string `yaml:"domains" json:"domains"` // 所属域名
StripPrefix string `yaml:"stripPrefix" json:"stripPrefix"` // 去除URL前缀
RequestURI string `yaml:"requestURI" json:"requestURI"` // 转发后的请求URI TODO
RequestHost string `yaml:"requestHost" json:"requestHost"` // 自定义主机名 TODO

View File

@@ -2,7 +2,6 @@ package serverconfigs
import (
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/schedulingconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/lists"
"sync"
@@ -45,11 +44,8 @@ type ReverseProxyConfig struct {
requestHostHasVariables bool
requestURIHasVariables bool
hasPrimaryOrigins bool
hasBackupOrigins bool
schedulingIsBackup bool
schedulingObject schedulingconfigs.SchedulingInterface
schedulingLocker sync.Mutex
schedulingGroupMap map[string]*SchedulingGroup // domain => *SchedulingGroup
schedulingLocker sync.RWMutex
addXRealIPHeader bool
addXForwardedForHeader bool
@@ -64,9 +60,68 @@ func (this *ReverseProxyConfig) Init() error {
this.requestHostHasVariables = configutils.HasVariables(this.RequestHost)
this.requestURIHasVariables = configutils.HasVariables(this.RequestURI)
this.hasPrimaryOrigins = len(this.PrimaryOrigins) > 0
this.hasBackupOrigins = len(this.BackupOrigins) > 0
// 将源站分组
this.schedulingGroupMap = map[string]*SchedulingGroup{}
for _, origin := range this.PrimaryOrigins {
if len(origin.Domains) == 0 {
group, ok := this.schedulingGroupMap[""]
if !ok {
group = &SchedulingGroup{}
if this.Scheduling != nil {
group.Scheduling = this.Scheduling.Clone()
}
this.schedulingGroupMap[""] = group
}
group.PrimaryOrigins = append(group.PrimaryOrigins, origin)
} else {
for _, domain := range origin.Domains {
group, ok := this.schedulingGroupMap[domain]
if !ok {
group = &SchedulingGroup{}
if this.Scheduling != nil {
group.Scheduling = this.Scheduling.Clone()
}
this.schedulingGroupMap[domain] = group
}
group.PrimaryOrigins = append(group.PrimaryOrigins, origin)
}
}
}
for _, origin := range this.BackupOrigins {
if len(origin.Domains) == 0 {
group, ok := this.schedulingGroupMap[""]
if !ok {
group = &SchedulingGroup{}
if this.Scheduling != nil {
group.Scheduling = this.Scheduling.Clone()
}
this.schedulingGroupMap[""] = group
}
group.BackupOrigins = append(group.BackupOrigins, origin)
} else {
for _, domain := range origin.Domains {
group, ok := this.schedulingGroupMap[domain]
if !ok {
group = &SchedulingGroup{}
if this.Scheduling != nil {
group.Scheduling = this.Scheduling.Clone()
}
this.schedulingGroupMap[domain] = group
}
group.BackupOrigins = append(group.BackupOrigins, origin)
}
}
}
// 初始化分组
for _, group := range this.schedulingGroupMap {
err := group.Init()
if err != nil {
return err
}
}
// 初始化Origin
for _, origins := range [][]*OriginConfig{this.PrimaryOrigins, this.BackupOrigins} {
for _, origin := range origins {
// 覆盖参数设置
@@ -131,54 +186,39 @@ func (this *ReverseProxyConfig) AddBackupOrigin(origin *OriginConfig) {
// NextOrigin 取得下一个可用的后端服务
func (this *ReverseProxyConfig) NextOrigin(call *shared.RequestCall) *OriginConfig {
this.schedulingLocker.Lock()
defer this.schedulingLocker.Unlock()
this.schedulingLocker.RLock()
defer this.schedulingLocker.RUnlock()
if this.schedulingObject == nil {
if len(this.schedulingGroupMap) == 0 {
return nil
}
if this.Scheduling != nil && call != nil && call.Options != nil {
for k, v := range this.Scheduling.Options {
call.Options[k] = v
// 空域名
if len(call.Domain) == 0 {
group, ok := this.schedulingGroupMap[""]
if ok {
return group.NextOrigin(call)
}
return nil
}
candidate := this.schedulingObject.Next(call)
// 末了重置状态
defer func() {
if candidate == nil {
this.schedulingIsBackup = false
}
}()
if candidate == nil {
// 启用备用服务器
if !this.schedulingIsBackup {
this.SetupScheduling(true, true, false)
candidate = this.schedulingObject.Next(call)
if candidate == nil {
// 不检查主要源站
this.SetupScheduling(false, false, false)
candidate = this.schedulingObject.Next(call)
if candidate == nil {
// 不检查备用源站
this.SetupScheduling(true, false, false)
candidate = this.schedulingObject.Next(call)
if candidate == nil {
return nil
}
}
// 按域名匹配
for domainPattern, group := range this.schedulingGroupMap {
if len(domainPattern) > 0 && configutils.MatchDomain(domainPattern, call.Domain) {
origin := group.NextOrigin(call)
if origin != nil {
return origin
}
}
if candidate == nil {
return nil
}
}
return candidate.(*OriginConfig)
// 再次查找没有设置域名的分组
group, ok := this.schedulingGroupMap[""]
if ok {
return group.NextOrigin(call)
}
return nil
}
// SetupScheduling 设置调度算法
@@ -188,40 +228,9 @@ func (this *ReverseProxyConfig) SetupScheduling(isBackup bool, checkOk bool, loc
defer this.schedulingLocker.Unlock()
}
this.schedulingIsBackup = isBackup
if this.Scheduling == nil {
this.schedulingObject = &schedulingconfigs.RandomScheduling{}
} else {
typeCode := this.Scheduling.Code
s := schedulingconfigs.FindSchedulingType(typeCode)
if s == nil {
this.Scheduling = nil
this.schedulingObject = &schedulingconfigs.RandomScheduling{}
} else {
this.schedulingObject = s["instance"].(schedulingconfigs.SchedulingInterface)
}
for _, group := range this.schedulingGroupMap {
group.SetupScheduling(isBackup, checkOk)
}
if !isBackup {
for _, origin := range this.PrimaryOrigins {
if origin.IsOn && (origin.IsOk || !checkOk) {
this.schedulingObject.Add(origin)
}
}
} else {
for _, origin := range this.BackupOrigins {
if origin.IsOn && (origin.IsOk || !checkOk) {
this.schedulingObject.Add(origin)
}
}
}
if !this.schedulingObject.HasCandidates() {
return
}
this.schedulingObject.Start()
}
// FindSchedulingConfig 获取调度配置对象

View File

@@ -0,0 +1,60 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package serverconfigs
import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"testing"
)
func TestReverseProxyConfig_Init(t *testing.T) {
var config = &ReverseProxyConfig{}
config.Scheduling = &SchedulingConfig{
Code: "random",
Options: nil,
}
config.AddPrimaryOrigin(&OriginConfig{
Addr: &NetworkAddressConfig{Host: "127.0.0.1"},
IsOn: true,
})
config.AddPrimaryOrigin(&OriginConfig{
Addr: &NetworkAddressConfig{Host: "127.0.0.2"},
IsOn: true,
})
config.AddPrimaryOrigin(&OriginConfig{
Addr: &NetworkAddressConfig{Host: "127.0.0.3"},
Domains: []string{"*.www.example.com", ".example.com"},
IsOn: true,
})
config.AddBackupOrigin(&OriginConfig{
Addr: &NetworkAddressConfig{Host: "127.0.0.4"},
IsOn: true,
})
err := config.Init()
if err != nil {
t.Fatal(err)
}
for domain, group := range config.schedulingGroupMap {
for _, origin := range group.PrimaryOrigins {
t.Log(domain, "primary", origin.Addr.Host)
}
for _, origin := range group.BackupOrigins {
t.Log(domain, "backup", origin.Addr.Host)
}
}
//config.ResetScheduling()
nextOrigin := config.NextOrigin(&shared.RequestCall{
Formatter: nil,
Request: nil,
Domain: "a.example.com",
ResponseCallbacks: nil,
Options: nil,
})
if nextOrigin == nil {
t.Log("not found")
} else {
t.Log("result:", nextOrigin.Addr.Host)
}
}

View File

@@ -2,13 +2,21 @@ package serverconfigs
import "github.com/iwind/TeaGo/maps"
// 调度算法配置
// SchedulingConfig 调度算法配置
type SchedulingConfig struct {
Code string `yaml:"code" json:"code"` // 类型
Options maps.Map `yaml:"options" json:"options"` // 选项
}
// 获取新对象
// NewSchedulingConfig 获取新对象
func NewSchedulingConfig() *SchedulingConfig {
return &SchedulingConfig{}
}
// Clone 克隆
func (this *SchedulingConfig) Clone() *SchedulingConfig {
return &SchedulingConfig{
Code: this.Code,
Options: maps.NewMap(this.Options),
}
}

View File

@@ -0,0 +1,124 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package serverconfigs
import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/schedulingconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/maps"
)
// SchedulingGroup 负载均衡分组
type SchedulingGroup struct {
Scheduling *SchedulingConfig `yaml:"scheduling" json:"scheduling"`
PrimaryOrigins []*OriginConfig
BackupOrigins []*OriginConfig
hasPrimaryOrigins bool
hasBackupOrigins bool
schedulingIsBackup bool
schedulingObject schedulingconfigs.SchedulingInterface
}
// Init 初始化
func (this *SchedulingGroup) Init() error {
this.hasPrimaryOrigins = len(this.PrimaryOrigins) > 0
this.hasBackupOrigins = len(this.BackupOrigins) > 0
if this.Scheduling == nil {
this.Scheduling = &SchedulingConfig{
Code: "random",
Options: maps.Map{},
}
}
return nil
}
// NextOrigin 取得下一个可用的后端服务
func (this *SchedulingGroup) NextOrigin(call *shared.RequestCall) *OriginConfig {
if this.schedulingObject == nil {
return nil
}
if this.Scheduling != nil && call != nil && call.Options != nil {
for k, v := range this.Scheduling.Options {
call.Options[k] = v
}
}
candidate := this.schedulingObject.Next(call)
// 末了重置状态
defer func() {
if candidate == nil {
this.schedulingIsBackup = false
}
}()
if candidate == nil {
// 启用备用服务器
if !this.schedulingIsBackup {
this.SetupScheduling(true, true)
candidate = this.schedulingObject.Next(call)
if candidate == nil {
// 不检查主要源站
this.SetupScheduling(false, false)
candidate = this.schedulingObject.Next(call)
if candidate == nil {
// 不检查备用源站
this.SetupScheduling(true, false)
candidate = this.schedulingObject.Next(call)
if candidate == nil {
return nil
}
}
}
}
if candidate == nil {
return nil
}
}
return candidate.(*OriginConfig)
}
// SetupScheduling 设置调度算法
func (this *SchedulingGroup) SetupScheduling(isBackup bool, checkOk bool) {
this.schedulingIsBackup = isBackup
if this.Scheduling == nil {
this.schedulingObject = &schedulingconfigs.RandomScheduling{}
} else {
typeCode := this.Scheduling.Code
s := schedulingconfigs.FindSchedulingType(typeCode)
if s == nil {
this.Scheduling = nil
this.schedulingObject = &schedulingconfigs.RandomScheduling{}
} else {
this.schedulingObject = s["instance"].(schedulingconfigs.SchedulingInterface)
}
}
if !isBackup {
for _, origin := range this.PrimaryOrigins {
if origin.IsOn && (origin.IsOk || !checkOk) {
this.schedulingObject.Add(origin)
}
}
} else {
for _, origin := range this.BackupOrigins {
if origin.IsOn && (origin.IsOk || !checkOk) {
this.schedulingObject.Add(origin)
}
}
}
if !this.schedulingObject.HasCandidates() {
return
}
this.schedulingObject.Start()
}

View File

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

View File

@@ -5,35 +5,35 @@ import (
"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

@@ -6,19 +6,19 @@ import (
"hash/crc32"
)
// Hash调度算法
// HashScheduling Hash调度算法
type HashScheduling struct {
Scheduling
count uint32
}
// 启动
// Start 启动
func (this *HashScheduling) Start() {
this.count = uint32(len(this.Candidates))
}
// 获取下一个候选对象
// Next 获取下一个候选对象
func (this *HashScheduling) Next(call *shared.RequestCall) CandidateInterface {
if this.count == 0 {
return nil
@@ -34,7 +34,7 @@ func (this *HashScheduling) Next(call *shared.RequestCall) CandidateInterface {
return this.Candidates[sum%this.count]
}
// 获取简要信息
// Summary 获取简要信息
func (this *HashScheduling) Summary() maps.Map {
return maps.Map{
"code": "hash",

View File

@@ -8,7 +8,7 @@ import (
"time"
)
// 随机调度算法
// RandomScheduling 随机调度算法
type RandomScheduling struct {
Scheduling
@@ -16,7 +16,7 @@ type RandomScheduling struct {
count uint // 实际总的服务器数
}
// 启动
// Start 启动
func (this *RandomScheduling) Start() {
sumWeight := uint(0)
for _, c := range this.Candidates {
@@ -55,7 +55,7 @@ func (this *RandomScheduling) Start() {
rand.Seed(time.Now().UnixNano())
}
// 获取下一个候选对象
// Next 获取下一个候选对象
func (this *RandomScheduling) Next(call *shared.RequestCall) CandidateInterface {
if this.count == 0 {
return nil
@@ -67,7 +67,7 @@ func (this *RandomScheduling) Next(call *shared.RequestCall) CandidateInterface
return this.array[index]
}
// 获取简要信息
// Summary 获取简要信息
func (this *RandomScheduling) Summary() maps.Map {
return maps.Map{
"code": "random",

View File

@@ -7,7 +7,7 @@ import (
"sync"
)
// 轮询调度算法
// RoundRobinScheduling 轮询调度算法
type RoundRobinScheduling struct {
Scheduling
@@ -19,7 +19,7 @@ type RoundRobinScheduling struct {
locker sync.Mutex
}
// 启动
// Start 启动
func (this *RoundRobinScheduling) Start() {
lists.Sort(this.Candidates, func(i int, j int) bool {
c1 := this.Candidates[i]
@@ -41,7 +41,7 @@ func (this *RoundRobinScheduling) Start() {
this.count = uint(len(this.Candidates))
}
// 获取下一个候选对象
// Next 获取下一个候选对象
func (this *RoundRobinScheduling) Next(call *shared.RequestCall) CandidateInterface {
if this.count == 0 {
return nil
@@ -69,7 +69,7 @@ func (this *RoundRobinScheduling) Next(call *shared.RequestCall) CandidateInterf
return c
}
// 获取简要信息
// Summary 获取简要信息
func (this *RoundRobinScheduling) Summary() maps.Map {
return maps.Map{
"code": "roundRobin",

View File

@@ -8,7 +8,7 @@ import (
"time"
)
// Sticky调度算法
// StickyScheduling Sticky调度算法
type StickyScheduling struct {
Scheduling
@@ -16,7 +16,7 @@ type StickyScheduling struct {
mapping map[string]CandidateInterface // code => candidate
}
// 启动
// Start 启动
func (this *StickyScheduling) Start() {
this.mapping = map[string]CandidateInterface{}
for _, c := range this.Candidates {
@@ -29,7 +29,7 @@ func (this *StickyScheduling) Start() {
rand.Seed(time.Now().UnixNano())
}
// 获取下一个候选对象
// Next 获取下一个候选对象
func (this *StickyScheduling) Next(call *shared.RequestCall) CandidateInterface {
if this.count == 0 {
return nil
@@ -95,7 +95,7 @@ func (this *StickyScheduling) Next(call *shared.RequestCall) CandidateInterface
return c
}
// 获取简要信息
// Summary 获取简要信息
func (this *StickyScheduling) Summary() maps.Map {
return maps.Map{
"code": "sticky",

View File

@@ -5,22 +5,24 @@ import (
"net/http"
)
// 请求调用
// RequestCall 请求调用
type RequestCall struct {
Formatter func(source string) string
Request *http.Request
Formatter func(source string) string // 当前变量格式化函数
Request *http.Request // 当前请求
Domain string // 当前域名
ResponseCallbacks []func(resp http.ResponseWriter)
Options maps.Map
}
// 获取新对象
// NewRequestCall 获取新对象
func NewRequestCall() *RequestCall {
return &RequestCall{
Options: maps.Map{},
}
}
// 重置
// Reset 重置
func (this *RequestCall) Reset() {
this.Formatter = nil
this.Request = nil
@@ -28,12 +30,12 @@ func (this *RequestCall) Reset() {
this.Options = maps.Map{}
}
// 添加响应回调
// AddResponseCall 添加响应回调
func (this *RequestCall) AddResponseCall(callback func(resp http.ResponseWriter)) {
this.ResponseCallbacks = append(this.ResponseCallbacks, callback)
}
// 执行响应回调
// CallResponseCallbacks 执行响应回调
func (this *RequestCall) CallResponseCallbacks(resp http.ResponseWriter) {
for _, callback := range this.ResponseCallbacks {
callback(resp)