ASP.NET限流器的简单实现

news2024/11/23 11:43:31

一、滑动时间窗口

我为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.coloradmin.cn/o/1212854.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【LeetCode刷题-滑动窗口】--1658.将x减到0的最小操作数

1658.将x减到0的最小操作数 思路与算法&#xff1a; 根据题目描述&#xff0c;在每一次操作中&#xff0c;可以移除数组nums最左边和最右边的元素&#xff0c;因此&#xff0c;在所有的操作完成后&#xff0c;数组nums的一个前缀以及一个后缀被移除&#xff0c;并且它们的和恰…

6.1810: Operating System Engineering <LEC 1>

课程链接&#xff1a;6.1810 / Fall 2023 一、本节任务 实验环境&#xff1a; 二、introduction and examples 2.1 open(), read(), write(), close(), dup() 使用 open 打开或创建文件&#xff0c;得到文件描述符 fd&#xff0c;再对 fd 进行 read 或者 write 操作。每个进…

绩效考核管理项目|记录1

项目用C#winformSQL Server写的&#xff0c;现在记录一下学习到的新东西。 winform工具 splitContainer&#xff1a;分割出两个容器&#xff0c;能添加面板之类的工具 treeview&#xff1a;展示标签页的分层集合&#xff08;用户管理、基数管理......&#xff09;&#xff0…

现货黄金职业交易员怎么使用技术分析?

职业的交易员每天要处理很多不同的信息&#xff0c;其中只一部分是涉及技术指标。在这一部分处理技术分析的时间里&#xff0c;只能再分出少之又少的时间给技术指标。那职业交易员会利用做技术指标做什么呢&#xff1f;下面我们就来讨论一下。 识别行情。技术指标的主要作用就是…

codemirror怎么高亮指定文本

