不能直接用IP字符串作map key限流,因IPv6格式不统一、代理私有IP误入、长字符串致哈希退化;应先标准化IP(IPv4不变,IPv6转16字节再String),再用xxhash哈希后位运算分片到sync.Map,且需TTL清理过期key。

为什么不能直接用 IP 字符串当 map key 做限流
直接把 r.RemoteAddr 或未经清洗的 X-Forwarded-For 当作 sync.Map 的 key,线上跑几天就会内存持续上涨甚至 OOM。原因有三:一是 IPv6 地址格式不统一(::1、0:0:0:0:0:0:0:1、2001:db8::1 都可能表示同一地址),导致同一客户端生成多个 key;二是代理链中插入的私有 IP(如 10.0.1.5)会被误计入;三是字符串本身长度不可控,sync.Map 的哈希桶会因 key 过长而退化为链表,查找变慢。
用 xxhash/v2 对标准化后的 IP 做哈希取模分片
真正轻量又抗碰撞的做法是:先标准化 IP,再哈希,最后对固定数取模路由到不同 sync.Map 实例。不要用 crypto/md5 或 sha256——太重;也不要依赖 map[string]*rate.Limiter 全局一把锁——并发瓶颈明显。
- 标准化步骤必须做:IPv4 保持原样;IPv6 调用
net.ParseIP(ipStr).To16()得到标准 16 字节形式,再用ip.String()转回可读格式(自动压缩零段) - 哈希用
github.com/cespare/xxhash/v2:单次哈希耗时约 20 ns,比hash/fnv快 3 倍,且分布均匀 - 分片数建议设为 64 或 128:太少易热点,太多增加管理成本;取模用位运算
hash & 0x3f(64=2⁶)比% 64更快 - 示例代码片段:
func hashIP(ipStr string) uint64 {
ip := net.ParseIP(ipStr)
if ip == nil {
return 0
}
// 标准化 IPv6
if ip.To16() != nil {
ip = ip.To16()
}
return xxhash.Sum64([]byte(ip.String())).Sum64()
}
const shardCount = 64
var shards [shardCount]*sync.Map // 每个 shard 是独立 sync.Map
func getLimiter(ipStr string, r rate.Limit, b int) *rate.Limiter {
hash := hashIP(ipStr)
shard := &shards[hash&0x3f] // 64 分片
key := strconv.FormatUint(hash, 10)
if lim, ok := shard.Load(key); ok {
return lim.(*rate.Limiter)
}
lim := rate.NewLimiter(r, b)
shard.Store(key, lim)
return lim
}
限流器实例必须带 TTL 清理,不能只靠哈希分片
哈希分片解决的是并发争抢和内存局部性问题,但不解决 key 泄漏。真实用户 IP 是动态、稀疏、大量短期活跃的,放任不管,sync.Map 里会积压数万无效条目。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 别用
time.AfterFunc单独 goroutine 定时清理——无法感知 key 是否还在被访问 - 推荐方案:每次访问时更新时间戳,另起一个低频 goroutine(比如每 30 秒)扫描所有 shard,删掉「最后访问时间 + TTL
- TTL 建议设为 10 分钟:短于这个值,爬虫或重试请求可能被误杀;长于这个值,内存压力明显上升
- 清理逻辑里避免阻塞:用
LoadAndDelete而非先Load再Delete,防止竞态
哈希后仍要防伪造 IP 导致的 key 扩张
哈希再快,也救不了被恶意构造的伪造 IP。如果前端 Nginx 没配 set_real_ip_from,攻击者发一堆随机 X-Forwarded-For: 1.1.1.1, 2.2.2.2, ..., 100.100.100.100,你解析出最右 IP 后哈希,照样生成上百个新 key,打爆内存。
立即学习“go语言免费学习笔记(深入)”;
- 永远只信任你可控的上游代理加的 header,配置可信跳数(例如
trustedHop = 1表示只取 XFF 最右一个非私有地址) - 私有 IP 列表必须完整检查:
127.0.0.0/8、10.0.0.0/8、172.16.0.0/12、192.168.0.0/16、::1/128、fc00::/7 - Cloudflare 用户优先读
Cf-Connecting-Ip,它不可伪造;fallback 时才校验 XFF - 若检测到不可信 IP(如私有地址、格式非法),统一 fallback 到一个兜底 key(如
"_invalid"),避免 key 爆炸

















