分析常见限流算法及手写三种(计数器、漏斗、令牌桶)代码实现

news2024/11/19 15:14:16

常见的限流算法分析

限流在我们日常生活中经常见到,如火车站门口的栏杆、一些景点的门票只出售一定的数量 等等。在我们的开发中也用到了这种思想。

为什么要限流

在保证可用的情况下尽可能多增加进入的人数,其余的人在排队等待,或者返回友好提示,保证里面的进行系统的用户可以正常使用, 防止系统雪崩。

限流算法

限流算法很多,常见的有三类,分别是 计数器算法 、漏桶算法、令牌桶算法 。

(1)计数器: 在一段时间间隔内,处理请求的最大数量固定,超过部分不做处理。 (2)漏桶: 漏桶大小固定,处理速度固定,但请求进入速度不固定(在突发情况请求过多时,会丢弃过多的请求)。 (3)令牌桶: 令牌桶的大小固定,令牌的产生速度固定,但是消耗令牌(即请求)速度不固定(可以应对一些某些时间请求过多的情况);每个请求都会从令牌桶中取出令牌,如果没有令牌则丢弃该次请求。

计数器限流

在一段时间间隔内,处理请求的最大数量固定,超过部分不做处理。

举个,比如我们规定对于A接口,我们1分钟的访问次数不能超过100次。

那么我们可以这么做:

在一开 始的时候,我们可以设置一个计数器counter,每当一个请求过来的时候,counter就加1,如果counter的值大于100并且该请求与第一个请求的间隔时间还在1分钟之内,那么说明请求数过多,拒绝访问;

如果该请求与第一个请求的间隔时间大于1分钟,且counter的值还在限流范围内,那么就重置 counter,就是这么简单粗暴。

​代码实现: 

import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; ​

//计数器 限流 public class CounterLimiter { ​ //起始时间 private static long startTime = System.currentTimeMillis(); ​ //时间间隔1000ms private static long interval = 1000; ​

//每个时间间隔内,限制数量 private static long limit = 3; ​

//累加器 private static AtomicLong accumulator = new AtomicLong(); ​ /** * true 代表放行,请求可已通过 * false 代表限制,不让请求通过 */ public static boolean tryAcquire() { long nowTime = System.currentTimeMillis();

//判断是否在上一个时间间隔内 if (nowTime < startTime + interval) { //如果还在上个时间间隔内 long count = accumulator.incrementAndGet(); if (count <= limit) { return true; } else { return false; } } else {

//如果不在上一个时间间隔内 synchronized (CounterLimiter.class) {

//防止重复初始化 if (nowTime > startTime + interval) { startTime = nowTime; accumulator.set(0); } }

//再次进行判断 long count = accumulator.incrementAndGet(); if (count <= limit) { return true; } else { return false; } } } ​ ​

// 测试 public static void main(String[] args) { ​ //线程池,用于多线程模拟测试 ExecutorService pool = Executors.newFixedThreadPool(10); // 被限制的次数 AtomicInteger limited = new AtomicInteger(0);

// 线程数 final int threads = 2; // 每条线程的执行轮数 final int turns = 20;

// 同步器 CountDownLatch countDownLatch = new CountDownLatch(threads); long start = System.currentTimeMillis(); for (int i = 0; i < threads; i++) { pool.submit(() -> { try { ​ for (int j = 0; j < turns; j++) { ​ boolean flag = tryAcquire(); if (!flag) {

// 被限制的次数累积 limited.getAndIncrement(); } Thread.sleep(200); } ​ } catch (Exception e) { e.printStackTrace(); }

//等待所有线程结束 countDownLatch.countDown(); }); } try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } float time = (System.currentTimeMillis() - start) / 1000F;

//输出统计结果 System.out.println("限制的次数为:" + limited.get() + ",通过的次数为:" + (threads * turns - limited.get())); System.out.println("限制的比例为:" + (float) limited.get() / (float) (threads * turns)); System.out.println("运行的时长为:" + time + "s"); } ​ } ​ 复制代码

