.NET Redis限制接口请求频率 滑动窗口算法

在.NET中使用Redis来限制接口请求频率(每10秒只允许请求一次)

NuGet setup

StackExchange.Redis

实现速率限制逻辑 :

在控制器或服务层中,编写Redis速率限制计数器。
设置Redis键 :

为每个用户或每个IP地址设置一个唯一的键。这个键将用于存储最后一次请求的时间戳和/请求计数。
检查时间戳 :

当请求到达时,从Redis中获取该键的值(时间戳)。如果键不存在或时间戳超过10秒,则允许请求并更新键的值(设置为当前时间戳)。
处理超过速率的请求 :

如果时间戳在10秒内,则拒绝或限制该请求(返回限制状态码)。

csharp 复制代码
   private static readonly Lazy<ConnectionMultiplexer> LazyConnection = new Lazy<ConnectionMultiplexer>(() =>
   {
       // 配置Redis连接字符串 "localhost,abortConnect=false"  
       return ConnectionMultiplexer.Connect("localhost:6379");
   });

   private static ConnectionMultiplexer Connection => LazyConnection.Value;

   private static IDatabase Db => Connection.GetDatabase();

   public async Task<ActionResult> MyAction()
   {

       IPAddress clientIpAddress = HttpContext.Connection.RemoteIpAddress;
       string ipAddress = clientIpAddress.ToString();
       string redisKey = $"rate-limit:{ipAddress}"; // 构建Redis键名  
  
       // 获取当前时间戳(可以是Unix时间戳或任何你选择的格式)  
       long currentTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();

       // 尝试从Redis获取时间戳  
       var redisValue = await Db.StringGetAsync(redisKey);
       long lastTimestamp = redisValue.HasValue ? (long)redisValue : 0;

       // 检查是否超过10秒  
       if (currentTimestamp - lastTimestamp >= 10)
       {
           // 如果超过10秒,则允许请求并更新Redis键  
           await Db.StringSetAsync(redisKey, currentTimestamp, TimeSpan.FromSeconds(10)); // 设置键的过期时间为10秒  
                                                                                        
           return Content("Request allowed.");
       }
       else
       {
           // 如果未超过10秒,则拒绝请求  
           HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.TooManyRequests)
           {
               ReasonPhrase = "Too Many Requests",
               Content = new StringContent("Rate limit exceeded. Please try again later.")
           };
           // 处理请求
           return Content("Please try again later. ");// throw new HttpResponseException(response); // 或者返回自定义的ActionResult  
       }
   }

扩展为参数

csharp 复制代码
MyAction(string p)
//...
string redisKey = $"rate-limit:{p}";

请求

/MyAction?p=2

/MyAction?p=3

滑动窗口算法

滑动窗口算法(Sliding Window Algorithm)是一种用于解决字符串/数组 问题的算法,它通过维护一个窗口(即一个连续的子串或子数组),并在字符串或数组上滑动这个窗口来寻找满足特定条件的子串或子数组。以下是滑动窗口算法的主要内容和特点:

维护窗口 :通过两个指针(左指针和右指针)来定义窗口的边界。
移动窗口 :通过移动右指针来扩展窗口,同时根据问题的要求调整左指针来缩小窗口。
更新信息:在窗口滑动的过程中,根据需要更新一些数据结构(如哈希表)来保存所需的信息。

实现方法
步骤1.初始化 :定义左指针和右指针,并初始化它们的位置。
步骤2.扩展窗口 :向右移动右指针,扩展窗口,同时更新所需的信息(如字符频率的哈希表)。
步骤3.检查条件 :当窗口满足特定条件时,开始收缩窗口。
步骤4.收缩窗口 :向右移动左指针,缩小窗口,同时更新所需的信息。
步骤5.更新最优解 :在收缩窗口的过程中,不断更新最优解(如最长子串、最短子串等)。
重复步骤:重复步骤2到步骤5,直到右指针到达字符串或数组的末尾。

在Redis中维护一个窗口内的请求时间戳列表,而不是仅仅存储最后一次请求的时间戳。移除超过窗口大小的时间戳。检查剩余的时间戳数是否超过了最大请求数 MaxRequests。如果超过,则返回超过的响应;否则,记录当前时间戳并允许请求。

csharp 复制代码
			private const int MaxRequests = 5; // 最大请求数
			private const int WindowSizeInSeconds = 10; // 窗口大小(秒)
			//...

            // 获取Redis中存储的时间戳列表
            var redisValue = await Db.ListRangeAsync(redisKey);
            var timestamps = redisValue.Select(value => (long)value).ToList();

            // 移除窗口之外的时间戳
            timestamps = timestamps.Where(timestamp => currentTimestamp - timestamp <= WindowSizeInSeconds).ToList();

            if (timestamps.Count >= MaxRequests)
            {
                // 如果请求数超过限制,则拒绝请求
                HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.TooManyRequests)
                {
                    ReasonPhrase = "Too Many Requests",
                    Content = new StringContent("Rate limit exceeded. Please try again later.")
                };
                return Content("Please try again later.");
            }
            else
            {
                // 如果请求数未超过限制,则允许请求并记录当前时间戳
                timestamps.Add(currentTimestamp);
                await Db.ListRightPushAsync(redisKey, timestamps.Select(timestamp => (RedisValue)timestamp).ToArray());
                await Db.KeyExpireAsync(redisKey, TimeSpan.FromSeconds(WindowSizeInSeconds)); // 设置键的过期时间为窗口大小

                return Content("Request allowed.");
            }

End

相关推荐
Jeff-Tsai12 小时前
Day 20:多语言定制与资源结构
.net·架构设计·erp·definition-driven
Jeff-Tsai1 天前
Day 19:定制层的范围与叠加粒度
.net·架构设计·erp·definition-driven
Jeff-Tsai3 天前
Day 18:错误契约与用户可见消息
.net·架构设计·erp·definition-driven
IT规划师3 天前
HybridCache-缓存架构决策框架落地
架构·.net
Jeff-Tsai4 天前
Day 17:Connector 与调用端的职责收敛
.net·架构设计·erp·definition-driven
IT规划师4 天前
NET10 高并发架构契约与落地实现
架构·.net
公子小六4 天前
基于.NET的Windows窗体编程之WinForms数据持久化(1)
windows·microsoft·c#·.net·winforms
Jeff-Tsai5 天前
Day 16:JSON-RPC 2.0:协议选型与请求管道
.net·架构设计·erp·definition-driven
IT规划师5 天前
Redis 数据类型全解 - Stream 深度:Pending 回收、重试死信与消息队列选型(十一)
redis·.net
学以智用5 天前
ASP.NET MVC5(.NET Framework)底层完整运行逻辑
.net