lolly/internal/loadbalance/consistent_hash.go
xfy d42844b2fa test(app,handler,server,http3): 补充单元测试覆盖率
- app: 添加信号处理、配置重载、日志重开测试
- handler/sendfile: 添加小文件、偏移量、错误情况测试
- server: 添加统计追踪、监听器、TLS配置测试
- http3: 新增 adapter 和 server 单元测试
- 格式修复: 末尾换行符、注释对齐
- 文档: AGENTS.md 添加 http3 模块说明

Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-03 16:25:21 +08:00

185 lines
4.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package loadbalance 提供一致性哈希负载均衡算法实现。
//
// 该文件实现基于虚拟节点的一致性哈希算法,适用于缓存代理场景。
//
// 主要用途:
//
// 用于将相同键的请求始终路由到同一后端服务器,提高缓存命中率。
//
// 算法特点:
// - 使用虚拟节点解决数据倾斜问题
// - 支持 FNV-64a 哈希算法
// - 支持多种哈希键来源IP、URI、Header
//
// 作者xfy
package loadbalance
import (
"fmt"
"hash/fnv"
"sort"
"sync"
)
// ConsistentHash 一致性哈希负载均衡器。
//
// 使用虚拟节点将请求均匀分布到各个目标,同时保证相同键的请求
// 始终路由到同一目标。
type ConsistentHash struct {
// virtualNodes 每个目标的虚拟节点数,默认 150
virtualNodes int
// circle 哈希环key 为哈希值value 为目标
circle map[uint64]*Target
// sortedHashes 排序后的哈希值列表,用于二分查找
sortedHashes []uint64
// hashKey 哈希键来源ip, uri, header:xxx
hashKey string
// mu 读写锁
mu sync.RWMutex
}
// NewConsistentHash 创建一致性哈希负载均衡器。
//
// 参数:
// - virtualNodes: 每个目标的虚拟节点数,默认 150
// - hashKey: 哈希键来源,支持 ip、uri、header:X-Name
func NewConsistentHash(virtualNodes int, hashKey string) *ConsistentHash {
if virtualNodes <= 0 {
virtualNodes = 150
}
return &ConsistentHash{
virtualNodes: virtualNodes,
circle: make(map[uint64]*Target),
hashKey: hashKey,
}
}
// Select 根据默认键选择目标。
//
// 由于一致性哈希需要具体键值,此方法返回 nil。
// 请使用 SelectByKey 方法。
func (c *ConsistentHash) Select(targets []*Target) *Target {
return c.SelectByKey(targets, "")
}
// SelectByKey 根据指定键选择目标。
//
// 参数:
// - targets: 可用目标列表
// - key: 哈希键值(如客户端 IP、URI 等)
//
// 返回值:
// - *Target: 选中的目标,如果没有健康目标则返回 nil
func (c *ConsistentHash) SelectByKey(targets []*Target, key string) *Target {
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)
// 二分查找最近的节点
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]]
}
// Rebuild 重建哈希环。
//
// 当目标列表发生变化时应调用此方法。
//
// 参数:
// - targets: 新的目标列表
func (c *ConsistentHash) Rebuild(targets []*Target) {
c.rebuildCircle(targets)
}
// rebuildCircle 重建哈希环(内部方法,需要持有锁)。
func (c *ConsistentHash) rebuildCircle(targets []*Target) {
c.mu.Lock()
defer c.mu.Unlock()
// 清空现有环
c.circle = make(map[uint64]*Target)
c.sortedHashes = make([]uint64, 0)
// 为每个目标添加虚拟节点
for _, target := range targets {
if !target.Healthy.Load() {
continue
}
for i := 0; i < c.virtualNodes; i++ {
key := fmt.Sprintf("%s#%d", target.URL, i)
hash := c.hashKeyString(key)
c.circle[hash] = target
c.sortedHashes = append(c.sortedHashes, hash)
}
}
// 排序哈希值
sort.Slice(c.sortedHashes, func(i, j int) bool {
return c.sortedHashes[i] < c.sortedHashes[j]
})
}
// hashKeyString 计算字符串的哈希值(使用 FNV-64a
func (c *ConsistentHash) hashKeyString(key string) uint64 {
h := fnv.New64a()
h.Write([]byte(key))
return h.Sum64()
}
// GetHashKey 返回哈希键配置。
func (c *ConsistentHash) GetHashKey() string {
return c.hashKey
}
// GetVirtualNodes 返回虚拟节点数。
func (c *ConsistentHash) GetVirtualNodes() int {
return c.virtualNodes
}
// Stats 返回一致性哈希统计信息。
type ConsistentHashStats struct {
VirtualNodes int // 虚拟节点数
CircleSize int // 哈希环大小
SortedHashes int // 排序哈希值数量
}
// GetStats 返回统计信息。
func (c *ConsistentHash) GetStats() ConsistentHashStats {
c.mu.RLock()
defer c.mu.RUnlock()
return ConsistentHashStats{
VirtualNodes: c.virtualNodes,
CircleSize: len(c.circle),
SortedHashes: len(c.sortedHashes),
}
}
// 验证接口实现
var _ Balancer = (*ConsistentHash)(nil)