限流器设计思路(浅入门)

news2024/11/19 3:46:44

限流器(Rate Limiter)是一种用于控制系统资源利用率和质量的重要机制。它通过限制单位时间内可以执行的操作数量,从而防止系统过载和保护服务的可靠性。在程序设计中,可以使用多种方式来实现限流器,下面是几个常见方案的介绍:

  • 令牌桶算法

  • 漏桶算法

  • 划窗算法

  • 固定窗口算法(缺点很大!)

  • 基于计数器的流量控制算法

  • ...

令牌桶算法(Token Bucket)

令牌桶算法是一种常见的限流实现方式。它维护一个存放令牌的桶,以固定的速率向桶中添加令牌。每次请求到来时,需要从桶中获取一个令牌,只有当桶中有足够的令牌时,请求才能被处理。否则,请求将被拒绝或阻塞。

image

实现思路如下:

  • 维护一个固定大小的令牌桶和一个记录上一次令牌被添加到桶中的时间戳。

  • 以固定的速率(每秒生成的令牌数)向桶中添加令牌。

  • 当请求到来时,尝试从桶中获取一个令牌。如果桶中有令牌,则处理请求并消耗一个令牌;否则,拒绝或阻塞请求。

  • 使用锁或其他同步机制来保证线程安全。

code:

package RateLimiter;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

// 令牌桶算法 (TokenBucketRateLimiter)
public class TokenBucketRateLimiter {
    /**
     * REFILL_PERIOD 表示令牌桶的refill周期,即每隔多长时间(秒)向桶中添加令牌。
     * MAX_TOKENS 表示令牌桶的最大容量,即桶中最多可以存放多少个令牌。
     * REFILL_TOKENS 表示每个refill周期向桶中添加的令牌数量。
     * lastRefillTimestamp 记录上一次refill的时间戳。
     * tokenBucket 是一个Semaphore实例,用于模拟令牌桶的行为。
     */
    private static final long REFILL_PERIOD = 1; // 1秒
    private static final long MAX_TOKENS = 5; // 桶容量
    private static final long REFILL_TOKENS = 2; // 每次添加令牌数
    /**
     * AtomicLong是Java中用于表示一个原子性的长整型值的类。它提供了一些原子操作方法,用于在多线程环境下安全地更新和访问长整型值。
     *  - 在这些限流器实现中,AtomicLong主要用于记录上一次令牌/请求刷新的时间戳。
     *  - 由于多个线程可能同时尝试获取令牌或请求,因此需要确保对时间戳的读写操作是原子性的,以避免竞态条件。
     */
    private AtomicLong lastRefillTimestamp = new AtomicLong(System.nanoTime());
    /**
     * Semaphore(信号量)是Java中一个并发控制工具,用于控制对共享资源的访问。
     *      - 它基于计数器的原理,可以限制同时访问某个资源的线程数量。用于模拟令牌桶的行为。
     *      - Semaphore使用acquire()和release()方法来获取和释放信号量:
     */
    private Semaphore tokenBucket = new Semaphore((int) MAX_TOKENS);

    /**
     * tryAcquire() 方法是获取令牌的入口:
     *
     * 1-获取当前时间戳 now。
     * 2-根据当前时间戳和上一次refill时间戳,计算出这段时间内应该添加多少个令牌 newTokens。
     * 3-更新上一次refill时间戳为当前时间戳。
     * 4-将新的令牌数量 newTokens 释放到 tokenBucket 中。
     * 5-尝试从 tokenBucket 中获取一个令牌,如果成功则返回 true,否则返回 false。
     */
    public boolean tryAcquire() {
        long now = System.nanoTime();
        long lastRefillTime = lastRefillTimestamp.get();
        long newTokens = calculateNewTokens(lastRefillTime, now);
        lastRefillTimestamp.set(now);

        tokenBucket.release((int) newTokens);
        return tokenBucket.tryAcquire();
    }

    /**
     * calculateNewTokens() 方法根据时间差计算出应该添加的令牌数量,但不会超过桶的最大容量。
     */
    private long calculateNewTokens(long lastRefillTime, long now) {
        long nanosElapsed = now - lastRefillTime;
        long refillPeriodCount = nanosElapsed / TimeUnit.SECONDS.toNanos(REFILL_PERIOD);
        return Math.min(refillPeriodCount * REFILL_TOKENS, MAX_TOKENS);
    }
}

