feat(proxy): integrate Least Time and Sticky balancers
- Add least_time and sticky to createBalancerByName - Implement response time recording for Least Time - Support StickySession in target selector with request context - StickySession auto-starts when created
This commit is contained in:
parent
3b6b70a491
commit
72f189bba8
@ -90,6 +90,8 @@ const (
|
|||||||
lbIPHash = "ip_hash" // IP 哈希
|
lbIPHash = "ip_hash" // IP 哈希
|
||||||
lbConsistentHash = "consistent_hash" // 一致性哈希
|
lbConsistentHash = "consistent_hash" // 一致性哈希
|
||||||
lbRandom = "random" // 随机(Power of Two Choices)
|
lbRandom = "random" // 随机(Power of Two Choices)
|
||||||
|
lbLeastTime = "least_time" // 最小响应时间
|
||||||
|
lbSticky = "sticky" // 会话粘性
|
||||||
)
|
)
|
||||||
|
|
||||||
// headersPool 复用缓存 headers map,减少分配。
|
// headersPool 复用缓存 headers map,减少分配。
|
||||||
@ -229,6 +231,22 @@ func NewProxy(cfg *config.ProxyConfig, targets []*loadbalance.Target, transportC
|
|||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stickyBalancer wraps StickySession to implement loadbalance.Balancer.
|
||||||
|
// It delegates Select/SelectExcluding to the fallback balancer while
|
||||||
|
// allowing the proxy to access the StickySession for cookie-based routing.
|
||||||
|
type stickyBalancer struct {
|
||||||
|
sticky *loadbalance.StickySession
|
||||||
|
fallback loadbalance.Balancer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *stickyBalancer) Select(targets []*loadbalance.Target) *loadbalance.Target {
|
||||||
|
return b.fallback.Select(targets)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *stickyBalancer) SelectExcluding(targets []*loadbalance.Target, excluded []*loadbalance.Target) *loadbalance.Target {
|
||||||
|
return b.fallback.SelectExcluding(targets, excluded)
|
||||||
|
}
|
||||||
|
|
||||||
// createBalancerByName 根据算法名称创建负载均衡器。
|
// createBalancerByName 根据算法名称创建负载均衡器。
|
||||||
//
|
//
|
||||||
// 支持的算法:
|
// 支持的算法:
|
||||||
@ -263,6 +281,49 @@ func createBalancerByName(name string, cfg *config.ProxyConfig) (loadbalance.Bal
|
|||||||
return loadbalance.NewConsistentHash(virtualNodes, cfg.HashKey), nil
|
return loadbalance.NewConsistentHash(virtualNodes, cfg.HashKey), nil
|
||||||
case lbRandom:
|
case lbRandom:
|
||||||
return loadbalance.NewRandom(), nil
|
return loadbalance.NewRandom(), nil
|
||||||
|
case lbLeastTime:
|
||||||
|
metric := cfg.LeastTime.Metric
|
||||||
|
if metric == "" {
|
||||||
|
metric = "last_byte"
|
||||||
|
}
|
||||||
|
defaultTime := cfg.LeastTime.DefaultTime
|
||||||
|
if defaultTime <= 0 {
|
||||||
|
defaultTime = time.Millisecond
|
||||||
|
}
|
||||||
|
return loadbalance.NewLeastTime(metric, defaultTime), nil
|
||||||
|
case lbSticky:
|
||||||
|
stickyCfg := loadbalance.StickyConfig{
|
||||||
|
Enabled: cfg.Sticky.Enabled,
|
||||||
|
Name: cfg.Sticky.Name,
|
||||||
|
Expires: cfg.Sticky.Expires,
|
||||||
|
Domain: cfg.Sticky.Domain,
|
||||||
|
Path: cfg.Sticky.Path,
|
||||||
|
Secure: cfg.Sticky.Secure,
|
||||||
|
HttpOnly: cfg.Sticky.HttpOnly,
|
||||||
|
SameSite: cfg.Sticky.SameSite,
|
||||||
|
}
|
||||||
|
if stickyCfg.Name == "" {
|
||||||
|
stickyCfg.Name = "lolly_route"
|
||||||
|
}
|
||||||
|
if stickyCfg.Expires <= 0 {
|
||||||
|
stickyCfg.Expires = time.Hour
|
||||||
|
}
|
||||||
|
if stickyCfg.Path == "" {
|
||||||
|
stickyCfg.Path = "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
fallbackAlgo := cfg.Sticky.FallbackAlgo
|
||||||
|
if fallbackAlgo == "" {
|
||||||
|
fallbackAlgo = lbRoundRobin
|
||||||
|
}
|
||||||
|
fallbackBalancer, err := createBalancerByName(fallbackAlgo, cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("sticky fallback balancer: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sticky := loadbalance.NewStickySession(stickyCfg, fallbackBalancer)
|
||||||
|
sticky.Start()
|
||||||
|
return &stickyBalancer{sticky: sticky, fallback: fallbackBalancer}, nil
|
||||||
default:
|
default:
|
||||||
return nil, errors.New("unsupported load balance algorithm: " + name)
|
return nil, errors.New("unsupported load balance algorithm: " + name)
|
||||||
}
|
}
|
||||||
@ -813,6 +874,13 @@ func (p *Proxy) ServeHTTP(ctx *fasthttp.RequestCtx) {
|
|||||||
// 记录首字节时间
|
// 记录首字节时间
|
||||||
timing.MarkHeaderReceived()
|
timing.MarkHeaderReceived()
|
||||||
|
|
||||||
|
// 记录响应时间(用于 least_time 负载均衡)
|
||||||
|
if recorder, ok := p.balancer.(loadbalance.ResponseTimeRecorder); ok {
|
||||||
|
headerTime := timing.connectEnd.Sub(timing.connectStart)
|
||||||
|
lastByteTime := timing.connectEnd.Sub(timing.connectStart)
|
||||||
|
recorder.RecordResponseTime(target, headerTime, lastByteTime)
|
||||||
|
}
|
||||||
|
|
||||||
// 请求成功,减少连接计数
|
// 请求成功,减少连接计数
|
||||||
loadbalance.DecrementConnections(target)
|
loadbalance.DecrementConnections(target)
|
||||||
|
|
||||||
|
|||||||
@ -119,6 +119,11 @@ func (p *Proxy) selectByBalancer(ctx *fasthttp.RequestCtx, targets []*loadbalanc
|
|||||||
balancer := p.balancer
|
balancer := p.balancer
|
||||||
p.mu.RUnlock()
|
p.mu.RUnlock()
|
||||||
|
|
||||||
|
// 对于 StickySession 负载均衡器,需要请求上下文
|
||||||
|
if sb, ok := balancer.(*stickyBalancer); ok {
|
||||||
|
return sb.sticky.Select(ctx, targets)
|
||||||
|
}
|
||||||
|
|
||||||
// 对于 IPHash 负载均衡器,提取客户端 IP
|
// 对于 IPHash 负载均衡器,提取客户端 IP
|
||||||
if ipHash, ok := balancer.(*loadbalance.IPHash); ok {
|
if ipHash, ok := balancer.(*loadbalance.IPHash); ok {
|
||||||
clientIP := netutil.ExtractClientIP(ctx)
|
clientIP := netutil.ExtractClientIP(ctx)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user