计数器限流的不足: 

这个算法虽然简单,但是存在临界问题,我们看下图:

​从上图中我们可以看到,假设有一个恶意用户,他在0:59时,瞬间发送了100个请求,并且1:00又瞬间发送了100个请求,那么其实这个用户在 1秒里面,瞬间发送了200个请求。

我们刚才规定的是1分钟最多100个请求(规划的吞吐量),也就是每秒钟最多1.7个请求,用户通过在时间窗口的重置节点处突发请求, 可以瞬间超过我们的速率限制。

用户有可能通过算法的这个漏洞,瞬间压垮我们的应用。🙇🏻‍♀️

漏桶限流

漏桶算法限流的基本原理为:水(对应请求)从进水口进入到漏桶里,漏桶以一定的速度出水(请求放行),当水流入速度过大,桶内的总水量大于桶容量会直接溢出,请求被拒绝。 大致的漏桶限流规则如下:

(1)进水口(对应客户端请求)以任意速率流入进入漏桶。

(2)漏桶的容量是固定的,出水(放行)速率也是固定的。

(3)漏桶容量是不变的,如果处理速度太慢,桶内水量会超出了桶的容量,则后面流入的水滴会溢出,表示请求拒绝。

​漏桶算法其实很简单,可以粗略的认为就是注水漏水过程,往桶中以任意速率流入水,以一定速率流出水,当水超过桶容量(capacity)则丢弃,因为桶容量是不变的,保证了整体的速率。 以一定速率流出水,

削峰: 有大量流量进入时,会发生溢出,从而限流保护服务可用

缓冲: 不至于直接请求到服务器, 缓冲压力

代码实现: 

import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; ​

//漏斗限流 public class LeakBucketLimiter { ​ //桶的大小 private static long capacity = 10;

//流出速率,每秒两个 private static long rate = 2; //开始时间 private static long startTime = System.currentTimeMillis();

//桶中剩余的水 private static AtomicLong water = new AtomicLong(); ​ /** * true 代表放行,请求可已通过 * false 代表限制,不让请求通过 */ public synchronized static boolean tryAcquire() {

//如果桶的余量问0,直接放行 if (water.get() == 0) { startTime = System.currentTimeMillis(); water.set(1); return true; }

//计算从当前时间到开始时间流出的水,和现在桶中剩余的水 //桶中剩余的水 water.set(water.get() - (System.currentTimeMillis() - startTime) / 1000 * rate);

//防止出现<0的情况 water.set(Math.max(0, water.get())); //设置新的开始时间 startTime += (System.currentTimeMillis() - startTime) / 1000 * 1000; //如果当前水小于容量,表示可以放行 if (water.get() < capacity) { water.incrementAndGet(); return true; } else { return false; } } ​ ​

// 测试 public static void main(String[] args) { ​

//线程池,用于多线程模拟测试 ExecutorService pool = Executors.newFixedThreadPool(10);

// 被限制的次数 AtomicInteger limited = new AtomicInteger(0);

// 线程数 final int threads = 2;

// 每条线程的执行轮数 final int turns = 20;

// 同步器 CountDownLatch countDownLatch = new CountDownLatch(threads); long start = System.currentTimeMillis(); for (int i = 0; i < threads; i++) { pool.submit(() -> { try { ​ for (int j = 0; j < turns; j++) { ​ boolean flag = tryAcquire(); if (!flag) {

// 被限制的次数累积 limited.getAndIncrement(); } Thread.sleep(200); } ​ } catch (Exception e) { e.printStackTrace(); }

//等待所有线程结束 countDownLatch.countDown(); }); } try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } float time = (System.currentTimeMillis() - start) / 1000F;

