首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >接口限流算法:漏桶算法&令牌桶算法&redis限流

接口限流算法:漏桶算法&令牌桶算法&redis限流

作者头像
阿东
发布于 2023-01-08 06:38:25
发布于 2023-01-08 06:38:25
2.1K01
代码可运行
举报
运行总次数:1
代码可运行

引言

高并发的系统通常有三把利器:缓存、降级和限流

缓存:缓存是提高系统访问速度,缓解CPU处理压力的关键,同时可以提高系统的处理容量。

降级:降级是在突然的压力剧增的情况,根据业务以及流量对一些服务和页面的策略降级,以此释放服务器资源。

限流:限流是对于并发访问/请求进行限速,或者一个时间窗口内限速保护系统,一旦到达限制速度可以拒绝服务、排队或者等待。

限流算法

令牌桶和和漏桶,比如Google的Guava的RateLimiter进行令牌痛控制。

漏桶算法

漏桶算法是把流量比作水,水先放在桶里面并且以限定的速度出水,水过多会直接溢出,就会拒绝服务。

漏洞存在出水口、进水口,出水口以一定速率出水,并且有最大出水率。

在漏斗没有水的时候:

  • 进水的速率小于等于最大出水率,那么出水速率等于进水速率,此时不会积水。
  • 如果进水速率大于最大出水速率,那么,漏斗以最大速率出水,此时,多余的水会积在漏斗中。

如果漏斗有水的时候:

  • 出水为最大速率。
  • 如果漏斗未满并且有进水,那么这些水会积在漏斗。
  • 如果漏斗已满并且有进水,那么水会溢出到漏斗外。

令牌桶算法

对于很多应用场景来说,除了要求能够限制数据的平均传输速率外,还要求允许某种程度的突发传输。这个时候使用令牌桶算法比较合适。

令牌桶算法以恒定的速率产生令牌,之后再把令牌放回到桶当中,令牌桶有一个容量,当令牌桶满了的时候,再向其中放令牌会被直接丢弃,

RateLimiter 用法

https://github.com/google/guava

首先添加Maven依赖:

代码语言:html
AI代码解释
复制
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->  
<dependency>  
   <groupId>com.google.guava</groupId>  
   <artifactId>guava</artifactId>  
   <version>31.1-jre</version>  
</dependency>

acquire(int permits)函数主要用于获取 permits 个令牌,并计算需要等待多长时间,进而挂起等待,并将该值返回。

代码语言:java
AI代码解释
复制
import com.google.common.util.concurrent.ListeningExecutorService;  
import com.google.common.util.concurrent.MoreExecutors;  
import com.google.common.util.concurrent.RateLimiter;  
  
import java.text.SimpleDateFormat;  
import java.util.Date;  
import java.util.concurrent.Executors;

public class Test {

    public static void main(String[] args) {
        ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(100));
        // 指定每秒放1个令牌
  RateLimiter limiter = RateLimiter.create(1);
        for (int i = 1; i < 50; i++) {
            // 请求RateLimiter, 超过permits会被阻塞
  //acquire(int permits)函数主要用于获取permits个令牌,并计算需要等待多长时间,进而挂起等待,并将该值返回
  Double acquire = null;
            if (i == 1) {
		        // `acquire 1` 时,并没有任何等待 0.0 秒 直接预消费了1个令牌  
                acquire = limiter.acquire(1);
            } else if (i == 2) {
		        // `acquire 10`时,由于之前预消费了 1 个令牌,故而等待了1秒,之后又预消费了10个令牌  
                acquire = limiter.acquire(10);
            } else if (i == 3) {
	             // `acquire 2` 时,由于之前预消费了 10 个令牌,故而等待了10秒,之后又预消费了2个令牌
                acquire = limiter.acquire(2);
            } else if (i == 4) {
	            //`acquire 20` 时,由于之前预消费了 2 个令牌,故而等待了2秒,之后又预消费了20个令牌  
                acquire = limiter.acquire(20);
            } else {
	            // `acquire 2` 时,由于之前预消费了 2 个令牌,故而等待了2秒,之后又预消费了2个令牌  
                acquire = limiter.acquire(2);
            }
            executorService.submit(new Task("获取令牌成功,获取耗:" + acquire + " 第 " + i + " 个任务执行"));
        }
    }
}
class Task implements Runnable {
    String str;
    public Task(String str) {
        this.str = str;
    }
    @Override
  public void run() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        System.out.println(sdf.format(new Date()) + " | " + Thread.currentThread().getName() + str);
    }
}