先上图 ![在这里插入图片描述](https://img-blog.csdnimg.cn/c070cde11a314c45a859ccb0ca7f41c7.png 产品经理的需求是编辑框中用了占位符的话&#xff0c;占位符中的变量高亮&#xff0c;简单吧&#xff01;还有一个附加要求&#xff0c;必须是外部定义的变量&#xff0c;不…

单区域OSPF配置

配置命令步骤&#xff1a; 1.使用router ospf 进程ID编号 启用OSPF路由 2.使用network 直连网络地址 反掩码 area 0 将其归于区域0 注意&#xff1a;1.进程ID编号可任意&#xff08;1-65535&#xff09;2.反掩码用4个255相减得到 如下图&#xff0c;根据给出要求配置OSPF单区…

【前端】使用json-server报错

当我们使用json-server模仿后端接口时需要运行json-server --watch index.json这个命令生成增删改查接口但是可能会报这个错误&#xff0c;如图 这时我们运行 npm i json-server -g命令即可&#xff0c;然后再重新运行json-server --watch index.json就行了

如何快速将钉钉员工信息同步到飞书

当企业内部在使用钉钉跟飞书时&#xff0c;那么当钉钉员工信息发生更改时&#xff0c;我们应该如何将信息快速同步到飞书上呢&#xff0c;接下来我们借助RestCloud AppLink平台进行演示。 第一步&#xff1a;获得钉钉以及飞书认证授权 钉钉授权 钉钉接入采用自建应用的方式&…

数字化档案管理系统解决方案

数字化档案管理系统解决方案是指将传统的纸质档案转化为数字化档案&#xff0c;采用计算机和网络技术实现对档案的存储、检索、管理、共享等操作的过程。 专久智能数字化档案管理解决方案及措施可以包括以下几个方面&#xff1a; 1. 采用数字化技术对档案进行数字化处理&#x…

拜耳阵列(Bayer Pattern)以及常见彩色滤波矩阵(CFA)

一、拜耳阵列的来源 图像传感器将光线转化成电流&#xff0c;光线越亮&#xff0c;电流的数值就越大&#xff1b;光线越暗&#xff0c;电流的数值就越小。图像传感器只能感受光的强弱&#xff0c;无法感受光的波长。由于光的颜色由波长决定&#xff0c;所以图像传播器无法记录…

北大腾讯打造多模态15边形战士!语言作“纽带”,拳打脚踢各模态,超越Imagebind

AI4Happiness 投稿 量子位 | 公众号 QbitAI 北大联合腾讯打造了一个多模态15边形战士&#xff01; 以语言为中心&#xff0c;“拳打脚踢”视频、音频、深度、红外理解等各模态。 具体来说&#xff0c;研究人员提出了一个叫做LanguageBind的多模态预训练框架。 用语言作为与其…

如何在 Linux 上部署 RabbitMQ

如何在 Linux 上部署 RabbitMQ 文章目录 如何在 Linux 上部署 RabbitMQ安装 Erlang从预构建的二进制包安装从源代码编译 Erlang RabbitMQ 的安装使用 RabbitMQ Assistant 连接 RabbitMQ Assistant 是一款优秀的RabbitMQ 可视化管理工具&#xff0c;提供丰富的管理功能。下载地址…

亚里士多德的思想统治西方世界,实体论证明人不是猪

苏格拉底、柏拉图、亚里士多德&#xff0c;并称古希腊三贤。 公元前384年&#xff0c;亚里士多德出生在马其顿的奴隶主家庭&#xff0c;父亲是马其顿国王腓力二世的宫廷御医。 公元前367年&#xff0c;亚里士多德迁居到雅典&#xff0c;进入柏拉图学园&#xff0c;跟随柏拉图…

systemverilog:interface中的modport用法

使用modport可以将interface中的信号分组并指定方向&#xff0c;方向是从modport连接的模块看过来的。简单示例如下&#xff1a; interface cnt_if (input bit clk);logic rstn;logic load_en;logic [3:0] load;logic [7:0] count;modport TEST (input clk, count,output rst…

知识解读:香港轻量云/云服务器/VPS性能差距解读

​  提起香港轻量云/云服务器/VPS 这三类&#xff0c;往往汇聚了中小企业和开发者等群体的讨论声音。当然&#xff0c;这跟它们本身产品定位有关&#xff0c;加上在初级配置这块价格上相差不大&#xff0c;也因此经常被拿来对比。 首先来简单了解一下最基础的区别&#xff1a…

C嘎嘎模板

> 作者简介&#xff1a;დ旧言~&#xff0c;目前大二&#xff0c;现在学习Java&#xff0c;c&#xff0c;c&#xff0c;Python等 > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;了解什么是模板&#xff0c;并且能熟练运用函数模…

力扣160:相交链表

力扣160&#xff1a;相交链表 题目描述&#xff1a; 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 题目数据 保证 整个链式…

【MySQL】随手笔记(积跬步至千里)

一、常用函数 &#xff08;1&#xff09;uuid() select uuid(); //生成随机数 select replace(uuid(),-,); //将随机数的-去掉 select upper(replace(uuid(),-,)); ///改成大写应用举例&#xff1a; &#xff08;1&#xff09;非自增主键 非自增主键&#xff0c;insert语…

【LabVIEW学习】1.对labview的初步使用,控制数据流动,快捷键,参考手册打不开怎么办

一。初步使用labview 1.程序图标 2.打开之后继续点击新建VI 原因&#xff1a;最后的程序后缀就是 .vi 3.新建之后&#xff0c;会有三个界面&#xff08;没有不要紧&#xff0c;找找肯定有&#xff09; 4.程序操作方法 1.拖动控件到前面板 2.此时程序框图会出现对应的控件 拖动…

分享篇:最近在研究的AIGC内容

最近在研究AIGC自助生成报告的内容&#xff0c;分享一些查到的资料 前言&#xff08;一些使用心得&#xff09; 1、大模型会颠覆一些生产力&#xff0c;让强的人更强 归根到底&#xff0c;大模型是工具&#xff0c;和早些年的excel、python、ps没差&#xff0c;能不能用好工…