//输出统计结果 System.out.println("限制的次数为:" + limited.get() + ",通过的次数为:" + (threads * turns - limited.get())); System.out.println("限制的比例为:" + (float) limited.get() / (float) (threads * turns)); System.out.println("运行的时长为:" + time + "s"); } ​ } 复制代码

漏桶的不足:

漏桶的出水速度固定,也就是请求放行速度是固定的。 漏桶出口的速度固定,不能灵活的应对后端能力提升。比如,通过动态扩容,后端流量从1000QPS提升到1WQPS,漏桶没有办法。​

令牌桶限流

令牌桶算法中新请求到来时会从桶里拿走一个令牌,如果桶内没有令牌可拿,就拒绝服务。 当然,令牌的数量也是有上限的。令牌的数量与时间和发放速率强相关,时间流逝的时间越长,会不断往桶里加入越多的令牌,如果令牌发放的速度比申请速度快,令牌桶会放满令牌,直到令牌占满整个令牌桶。

令牌桶限流大致的规则如下:

(1)进水口按照某个速度,向桶中放入令牌。

(2)令牌的容量是固定的,但是放行的速度不是固定的,只要桶中还有剩余令牌,一旦请求过来就能申请成功,然后放行。

(3)如果令牌的发放速度,慢于请求到来速度,桶内就无牌可领,请求就会被拒绝。

总之,令牌的发送速率可以设置,从而可以对突发的出口流量进行有效的应对。

​代码实现: 

import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; ​

//令牌桶 public class TokenBucketLimiter { //桶的容量 private static long capacity = 10;

//放入令牌的速率,每秒2个 private static long rate = 2; //上次放置令牌的时间 private static long lastTime = System.currentTimeMillis();

//桶中令牌的余量 private static AtomicLong tokenNum = new AtomicLong(); ​ /** * true 代表放行,请求可已通过 * false 代表限制,不让请求通过 */ public synchronized static boolean tryAcquire() {

//更新桶中剩余令牌的数量 long now = System.currentTimeMillis(); tokenNum.addAndGet((now - lastTime) / 1000 * rate); tokenNum.set(Math.min(capacity, tokenNum.get()));

//更新时间 lastTime += (now - lastTime) / 1000 * 1000;

//桶中还有令牌就放行 if (tokenNum.get() > 0) { tokenNum.decrementAndGet(); return true; } else { return false; } } ​ ​

//测试 public static void main(String[] args) { ​ //线程池,用于多线程模拟测试 ExecutorService pool = Executors.newFixedThreadPool(10);

// 被限制的次数 AtomicInteger limited = new AtomicInteger(0);

// 线程数 final int threads = 2; // 每条线程的执行轮数 final int turns = 20;

// 同步器 CountDownLatch countDownLatch = new CountDownLatch(threads); long start = System.currentTimeMillis(); for (int i = 0; i < threads; i++) { pool.submit(() -> { try { ​ for (int j = 0; j < turns; j++) { ​ boolean flag = tryAcquire(); if (!flag) {

// 被限制的次数累积 limited.getAndIncrement(); } Thread.sleep(200); } ​ } catch (Exception e) { e.printStackTrace(); }

//等待所有线程结束 countDownLatch.countDown(); }); } try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } float time = (System.currentTimeMillis() - start) / 1000F;

//输出统计结果 System.out.println("限制的次数为:" + limited.get() + ",通过的次数为:" + (threads * turns - limited.get())); System.out.println("限制的比例为:" + (float) limited.get() / (float) (threads * turns)); System.out.println("运行的时长为:" + time + "s"); } ​ } 复制代码

令牌桶的好处:

令牌桶的好处之一就是可以方便地应对 突发出口流量(后端能力的提升)。

比如,可以改变令牌的发放速度,算法能按照新的发送速率调大令牌的发放数量,使得出口突发流量能被处理。

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

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

相关文章

机器如何快速学习数据采集