漏桶算法(Leaky Bucket)

漏桶算法类似于令牌桶算法,不同之处在于它维护一个存放请求的队列,而不是令牌桶。当请求到来时,它们会被添加到队列中。队列以固定的速率漏水,即以固定的速率处理请求。

image

实现思路如下:

  • 维护一个固定大小的请求队列和一个上次处理请求的时间戳。

  • 当请求到来时,将其添加到队列中。如果队列已满,则拒绝或阻塞请求。

  • 以固定的速率(每秒处理的请求数)从队列中取出请求并处理。

  • 使用锁或其他同步机制来保证线程安全。

code:

package RateLimiter;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

// 漏桶算法 (LeakyBucketRateLimiter)
public class LeakyBucketRateLimiter {
    /**
     * REFILL_PERIOD 表示漏桶的refill周期,即每隔多长时间(秒)处理请求。
     * MAX_REQUESTS 表示漏桶的最大容量,即桶中最多可以存放多少个请求。
     * REFILL_REQUESTS 表示每个refill周期处理的请求数量。
     * requestQueue 是一个队列,用于存放待处理的请求。
     * lastRefillTimestamp 记录上一次refill的时间戳。
     */
    private static final long REFILL_PERIOD = 1; // 1秒
    private static final long MAX_REQUESTS = 5; // 桶容量
    private static final long REFILL_REQUESTS = 2; // 每次处理请求数

    private Queue<Long> requestQueue = new LinkedList<>();
    private AtomicLong lastRefillTimestamp = new AtomicLong(System.nanoTime());

    /**
     * tryAcquire() 方法是获取请求的入口:
     *  - 获取当前时间戳 now。
     *  - 根据当前时间戳和上一次refill时间戳,计算出这段时间内应该处理多少个请求 newRequests。
     *  - 更新上一次refill时间戳为当前时间戳。
     *  - 将新的请求数量 newRequests 添加到 requestQueue 中,如果队列已满则移除最早的请求。
     *  - 如果队列大小不超过最大容量 MAX_REQUESTS,则返回 true,否则返回 false。
     */
    public boolean tryAcquire() {
        long now = System.nanoTime();
        long lastRefillTime = lastRefillTimestamp.get();
        long newRequests = calculateNewRequests(lastRefillTime, now);
        lastRefillTimestamp.set(now);

        for (long i = 0; i < newRequests; i++) {
            if (requestQueue.size() >= MAX_REQUESTS) {
                requestQueue.poll();
            }
            requestQueue.offer(now);
        }

        return requestQueue.size() <= MAX_REQUESTS;
    }

    /**
     * calculateNewRequests() 方法根据时间差计算出应该处理的请求数量。
     */
    private long calculateNewRequests(long lastRefillTime, long now) {
        long nanosElapsed = now - lastRefillTime;
        long refillPeriodCount = nanosElapsed / TimeUnit.SECONDS.toNanos(REFILL_PERIOD);
        return refillPeriodCount * REFILL_REQUESTS;
    }
}

滑动窗口(Sliding Window)

滑动窗口算法通过维护一个固定大小的窗口来限制单位时间内的请求数。当请求到来时,它会检查窗口内的请求数是否已达到限制。如果没有,则允许请求;否则,拒绝或阻塞请求。窗口会随着时间推移而滑动,移除较早的请求记录。

冷知识:TCP协议中数据包的传输,同样也是采用滑动窗口来进行流量控制。

实现思路如下:

  • 维护一个队列或其他数据结构来存储请求的时间戳。

  • 当请求到来时,将其时间戳添加到队列中。

  • 检查队列中最近的一段时间内(窗口大小)的请求数是否超过限制。如果没有,则允许请求;否则,拒绝或阻塞请求。

  • 定期(或在每次请求到来时)移除队列中较早的请求记录,以维护窗口大小。

  • 使用锁或其他同步机制来保证线程安全。

Reference:限流之固定窗口/滑动窗口计数法理解-CSDN博客

原理:需要先看看固定窗口算法的原理和缺点,

image

动图:

image

image

image

code:

package RateLimiter;
import java.util.LinkedList;
import java.util.Queue;

/**
 * 滑动窗口算法 (SlidingWindowRateLimiter)
 */