一个RateLimiter主要定义了发放permits的速率。如果没有额外的配置,permits将以固定的速度分配,单位是每秒多少permits。默认情况下,Permits将会被稳定的平缓的发放。

RateLimiter 的执行结果如下

代码语言:java
AI代码解释
复制
2023-01-03 06:18:53.684 | pool-1-thread-1获取令牌成功,获取耗:0.01 个任务执行
2023-01-03 06:18:54.653 | pool-1-thread-2获取令牌成功,获取耗:0.9850862 个任务执行
2023-01-03 06:19:04.640 | pool-1-thread-3获取令牌成功,获取耗:9.9869423 个任务执行
2023-01-03 06:19:06.643 | pool-1-thread-4获取令牌成功,获取耗:2.0003654 个任务执行
2023-01-03 06:19:26.641 | pool-1-thread-5获取令牌成功,获取耗:19.997025 个任务执行
2023-01-03 06:19:28.640 | pool-1-thread-6获取令牌成功,获取耗:1.9994566 个任务执行
2023-01-03 06:19:30.651 | pool-1-thread-7获取令牌成功,获取耗:2.0003177 个任务执行
2023-01-03 06:19:32.640 | pool-1-thread-8获取令牌成功,获取耗:1.9886478 个任务执行

从上面的结果可以知道,令牌桶具备预消费能力。

代码语言:java
AI代码解释
复制
`acquire 1` 时,并没有任何等待 0.0 秒 直接预消费了1个令牌  
`acquire 10`时,由于之前预消费了 1 个令牌,故而等待了1秒,之后又预消费了10个令牌  
`acquire 2` 时,由于之前预消费了 10 个令牌,故而等待了10秒,之后又预消费了2个令牌  
`acquire 20` 时,由于之前预消费了 2 个令牌,故而等待了2秒,之后又预消费了20个令牌  
`acquire 2` 时,由于之前预消费了 20 个令牌,故而等待了20秒,之后又预消费了2个令牌  
`acquire 2` 时,由于之前预消费了 2 个令牌,故而等待了2秒,之后又预消费了2个令牌  
`acquire 2` 时 .....

预消费能力是一个类似前人挖坑,后人填坑的过程,从上面的运行结果来看,如果上一次请求获取的permit数越多,那么下一次再获取授权时更待的时候会更长,反之,如果上一次获取的少,那么时间向后推移的就少,下一次获得许可的时间更短。

Redis 限流

基于Redis的setnx的操作

限流的主要目的就是为了在单位时间内,有且仅有N数量的请求能够访问我的代码程序,依靠setnx 可以轻松完成CAS操作,同时被获取的相同Key设置过期时间(expire),比如10秒内限定20个请求,那么我们在setnx的时候可以设置过期时间10,当请求的setnx数量达到20时候即达到了限流效果。

setnx的操作的弊端是无法进行限流统计,如果需要统计10秒内获取了多少个“桶”,需要在统计的过程中所有的桶都被持有。

基于Redis的数据结构zset

限流涉及的最主要的就是滑动窗口,上面也提到1-10怎么变成2-11。其实也就是起始值和末端值都各+1即可。

zset数组可以实现类似滑动数组的方式,每次请求进入的时候,可以生成唯一的UUID作为value,score可以用当前时间戳表示,因为score我们可以用来计算当前时间戳之内有多少的请求数量,同时Zset的数据结构提供range方法可以获取两个时间戳范围内有多少个请求。