很多人都在思考如何利用机器学习&#xff08;ML&#xff09;算法来提高产品或服务的质量。 如果你正在考虑采用ML&#xff0c;以正确的格式收集正确的数据&#xff0c;将会降低你的数据清理工作以及数据浪费。 要收集所有数据 收集所有数据是非常重要的。除非你真正训练一个…

Excel基于分隔符拆分列

1、示例数据 id name describe 1 张三 学生 2 李四 老师 3 王五 学生 2、将数据复制到Excel中 数据目前都在A列中 3、将数据一次拆分到多个列 Excel基于分隔符拆分列&#xff0c;将数据一次拆分到多个列。 选中数据&#xff0c;数据-分列-分列 设置分隔符 点击完成后&…

【Python自然语言处理】使用逻辑回归(logistic)对电影评论情感分析实战(超详细 附源码)

需要源码和数据集请点赞关注收藏后评论区留言私信~~~ 一、舆情分析 舆情分析很多情况下涉及到用户的情感分析&#xff0c;或者亦称为观点挖掘&#xff0c;是指用自然语言处理技术、文本挖掘以及计算机语言学等方法来正确识别和提取文本素材中的主观信息&#xff0c;通过对带有…

使用Visual Studio Code 进行Python编程(一)

1、下载Visual Studio Code 到微软的Visual Studio Code官方主页下载Visual Studio Code: Visual Studio: 面向软件开发人员和 Teams 的 IDE 和代码编辑器Visual Studio 开发工具和服务让任何开发人员在任何平台和语言的应用开发都更加轻松。 随时随地免费使用代码编辑器或 I…

Spire.Office for .NET 7.12.0 2022年最后版本?

谷歌能找到破解版是破坏强签名&#xff0c;不能用web&#xff0c;请把大家不要用Spire.Office for .NET is a combination of Enterprise-Level Office .NET API offered by E-iceblue. It includes Spire.Doc, Spire.XLS, Spire.Spreadsheet, Spire.Presentation, Spire.PDF, …

数据库开发项目 flask + html 01

目的 开放平台&#xff08;网站&#xff09; 前端开发 HTML CSS JavaScript Web框架&#xff1a; 接受请求并处理 MySQL数据库&#xff1a; 存储数据 快速上手&#xff1a; 基于 Flask Web框架 快速搭建网站。 进阶&#xff1a; 基于 Django框架 1. 快速开发网站 安装框架 …

(附源码)SSM介绍信智能实现系统 毕业设计 260930

SSM介绍信智能实现系统 摘 要 科技进步的飞速发展引起人们日常生活的巨大变化&#xff0c;电子信息技术的飞速发展使得电子信息技术的各个领域的应用水平得到普及和应用。信息时代的到来已成为不可阻挡的时尚潮流&#xff0c;人类发展的历史正进入一个新时代。在现实运用中&…

java+mysql基于SSM的大学生兼职信息系统-计算机毕业设计

开发环境 运行环境&#xff1a; 开发工具:IDEA /Eclipse 数据库:MYSQL5.7 应用服务:Tomcat7/Tomcat8 使用框架:SSM(springspringMVCmybatis)vue 项目介绍 论文主要是对大学生兼职信息系统进行了介绍&#xff0c;包括研究的现状&#xff0c;还有涉及的开发背景&#xff0c;然…

分享几款免费实用的国产内网穿透工具

对于没有公网IP的用户来说&#xff0c;如何实现远程管理或让局域网的服务可以被公网访问到是一个问题。当然&#xff0c;也有很多类似的需求&#xff0c;比如&#xff1a; 微信公众号小程序开发调试公网访问本地web项目异地远程处理公司服务问题异地访问公司内网财务/管理系统…

Qt 中模型视图编程的基本概念

背景 一个应用程序本质可以抽象为三部分&#xff1a;界面、逻辑处理、数据。程序中存储有大量的数据&#xff0c;经过逻辑处理后、通过界面展示给用户&#xff0c;同时用户可以通过界面对数据进行编辑&#xff0c;如下图所示&#xff1a; Qt 中的模型视图架构就是用来实现大量…

