从零开始掌握限流技术:计数器、滑动窗口、漏桶与令牌桶详解

为什么需要限流呢?

🔹想象一下,你的服务器就像一个繁忙的餐馆,而你的应用就像是餐馆的服务员。餐馆里人山人海,每个人都在争先恐后地想要点餐。这时候,如果没有一个好的限流机制,会发生什么呢?

  1. 厨房厨师忙不过来:

    • 如果没有限流,服务员会不停地接单,导致厨房里的厨师忙得团团转,最后可能连基本的炒菜都做不好了。
    • 限流就像是给餐馆安装了一个“排队机”,确保顾客有序地排队等待点餐,这样厨师就能从容不迫地做好每一盘菜。
  2. 顾客体验变差:

    • 如果不限制人数,餐馆可能会变得拥挤不堪,顾客需要等很久才能得到服务,甚至可能会有人因为等不及而离开。
    • 限流就像是安排一个“领位员”,确保顾客不会等太久,每个人都能享受到愉快的用餐体验。
  3. 资源耗尽:

    • 如果不限制人数,餐馆可能会消耗掉所有的食材,导致后面来的顾客无餐可吃。
    • 限流就像是合理分配食材,确保每个人都能吃到美味的食物,而不是让少数人吃得太多,其他人饿肚子。
  4. 系统崩溃:

    • 如果不限制请求,服务器可能会因为负载过高而崩溃,就像餐馆因为人太多而导致椅子、桌子倒塌一样。
    • 限流就像是给餐馆加上一些结实的家具,确保即使有很多人也能保持稳定。

🔸总之,限流就像是给餐馆安装了一个智能的“门卫”系统,确保餐馆既能容纳足够的顾客,又能保证每个人都能获得良好的服务。而对于服务器来说,限流有助于保护资源,提升用户体验,避免系统过载,确保服务的稳定性和可靠性。

什么场景下需要限流?

随着互联网业务的发展,如秒杀活动双十一促销等场景,系统经常会面临高并发流量的挑战。在这种情况下,如果不加以限制,系统可能会因为流量过大而崩溃。为了避免这种情况发生,就需要采取限流措施来保护系统。

限流是指在一定时间内对请求量或并发数进行限制,以保护系统免受过大的负载压力。这样可以确保系统在高并发的情况下仍然能够稳定运行,同时也能够防止恶意攻击。常见的限流算法,有计数器算法滑动窗口计数算法漏桶算法令牌桶算法

在这里插入图片描述

计数器算法

⭐️简介

计数器算法通过在一个固定周期内累加访问次数来限制请求数量。在这个算法中,系统会在设定的时间窗口内记录所有请求的次数,并设置一个最大阈值。当在一个时间窗口内的请求次数达到设定的阈值时,系统会触发拒绝策略,拒绝所有后续请求。每当时间窗口结束,计数器会自动重置为零,开始新的计数周期。

例如,如果我们设置系统在1秒内最多允许100次请求,那么在计数器算法中,这1秒就被划分为一个时间窗口(因此计数器算法也称为固定窗口算法Fixed Window)。在每个时间窗口中,计数器记录当前的请求数量。一旦请求数超过100次,所有后续请求将在这个时间窗口内被拒绝,直到1秒结束,计数器重新开始记录。

在这里插入图片描述

计数器算法虽然简单高效,但存在一个关键问题:它难以处理非均匀分布的流量峰值。例如,在一个1秒的时间窗口内,如果在第1.9秒和第2.1秒分别出现了100次的瞬时高并发请求,虽然这两个时间点分别落在不同的1秒窗口内,但实际上在很短的时间内系统承受了200次请求,超出了设定的阈值。尽管其他时间段的流量是正常的,这种短时间内超过阈值的情况仍然可能导致系统出现问题。

在这里插入图片描述

🔥代码实现(Java)

这段代码定义了一个 SimpleCounterRateLimiter 类,它可以用来限制每秒内的请求数量。当每秒内的请求数量达到或超过设定的最大值时,新的请求将被拒绝。

import java.time.LocalTime;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;public class MyFixedWindowRateLimiterDemo {// 阈值private static final int QPS = 2;// 计数器private static final AtomicInteger counter = new AtomicInteger();// 初始化调度器,定期重置计数器private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);static {scheduler.scheduleAtFixedRate(() -> {counter.set(0);}, 0, 1, TimeUnit.SECONDS);  // 每1秒重置计数器}public static boolean tryAcquire() {return counter.incrementAndGet() <= QPS;}public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 10; i++) {Thread.sleep(250);LocalTime now = LocalTime.now();if (!tryAcquire()) {System.out.println(now + " 请求被限流");} else {System.out.println(now + " 请求通过");}}scheduler.shutdown(); // 在程序结束时关闭调度器}
}