具体实现代码如下:

代码语言:java
AI代码解释
复制
public Response limitFlow(){
        Long currentTime = new Date().getTime();
        System.out.println(currentTime);
        if(redisTemplate.hasKey("limit")) {
            Integer count = redisTemplate.opsForZSet().rangeByScore("limit", currentTime -  intervalTime, currentTime).size();        // intervalTime是限流的时间 
            System.out.println(count);
            if (count != null && count > 5) {
                return Response.ok("每分钟最多只能访问5次");
            }
        }
        redisTemplate.opsForZSet().add("limit",UUID.randomUUID().toString(),currentTime);
        return Response.ok("访问成功");
    }

zset的实现方式也比较简单,每N秒可以产生M个请求,缺点是zset会随着构建数据不断增长。

基于Redis的令牌桶算法

我们根据前文介绍的令牌桶可以得知,当输出的速率大于输入的速率,会出现“溢出”的情况。guava通过acquire 方法挂起等待获取令牌,这种方法虽然可以做到精确的流量控制,但是会出现“前人挖坑,后人埋坑”的情况,并且只能用于单JVM内存。

面对分布式项目,我们可以通过Redis的List结构进行改造,实现方式同样非常简单。

依靠List的leftPop来获取令牌:

代码语言:java
AI代码解释
复制
// 输出令牌
public Response limitFlow2(Long id){
	Object result = redisTemplate.opsForList().leftPop("limit_list");
	if(result == null){
		return Response.ok("当前令牌桶中无令牌");
	}
	return Response.ok(articleDescription2);
}

leftPop 语法:LPOP key [count] 移除并返回存储在.key的列表中的第一个元素。默认情况下,该命令从列表的开头弹出一个元素。 案例:

代码语言:shell
AI代码解释
复制
redis> RPUSH mylist "one" "two" "three" "four" "five"
(integer) 5
redis> LPOP mylist
"one"
redis> LPOP mylist 2
1) "two"
2) "three"

再依靠Java的定时任务,定时往List中rightPush令牌,为了保证分布式环境的强唯一性,可以使用redission生成唯一ID或者使用雪花算法生成ID,这样的结果更为靠谱。

上面代码的集成可以使用AOP或者Filter中加入限流代码即可。较为完美的方案是依靠Redis的限流,这样可以做到部署多个JVM也可以进行正常工作。

如果是单JVM则使用UUID的结果即可:

代码语言:java
AI代码解释
复制
// 10S的速率往令牌桶中添加UUID,只为保证唯一性     @Scheduled(fixedDelay = 10_000,initialDelay = 0)     public void setIntervalTimeTask(){         redisTemplate.opsForList().rightPush("limit_list",UUID.randomUUID().toString());     }

令牌桶算法VS漏桶算法VSRedis限流

漏桶算法的出水速度是恒定的,那么意味如果出现大流量会把大部分请求同时丢弃(水溢出)。令牌桶的算法也是恒定的,请求获取令牌没有限制,对于大流量可以短时间产生大量令牌,同样获取令牌的过程消耗不是很大。

Redis的限流不依赖JVM,是较为靠谱和推荐的方式,具体的实现可以依赖Redis本身的数据结构和Redis命令天然的原子性实现,唯一需要注意的是在具体编程语言接入的时候能否写出具备线程安全的代码。

小结

注意本文介绍的限流算法都是在JVM级别的限流,限流的令牌都是在内存中生成的,需要注意在分布式的环境下不能使用,如果要分布式限流,可以用redis限流。

使用guava限流是比较常见的,而Redis的限流是依赖中间件完成的,实现起来更为简单也更推荐。

参考资料

Redis 实现限流的三种方式 - 掘金 (juejin.cn)

