当前位置: 首页 > news >正文

ASP.NET限流器的简单实现

一、滑动时间窗口

我为RateLimiter定义了如下这个简单的IRateLimiter接口,唯一的无参方法TryAcquire利用返回的布尔值确定当前是否超出设定的速率限制。我只提供的两种基于时间窗口的实现,如下所示的基于“滑动时间窗口”的实现类型SliddingWindowRateLimiter,我们在构造的时候指定时间窗口和阈值。SliddingWindowRateLimiter采用一种“讨巧”的实现,它直接利用了BoundedChannel<DateTimeOffset>对象,我们将指定的阈值作为它的最大容量。

public interface IRateLimiter
{bool TryAcquire();
}public sealed class SliddingWindowRateLimiter: IRateLimiter
{private readonly TimeSpan _window;private readonly ChannelReader<DateTimeOffset> _reader;private readonly ChannelWriter<DateTimeOffset> _writer;public SliddingWindowRateLimiter(TimeSpan window, int permit){_window = window;var options = new BoundedChannelOptions (permit){FullMode = BoundedChannelFullMode.Wait,SingleReader = false,SingleWriter = true};var channel = Channel.CreateBounded<DateTimeOffset>(options);_reader = channel.Reader;_writer = channel.Writer;Task.Factory.StartNew(Trim,TaskCreationOptions.LongRunning);}public bool TryAcquire() => _writer.TryWrite(DateTimeOffset.UtcNow);private void Trim(){if (!_reader.TryPeek(out var timestamp)){Task.Delay(_window).Wait();Trim();}else{var delay = _window - (DateTimeOffset.UtcNow - timestamp);if (delay > TimeSpan.Zero){Task.Delay(delay).Wait();Trim();}else{var valueTask = _reader.ReadAsync();if (!valueTask.IsCompleted) _ = valueTask.Result;Trim();}}}
}

在实现的TryAcquire方法中,我们试着将当前时间戳写入这个Channel,并将写入的结果(成功或者失败)作为返回值。为了让Channel中只包含指定时间窗口的时间戳,我们利用一个LongRuning的Task执行Trim方法对过期的时间戳进行“裁剪”。Trim会调用ChannelReader的TRyPeek方法,如果返回False,意味着Channel为空,此时会等待一段窗口时间再进行“裁剪”。如果提取出来时间戳在Now-Window与当前时间之间,意味着Channel里面的时间戳均在设定的窗口内,此时同样需要等待,等待时间为Window - (Now - Timestamp);只有在提取的时间超出窗口范围,我们才需要将其从Channel中移除。

var limiter = new SliddingWindowRateLimiter(TimeSpan.FromSeconds(2),2);var index = 0;
await Task.WhenAll( Enumerable.Range(1, 100).Select(_ => Task.Run(() => {while (true){if (limiter.TryAcquire()){Console.WriteLine($"[{DateTimeOffset.Now}]{Interlocked.Increment(ref index)}");} }})));

我们在上面的演示程序中使用这个SliddingWindowRateLimiter,设定的限速规则为 2/2s。我们创建了100个Task并发地调用这个SliddingWindowRateLimiter,并将它返回True时的时间戳显示出来,具体输出如下所示。

image

二、固定时间窗口

如下这个FixedWindowRateLimiter类型是针对“固定窗口”的实现,字段_windowTicks和_permit同样表示时间窗口的时长(这里我们使用Int64类型的Ticks属性)和阈值。 _nextWindowStartTimeTicks表示下一次固定窗口的起始时间,这个需要动态调整,为了确保只有一个线程能够修改它,我们定义了_windowReseting这个“信号量”。_count是一个计数器,我们使用它确定是否“超速”。

public sealed class FixedWindowRateLimiter : IRateLimiter
{private readonly long _windowTicks;private readonly int _permit;private long _nextWindowStartTimeTicks;private volatile int _count = 0;public FixedWindowRateLimiter(TimeSpan window, int permit){_windowTicks = window.Ticks;_permit = permit;_nextWindowStartTimeTicks = DateTimeOffset.UtcNow.Add(window).Ticks;}public bool TryAcquire(){// 超出时间窗口,重置计数器,并调整下一个时间窗口的开始时间var now = DateTimeOffset.UtcNow.Ticks;var nextWindowStartTimeTicks = nextWindowStartTimeTicks;if (now >= nextWindowStartTimeTicks && Interlocked.CompareExchange(ref _nextWindowStartTimeTicks, now + _windowTicks, nextWindowStartTimeTicks) == nextWindowStartTimeTicks){Interlocked.Exchange(ref _count, 1);return true;}return _count < _permit && Interlocked.Increment(ref _count) <= _permit;}
}

在实现的TryAcquire方法中,我们先确定当前时间是否超过了设定的“下一个窗口开始时间”,如果是则调用Interlocked.CompareExchange方法修改__nextWindowStartTimeTicks字段。成功修改__nextWindowStartTimeTicks的线程会调整窗口开始时间,并重置计数器_count为1,并返回True。如果计数器大于等于设定阈值,方法返回False。否则我们让计数器+1,如果该值<=阈值,返回True,否则返回False。

IRateLimiter limiter = new FixedWindowRateLimiter(window: TimeSpan.FromSeconds(2), permit: 2);var index = 0;
await Task.WhenAll( Enumerable.Range(1, 100).Select(_ => Task.Run(() => {while (true){if (limiter.TryAcquire()){Console.WriteLine($"[{DateTimeOffset.Now}]{Interlocked.Increment(ref index)}");}       }})));

将FixedWindowRateLimiter应用到上面的演示程序,依然能得到我们希望的输出结果。

image

http://www.lryc.cn/news/232431.html

相关文章:

  • 汇编语言循环左移和循环右移如何实现的,详细的比喻一下
  • ChromeDriver 各版本下载地址
  • 计算机网络之物理层
  • 沉浸式航天vr科普馆VR太空主题馆展示
  • AI电话机器人能否代替人工?优缺点介绍
  • Java —— 多态
  • UI自动化测试(弹出框,多窗口)
  • Python爬虫程序网络请求及内容解析
  • C嘎嘎模板
  • 数据结构和算法八股与手撕
  • windiws docker 部署jar window部署docker 转载
  • 使用git上传代码至gitee入门(1)
  • 分类预测 | MATLAB实现基于Isomap降维算法与改进蜜獾算法IHBA的Adaboost-SVM集成多输入分类预测
  • 如何解决3d max渲染效果图全白这类异常问题?
  • 振南技术干货集:比萨斜塔要倒了,倾斜传感器快来!(2)
  • 图形学 -- Geometry几何
  • opencv中边缘检测的方法
  • DigitalVirt 洛杉矶 CMIN2 VPS 测评
  • Qt DragDrop拖动与放置
  • thinkphp8 多级控制器调用
  • 设计测试用例的6种基本原则
  • java的Exception.getMessage为null
  • EXTI (2)
  • Django实战项目-学习任务系统-任务完成率统计
  • 安卓调用手机邮箱应用发送邮件
  • Vue-Pinia
  • C语言,编写程序输出半径为1到15的圆的面积,若面积在30到100之间则予以输出,否则,不予输出
  • Ansys Electronics Desktop仿真——HFSS线圈寄生电阻,电感
  • 对数据库密码使用MD5加密算法加密,并进行登录验证
  • 关于Chrome中F12调试Console输入多行