Spring_第3章_AOP+事务

Spring_第3章_AOP事务 文章目录Spring_第3章_AOP事务一、AOP1 AOP简介问题导入1.1 AOP简介和作用【理解】1.2 AOP中的核心概念【理解】2 AOP入门案例【重点】问题导入2.1 AOP入门案例思路分析2.2 AOP入门案例实现【第一步】导入aop相关坐标【第二步】定义dao接口与实现类【第三…

8 常规聚类

常规聚类 聚类分析是解决数据全方位自动分组的有效方式。若将数据全体视为一个大类&#xff0c;这个大类很可能是由若干个包含了一定数量观测的自然小类”组成的。聚类分析的目的就是找到这些隐藏于数据中的客观存在的“自然小类”&#xff0c;并通过刻画“自然小类”体现数据…

舆情监控软件

随着中国互联网的快速发展&#xff0c;舆情监测成为工作中的一部分&#xff0c;如果没有舆情监控软件的及时介入&#xff0c;负面舆情将会迅速扩大并蔓延到各个方面&#xff0c;对社会以及公众造成严重的影响&#xff0c;舆情监控软件对企业政府有着深远影响&#xff0c;接下来…

Python学习小组课程P5-Python办公(2)Excel读取与Word生成

一、前言 注意&#xff1a;此为内部小组学习资料&#xff0c;非售卖品&#xff0c;仅供学习参考。 本系列课程&#xff1a; Python学习小组课程-课程大纲与Python开发环境安装 Python学习小组课程P1-Python基础&#xff08;1&#xff09;语法与数组 Python学习小组课程P2-Pyth…

【配电网重构】基于yalmip求解含sop+二阶锥配电网重构附matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

ouster-32激光雷达使用---雷达输出数据分析

ouster-32激光雷达使用---雷达输出数据分析雷达输出数据分析所有数据imu数据雷达数据坐标系Rviz显示雷达输出数据分析 所有数据 查看当前topic消息种类 rostopic list终端输出 /clicked_point /initialpose /move_base_simple/goal /os_node/imu_packets /os_node/lidar_pa…

ADSP-21489的开发详解:VDSP+自己编程写代码开发(2-软件和硬件的开发环境搭建)

Visual DSP软件的安装 运行 setup 软件安装包&#xff0c;全部下一步即可完成软件安装&#xff0c;非常简单。我们的资料里提供了 VDSP5.1.2 软件&#xff0c;当然您也可以通过 ADI 公司官网下载。 VDSP5.1.2 软件官网下载地址&#xff1a; Visual DSP5.1.2的ADI官网下载链接…

2022深入学习C++教程

2022深入学习C教程 课堂和实践课程 – C 11 的功能、异常处理和 STL – 适用于学术界和工业界 课程英文名&#xff1a;Learn C Programming -Beginner to Advance- Deep Dive in C 此视频教程共30.0小时&#xff0c;中英双语字幕&#xff0c;画质清晰无水印&#xff0c;源码…

Composer交互文档如何在PPT当中使用

在往期的公开课中我们讲解了SOLIDWORKS Composer这样一款三维制作软件&#xff0c;Composer可以很好的利用SOLIDWORKS所设计的数据自动生成产品手册、装配目录、维修说明&#xff0c;以及销售和培训视频等&#xff0c;还可以为用户提供非常满意的交互式体验。 并且Composer和S…

抖音怎么录屏?这个方法,亲测好用

​抖音是现在流行的短视频软件之一&#xff0c;很多小伙伴喜欢用它来记录生活&#xff0c;分享生活中新鲜有趣的事情。有时候&#xff0c;在抖音上看到了喜欢的视频&#xff0c;想要分享给好友&#xff0c;发现抖音无法分享&#xff0c;这个时候就需要使用到屏幕录制功能了。那…