❄️利弊

计数器限流算法简单高效,易于实现且资源消耗低,但其主要缺点是在处理非均匀分布的突发流量时可能无法准确反映瞬时峰值,从而导致系统过载。

滑动窗口算法

⭐️简介

滑动窗口算法改进了固定窗口算法中的临界问题,通过将固定窗口进一步细分为多个小周期,并为每个小周期记录请求次数。随着窗口向前滑动,过期的小周期会被移除,而在判断是否限流时,需累加当前窗口内所有小周期的请求次数并与阈值进行比较。这种方法能更准确地捕捉瞬时流量峰值,避免系统过载。

在这里插入图片描述

为了应对固定窗口算法中的临界问题,我们采用滑动窗口算法,将1秒的时间窗口细分为5个200毫秒的小周期,并记录每个小周期内的请求次数。以之前的场景为例,在1.9秒和2.1秒的时间点分别发生了100次恶意并发请求。当滑动窗口到达第5个小周期时,该周期内的请求次数为100次,尚未达到阈值。然而,当窗口滑动到第6个小周期时,累计请求次数变为200次,这时超过了阈值,从而触发了访问限制。

🔥代码实现(Java)

public class MySlidingWindowRateLimiterDemo {/** 队列id和队列的映射关系,队列里面存储的是每一次通过时候的时间戳,这样可以使得程序里有多个限流队列 */private volatile static Map<String, List<Long>> MAP = new ConcurrentHashMap<>();//阈值private static int QPS = 2;//时间窗口总大小(毫秒)private static long WindowSize = 10 * 1000;/*** 滑动时间窗口限流算法* 在指定时间窗口,指定限制次数内,是否允许通过** @param listId     队列id* @param count      限制次数* @param timeWindow 时间窗口大小* @return 是否允许通过*/public static synchronized boolean tryAcquire(String listId, int count, long timeWindow) {// 获取当前时间long nowTime = System.currentTimeMillis();// 根据队列id,取出对应的限流队列,若没有则创建List<Long> list = MAP.computeIfAbsent(listId, k -> new LinkedList<>());// 如果队列还没满,则允许通过,并添加当前时间戳到队列开始位置if (list.size() < count) {list.add(0, nowTime);return true;}// 队列已满(达到限制次数),则获取队列中最早添加的时间戳Long farTime = list.get(count - 1);// 用当前时间戳 减去 最早添加的时间戳if (nowTime - farTime <= timeWindow) {// 若结果小于等于timeWindow,则说明在timeWindow内,通过的次数大于count// 不允许通过return false;} else {// 若结果大于timeWindow,则说明在timeWindow内,通过的次数小于等于count// 允许通过,并删除最早添加的时间戳,将当前时间添加到队列开始位置list.remove(count - 1);list.add(0, nowTime);return true;}}public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 20; i++) {Thread.sleep(1000);LocalTime now = LocalTime.now();if (!tryAcquire("ip", QPS, WindowSize)) {// 任意10秒内,只允许2次通过;自定义限流规则“ip”System.out.println(now + " 请求被限流");} else {System.out.println(now + " 请求通过");}}}
}

❄️利弊

滑动窗口限流算法能够更精确地控制瞬时流量峰值,有效避免因突发请求而导致的系统过载,但其实现复杂度较高且资源消耗相较于简单计数器算法更大。

漏桶算法

⭐️简介

漏桶算法(Leaky Bucket) 是一个形象的比喻,它描述了一个桶(即队列)接收来自生产端的水(即请求或流量),并且这个桶底部有一个孔,以恒定的速度漏水(即消费端不断地处理队列中的请求)。如果水流入桶的速度超过了漏水的速度(即生产端的请求速率超过了消费端的处理能力),桶中的水就会逐渐积累。一旦桶内的水量超过了桶的容量,多余的水就会溢出(即请求被拒绝),从而实现了网络流量的整形和限流功能。

在这个模型中,漏水的速度代表了限流的阈值,即单位时间内系统可以处理的最大请求量。例如,如果QPS设置为100,则表示系统每秒最多可以处理100个请求。如果生产端的请求速率超过了这个阈值,请求就会在队列中堆积,最终导致超出桶的容量而被拒绝。

在这里插入图片描述

🔥代码实现(Java)

