高性能网关:Go 原生单机缓存与自适应回源限流实践
一、引言
网关作为系统的流量入口,在高并发场景下直接影响后端服务的稳定性。大量请求如果全部透传给后端,可能导致数据库连接数耗尽或微服务节点 CPU 过载。在网关层加缓存,是减少后端压力的常用做法。
但简单缓存遇到热点数据失效时会有问题。比如某个热点 key 突然过期,或者大量请求同时发现缓存未命中,这些请求会同时打到后端。瞬间的回源流量可能让后端服务扛不住。
我们做了两层保护:本地单机缓存 + 自适应回源限流。缓存拦截大部分重复请求,限流器在缓存失效时控制回源流量,让后端不会被打挂。
二、架构设计
架构有两层。第一层是 Go 进程内的本地缓存,读内存没有网络开销,响应在微秒级,能扛住每秒几十万次查询。
第二层是自适应回源限流器,放在缓存未命中和实际回源之间。请求在本地缓存找不到数据时,限流器判断是否允许回源。用令牌桶算法,但速率不是固定的,会根据后端健康状况动态调整。
后端响应变慢或有丢包时,限流器自动降低令牌生成速率,把多余的回源请求拦在网关层。后端恢复后,再逐步放开。这样既保护后端,也尽量利用可用带宽。
三、核心实现
代码完全用 Go 标准库编写,没有第三方依赖。实现了一个支持过期清理的并发安全缓存,和一个支持动态速率调整的令牌桶限流器。
go
package main
import (
"context"
"errors"
"fmt"
"sync"
"time"
)
// CacheItem 表示缓存项
type CacheItem struct {
Value interface{}
ExpiredAt time.Time
}
// LocalCache 结构体,提供并发安全的本地缓存
type LocalCache struct {
mu sync.RWMutex
items map[string]CacheItem
}
// NewLocalCache 创建本地缓存实例
func NewLocalCache() *LocalCache {
c := &LocalCache{
items: make(map[string]CacheItem),
}
// 启动后台协程定期清理过期数据
go c.startJanitor(time.Second * 5)
return c
}
// Set 设置缓存
func (c *LocalCache) Set(key string, val interface{}, ttl time.Duration) {
c.mu.Lock()
defer c.mu.Unlock()
c.items[key] = CacheItem{
Value: val,
ExpiredAt: time.Now().Add(ttl),
}
}
// Get 获取缓存
func (c *LocalCache) Get(key string) (interface{}, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
item, found := c.items[key]
if !found {
return nil, false
}
if time.Now().After(item.ExpiredAt) {
return nil, false
}
return item.Value, true
}
// startJanitor 定期清理过期项
func (c *LocalCache) startJanitor(interval time.Duration) {
ticker := time.NewTicker(interval)
for range ticker.C {
c.mu.Lock()
now := time.Now()
for k, item := range c.items {
if now.After(item.ExpiredAt) {
delete(c.items, k)
}
}
c.mu.Unlock()
}
}
// AdaptiveLimiter 自适应限流器
type AdaptiveLimiter struct {
mu sync.Mutex
capacity float64 // 桶容量
tokens float64 // 当前令牌数
rate float64 // 每秒生成的令牌数(回源速率)
lastRefreshed time.Time // 上次刷新时间
}
// NewAdaptiveLimiter 创建自适应限流器
func NewAdaptiveLimiter(rate, capacity float64) *AdaptiveLimiter {
return &AdaptiveLimiter{
capacity: capacity,
tokens: capacity,
rate: rate,
lastRefreshed: time.Now(),
}
}
// Allow 判断是否允许请求通过
func (l *AdaptiveLimiter) Allow() bool {
l.mu.Lock()
defer l.mu.Unlock()
now := time.Now()
elapsed := now.Sub(l.lastRefreshed).Seconds()
l.lastRefreshed = now
// 补充令牌
l.tokens += elapsed * l.rate
if l.tokens > l.capacity {
l.tokens = l.capacity
}
if l.tokens >= 1.0 {
l.tokens -= 1.0
return true
}
return false
}
// AdjustRate 动态调整回源速率
func (l *AdaptiveLimiter) AdjustRate(newRate float64) {
l.mu.Lock()
defer l.mu.Unlock()
if newRate < 1.0 {
newRate = 1.0 // 保持最低回源能力
}
l.rate = newRate
}
// Gateway 模拟网关
type Gateway struct {
cache *LocalCache
limiter *AdaptiveLimiter
}
func NewGateway() *Gateway {
return &Gateway{
cache: NewLocalCache(),
limiter: NewAdaptiveLimiter(5.0, 10.0), // 初始每秒5次请求,最大容量10
}
}
// Request 处理客户端请求
func (g *Gateway) Request(ctx context.Context, key string) (string, error) {
// 1. 尝试从本地缓存获取
if val, found := g.cache.Get(key); found {
return val.(string), nil
}
// 2. 缓存未命中,进行回源限流判断
if !g.limiter.Allow() {
return "", errors.New("rate limit exceeded for origin server")
}
// 3. 执行回源(模拟后端业务耗时)
data, err := g.fetchFromOrigin(ctx, key)
if err != nil {
return "", err
}
// 4. 回源成功,更新本地缓存,设置过期时间为3秒
g.cache.Set(key, data, time.Second*3)
return data, nil
}
// fetchFromOrigin 模拟从后端服务获取数据
func (g *Gateway) fetchFromOrigin(ctx context.Context, key string) (string, error) {
// 模拟网络延迟
select {
case <-time.After(time.Millisecond * 100):
return fmt.Sprintf("data_for_%s_at_%d", key, time.Now().UnixNano()), nil
case <-ctx.Done():
return "", ctx.Err()
}
}
四、动态反馈与高负载测试
自适应限流的关键是反馈信号。网关后台协程统计回源请求的成功率和平均响应延迟。延迟超过健康阈值或有超时,就调用限流器的调整方法降低回源速率。
缓存失效或冷启动时,这套机制效果最明显。假设上万个并发请求同时访问一个刚失效的 key,网关缓存层全部判定未命中。没限流的话,这些请求会同时回源。有自适应限流器时,绝大部分请求被拦截,只放行少数几个。
这几个请求拿到后端数据后更新本地缓存。缓存一更新,后续请求直接在缓存层返回,回源流量降下来。"限流回源 → 快速建仓 → 缓存响应"这个闭环,用很小的代价扛过流量洪峰。
五、结语
高并发网关不一定非要堆分布式中间件。用 Go 的读写锁和协程,在进程内就能做出高性能的本地缓存。配合自适应令牌桶限流,缓存失效时能给后端提供保护。这套方案在云原生架构里比较实用。
主要修改:
| 原文问题 | 修改方式 |
|---|---|
| "生死存亡""洪水一样冲垮""安然无恙" | 删除戏剧化比喻,改为直接描述 |
| "业界公认最直接有效" | 改为"常用做法" |
| "两道核心防线""敏锐地捕捉到" | 删除拟人化和过度修饰 |
| "既保护了后端,又最大化地利用了" | 简化为"既保护后端,也尽量利用" |
| "轻量级与通用性""完全基于" | 删除宣传性表述 |
| "强调了系统自愈与自我保护的能力""具有非常高的实用价值" | 改为"比较实用" |
| 三段式排比结构 | 打散为更自然的句式 |
| "在实际生产运行中"等填充词 | 删除 |