java - 接口限流算法:漏桶算法&令牌桶算法 - 搜云库技术团队 - SegmentFault 思否

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
​云开发·云调用生成小程序码
小程序云开发已经支持云调用,开放了很多接口,一直想要的获取小程序码也支持了。这下轻量的小程序也可以有自定义小程序码的功能。
PlayerYK
2020/10/29
1.8K0
​云开发·云调用生成小程序码
微信小程序云开发实现图片的上传、存储、访问
我们在进行项目开发时,经常需要处理用户上传的图片,如果用传统的后端开发,处理起来是比较繁琐的。微信小程序云开发提供了一系列API供开发者完成想要的效果。 下面我们要实现用户图片的上传、存储及访问。
kif
2023/02/27
5.4K0
微信小程序云开发实现图片的上传、存储、访问
微信会话语音文件的一句话识别
在wordRecognize云函数目录上右键选择在"在终端中打开",执行"npm install"安装依赖
张世强
2020/07/24
2K1
微信会话语音文件的一句话识别
微信小程序云存储(文件上传到云端)
  我们直到,云开发控制台更多的是对项目中的初始文件的操作管理,例如项目的Logo图片可以通过云开发控制台提起上传到云端。项目在执行的过程中也会涉及文件的操作,例如用户上传图片的操作,这时就需要用到云开发存储API。
