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 帧(含 header + body)后再触发一次限速检查。
-
net/rpc使用自定义二进制协议,每个请求以 4 字节长度头开始,需先读头再读体 - 直接包装
net.Conn并在Read()中做limiter.WaitN(ctx, n)可控但复杂 - 更稳妥的做法是改用
jsonrpc2或gRPC等带上下文和中间件能力的框架
实际项目中推荐用 gRPC + grpc-middleware 实现 QPS 限速
如果你的场景真需要精确到每秒 N 次方法调用的限速(比如 User.Create 接口限 100 QPS),net/rpc 不是合适选择。gRPC 天然支持拦截器,配合 go.uber.org/ratelimit 或 golang.org/x/time/rate 很容易实现。
立即学习“go语言免费学习笔记(深入)”;
- 限速粒度可设为 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 的容忍度可能差十倍;而误把健康检查探针当成恶意请求限掉,这种事在灰度期特别常见。










