如何在Golang中实现RPC调用限速_Golang RPC请求流量控制方法

Go net/rpc 本身不支持限速,需通过包装 net.Listener 实现连接频次限制,或包装 net.Conn 在 Read() 中按 RPC 帧限速;精确 QPS 限速应改用 gRPC + 拦截器。

Go net/rpc 本身不支持限速,必须自己加中间层

Go 标准库的 net/rpc 是一个轻量级、无状态的 RPC 框架,它不提供任何请求频率控制能力。限速逻辑不能写在服务端方法内部(那样会污染业务),也不能依赖 HTTP 中间件(因为 net/rpc 默认走 TCP,不经过 HTTP 栈)。真正可行的方式是在连接建立后、请求分发前插入限速器。

net.Listener 包装器实现连接级限速

最干净的做法是包装 net.Listener,在 Accept() 返回连接前做速率判断。这样能控制新连接建立频率,适合防御突发连接洪峰。注意:这不是“每秒请求数”(QPS)限速,而是“每秒新建连接数”限制。

  • 适用于防止客户端疯狂重连或扫描类攻击
  • 使用 golang.org/x/time/rate.Limiter 配合 net.Listener 实现
  • 不要在 Accept() 中阻塞太久,建议设置超时或使用非阻塞 TryAccept 类逻辑
type RateLimitedListener struct {
    net.Listener
    limiter *rate.Limiter
}

func (l *RateLimitedListener) Accept() (net.Conn, error) {
    if !l.limiter.Allow() {
        return nil, errors.New("connection rejected: rate limit exceeded")
    }
    return l.Listener.Accept()
}

rpc.ServeConn 前拦截并限速单连接内请求

如果要控制单个 TCP 连接内的请求处理速度(比如防止一个恶意客户端复用连接狂发请求),就得在 rpc.ServeConn 调用前包装 net.Conn。核心是重写 Read() 方法,在每次读到完整 RPC 帧(含 head

er + body)后再触发一次限速检查。

  • net/rpc 使用自定义二进制协议,每个请求以 4 字节长度头开始,需先读头再读体
  • 直接包装 net.Conn 并在 Read() 中做 limiter.WaitN(ctx, n) 可控但复杂
  • 更稳妥的做法是改用 jsonrpc2gRPC 等带上下文和中间件能力的框架

实际项目中推荐用 gRPC + grpc-middleware 实现 QPS 限速

如果你的场景真需要精确到每秒 N 次方法调用的限速(比如 User.Create 接口限 100 QPS),net/rpc 不是合适选择。gRPC 天然支持拦截器,配合 go.uber.org/ratelimitgolang.org/x/time/rate 很容易实现。

  • 限速粒度可设为 method、user-id、ip 等维度
  • 错误返回统一为 status.Errorf(codes.ResourceExhausted, "...")
  • 注意:gRPC 流式接口需单独处理,limiter.Allow() 要放在每次 Recv() 后而非连接建立时
func rateLimitInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
    if !limiter.Allow() {
        return nil, status.Errorf(codes.ResourceExhausted, "rate limit exceeded")
    }
    return handler(ctx, req)
}

真正难的不是写限速代码,而是确定限速维度和阈值——同一接口对内部系统和外部 API 的容忍度可能差十倍;而误把健康检查探针当成恶意请求限掉,这种事在灰度期特别常见。