如何为gRPC Unary调用添加请求限流 实现单用户每秒最多10次请求
gRPC Unary 调用单用户限流实现方案
你调研的通过自定义 Interceptor 实现限流的方案是完全可行的,以下是可直接落地的实现逻辑:
实现核心逻辑
需要两个核心模块配合:
- 从 gRPC 请求元数据中获取唯一用户标识(通常是请求头里的用户ID、Token解析后的用户ID等)
- 为每个用户维护独立的请求时间窗口与计数,超过阈值直接返回限流响应
完整示例代码
using Grpc.Core; using Grpc.Core.Interceptors; using System.Collections.Concurrent; public class LimitClientRequestsInterceptor : Interceptor { // 存储每个用户的请求计数与时间窗口起点,Key为用户唯一标识,Value为(请求数, 窗口开始时间) private static readonly ConcurrentDictionary<string, (int Count, DateTime WindowStart)> _userRequestLimit = new(); // 限流阈值:10次/秒 private const int _maxRequestsPerSecond = 10; private const int _windowSeconds = 1; public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>( TRequest request, ServerCallContext context, UnaryServerMethod<TRequest, TResponse> continuation) { // 1. 从请求头中获取用户唯一标识,此处以自定义头x-user-id为例,可根据实际业务替换为Token解析等逻辑 var userId = context.RequestHeaders.Get("x-user-id")?.Value; if (string.IsNullOrEmpty(userId)) { // 无用户标识的情况可自行处理,比如统一按匿名用户限流,或者直接拒绝请求 throw new RpcException(new Status(StatusCode.PermissionDenied, "缺少用户标识")); } // 2. 限流校验逻辑 var now = DateTime.UtcNow; var userLimitInfo = _userRequestLimit.AddOrUpdate( key: userId, addValueFactory: (id) => (1, now), updateValueFactory: (id, oldInfo) => { // 当前时间超出上一个窗口,重置计数与窗口时间 if (now.Subtract(oldInfo.WindowStart).TotalSeconds >= _windowSeconds) { return (1, now); } // 窗口内计数+1 return (oldInfo.Count + 1, oldInfo.WindowStart); }); // 3. 超出限流阈值返回资源耗尽错误 if (userLimitInfo.Count > _maxRequestsPerSecond) { throw new RpcException(new Status(StatusCode.ResourceExhausted, "请求频率过高,请稍后重试")); } // 4. 限流校验通过,继续执行后续逻辑 return await continuation(request, context); } }
启用限流拦截器
在服务端的Program.cs中注册拦截器即可全局生效:
builder.Services.AddGrpc(options => { options.Interceptors.Add<LimitClientRequestsInterceptor>(); });
注意事项
- 上述代码为单机部署场景的实现,如果是多节点分布式部署,需要将限流计数存储到Redis等集中式缓存中,避免每个节点单独计数导致总阈值超出
- 可以根据业务需求调整限流规则,比如为不同等级的用户配置不同的请求阈值
- 建议客户端识别
ResourceExhausted状态码,配合退避重试逻辑优化体验
内容的提问来源于stack exchange,提问作者Gabriel Ribeiro
相关产品推荐
相关产品推荐