public class SlidingWindowRateLimiter {
    /**
     * WINDOW_SIZE 表示滑动窗口的大小(秒)。
     * MAX_REQUESTS 表示窗口内允许的最大请求数量。
     * requestTimestamps 是一个队列,用于存放请求的时间戳。
     */
    private static final long WINDOW_SIZE = 5; // 窗口大小(秒)
    private static final long MAX_REQUESTS = 10; // 最大请求数
    private Queue<Long> requestTimestamps = new LinkedList<>();

    /**
     * tryAcquire() 方法是获取请求的入口:
     *  - 获取当前时间戳 now。
     *  - 将当前时间戳添加到 requestTimestamps 队列中。
     *  - 计算窗口的开始时间 windowStartTime。
     *  - 移除队列中早于 windowStartTime 的时间戳,即移除窗口之外的请求记录。
     *  - 如果队列大小不超过 MAX_REQUESTS,则返回 true,否则返回 false。
     */
    public boolean tryAcquire() {
        long now = System.currentTimeMillis();
        requestTimestamps.add(now);

        long windowStartTime = now - WINDOW_SIZE * 1000;
        while (!requestTimestamps.isEmpty() && requestTimestamps.peek() < windowStartTime) {
            requestTimestamps.poll();
        }

        return requestTimestamps.size() <= MAX_REQUESTS;
    }
}

演示code:

package RateLimiter;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class RateLimiterDemo {
    public static void main(String[] args) {
        // 创建限流器实例
        TokenBucketRateLimiter tokenBucketRateLimiter = new TokenBucketRateLimiter();
        LeakyBucketRateLimiter leakyBucketRateLimiter = new LeakyBucketRateLimiter();
        SlidingWindowRateLimiter slidingWindowRateLimiter = new SlidingWindowRateLimiter();

        // 创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        // 提交任务
        for (int i = 0; i < 20; i++) {
            executorService.submit(() -> {
                boolean tokenBucketAllowed = tokenBucketRateLimiter.tryAcquire();
                boolean leakyBucketAllowed = leakyBucketRateLimiter.tryAcquire();
                boolean slidingWindowAllowed = slidingWindowRateLimiter.tryAcquire();

                System.out.println("Token Bucket: " + tokenBucketAllowed +
                        ", Leaky Bucket: " + leakyBucketAllowed +
                        ", Sliding Window: " + slidingWindowAllowed);

                try {
                    TimeUnit.MILLISECONDS.sleep(500); // 模拟处理请求
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }

        // 关闭线程池
        executorService.shutdown();
    }
}

out:

Token Bucket: true, Leaky Bucket: true, Sliding Window: true
Token Bucket: false, Leaky Bucket: true, Sliding Window: true
Token Bucket: false, Leaky Bucket: true, Sliding Window: true
Token Bucket: false, Leaky Bucket: true, Sliding Window: true
Token Bucket: true, Leaky Bucket: true, Sliding Window: true
Token Bucket: false, Leaky Bucket: true, Sliding Window: true
Token Bucket: false, Leaky Bucket: true, Sliding Window: true
Token Bucket: true, Leaky Bucket: true, Sliding Window: true
Token Bucket: true, Leaky Bucket: true, Sliding Window: true
Token Bucket: true, Leaky Bucket: true, Sliding Window: true
Token Bucket: false, Leaky Bucket: true, Sliding Window: false
Token Bucket: false, Leaky Bucket: true, Sliding Window: false
Token Bucket: false, Leaky Bucket: true, Sliding Window: false
Token Bucket: false, Leaky Bucket: true, Sliding Window: false
Token Bucket: false, Leaky Bucket: true, Sliding Window: false
Token Bucket: false, Leaky Bucket: true, Sliding Window: false
Token Bucket: false, Leaky Bucket: true, Sliding Window: false
Token Bucket: false, Leaky Bucket: true, Sliding Window: false
Token Bucket: false, Leaky Bucket: true, Sliding Window: false
Token Bucket: false, Leaky Bucket: true, Sliding Window: false

总结

这三种算法都是通过控制请求的速率或数量来实现限流,但具体的实现方式有所不同。令牌桶算法和漏桶算法都依赖于时间来控制速率,而滑动窗口算法则是基于请求数量来控制。它们各有优缺点,适合不同的场景。具体选择哪种需要自己根据应用场景进行选择和调整。

同时,也可以考虑使用现有的限流器库或框架,如Guava RateLimiter、Netflix Hystrix等,以简化开发过程。

文章转载自:Mysticbinary

原文链接:https://www.cnblogs.com/mysticbinary/p/18237664

体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1811754.html

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

相关文章

这三款很实用的工具,助你事半功倍!

AllDup——删除重复文件的工具 AllDup是一款用于删除电脑内重复文件的工具&#xff0c;避免重复占用电脑的存储空间&#xff0c;支持搜索整个文件夹中的重复文件或者使用对比功能&#xff0c;查询出不同文件夹中相同的文件。 使用排除功能&#xff0c;可以过滤掉某些不需要搜索…

SpringCloudAlibaba组件集成

SpringCloudAlibaba组件集成 Nacos服务注册与发现 1.Nacos认识与安装 1.1.什么是Nacos Nacos和Eureka有着相同的能力&#xff0c;甚至更为强大&#xff0c;作为Dubbo 生态系统中重要的注册中心实现。官方对它有如下定义&#xff1a; Nacos致力于帮助您发现&#xff0c;配置…

亚信安慧AntDB数据库与云信达eCloud Data Master 云数据管理系统软件V4完成兼容性互认证

日前&#xff0c;湖南亚信安慧科技有限公司&#xff08;简称&#xff1a;亚信安慧&#xff09;与南京云信达科技有限公司&#xff08;简称&#xff1a;云信达&#xff09;&#xff0c;完成了AntDB数据库产品与云信达eCloud Data Master云数据管理系统软件V4的兼容性互认证。 双…

javaWeb项目-在线考试系统详细功能介绍

项目关键技术 开发工具&#xff1a;IDEA 、Eclipse 编程语言: Java 数据库: MySQL5.7 框架&#xff1a;ssm、Springboot 前端&#xff1a;Vue、ElementUI 关键技术&#xff1a;springboot、SSM、vue、MYSQL、MAVEN 数据库工具&#xff1a;Navicat、SQLyog 1、Java简介 Java语…

mysql中 事务的隔离级别与MVCC

大家好。今天我们来讲一下事务的隔离级别和MVCC。在讲之前&#xff0c;我们先创建一张表&#xff0c;方便我们的讲解&#xff1a; CREATE TABLE hero ( number INT, name VARCHAR(100), country varchar(100), PRIMARY KEY (number) ) EngineInnoDB CHARSETutf8;创建完毕后我…

专业编程显示器明基RD280U,赋能开发者共创科技新纪元

距离ChatGPT问世仅有17个月&#xff0c;OpenAI就推出了颠覆生产力的GPT-4o。这是整个智能时代的一次再进化&#xff0c;GPT-4o不但能实时处理文本、音频和图像&#xff0c;甚至能在232毫秒内实时响应音频输入&#xff0c;几乎与真人对话无异。 当人们惊叹于这项“充满人性”的…

Docker未授权访问漏洞详解

目录 Docker简介 Docker未授权访问漏洞原理 Docker未授权访问漏洞复现 环境搭建 漏洞利用 写入密钥免密登录 写入计划任务反弹shell fofa实战 Docker简介 Docker是一个开源的引擎&#xff0c;可以轻松地为任何应用创建一个轻量级的、可移植的、自给自足的容器。 Docke…

DDei在线设计器-DDeiCore-图形插件

DDei-Core-图形 DDei-Core-图形插件包含了基础绘图形状与基础流程形状两个分组&#xff0c;大约100来个图形&#xff0c;能够满足很基本的框图、架构图、流程图的绘制。 图形以分组的形式组织&#xff0c;一个分组中包含多个图形&#xff0c;一个图形也能够同时存在于多个分组。…

李廉洋:6.11黄金原油持续震荡,今日美盘最新分析策略。

黄金消息面分析&#xff1a;周一&#xff08;6月10日&#xff09;金价有所回升&#xff0c;此前一个交易日&#xff0c;金价创下了三年半以来的最大跌幅&#xff0c;原因是中国和美国公布的数据令押注中国需求和美联储降息的投机者感到失望。截至发稿&#xff0c;现货金上涨0.5…

【Unity+AI01】在Unity中调用DeepSeek大模型!实现AI对话功能!

要在Unity中调用DeepSeek的API并实现用户输入文本后返回对话的功能&#xff0c;你需要遵循以下步骤&#xff1a; 获取API密钥&#xff1a; 首先&#xff0c;你需要从DeepSeek获取API密钥。这通常涉及到注册账户&#xff0c;并可能需要订阅相应的服务。 集成HTTP请求库&#xf…

西南交通大学【操作系统实验6】

实验目的 学习如何产生一个系统调用&#xff0c;以及怎样通过往内核中增加一个新函数&#xff0c;从而在内核空间中实现对用户空间的读/写。学习重建内核。 实验内容 &#xff08;1&#xff09;设计并实现一个新的系统调用pedagogictime() &#xff0c;该函数通过使用一个引用…

Stable diffusion的SDXL模型,针不错!(含实操)

与之前的SD1.5大模型不同&#xff0c;这次的SDXL在架构上采用了“两步走”的生图方式&#xff1a; 以往SD1.5大模型&#xff0c;生成步骤为 Prompt → Base → Image&#xff0c;比较简单直接&#xff1b;而这次的SDXL大模型则是在中间加了一步 Refiner。Refiner的作用是什么呢…

Base64编码和ASCII编码

1字节Byte 8比特bit 单位换算关系Byte1Byte 8bitKB1024ByteMB1024KBGB1024MB 网速中的Mbps和MB/s Mbps 1M bit/s 1024 K bit/s 1024 * 1024 bit/s MB/s 1024 KB/s 1024 * 1024 B/s 1024 * 1024 * 8 bit/s 所以&#xff1a;1Mbps 1/8 MB/s 1个bit&#xff0c;可以表示…

利用Python的NLTK库来查询指定单词的同义词

一、NTLK库介绍 NLTK&#xff08;Natural Language Toolkit&#xff09; 是Python中最为知名的自然语言处理&#xff08;NLP&#xff09;库之一&#xff0c;它提供了丰富的模块和数据结构&#xff0c;专门用于人类语言数据的统计自然语言处理。它包含了文本处理库用于分类、标记…

手机丢失不惊慌,华为手机已升级至楼层级设备查找!

出门总是丢三落四&#xff0c;手机丢了怎么办&#xff1f;不要怕&#xff0c;只要你的华为手机升级至云空间新版本&#xff0c;就可以进行楼层级设备查找&#xff0c;现在可以查看到具体的楼层了&#xff01; 之前有手机丢失过的朋友&#xff0c;肯定有相似的经历&#xff0c…

网页宽度现在右侧有一个竖条空白,怎么啦车网站都没办法完全铺满宽度,怎么回事怎么解决

问: 网页宽度怎么设置全覆盖 回答: 经过检查,发现 是这个网站的最外层html标签设置了宽度,所以导致,当我们删除html的宽度后就解决了这个问题.

【会议征稿,ACM出版】2024年粤港澳大湾区教育数字化与计算机科学国际学术会议(EDCS 2024,6月21-23)

人工智能、区块链、虚拟现实技术等新一轮技术革命正在推进社会结构变革&#xff1b;数字化转型正在重塑社会、劳动力市场和未来工作形式&#xff0c;其中关于教育领域&#xff0c;2019&#xff0c;2020的冠状病毒病大流行给全球教育带来巨大挑战&#xff0c;加速了教育数字化转…

海南聚广众达电子商务咨询有限公司打造一站式电商服务

在数字经济的浪潮中&#xff0c;电商行业蓬勃发展&#xff0c;各种平台和服务商如雨后春笋般涌现。其中&#xff0c;海南聚广众达电子商务咨询有限公司凭借其专业的团队和丰富的经验&#xff0c;在抖音电商服务领域独树一帜&#xff0c;成为业界的佼佼者。 海南聚广众达电子商…

Advanced Intelligent Systems 清华大学曲钧天团队设计了基于光纤传感技术的多模态触觉感知仿生机械手

随着软体机器人技术的发展&#xff0c;触觉感知在人机安全交互、可穿戴设备和医疗器械领域发挥着重要作用。如何用简单的系统集成更多的触觉感知、获取更多交互信息面临着诸多挑战。 近日&#xff0c;清华大学国际研究生院曲钧天助理教授团队在国际期刊Advanced Intelligent S…

python的resample()函数

介绍 在Python中,resample()函数是一个常用的工具,用于对时间序列数据进行重新采样。这个函数可以将时间序列数据从一个频率转换为另一个频率,比如将每天的数据转换为每月的数据。在本教程中,我将向你展示如何使用resample()函数,并解释每个步骤的具体含义。 整体流程 首先…