perf(loadbalance): eliminate double-lock in ConsistentHash with atomic.Bool rebuild guard

SelectByKey and SelectExcludingByKey previously had a RLock→RUnlock→
rebuildCircle(Lock)→RLock pattern when the hash ring was empty. Under
cold-start concurrency, multiple goroutines could trigger simultaneous
rebuild attempts.

Add atomic.Bool 'rebuilt' flag with ensureRebuilt() check before any
RLock acquisition:
- Fast path: atomic load returns true → skip rebuild, proceed to RLock
- Cold start: first caller rebuilds and sets flag, subsequent callers
  see the flag and skip rebuild
- Rebuild() explicitly resets the flag for explicit ring invalidation

Eliminates the RLock→Unlock→Lock→RLock transition entirely. The ring
is guaranteed ready before RLock is acquired.
This commit is contained in:
xfy 2026-06-04 00:20:43 +08:00
parent c6e7091089
commit 7fe1ca6bec

View File

@ -19,6 +19,7 @@ import (
"slices" "slices"
"sort" "sort"
"sync" "sync"
"sync/atomic"
) )
// ConsistentHash 一致性哈希负载均衡器。 // ConsistentHash 一致性哈希负载均衡器。
@ -31,6 +32,7 @@ type ConsistentHash struct {
sortedHashes []uint64 sortedHashes []uint64
virtualNodes int virtualNodes int
mu sync.RWMutex mu sync.RWMutex
rebuilt atomic.Bool
} }
// NewConsistentHash 创建一致性哈希负载均衡器。 // NewConsistentHash 创建一致性哈希负载均衡器。
@ -66,33 +68,22 @@ func (c *ConsistentHash) Select(targets []*Target) *Target {
// 返回值: // 返回值:
// - *Target: 选中的目标,如果没有健康目标则返回 nil // - *Target: 选中的目标,如果没有健康目标则返回 nil
func (c *ConsistentHash) SelectByKey(targets []*Target, key string) *Target { func (c *ConsistentHash) SelectByKey(targets []*Target, key string) *Target {
c.ensureRebuilt(targets)
c.mu.RLock() c.mu.RLock()
defer c.mu.RUnlock() defer c.mu.RUnlock()
// 如果环为空,重建哈希环
if len(c.circle) == 0 {
c.mu.RUnlock()
c.rebuildCircle(targets)
c.mu.RLock()
}
if len(c.sortedHashes) == 0 { if len(c.sortedHashes) == 0 {
return nil return nil
} }
// 计算键的哈希值 hash := fnvHash64a(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
}) })
// 环形回绕
if idx >= len(c.sortedHashes) { if idx >= len(c.sortedHashes) {
idx = 0 idx = 0
} }
return c.circle[c.sortedHashes[idx]] return c.circle[c.sortedHashes[idx]]
} }
@ -103,6 +94,14 @@ func (c *ConsistentHash) SelectByKey(targets []*Target, key string) *Target {
// 参数: // 参数:
// - targets: 新的目标列表 // - targets: 新的目标列表
func (c *ConsistentHash) Rebuild(targets []*Target) { func (c *ConsistentHash) Rebuild(targets []*Target) {
c.rebuilt.Store(false)
c.rebuildCircle(targets)
}
func (c *ConsistentHash) ensureRebuilt(targets []*Target) {
if c.rebuilt.Load() {
return
}
c.rebuildCircle(targets) c.rebuildCircle(targets)
} }
@ -139,6 +138,7 @@ func (c *ConsistentHash) rebuildCircle(targets []*Target) {
// 排序哈希值 // 排序哈希值
slices.Sort(c.sortedHashes) slices.Sort(c.sortedHashes)
c.rebuilt.Store(true)
} }
// hashKeyString 计算字符串的哈希值(使用 FNV-64a // hashKeyString 计算字符串的哈希值(使用 FNV-64a
@ -234,14 +234,9 @@ func (c *ConsistentHash) SelectExcluding(targets []*Target, excluded []*Target)
// 若不一致如多上游组场景targetSet 校验将拒绝所有候选,返回 nil。 // 若不一致如多上游组场景targetSet 校验将拒绝所有候选,返回 nil。
// 调用方应在 targets 列表变化时调用 Rebuild() 更新主哈希环。 // 调用方应在 targets 列表变化时调用 Rebuild() 更新主哈希环。
func (c *ConsistentHash) SelectExcludingByKey(targets []*Target, excluded []*Target, key string) *Target { func (c *ConsistentHash) SelectExcludingByKey(targets []*Target, excluded []*Target, key string) *Target {
c.mu.RLock() c.ensureRebuilt(targets)
// 如果环为空,尝试重建 c.mu.RLock()
if len(c.circle) == 0 {
c.mu.RUnlock()
c.rebuildCircle(targets)
c.mu.RLock()
}
fc := acquireFilterContext() fc := acquireFilterContext()
defer releaseFilterContext(fc) defer releaseFilterContext(fc)