首页 > 解决方案 > gRPC - 限制客户端请求(限制)

问题描述

有没有办法在 gRPC 一元调用中添加节流阀?我的目标是将每个用户限制为每秒 10 个请求。

在我的研究中,我发现(也许)它将被放置在一个继承 Interceptor 的类中,如下所示:

public class LimitClientRequestsInterceptor : Interceptor
{
   public override UnaryServerHandler<TRequest, TResponse>(
            TRequest request,
            ServerCallContext context,
            UnaryServerMethod<TRequest, TResponse> continuation)
   {
      // Add code to limit 10 requests per second for each user.
      return await base.UnaryServerHandler(request, context, continuation);
   }
}

标签: c#.net-coregrpc

解决方案


我找到了解决问题的方法,因为 gRPC 没有用于速率限制或节流的内置方法(在 c# 中)。但是,我能够做到这一点,如下所示。

我的拦截器类:

public class LimitClientRequestsInterceptor : Interceptor
{
   private readonly ThrottleGauge _gauge;

   public override UnaryServerHandler<TRequest, TResponse>(
            TRequest request,
            ServerCallContext context,
            UnaryServerMethod<TRequest, TResponse> continuation)
   {
      var username = context.GetHttpContext().User.GetUserNameInIdentity();
      if (ThrottlingAttribute.UserHasReachedMaxRateLimit(username))
      {
         throw new RpcException(new Status(
         StatusCode.Cancelled,
         Newtonsoft.Json.JsonConvert.SerializeObject(new
         { Code = 429, Detail = $"Throttle: {username} exceeded 
         {_gauge.MaxMessagesPerTimeSlice} messages in {_gauge.TimeSlice}" })));
       }

      return await base.UnaryServerHandler(request, context, continuation);
   }
}

我的油门类:

internal class ThrottlingAttribute
{
   private static Dictionary<string, ThrottleGauge> _byUser;
   private static TimeSpan _defaultThrottle_TimeSliceInMilliseconds = TimeSpan.FromMilliseconds(ServiceSettings.Instance.DefaultThrottle_TimeSliceInMilliseconds);
   private static int _defaultThrottle_MaxMessagesPerTimeSlice = ServiceSettings.Instance.DefaultThrottle_MaxMessagesPerTimeSlice;

   public static bool UserHasReachedMaxRateLimit(string username)
   {
       ThrottleGauge gauge;
       if (!_byUser.TryGetValue(username, out gauge))
       {
           gauge = new ThrottleGauge(
               _defaultThrottle_TimeSliceInMilliseconds,
               _defaultThrottle_MaxMessagesPerTimeSlice);
               _byUser[username] = gauge;
       }

       return gauge.WillExceedRate();
   }
}

还有我的 ThrottleGauge 课程:

   internal class ThrottleGauge
    {
        private readonly object _locker = new object();
        private Queue<DateTime> _Queue = new Queue<DateTime>();

        public TimeSpan TimeSlice { get; private set; }
        public int MaxMessagesPerTimeSlice { get; private set; }

        public ThrottleGauge(TimeSpan timeSlice, int maxMessagesPerTimeSlice)
        {
            TimeSlice = timeSlice;
            MaxMessagesPerTimeSlice = maxMessagesPerTimeSlice;
        }

        // returns true if sending a message now message exceeds limit rate
        public bool WillExceedRate()
        {
            lock (_locker)
            {
                var now = DateTime.Now;
                if (_Queue.Count < MaxMessagesPerTimeSlice)
                {
                    _Queue.Enqueue(now);
                    return false;
                }

                DateTime oldest = _Queue.Peek();
                if ((now - oldest).TotalMilliseconds < TimeSlice.TotalMilliseconds)
                    return true;

                _Queue.Dequeue();
                _Queue.Enqueue(now);
                return false;
            }
        }
    }

推荐阅读