From 7fe1ca6becbf6f2b2020cfebb5033663ddb59434 Mon Sep 17 00:00:00 2001 From: xfy Date: Thu, 4 Jun 2026 00:20:43 +0800 Subject: [PATCH] perf(loadbalance): eliminate double-lock in ConsistentHash with atomic.Bool rebuild guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- internal/loadbalance/consistent_hash.go | 37 +++++++++++-------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/internal/loadbalance/consistent_hash.go b/internal/loadbalance/consistent_hash.go index 3057ddd..256614e 100644 --- a/internal/loadbalance/consistent_hash.go +++ b/internal/loadbalance/consistent_hash.go @@ -19,6 +19,7 @@ import ( "slices" "sort" "sync" + "sync/atomic" ) // ConsistentHash 一致性哈希负载均衡器。 @@ -31,6 +32,7 @@ type ConsistentHash struct { sortedHashes []uint64 virtualNodes int mu sync.RWMutex + rebuilt atomic.Bool } // NewConsistentHash 创建一致性哈希负载均衡器。 @@ -66,33 +68,22 @@ func (c *ConsistentHash) Select(targets []*Target) *Target { // 返回值: // - *Target: 选中的目标,如果没有健康目标则返回 nil func (c *ConsistentHash) SelectByKey(targets []*Target, key string) *Target { + c.ensureRebuilt(targets) + c.mu.RLock() defer c.mu.RUnlock() - // 如果环为空,重建哈希环 - if len(c.circle) == 0 { - c.mu.RUnlock() - c.rebuildCircle(targets) - c.mu.RLock() - } - if len(c.sortedHashes) == 0 { return nil } - // 计算键的哈希值 - hash := c.hashKeyString(key) - - // 二分查找最近的节点 + hash := fnvHash64a(key) idx := sort.Search(len(c.sortedHashes), func(i int) bool { return c.sortedHashes[i] >= hash }) - - // 环形回绕 if idx >= len(c.sortedHashes) { idx = 0 } - return c.circle[c.sortedHashes[idx]] } @@ -103,6 +94,14 @@ func (c *ConsistentHash) SelectByKey(targets []*Target, key string) *Target { // 参数: // - targets: 新的目标列表 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) } @@ -139,6 +138,7 @@ func (c *ConsistentHash) rebuildCircle(targets []*Target) { // 排序哈希值 slices.Sort(c.sortedHashes) + c.rebuilt.Store(true) } // hashKeyString 计算字符串的哈希值(使用 FNV-64a)。 @@ -234,14 +234,9 @@ func (c *ConsistentHash) SelectExcluding(targets []*Target, excluded []*Target) // 若不一致(如多上游组场景),targetSet 校验将拒绝所有候选,返回 nil。 // 调用方应在 targets 列表变化时调用 Rebuild() 更新主哈希环。 func (c *ConsistentHash) SelectExcludingByKey(targets []*Target, excluded []*Target, key string) *Target { - c.mu.RLock() + c.ensureRebuilt(targets) - // 如果环为空,尝试重建 - if len(c.circle) == 0 { - c.mu.RUnlock() - c.rebuildCircle(targets) - c.mu.RLock() - } + c.mu.RLock() fc := acquireFilterContext() defer releaseFilterContext(fc)