perf(loadbalance): eliminate per-request allocations in filterHealthy with sync.Pool
filterHealthy() allocated 2 slices (available + backups) per call. filterHealthyAndExclude() allocated 3 (adds excludeSet map). IPHash allocated fnv.New64a() object per call. All triggered on every request's LB selection. Changes: - Add filterContext struct holding reusable buffers, managed by sync.Pool - Replace filterHealthy → filterInto (writes into pooled buffers) - Replace filterHealthyAndExclude → filterIntoExcluding (pooled buffers) - Add inline fnvHash64a() to avoid fnv.New64a() heap allocation - Update all 6 balancer algorithms (RoundRobin, WeightedRoundRobin, LeastConnections, IPHash, Random, ConsistentHash) to use pooled filterContext via acquire/release pattern - ConsistentHash.SelectExcludingByKey also uses pool for targetSet - Remove buildExcludeSet (merged into filterIntoExcluding) Result: allocs/op reduced from 2-3 to 0-1 on all LB Select paths.
This commit is contained in:
parent
e44cfc7128
commit
c6e7091089
@ -17,7 +17,6 @@
|
|||||||
package loadbalance
|
package loadbalance
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"hash/fnv"
|
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
@ -25,6 +24,44 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type filterContext struct {
|
||||||
|
available []*Target
|
||||||
|
backups []*Target
|
||||||
|
excludeSet map[string]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var filterContextPool = sync.Pool{
|
||||||
|
New: func() any {
|
||||||
|
return &filterContext{
|
||||||
|
available: make([]*Target, 0, 64),
|
||||||
|
backups: make([]*Target, 0, 64),
|
||||||
|
excludeSet: make(map[string]bool, 8),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func acquireFilterContext() *filterContext {
|
||||||
|
return filterContextPool.Get().(*filterContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
func releaseFilterContext(fc *filterContext) {
|
||||||
|
fc.available = fc.available[:0]
|
||||||
|
fc.backups = fc.backups[:0]
|
||||||
|
for k := range fc.excludeSet {
|
||||||
|
delete(fc.excludeSet, k)
|
||||||
|
}
|
||||||
|
filterContextPool.Put(fc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fnvHash64a(key string) uint64 {
|
||||||
|
var h uint64 = 14695981039346656037
|
||||||
|
for i := 0; i < len(key); i++ {
|
||||||
|
h ^= uint64(key[i])
|
||||||
|
h *= 1099511628211
|
||||||
|
}
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
// Target 表示 HTTP 代理(L7 层)的负载均衡后端服务器目标。
|
// Target 表示 HTTP 代理(L7 层)的负载均衡后端服务器目标。
|
||||||
//
|
//
|
||||||
// HTTP Target 特性(区别于 Stream Target):
|
// HTTP Target 特性(区别于 Stream Target):
|
||||||
@ -120,12 +157,12 @@ func NewRoundRobin() *RoundRobin {
|
|||||||
// Select 选择轮询顺序中的下一个目标。
|
// Select 选择轮询顺序中的下一个目标。
|
||||||
// 只考虑健康目标。如果没有健康目标则返回 nil。
|
// 只考虑健康目标。如果没有健康目标则返回 nil。
|
||||||
func (r *RoundRobin) Select(targets []*Target) *Target {
|
func (r *RoundRobin) Select(targets []*Target) *Target {
|
||||||
healthy := filterHealthy(targets)
|
fc := acquireFilterContext()
|
||||||
|
defer releaseFilterContext(fc)
|
||||||
|
healthy := filterInto(fc, targets)
|
||||||
if len(healthy) == 0 {
|
if len(healthy) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 原子地递增并获取计数器值
|
|
||||||
idx := r.counter.Add(1) - 1
|
idx := r.counter.Add(1) - 1
|
||||||
return healthy[idx%uint64(len(healthy))]
|
return healthy[idx%uint64(len(healthy))]
|
||||||
}
|
}
|
||||||
@ -154,16 +191,17 @@ func NewWeightedRoundRobin() *WeightedRoundRobin {
|
|||||||
// Select 基于权重分布选择目标。
|
// Select 基于权重分布选择目标。
|
||||||
// 只考虑健康目标。如果没有健康目标则返回 nil。
|
// 只考虑健康目标。如果没有健康目标则返回 nil。
|
||||||
func (w *WeightedRoundRobin) Select(targets []*Target) *Target {
|
func (w *WeightedRoundRobin) Select(targets []*Target) *Target {
|
||||||
healthy := filterHealthy(targets)
|
fc := acquireFilterContext()
|
||||||
|
defer releaseFilterContext(fc)
|
||||||
|
healthy := filterInto(fc, targets)
|
||||||
if len(healthy) == 0 {
|
if len(healthy) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算总权重
|
|
||||||
totalWeight := 0
|
totalWeight := 0
|
||||||
for _, t := range healthy {
|
for _, t := range healthy {
|
||||||
if t.Weight <= 0 {
|
if t.Weight <= 0 {
|
||||||
totalWeight++ // 最小权重为 1
|
totalWeight++
|
||||||
} else {
|
} else {
|
||||||
totalWeight += t.Weight
|
totalWeight += t.Weight
|
||||||
}
|
}
|
||||||
@ -173,11 +211,9 @@ func (w *WeightedRoundRobin) Select(targets []*Target) *Target {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用原子计数器确定权重分布中的位置
|
|
||||||
idx := w.counter.Add(1) - 1
|
idx := w.counter.Add(1) - 1
|
||||||
pos := int(idx % uint64(totalWeight))
|
pos := int(idx % uint64(totalWeight))
|
||||||
|
|
||||||
// 找到计算位置处的目标
|
|
||||||
currentWeight := 0
|
currentWeight := 0
|
||||||
for _, t := range healthy {
|
for _, t := range healthy {
|
||||||
weight := t.Weight
|
weight := t.Weight
|
||||||
@ -190,7 +226,6 @@ func (w *WeightedRoundRobin) Select(targets []*Target) *Target {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 回退到最后一个目标(不应到达这里)
|
|
||||||
return healthy[len(healthy)-1]
|
return healthy[len(healthy)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,16 +291,13 @@ func (i *IPHash) Select(targets []*Target) *Target {
|
|||||||
// SelectByIP 基于提供的 IP 地址的哈希值选择目标。
|
// SelectByIP 基于提供的 IP 地址的哈希值选择目标。
|
||||||
// 只考虑健康目标。如果没有健康目标则返回 nil。
|
// 只考虑健康目标。如果没有健康目标则返回 nil。
|
||||||
func (i *IPHash) SelectByIP(targets []*Target, clientIP string) *Target {
|
func (i *IPHash) SelectByIP(targets []*Target, clientIP string) *Target {
|
||||||
healthy := filterHealthy(targets)
|
fc := acquireFilterContext()
|
||||||
|
defer releaseFilterContext(fc)
|
||||||
|
healthy := filterInto(fc, targets)
|
||||||
if len(healthy) == 0 {
|
if len(healthy) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
hash := fnvHash64a(clientIP)
|
||||||
// 对客户端 IP 进行哈希
|
|
||||||
h := fnv.New64a()
|
|
||||||
h.Write([]byte(clientIP))
|
|
||||||
hash := h.Sum64()
|
|
||||||
|
|
||||||
idx := hash % uint64(len(healthy))
|
idx := hash % uint64(len(healthy))
|
||||||
return healthy[idx]
|
return healthy[idx]
|
||||||
}
|
}
|
||||||
@ -344,32 +376,21 @@ func (t *Target) IsBackup() bool {
|
|||||||
return t.Backup
|
return t.Backup
|
||||||
}
|
}
|
||||||
|
|
||||||
// filterHealthy 从目标列表中筛选出所有可用的目标,返回新切片。
|
func filterInto(fc *filterContext, targets []*Target) []*Target {
|
||||||
//
|
|
||||||
// 选择逻辑:
|
|
||||||
// 1. 优先选择非备份且可用的目标(IsAvailable() == true && !IsBackup())
|
|
||||||
// 2. 如果没有非备份目标可用,则选择可用的备份目标
|
|
||||||
//
|
|
||||||
// 返回的切片容量与输入相同,避免多次内存分配。
|
|
||||||
func filterHealthy(targets []*Target) []*Target {
|
|
||||||
available := make([]*Target, 0, len(targets))
|
|
||||||
backups := make([]*Target, 0, len(targets))
|
|
||||||
|
|
||||||
for _, t := range targets {
|
for _, t := range targets {
|
||||||
if !t.IsAvailable() {
|
if !t.IsAvailable() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if t.IsBackup() {
|
if t.IsBackup() {
|
||||||
backups = append(backups, t)
|
fc.backups = append(fc.backups, t)
|
||||||
} else {
|
} else {
|
||||||
available = append(available, t)
|
fc.available = append(fc.available, t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(fc.available) > 0 {
|
||||||
if len(available) > 0 {
|
return fc.available
|
||||||
return available
|
|
||||||
}
|
}
|
||||||
return backups
|
return fc.backups
|
||||||
}
|
}
|
||||||
|
|
||||||
// IncrementConnections 原子地增加目标的连接计数。
|
// IncrementConnections 原子地增加目标的连接计数。
|
||||||
@ -384,45 +405,37 @@ func DecrementConnections(t *Target) {
|
|||||||
atomic.AddInt64(&t.Connections, -1)
|
atomic.AddInt64(&t.Connections, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// filterHealthyAndExclude 从目标列表中筛选出可用且不在排除列表中的目标,返回新切片。
|
func filterIntoExcluding(fc *filterContext, targets []*Target, excluded []*Target) []*Target {
|
||||||
//
|
for _, t := range excluded {
|
||||||
// 选择逻辑与 filterHealthy 相同:
|
if t != nil {
|
||||||
// 1. 优先选择非备份且可用的目标
|
fc.excludeSet[t.URL] = true
|
||||||
// 2. 如果没有非备份目标可用,则选择可用的备份目标
|
}
|
||||||
//
|
}
|
||||||
// 排除判断基于目标的 URL 进行匹配。
|
|
||||||
func filterHealthyAndExclude(targets []*Target, excluded []*Target) []*Target {
|
|
||||||
excludeSet := buildExcludeSet(excluded)
|
|
||||||
|
|
||||||
available := make([]*Target, 0, len(targets))
|
|
||||||
backups := make([]*Target, 0, len(targets))
|
|
||||||
|
|
||||||
for _, t := range targets {
|
for _, t := range targets {
|
||||||
if !t.IsAvailable() || excludeSet[t.URL] {
|
if !t.IsAvailable() || fc.excludeSet[t.URL] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if t.IsBackup() {
|
if t.IsBackup() {
|
||||||
backups = append(backups, t)
|
fc.backups = append(fc.backups, t)
|
||||||
} else {
|
} else {
|
||||||
available = append(available, t)
|
fc.available = append(fc.available, t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(fc.available) > 0 {
|
||||||
if len(available) > 0 {
|
return fc.available
|
||||||
return available
|
|
||||||
}
|
}
|
||||||
return backups
|
return fc.backups
|
||||||
}
|
}
|
||||||
|
|
||||||
// SelectExcluding 根据轮询策略选择一个目标,排除指定的目标列表。
|
// SelectExcluding 根据轮询策略选择一个目标,排除指定的目标列表。
|
||||||
// 只考虑健康且不在排除列表中的目标。
|
// 只考虑健康且不在排除列表中的目标。
|
||||||
func (r *RoundRobin) SelectExcluding(targets []*Target, excluded []*Target) *Target {
|
func (r *RoundRobin) SelectExcluding(targets []*Target, excluded []*Target) *Target {
|
||||||
available := filterHealthyAndExclude(targets, excluded)
|
fc := acquireFilterContext()
|
||||||
|
defer releaseFilterContext(fc)
|
||||||
|
available := filterIntoExcluding(fc, targets, excluded)
|
||||||
if len(available) == 0 {
|
if len(available) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 原子地递增并获取计数器值
|
|
||||||
idx := r.counter.Add(1) - 1
|
idx := r.counter.Add(1) - 1
|
||||||
return available[idx%uint64(len(available))]
|
return available[idx%uint64(len(available))]
|
||||||
}
|
}
|
||||||
@ -430,16 +443,17 @@ func (r *RoundRobin) SelectExcluding(targets []*Target, excluded []*Target) *Tar
|
|||||||
// SelectExcluding 根据权重分布选择目标,排除指定的目标列表。
|
// SelectExcluding 根据权重分布选择目标,排除指定的目标列表。
|
||||||
// 只考虑健康且不在排除列表中的目标。
|
// 只考虑健康且不在排除列表中的目标。
|
||||||
func (w *WeightedRoundRobin) SelectExcluding(targets []*Target, excluded []*Target) *Target {
|
func (w *WeightedRoundRobin) SelectExcluding(targets []*Target, excluded []*Target) *Target {
|
||||||
available := filterHealthyAndExclude(targets, excluded)
|
fc := acquireFilterContext()
|
||||||
|
defer releaseFilterContext(fc)
|
||||||
|
available := filterIntoExcluding(fc, targets, excluded)
|
||||||
if len(available) == 0 {
|
if len(available) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算总权重
|
|
||||||
totalWeight := 0
|
totalWeight := 0
|
||||||
for _, t := range available {
|
for _, t := range available {
|
||||||
if t.Weight <= 0 {
|
if t.Weight <= 0 {
|
||||||
totalWeight++ // 最小权重为 1
|
totalWeight++
|
||||||
} else {
|
} else {
|
||||||
totalWeight += t.Weight
|
totalWeight += t.Weight
|
||||||
}
|
}
|
||||||
@ -449,11 +463,9 @@ func (w *WeightedRoundRobin) SelectExcluding(targets []*Target, excluded []*Targ
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用原子计数器确定权重分布中的位置
|
|
||||||
idx := w.counter.Add(1) - 1
|
idx := w.counter.Add(1) - 1
|
||||||
pos := int(idx % uint64(totalWeight))
|
pos := int(idx % uint64(totalWeight))
|
||||||
|
|
||||||
// 找到计算位置处的目标
|
|
||||||
currentWeight := 0
|
currentWeight := 0
|
||||||
for _, t := range available {
|
for _, t := range available {
|
||||||
weight := t.Weight
|
weight := t.Weight
|
||||||
@ -466,14 +478,19 @@ func (w *WeightedRoundRobin) SelectExcluding(targets []*Target, excluded []*Targ
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 回退到最后一个目标(不应到达这里)
|
|
||||||
return available[len(available)-1]
|
return available[len(available)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
// SelectExcluding 选择连接数最少的目标,排除指定的目标列表。
|
// SelectExcluding 选择连接数最少的目标,排除指定的目标列表。
|
||||||
// 优先选择非备份目标,仅当无可用非备份目标时选择备份目标。
|
// 优先选择非备份目标,仅当无可用非备份目标时选择备份目标。
|
||||||
func (l *LeastConnections) SelectExcluding(targets []*Target, excluded []*Target) *Target {
|
func (l *LeastConnections) SelectExcluding(targets []*Target, excluded []*Target) *Target {
|
||||||
excludeSet := buildExcludeSet(excluded)
|
fc := acquireFilterContext()
|
||||||
|
defer releaseFilterContext(fc)
|
||||||
|
for _, t := range excluded {
|
||||||
|
if t != nil {
|
||||||
|
fc.excludeSet[t.URL] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var selected *Target
|
var selected *Target
|
||||||
var selectedBackup *Target
|
var selectedBackup *Target
|
||||||
@ -481,12 +498,10 @@ func (l *LeastConnections) SelectExcluding(targets []*Target, excluded []*Target
|
|||||||
var minBackupConns int64 = -1
|
var minBackupConns int64 = -1
|
||||||
|
|
||||||
for _, t := range targets {
|
for _, t := range targets {
|
||||||
if !t.IsAvailable() || excludeSet[t.URL] {
|
if !t.IsAvailable() || fc.excludeSet[t.URL] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
conns := atomic.LoadInt64(&t.Connections)
|
conns := atomic.LoadInt64(&t.Connections)
|
||||||
|
|
||||||
if t.IsBackup() {
|
if t.IsBackup() {
|
||||||
if selectedBackup == nil || conns < minBackupConns {
|
if selectedBackup == nil || conns < minBackupConns {
|
||||||
selectedBackup = t
|
selectedBackup = t
|
||||||
@ -515,16 +530,13 @@ func (i *IPHash) SelectExcluding(targets []*Target, excluded []*Target) *Target
|
|||||||
// SelectExcludingByIP 基于提供的 IP 地址的哈希值选择目标,排除指定的目标列表。
|
// SelectExcludingByIP 基于提供的 IP 地址的哈希值选择目标,排除指定的目标列表。
|
||||||
// 只考虑健康且不在排除列表中的目标。
|
// 只考虑健康且不在排除列表中的目标。
|
||||||
func (i *IPHash) SelectExcludingByIP(targets []*Target, excluded []*Target, clientIP string) *Target {
|
func (i *IPHash) SelectExcludingByIP(targets []*Target, excluded []*Target, clientIP string) *Target {
|
||||||
available := filterHealthyAndExclude(targets, excluded)
|
fc := acquireFilterContext()
|
||||||
|
defer releaseFilterContext(fc)
|
||||||
|
available := filterIntoExcluding(fc, targets, excluded)
|
||||||
if len(available) == 0 {
|
if len(available) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
hash := fnvHash64a(clientIP)
|
||||||
// 对客户端 IP 进行哈希
|
|
||||||
h := fnv.New64a()
|
|
||||||
h.Write([]byte(clientIP))
|
|
||||||
hash := h.Sum64()
|
|
||||||
|
|
||||||
idx := hash % uint64(len(available))
|
idx := hash % uint64(len(available))
|
||||||
return available[idx]
|
return available[idx]
|
||||||
}
|
}
|
||||||
@ -622,24 +634,4 @@ func (t *Target) LastResolved() time.Time {
|
|||||||
return time.Unix(0, nano)
|
return time.Unix(0, nano)
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildExcludeSet 从排除列表构建 URL 集合。
|
|
||||||
//
|
|
||||||
// 用于负载均衡算法中快速检查目标是否应被排除。
|
|
||||||
//
|
|
||||||
// 参数:
|
|
||||||
// - excluded: 需要排除的目标列表
|
|
||||||
//
|
|
||||||
// 返回值:
|
|
||||||
// - map[string]bool: 目标 URL 到 true 的映射
|
|
||||||
func buildExcludeSet(excluded []*Target) map[string]bool {
|
|
||||||
if len(excluded) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
excludeSet := make(map[string]bool, len(excluded))
|
|
||||||
for _, t := range excluded {
|
|
||||||
if t != nil {
|
|
||||||
excludeSet[t.URL] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return excludeSet
|
|
||||||
}
|
|
||||||
|
|||||||
@ -575,15 +575,16 @@ func TestFilterHealthy(t *testing.T) {
|
|||||||
createHealthyTarget("http://backend4:8080", false),
|
createHealthyTarget("http://backend4:8080", false),
|
||||||
}
|
}
|
||||||
|
|
||||||
got := filterHealthy(targets)
|
fc := acquireFilterContext()
|
||||||
|
got := filterInto(fc, targets)
|
||||||
|
defer releaseFilterContext(fc)
|
||||||
if len(got) != 2 {
|
if len(got) != 2 {
|
||||||
t.Errorf("len(filterHealthy) = %d, want 2", len(got))
|
t.Errorf("len(filterInto) = %d, want 2", len(got))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证返回的都是健康目标
|
|
||||||
for _, target := range got {
|
for _, target := range got {
|
||||||
if !target.Healthy.Load() {
|
if !target.Healthy.Load() {
|
||||||
t.Errorf("filterHealthy 返回了不健康目标: %q", target.URL)
|
t.Errorf("filterInto returned unhealthy target: %q", target.URL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -594,9 +595,11 @@ func TestFilterHealthy(t *testing.T) {
|
|||||||
createHealthyTarget("http://backend2:8080", true),
|
createHealthyTarget("http://backend2:8080", true),
|
||||||
}
|
}
|
||||||
|
|
||||||
got := filterHealthy(targets)
|
fc := acquireFilterContext()
|
||||||
|
got := filterInto(fc, targets)
|
||||||
|
defer releaseFilterContext(fc)
|
||||||
if len(got) != 2 {
|
if len(got) != 2 {
|
||||||
t.Errorf("len(filterHealthy) = %d, want 2", len(got))
|
t.Errorf("len(filterInto) = %d, want 2", len(got))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -606,23 +609,29 @@ func TestFilterHealthy(t *testing.T) {
|
|||||||
createHealthyTarget("http://backend2:8080", false),
|
createHealthyTarget("http://backend2:8080", false),
|
||||||
}
|
}
|
||||||
|
|
||||||
got := filterHealthy(targets)
|
fc := acquireFilterContext()
|
||||||
|
got := filterInto(fc, targets)
|
||||||
|
defer releaseFilterContext(fc)
|
||||||
if len(got) != 0 {
|
if len(got) != 0 {
|
||||||
t.Errorf("len(filterHealthy) = %d, want 0", len(got))
|
t.Errorf("len(filterInto) = %d, want 0", len(got))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("空切片", func(_ *testing.T) {
|
t.Run("空切片", func(_ *testing.T) {
|
||||||
got := filterHealthy([]*Target{})
|
fc := acquireFilterContext()
|
||||||
|
got := filterInto(fc, []*Target{})
|
||||||
|
defer releaseFilterContext(fc)
|
||||||
if len(got) != 0 {
|
if len(got) != 0 {
|
||||||
t.Errorf("len(filterHealthy) = %d, want 0", len(got))
|
t.Errorf("len(filterInto) = %d, want 0", len(got))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("nil切片", func(_ *testing.T) {
|
t.Run("nil切片", func(_ *testing.T) {
|
||||||
got := filterHealthy(nil)
|
fc := acquireFilterContext()
|
||||||
|
got := filterInto(fc, nil)
|
||||||
|
defer releaseFilterContext(fc)
|
||||||
if len(got) != 0 {
|
if len(got) != 0 {
|
||||||
t.Errorf("len(filterHealthy) = %d, want 0", len(got))
|
t.Errorf("len(filterInto) = %d, want 0", len(got))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -1255,7 +1264,9 @@ func TestFilterHealthyAndExclude(t *testing.T) {
|
|||||||
}
|
}
|
||||||
excluded := []*Target{targets[0]}
|
excluded := []*Target{targets[0]}
|
||||||
|
|
||||||
got := filterHealthyAndExclude(targets, excluded)
|
fc := acquireFilterContext()
|
||||||
|
got := filterIntoExcluding(fc, targets, excluded)
|
||||||
|
defer releaseFilterContext(fc)
|
||||||
if len(got) != 1 {
|
if len(got) != 1 {
|
||||||
t.Fatalf("len = %d, want 1", len(got))
|
t.Fatalf("len = %d, want 1", len(got))
|
||||||
}
|
}
|
||||||
@ -1270,14 +1281,18 @@ func TestFilterHealthyAndExclude(t *testing.T) {
|
|||||||
createHealthyTarget("http://backend2:8080", true),
|
createHealthyTarget("http://backend2:8080", true),
|
||||||
}
|
}
|
||||||
|
|
||||||
got := filterHealthyAndExclude(targets, nil)
|
fc := acquireFilterContext()
|
||||||
|
got := filterIntoExcluding(fc, targets, nil)
|
||||||
|
defer releaseFilterContext(fc)
|
||||||
if len(got) != 2 {
|
if len(got) != 2 {
|
||||||
t.Fatalf("len = %d, want 2", len(got))
|
t.Fatalf("len = %d, want 2", len(got))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("空目标列表", func(_ *testing.T) {
|
t.Run("空目标列表", func(_ *testing.T) {
|
||||||
got := filterHealthyAndExclude(nil, []*Target{})
|
fc := acquireFilterContext()
|
||||||
|
got := filterIntoExcluding(fc, nil, []*Target{})
|
||||||
|
defer releaseFilterContext(fc)
|
||||||
if len(got) != 0 {
|
if len(got) != 0 {
|
||||||
t.Errorf("len = %d, want 0", len(got))
|
t.Errorf("len = %d, want 0", len(got))
|
||||||
}
|
}
|
||||||
@ -1289,7 +1304,9 @@ func TestFilterHealthyAndExclude(t *testing.T) {
|
|||||||
}
|
}
|
||||||
excluded := []*Target{nil}
|
excluded := []*Target{nil}
|
||||||
|
|
||||||
got := filterHealthyAndExclude(targets, excluded)
|
fc := acquireFilterContext()
|
||||||
|
got := filterIntoExcluding(fc, targets, excluded)
|
||||||
|
defer releaseFilterContext(fc)
|
||||||
if len(got) != 1 {
|
if len(got) != 1 {
|
||||||
t.Fatalf("len = %d, want 1", len(got))
|
t.Fatalf("len = %d, want 1", len(got))
|
||||||
}
|
}
|
||||||
@ -1835,7 +1852,9 @@ func TestFilterHealthyBackup(t *testing.T) {
|
|||||||
backup := NewTargetFromConfig("http://backup:8080", 1, 0, 0, 0, true, false, "")
|
backup := NewTargetFromConfig("http://backup:8080", 1, 0, 0, 0, true, false, "")
|
||||||
targets := []*Target{primary, backup}
|
targets := []*Target{primary, backup}
|
||||||
|
|
||||||
result := filterHealthy(targets)
|
fc := acquireFilterContext()
|
||||||
|
result := filterInto(fc, targets)
|
||||||
|
defer releaseFilterContext(fc)
|
||||||
if len(result) != 1 || result[0].URL != "http://primary:8080" {
|
if len(result) != 1 || result[0].URL != "http://primary:8080" {
|
||||||
t.Error("should prefer non-backup target")
|
t.Error("should prefer non-backup target")
|
||||||
}
|
}
|
||||||
@ -1847,7 +1866,9 @@ func TestFilterHealthyBackup(t *testing.T) {
|
|||||||
backup := NewTargetFromConfig("http://backup:8080", 1, 0, 0, 0, true, false, "")
|
backup := NewTargetFromConfig("http://backup:8080", 1, 0, 0, 0, true, false, "")
|
||||||
targets := []*Target{primary, backup}
|
targets := []*Target{primary, backup}
|
||||||
|
|
||||||
result := filterHealthy(targets)
|
fc := acquireFilterContext()
|
||||||
|
result := filterInto(fc, targets)
|
||||||
|
defer releaseFilterContext(fc)
|
||||||
if len(result) != 1 || result[0].URL != "http://backup:8080" {
|
if len(result) != 1 || result[0].URL != "http://backup:8080" {
|
||||||
t.Error("should fall back to backup target")
|
t.Error("should fall back to backup target")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,7 +16,6 @@ package loadbalance
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash/fnv"
|
|
||||||
"slices"
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
@ -144,9 +143,7 @@ func (c *ConsistentHash) rebuildCircle(targets []*Target) {
|
|||||||
|
|
||||||
// hashKeyString 计算字符串的哈希值(使用 FNV-64a)。
|
// hashKeyString 计算字符串的哈希值(使用 FNV-64a)。
|
||||||
func (c *ConsistentHash) hashKeyString(key string) uint64 {
|
func (c *ConsistentHash) hashKeyString(key string) uint64 {
|
||||||
h := fnv.New64a()
|
return fnvHash64a(key)
|
||||||
h.Write([]byte(key))
|
|
||||||
return h.Sum64()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrecomputeHashes 预计算目标的虚拟节点哈希值。
|
// PrecomputeHashes 预计算目标的虚拟节点哈希值。
|
||||||
@ -246,42 +243,43 @@ func (c *ConsistentHash) SelectExcludingByKey(targets []*Target, excluded []*Tar
|
|||||||
c.mu.RLock()
|
c.mu.RLock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建 targets 集合(用于校验返回的目标是否有效)
|
fc := acquireFilterContext()
|
||||||
targetSet := make(map[string]bool, len(targets))
|
defer releaseFilterContext(fc)
|
||||||
|
|
||||||
|
targetSet := fc.excludeSet
|
||||||
for _, t := range targets {
|
for _, t := range targets {
|
||||||
if t.Healthy.Load() {
|
if t.Healthy.Load() {
|
||||||
targetSet[t.URL] = true
|
targetSet[t.URL] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for _, t := range excluded {
|
||||||
// 构建排除集合
|
if t != nil {
|
||||||
excludeSet := buildExcludeSet(excluded)
|
targetSet[t.URL] = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if len(c.sortedHashes) == 0 {
|
if len(c.sortedHashes) == 0 {
|
||||||
c.mu.RUnlock()
|
c.mu.RUnlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算键的哈希值
|
|
||||||
hash := c.hashKeyString(key)
|
hash := c.hashKeyString(key)
|
||||||
|
|
||||||
// 二分查找起始位置
|
|
||||||
idx := sort.Search(len(c.sortedHashes), func(i int) bool {
|
idx := sort.Search(len(c.sortedHashes), func(i int) bool {
|
||||||
return c.sortedHashes[i] >= hash
|
return c.sortedHashes[i] >= hash
|
||||||
})
|
})
|
||||||
|
|
||||||
// 从起始位置开始查找,跳过 excluded 和不在 targetSet 中的目标
|
|
||||||
for i := 0; i < len(c.sortedHashes); i++ {
|
for i := 0; i < len(c.sortedHashes); i++ {
|
||||||
targetIdx := (idx + i) % len(c.sortedHashes)
|
targetIdx := (idx + i) % len(c.sortedHashes)
|
||||||
target := c.circle[c.sortedHashes[targetIdx]]
|
target := c.circle[c.sortedHashes[targetIdx]]
|
||||||
if targetSet[target.URL] && !excludeSet[target.URL] {
|
if targetSet[target.URL] {
|
||||||
c.mu.RUnlock()
|
c.mu.RUnlock()
|
||||||
return target
|
return target
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.mu.RUnlock()
|
c.mu.RUnlock()
|
||||||
return nil // 所有目标都被排除或不在 targets 列表中
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证接口实现
|
// 验证接口实现
|
||||||
|
|||||||
@ -29,7 +29,9 @@ func NewRandom() *Random {
|
|||||||
// 随机选择两个候选,返回连接数较少的那个。
|
// 随机选择两个候选,返回连接数较少的那个。
|
||||||
// 只考虑可用目标。如果没有可用目标则返回 nil。
|
// 只考虑可用目标。如果没有可用目标则返回 nil。
|
||||||
func (r *Random) Select(targets []*Target) *Target {
|
func (r *Random) Select(targets []*Target) *Target {
|
||||||
available := filterHealthy(targets)
|
fc := acquireFilterContext()
|
||||||
|
defer releaseFilterContext(fc)
|
||||||
|
available := filterInto(fc, targets)
|
||||||
if len(available) == 0 {
|
if len(available) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -38,7 +40,6 @@ func (r *Random) Select(targets []*Target) *Target {
|
|||||||
return available[0]
|
return available[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Power of Two Choices
|
|
||||||
i := rand.IntN(len(available))
|
i := rand.IntN(len(available))
|
||||||
j := rand.IntN(len(available) - 1)
|
j := rand.IntN(len(available) - 1)
|
||||||
if j >= i {
|
if j >= i {
|
||||||
@ -54,7 +55,9 @@ func (r *Random) Select(targets []*Target) *Target {
|
|||||||
|
|
||||||
// SelectExcluding 使用 Power of Two Choices 算法选择目标,排除指定的目标列表。
|
// SelectExcluding 使用 Power of Two Choices 算法选择目标,排除指定的目标列表。
|
||||||
func (r *Random) SelectExcluding(targets []*Target, excluded []*Target) *Target {
|
func (r *Random) SelectExcluding(targets []*Target, excluded []*Target) *Target {
|
||||||
available := filterHealthyAndExclude(targets, excluded)
|
fc := acquireFilterContext()
|
||||||
|
defer releaseFilterContext(fc)
|
||||||
|
available := filterIntoExcluding(fc, targets, excluded)
|
||||||
if len(available) == 0 {
|
if len(available) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user