这个例子使用了一个 AtomicLong 作为桶中的水量,并使用 ScheduledExecutorService 来模拟桶漏水的过程。

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;public class LeakyBucketRateLimiter {private final AtomicLong bucket = new AtomicLong(0L);private final long capacity;private final long leakRate;private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();private final Runnable leakAction = this::leakBucket;public LeakyBucketRateLimiter(long capacity, long leakRate) {this.capacity = capacity;this.leakRate = leakRate;scheduleLeak();}public synchronized boolean allowRequest() {long currentWaterLevel = bucket.get();// 如果桶满了,拒绝请求if (currentWaterLevel >= capacity) {return false;}// 否则,添加一个单位的水bucket.addAndGet(1);return true;}private void leakBucket() {long currentWaterLevel = bucket.getAndAdd(-leakRate);if (currentWaterLevel - leakRate < 0) {bucket.set(0);}}private void scheduleLeak() {scheduler.scheduleAtFixedRate(leakAction, 1, 1, TimeUnit.SECONDS);}public static void main(String[] args) {LeakyBucketRateLimiter rateLimiter = new LeakyBucketRateLimiter(200, 10); // 容量200,每秒漏10个单位// 模拟客户端请求ExecutorService executor = Executors.newFixedThreadPool(10);for (int i = 0; i < 500; i++) {int requestId = i;executor.submit(() -> {if (rateLimiter.allowRequest()) {System.out.println("Request " + requestId + " is allowed.");} else {System.out.println("Request " + requestId + " is denied.");}});}executor.shutdown();}
}

❄️利弊

漏桶算法能够平滑输入流量并确保系统负载稳定,但其主要缺点在于无法区分突发流量持续高负载,可能导致正常突发流量被错误地限制。

令牌桶算法

⭐️简介

令牌桶算法是漏桶算法的一种改进版本,常用于网络流量整形限流。它同样使用一个桶,但桶中存放的是固定数量的令牌。算法以一定的速率(阈值)向桶中发放令牌。每当一个请求到来时,必须先从桶中获取一个令牌才能继续处理,处理完成后令牌被丢弃;如果没有可用令牌,则执行拒绝策略(如直接拒绝或排队等待)。此外,向桶中发放令牌的动作是持续不断的,如果桶满则令牌会被丢弃。

在这里插入图片描述

表面上看,令牌桶算法和漏桶算法是相反的一个"进水",一个是"漏水"。但与漏桶算法实际不同的是,令牌桶不仅能够在限制客户端请求流量速率的同时还能允许一定程度的突发流量。限流和允许瞬时流量其实并不矛盾,在大多数场景中,小批量的突发流量系统是完全可以接受的。

在令牌桶算法中,令牌是持续不断地生成并存入到一个“桶”中。当网络流量相对平缓时,这个桶可以积累额外的令牌作为储备。一旦出现突发的大流量(即流量尖峰),这些储备的令牌就可以立即用于处理这些额外的请求,确保它们能够被快速处理而不必等待。

只有当流量超过了预设的最大阈值时,也就是桶中的令牌被耗尽之后,新的请求才会因为拿不到令牌而被延迟处理或直接拒绝。这种方式有助于保护后端系统免受流量峰值的影响,保持其稳定运行。

🔥代码实现(Java)

这里提供一下代码实现,单机下推荐使用(或者仿写)Google Guava自带的限流工具类RateLimiter

先引入依赖:

<dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>30.0-jre</version>
</dependency>

设置发放令牌的速率,然后直接调用tryAcquire,大家感兴趣的可以看看其源码实现

public class MyTokenBucketRateLimiterDemo {public static void main(String[] args) throws InterruptedException {// 每1s发放5个令牌到桶里RateLimiter rateLimiter = RateLimiter.create(5);for (int i = 0; i < 10; i++) {Thread.sleep(100L);if(rateLimiter.tryAcquire()) {System.out.println("get one token");}else {System.out.println("no token");}}}
}

❄️利弊

漏桶算法相比,令牌桶算法的实现更为复杂。它不仅需要维护令牌的生成和消耗过程,还需要有精确的时间控制来确保令牌的生成速率符合预期,并且还需要一定的内存空间来存储这些令牌 尽管如此,令牌桶算法提供了更灵活的流量控制机制,允许一定程度上的突发流量处理,而不仅仅是简单地丢弃超出限制的数据包。

小结

算法名称优点缺点主要应用场景
漏桶算法- 实现简单
- 适用于简单的流量控制
- 无法处理突发流量
- 可能导致数据丢失
- 网络流量控制
- 基础的限流策略
令牌桶算法- 支持突发流量
- 可以调整速率
- 实现较复杂
- 需要更多内存
- API调用频率限制
- 网络流量控制
- 高级限流策略
固定窗口算法- 实现简单
- 易于理解和维护
- 在窗口切换时可能会导致瞬时的流量突变- 简单的API调用频率限制
- 基础的限流策略
滑动窗口算法- 能够平滑流量突变
- 提供更精确的限流
- 实现复杂度较高
- 需要维护更多的状态信息
- 高精度的API调用频率限制
- 网络流量控制
- 复杂的限流策略

当然了,每种算法都有其特定的应用场景和优缺点,选择哪种算法取决于具体的需求和环境

例如,如果你的应用需要支持突发流量并且不能轻易丢失数据,那么令牌桶算法可能是一个更好的选择。如果你的应用只需要简单的流量控制并且不需要支持突发流量,那么漏桶算法可能就足够了。对于需要更精确控制的场景滑动窗口算法可能是最佳选择。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

京东2025届秋招 算法开发工程师 第2批笔试

目录 1. 第一题2. 第二题3. 第三题 ⏰ 时间&#xff1a;2024/08/17 &#x1f504; 输入输出&#xff1a;ACM格式 ⏳ 时长&#xff1a;2h 本试卷还有选择题部分&#xff0c;但这部分比较简单就不再展示。 1. 第一题 村子里有一些桩子&#xff0c;从左到右高度依次为 1 , 1 2…

【免费】企业级大模型应用推荐:星环科技无涯·问知

无涯问知是星环科技发布的大模型应用系统&#xff0c;那么我们先简单了解下星环科技吧&#xff01; 星环科技&#xff08;股票代码&#xff1a;688031&#xff09;致力于打造企业级大数据和人工智能基础软件&#xff0c;围绕数据的集成、存储、治理、建模、分析、挖掘和流通等数…

这个是git使用的合集

如果遇到了关于git和github的bug就会写这里 2024/8/16 github一直没有打卡和上传代码是因为感觉除了做项目的情况&#xff0c;普通的学习和普通的笔记没必要记在github里&#xff1b;如果是笔记类的东西为什么不记在csdn上呢&#xff1f;如果是算法题算法网站上回有记录啊&am…

CAD图纸加密软件哪个好?(这六款大众好评度高!)

在CAD图纸加密软件领域&#xff0c;有多款软件因其高效、安全、易用等特点而广受好评。 以下是六款大众好评度较高的CAD图纸加密软件&#xff0c;它们各自具有独特的功能和优势&#xff1a; 1.安企神 特点&#xff1a;它以其强大的透明加密技术和精细化的权限管理功能著称。 …

python爬虫爬取某图书网页实例

文章目录 导入相应的库正确地设置代码的基础部分设置循环遍历遍历URL保存图片和文档全部代码即详细注释 下面是通过requests库来对ajax页面进行爬取的案例&#xff0c;与正常页面不同&#xff0c;这里我们获取url的方式也会不同&#xff0c;这里我们通过爬取一个简单的ajax小说…

MPU6050详细介绍

一、MPU6050介绍 MPU6050是由三个陀螺仪和三个加速度传感器组成的6轴运动处理组件 内部主要结构&#xff1a;陀螺仪、加速度计、数字运动处理器DMP&#xff08;Digital Motion Processor&#xff09; MPU6050有两个IIC接口&#xff0c;第一IIC接口可作为主接口给单片机传输数…

CSP-CCF 202012-1 期末预测之安全指数

一、问题描述 二、解答 #include<iostream> using namespace std; int main() {int n;cin >> n;int w[100001] { 0 };int score[100001] { 0 };for (int i 1; i < n; i){cin >> w[i] >> score[i];}int y 0;for (int i 1; i < n; i){y y …

电脑监控软件有哪些,哪款更好用?一网打尽!电脑监控软件大搜罗,总有一款适合你!

甲&#xff1a;哎&#xff0c;您听说了吗&#xff1f;这年头&#xff0c;电脑监控软件那是五花八门&#xff0c;跟变戏法似的&#xff01; 乙&#xff1a;哦&#xff1f;怎么个五花八门法&#xff1f; 甲&#xff1a;嘿&#xff0c;您还别说&#xff0c;从实时监控到网络追踪…

在HFSS中对曲线等结构进行分割(Split)

在HFSS中对曲线进行分割 我们往往需要把DXF等其他类型文件导入HFSS进行分析&#xff0c;但是有时需要对某一个曲线单独进行分割成两段修改。 如果是使用HFSS绘制的曲线&#xff0c;我们修改起来非常方便&#xff0c;修改参数即可。但是如果是导入的曲线&#xff0c;则需要使用…

js实现图片以鼠标为中心滚轮缩放-vue

功能背景 实现以鼠标在图中的位置为中心进行图片的滚轮缩放&#xff0c;现在是无论鼠标位置在哪都以图片中心进行缩放&#xff0c;这不符合预期&#xff1b; 关键点 缩放前鼠标在的位置是 A&#xff08;clinetX,clientY&#xff09; 点&#xff0c;缩放后鼠标的位置是 A’&a…

技术分享-商城篇-订单支付微信篇(十二)

B2C商城微信支付全解析&#xff1a;H5支付、小程序支付、JSAPI支付与APP支付 引言 在之前的文章中&#xff0c;我们聊了B2B2C的商城相关功能模块&#xff0c;如&#xff1a;首页布局、商品、购物车、购物结算、订单支付等&#xff0c;但是B2C商城的订单支付方式的选择&#x…

【Docker】Centos系统没有Vpn时候安装Docker

【Docker】没有Vpn时候安装Docker 背景1.安装docker之前先卸载2.基础配置3.安装docker5. 问题解决6.配置docker镜像源&#xff0c;解决网络超时 背景 工作中习惯VPN或者服务器节点为国外或者香港节点&#xff0c;最近买了一台国内服务器网络受到各种限制。 1.安装docker之前先…

uniapp/vue个性化单选、复选组件

个性化单选和复选组件在网页设计中非常常见&#xff0c;它们不仅能够提升用户界面的美观度&#xff0c;还能改善用户体验。此组件是使用vue uniapp实现的个性化单选复选组件。设计完成后&#xff0c;点击生成源码即可。 拖动组件过设计区 每行显示数量 默认支持每行三个&#…

扎心“我学了六个月 Python,怎么还是会找不到工作”

前言 &#x1f449; 小编已经为大家准备好了完整的代码和完整的Python学习资料&#xff0c;朋友们如果需要可以扫描下方CSDN官方认证二维码或者点击链接免费领取【保证100%免费】 在编程界&#xff0c;Python是一种神奇的存在。有人认为&#xff0c;只有用Python才能优雅写代码…

等保测评中的安全需求分析:构建精准的信息安全防护体系

在数字化转型的时代背景下&#xff0c;信息安全成为企业发展的关键因素之一。等保测评&#xff0c;作为我国信息安全等级保护制度的重要组成部分&#xff0c;要求企业进行详细的安全需求分析&#xff0c;以构建精准、有效的信息安全防护体系。本文旨在探讨等保测评中的安全需求…

ThreeJs学习笔记--坐标系,光源,相机控件

坐标系 一、创建添加坐标系 给场景添加坐标系THREE.AxesHelper()的参数表示坐标系坐标轴线段尺寸大小&#xff0c;你可以根据需要改变尺寸 const axesHelper new THREE.AxesHelper(200)//数值是坐标的尺寸 scene.add(axesHelper)//添加到场景里 坐标系包含三个坐标轴&…

本地ComfyUI安装全记录

资料 先看我写的stable diffusion全记录 ComfyUI 完全入门&#xff1a;安装部署 ComfyUI 完全入门&#xff1a;图生视频 ComfyUI【强烈推荐】 秋葉aaaki comfy UI整合包 可以使用stable diffusion的大模型&#xff0c;通过修改文件重新指向 修改路径即可 下载秋叶大佬的…

python之matplotlib (3 坐标轴设置)

写在前面 在说明坐标轴设置之前&#xff0c;我有必要和大家说清楚图像设置的一些方法&#xff0c;避免陷入困扰模糊的地步。前面我们说过&#xff0c;画图的三种方法&#xff08;python之matplotlib &#xff08;1 介绍及基本用法&#xff09;-CSDN博客&#xff09;。而设置也…

yolov8目标检测与速度估计

我们可能都见过限速路牌。我们中的一些人甚至可能收到过通过邮寄或电子邮件发送的自动限速违规通知。人工智能&#xff08;AI&#xff09;交通管理系统可以利用计算机视觉技术自动标记超速违规行为。路灯和高速公路上的摄像头拍摄的实时画面可用于估算车速和加强道路安全。 车速…

博世(BOSCH)× Milvus:智能驾驶领域的数据挖掘革新

01.博世智能驾控&#xff1a;智能驾驶技术的领航者 博世&#xff08;BOSCH&#xff09;智能驾控是全球汽车技术领域的领导者&#xff0c;以其在自动驾驶技术上的创新和深厚历史而闻名。博世的自动驾驶解决方案&#xff0c;包括先进的驾驶辅助系统&#xff08;ADAS&#xff09;…