别团等shy哥发育
2023/02/25
7.3K0
微信小程序云存储(文件上传到云端)
【愚公系列】《微信小程序与云开发从入门到实践》049-使用云函数
在微信小程序的开发过程中,如何高效地处理业务逻辑和实现复杂功能是每位开发者面临的挑战。云函数作为一种灵活、高效的无服务器计算解决方案,为小程序提供了强大的后台支持,使得开发者能够在云端运行代码,而无需关心服务器的管理与维护。这种架构不仅简化了开发流程,还提升了应用的可扩展性和安全性。
愚公搬代码
2025/01/28
4390
无服务器开发人脸识别小程序
从2006年AWS发布的第一个云服务S3开始,存储,计算等IT基础设施的能力纷纷被以服务的方式提供给用户。过去十年,云服务深刻的改变了社会获取和使用计算能力的方式,云服务自身也以极快的速度演进,新的服务形态不断涌现,无服务器计算(serverless computing)就是其中之一。国内各大厂商也在近两年推出了自家的无服务器计算产品,比如腾讯云的无服务器云函数 SCF,阿里云的函数计算等产品。
璟櫆
2019/01/31
15.6K5
无服务器开发人脸识别小程序
小程序导出数据到excel表,借助云开发后台实现excel数据的保存
关于云函数的创建,我这里不多说了。如果你连云函数的创建都不知道,建议你去小程序云开发官方文档去看看。或者看下我录制的云开发入门的视频:https://edu.csdn.net/course/detail/9604
编程小石头
2019/09/07
6.3K1
小程序导出数据到excel表,借助云开发后台实现excel数据的保存
借助云开发轻松实现后台数据批量导出丨实战
这里需要用到云函数,云存储和云数据库。可以说通过这一个例子,把小程序云开发相关的知识都用到了。下面就来介绍如何实现
腾讯云开发TCB
2019/09/10
1.9K0
微信小程序与云开发
Java、NodeJS、JavaScript、HTML5、CSS3、VueJs、ReactJs、前端工程化、前端架构
达达前端
2019/08/05
9.7K0
微信小程序与云开发
如何进行小程序云函数开发
在以前的文章中,我们给大家介绍了小程序的基本使用,近期微信团队联合腾讯云合作开发了一项新的产品,不用服务器就可以在小程序端进行服务端开发。为开发者提供完整的云端支持,弱化后端和运维操作,使用平台原生 API 进行核心业务开发,实现快速上线和迭代。
英特奈特
2019/01/28
8.8K2
如何进行小程序云函数开发
微信小程序开发技巧总结(二) -- 文件的选取、移动、上传和下载
也为其提供了视频和图片的二合一接口,这个接口不建议调用,图片和视频的上传建议区分开。
Kindear
2020/02/23
2.3K0
小程序的全栈开发新时代
小程序·云开发是微信团队和腾讯云团队共同研发的一套小程序基础能力,简言之就是:云能力将会成为小程序的基础能力。整套功能是基于腾讯云全新推出的云开发(Tencent Cloud Base)所研发出来的一套完备的小程序后台开发方案。
李成熙heyli
2018/09/18
14K2
小程序的全栈开发新时代
小程序中使用云开发调用智能结构化OCR
随着智慧城市、智慧政务、智慧企业的科技发展潮流,之前非常多的纸质文件、档案等都被要求进行电子化管理,一是为了能够更方便的储存和备用,但更重要的是,在数据文件日益增长的情况下,引用电子化文档,更能增加搜索、调用文件的效率。
爱去西
2025/01/12
2880
小程序中使用云开发调用智能结构化OCR
小程序云开发:菜鸟也能全栈做产品
很多时候当我们有一个产品想法的时候,我们往往发现,前端写完了,后端怎么搞?数据库怎么搞?域名怎么搞?域名还要备案?应用部署怎么搞?我得买什么样的服务器啊?静态资源 CDN 怎么搞?文件上传服务器怎么搞?万一访问用户多了能撑住吗?等等……问题很多,导致你的一个个想法,都只是在脑海中昙花一现,从来都无法将它们实现,或者说你激情饱满的实现了其中自己最擅长的一部分,当碰到其他难题的时候就止步了。于是仰天长啸:我就想独立做一个完整的产品为什么这么难?年轻人,这一切都不怪你……
极乐君
2019/12/18
1.2K0
TCB系列学习文章——云开发的云存储篇(六)
云开发为开发者提供了存储空间、将文件上传到云端存储空间内以及带权限的云端文件下载能力,开发者可以使用云开发控制台或使用 SDK 调用接口来使用存储功能。
F颜
2020/06/30
1.9K0
小程序云开发使用体验
这里发现直接更新有点慢,直接下载最新版然后覆盖比较快,打开最新的开发者工具之后会发现多了个云开发:
Bug生活2048
2018/10/09
2.4K0
小程序云开发使用体验
手把手教你小程序导出Excel
2022年注定世界不太平,在7月份欧元兑美元跌破1:1的汇率;而微信云开发又将在8月8日开始调整计费,改为 “基础套餐+按量付费” 的计费模式,即:最低每月19.9起步。。。好吧,时间的车轮还是向前的谁也没法抵挡。我们还是回到我们的技术本身,通过技术去幻想改变世界吧。今天我们就讲一下怎么腾讯云来导出Excel,这里将涉及到2个功能:1、云函数 2、云储存。如果用19.9那么云函数的调用量为20W/月,云存储 为 2GB;量还是够的。好吧,我们接下来就开始如何构筑这个功能。
谭广健
2022/07/18
4.5K0
云调用02-生成小程序码
官方文档:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/functions/openapi.html
专注APP开发
2019/11/07
1.3K0
云调用02-生成小程序码
小程序识别身份证,银行卡,营业执照,驾照
功能其实很简单,就是我们点对应的按钮后,去拍照或者去相册选择对应的图片。然后把图片上传到云存储,会有一个对应的图片url,然后把这个图片url传递到云函数,然后云函数里使用小程序的开发ocr能力,来识别图片,返回对应的信息回来。如下图所示,我们识别银行卡(身份证什么的就不演示了,涉及到石头哥个人隐私)
编程小石头
2019/10/30
7.3K6
小程序识别身份证,银行卡,营业执照,驾照
【愚公系列】《微信小程序与云开发从入门到实践》048-使用云存储
在信息技术飞速发展的今天,数据的存储和管理变得尤为重要。对于微信小程序的开发者而言,如何高效、安全地存储和访问用户数据、应用资源,直接影响到小程序的性能和用户体验。云存储作为一种现代化的数据存储解决方案,以其灵活性、可扩展性和高可用性,成为了小程序开发中的重要工具。
愚公搬代码
2025/01/28
3670
推荐阅读
相关推荐
​云开发·云调用生成小程序码
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验