限流算法八股笔记

前言:本文是限流算法的八股总结和详解,博主在准备暑期实习,应该会持续更新

参考文章

  • 服务限流详解

  • 5种限流算法,7种限流方式,挡住突发流量?

  • 超详细的Guava RateLimiter限流原理解析

  • 使用Guava实现限流器

  • Guava中常用的4种经典限流算法介绍_guava 限流-CSDN博客

  • SpringBoot 中使用Guava实现单机令牌桶限流

  • 常见的限流算法分析以及手写实现(计数器、漏斗、令牌桶)-阿里云开发者社区

  • 字节面试官:来,年轻人!请手撸5种常见限流算法!

  • Guava限流器原理浅析_guava 限流器-CSDN博客

  • JAVA实现简单限流器(下)

目录

基本知识

为什么要限流?

针对什么来进行限流?

限流场景

常用的限流方式和场景

常用限流算法⭐

计数器(固定窗口)

滑动窗口⭐(Sentinel使用)

漏桶算法

令牌桶算法 ⭐(guava使用)

滑动日志

单机限流

Guava (详细)

Guava组件&API

Guava使用

Guava是如何实现令牌桶算法的?⭐

Bucket4j (简介)

Resilience4j(简介)

分布式限流

redis + lua

Sentinel (简介)


01 基本知识

为什么要限流?

  • 大多数情况是服务器资源不足,短时间内大量流量请求到服务器。

  • 也有可能因为安全问题,例如疯狂撞接口

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

保护高并发系统的三把利器

  • 限流

  • 缓存

  • 降级

分布式服务治理

熔断、降级、限流、切量、黑白名单、人群等

针对什么来进行限流?

常见的限流对象如下:

  • IP :针对 IP 进行限流,适用面较广,简单粗暴。

  • 业务 ID:挑选唯一的业务 ID 以实现更针对性地限流。例如,基于用户 ID 进行限流。

  • 个性化:根据用户的属性或行为,进行不同的限流策略。例如, VIP 用户不限流,而普通用户限流。根据系统的运行指标(如 QPS、并发调用数、系统负载等),动态调整限流策略。例如,当系统负载较高的时候,控制每秒通过的请求减少。

针对 IP 进行限流是目前比较常用的一个方案。

实际应用中需要注意用户真实 IP 地址的正确获取。常用的真实 IP 获取方法有 X-Forwarded-For 和 TCP Options 字段承载真实源 IP 信息。

限流场景

Guava RateLimiter只适用于单机限流,在针对单机或者在集群中对单台机器进行qps、tps进行限制时非常可靠和稳定

如果想要分布式系统集群限流,则需要引入 redis或者阿里开源的 sentinel中间件:

  • 基于 sentinel 限流(分布式)

  • 基于 redis+lua 限流(分布式)

  • 网关限流(分布式)

分布式限流

分布式限流最简单的实现就是利用中心化存储,即将单机限流存储在本地的数据存储到同一个存储空间中,如常见的Redis等。

当然也可以从上层流量入口进行限流,Nginx代理服务就提供了限流模块,同样能够实现高性能,精准的限流,其底层是漏桶算法。

常用的限流方式和场景

  • 限制总并发数(比如数据库连接池、线程池)

  • 限制瞬时并发数(如nginx的limitconn模块,用来限制瞬时并发连接数,Java的Semaphore也可以实现)

  • 限制时间窗口内的平均速率(如Guava的RateLimiter、nginx的limitreq模块,限制每秒的平均速率)

  • 其他:比如如限制远程接口调用速率、限制MQ的消费速率。另外还可以根据网络连接数、网络流量、CPU或内存负载等来限流。

常用限流算法⭐

以下几种限流算法的实现都仅适合单机限流。

一共五种:计数器、滑动窗口、漏桶、令牌桶算法、滑动日志

前四种比较著名,最后一个比较冷门。

计数器(固定窗口)

计数器限流(Fixed Window)算法,又称固定窗口算法, 是最为简单粗暴的解决方案,通过在单位时间内维护的计数器来控制该时间单位内的最大访问量。

主要用来限制总并发数,比如数据库连接池大小、线程池大小、接口访问并发数等都是使用计数器算法。例如:使用 AomicInteger 来进行统计当前正在并发执行的次数,如果超过阈值就直接拒绝请求,提示系统繁忙。

假如我们规定系统中某个接口 1 分钟只能访问 33 次

  • 给定一个变量 counter 来记录当前接口处理的请求数量,初始值为 0(代表接口当前 1 分钟内还未处理请求)。

  • 1 分钟之内每处理一个请求之后就将 counter+1 ,当 counter=33 之后(也就是说在这 1 分钟内接口已经被访问 33 次的话),后续的请求就会被全部拒绝。

  • 等到 1 分钟结束后,将 counter 重置 0,重新开始计数。

计数器算法的优点与缺点

优点:

  • 实现简单,容易理解

  • 占用内存小

缺点:

不够平滑,会导致以下问题:

  • 一段时间内服务不可用——比如 10s 内限流 100 次请求,但是 0s-1s 就来了 100 个请求,则 2s-10s 的请求都是被拒绝的,无法处理。

  • 临界问题无法保证限流速率,窗口切换时可能会出现两倍阈值的请求——用户通过在时间窗口的重置节点处突发请求, 可以瞬间超过我们的速率限制。假设有一个恶意用户,他在0:59时,瞬间发送了100个请求,并且1:00又瞬间发送了100个请求,那么其实这个用户在 1秒里面,瞬间发送了200个请求。

手写固定窗口限流算法

属性:

  • 阈值 QPS

  • 计数器 REQ_COUNT

  • 时间窗口 TIME_WINDOW 1000

  • 窗口起始时间 START_TIME

方法:

  • System.currentTimeMillis(); // 获取当前毫秒时间

  • tryAcquire() // 获取

假设限制每分钟请求量不超过2,设置一个计数器,当请求到达时如果计数器到达阈值,则拒绝请求,否则计数器加1;每秒重置计数器为0。代码实现如下:

public class RateLimiterSimpleWindow {// 阈值private static Integer QPS = 2;// 时间窗口(毫秒)private static long TIME_WINDOWS = 1000;// 计数器private static AtomicInteger REQ_COUNT = new AtomicInteger();// 起始时间private static long START_TIME = System.currentTimeMillis();public synchronized static boolean tryAcquire() {if ((System.currentTimeMillis() - START_TIME) > TIME_WINDOWS) {REQ_COUNT.set(0);START_TIME = System.currentTimeMillis();}return REQ_COUNT.incrementAndGet() <= QPS;}public static void main(String[] args) throws InterruptedException {      // 先休眠 400ms,可以更快的到达时间窗口。模拟临界情况// Thread.sleep(400);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 + " 做点什么");}}}
}

滑动窗口⭐(Sentinel使用)

  • 滑动窗口计数器算法 算的上是固定窗口计数器算法的升级版。最早接触滑动窗口是在TCP协议,流量控制和拥塞控制。

  • 为了防止瞬时流量,可以把固定窗口近一步划分成多个格子。

  • 每次向后移动一小格,而不是固定窗口大小,这就是滑动窗口(Sliding Window)。

例如,定义时间窗口为5秒,滑动窗口不断向前移动,控制这5秒内的请求数目。时间窗口的跨度越长时,限流效果就越平滑。

优点与缺点

优点:

  • 和固定窗口算法相比,避免了临界问题

  • 和漏桶算法相比,新来的请求也能够被处理到,避免了饥饿问题

缺点:

  • 不够平滑——当窗口中流量到达阈值时,流量会瞬间切断

手写滑动窗口算法⭐

属性:

  • 阈值 QPS

  • 单位时间窗口(小格子) SUB_CYCLE

  • 滑动窗口大小 TIME_WINDOW

  • 小格子列表/哈希表 counters

例如针对每分钟来限流10个请求:设置QPS=10,TIME_WINDOW=60,每 10s 一个小格子窗口,意味着一分钟(60秒)有6个窗口

设计思路:

记录每次请求时间戳,每次计算时间窗口是否超过设定值,没有超出计数,超出清空,计数归零,重新计算窗口。

public class SlidingWindow {// 每分钟限制请求 10个private static long QPS = 10;// 单位时间划分的小周期// 窗口如果是1分钟,10s一个小格子窗口,一共6个格子)private static int SUB_CYCLE = 10; // 单位:秒// 滑动窗口大小(60秒)private static int TIME_WINDOW = 60; // 单位:秒private static TreeMap<Long, Integer> counters = new TreeMap<>();private synchronized boolean tryAcquire(){// 获取当前时间的所在的子窗口值; 10s一个窗口// toEpochSecond:转化为秒   ZoneOffset.UTC: 区域偏移量long currentWindowTime = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC) / SUB_CYCLE * SUB_CYCLE;// 获取当前窗口的请求总量int currentWindowNum = countCurrentWindow(currentWindowTime);if(currentWindowNum >= QPS){return false;}// 计数器 + 1counters.merge(currentWindowTime, 1, Integer::sum);return true;}/*** 获取当前窗口中的所有请求数(并删除所有无效的子窗口计数器)* @param countCurrentWindow 当前子窗口时间* @return 当前窗口中的计数*/private synchronized int countCurrentWindow(long currentWindowTime) {// 计算窗口开始位置long startTime = currentWindowTime - SUB_CYCLE * (TIME_WINDOW / SUB_CYCLE - 1);int count = 0;Iterator<Map.Entry<Long, Integer>> iterator = counters.entrySet().iterator();while (iterator.hasNext()) {Map.Entry<Long, Integer> entry = iterator.next();if (entry.getKey() < startTime) {iterator.remove();} else {count += entry.getValue();}}return count;}public static void main(String[] args) throws InterruptedException {SlidingWindow slidingWindow = new SlidingWindow();for (int i = 0; i < 40; i++) {Thread.sleep(1000); // 每1秒一个请求LocalTime now = LocalTime.now();if(slidingWindow.tryAcquire()){System.out.println(now + "  pass!");}else{System.out.println(now + "  ban!");}}}
}

漏桶算法

漏桶限流规则

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

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

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

漏桶的实现思想

  • 漏桶算法的实现往往依赖于 队列,请求到达如果队列未满则直接放入队列,然后有一个处理器按照 固定频率 从队列头取出请求进行处理。

  • 如果请求量大,则会导致队列满,那么新来的请求就会被抛弃。

漏桶的优点与不足

优点:

  • 削峰:有大量流量进入时,可以控制限流速率,多的会发生溢出,从而限流保护服务可用,起到整流的作用

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

不足:

  • 漏桶出水速度固定,也就是请求放行速度是固定的

  • 漏桶出口的速度固定,不能解决流量突发的问题,不能灵活的应对后端能力提升。比如,通过动态扩容,后端流量从1000QPS提升到1WQPS,漏桶没有办法。

因此,实际业务场景中,基本不会使用 漏桶算法。

手写漏桶算法

属性:

  • 桶容量 capacity

  • 出水速度 permitsPerSecond QPS

  • 剩余水量 leftWater

  • 上次注入时间 timeStamp

public class LeakyBucketRateLimiter{// 桶的容量private final int capacity;// 漏出速率private final int permitsPerSecond;// 剩余水量private long leftWater;// 上次注入时间private long timeStamp = System.currentTimeMillis();public LeakyBucketRateLimiter(int permitsPerSecond, int capacity) {this.capacity = capacity;this.permitsPerSecond = permitsPerSecond;}public synchronized boolean tryAcquire() {long now = System.currentTimeMillis();//1. 计算剩余水量long timeGap = (now - timeStamp) / 1000; // 当前-上次注入leftWater = Math.max(0, leftWater - timeGap * permitsPerSecond);timeStamp = now;// 如果未满,则放行;否则限流if (leftWater < capacity) {leftWater += 1;return true;}return false;}
}

令牌桶算法 ⭐(guava使用)

令牌桶算法是一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌。

桶中存放的令牌数有最大上限,超出之后就被丢弃或者拒绝。当流量或者网络请求到达时,每个请求都要获取一个令牌,如果能够获取到,则直接处理,并且令牌桶删除一个令牌。如果获取不同,该请求就要被限流,要么直接丢弃,要么在缓冲区等待。

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

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

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

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

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

令牌桶好处

  • 能够在限制调用的平均速率

  • 可以方便地应对 突发出口流量(后端能力的提升)可以方便地应对 突发出口流量(后端能力的提升)

  • 动态调整生成令牌的速率

手写令牌桶限流算法

属性:

  • 令牌桶容量 capacity

  • 令牌发放速率 generatedPerSeconds

  • 最后一个令牌发放时间 lastTokenTime

  • 当前令牌数目 currentTokens

public class TokenBucketRateLimiter {// 令牌桶的容量「限流器允许的最大突发流量」private final long capacity;// 令牌发放速率private final long generatedPerSeconds;// 最后一个令牌发放的时间long lastTokenTime = System.currentTimeMillis();// 当前令牌数量private long currentTokens;public TokenBucketRateLimiter(long generatedPerSeconds, int capacity) {this.generatedPerSeconds = generatedPerSeconds;this.capacity = capacity;}// 尝试获取令牌public synchronized boolean tryAcquire() {/*** 计算令牌当前数量* 请求时间在最后令牌是产生时间相差大于等于额1s*(为啥时1s?因为生成令牌的最小时间单位时s),则* 1. 重新计算令牌桶中的令牌数* 2. 将最后一个令牌发放时间重置为当前时间*/long now = System.currentTimeMillis();if (now - lastTokenTime >= 1000) {long newPermits = (now - lastTokenTime) / 1000 * generatedPerSeconds;currentTokens = Math.min(currentTokens + newPermits, capacity);lastTokenTime = now;}if (currentTokens > 0) {currentTokens--;return true;}return false;}}

令牌桶和漏桶有什么区别?

  • 限流方式不同

    • 令牌桶是按照固定速率往桶中添加令牌,请求是否被处理需要看桶中令牌是否足够,当令牌数减为零时则拒绝新的请求;

    • 漏桶则是按照常量固定速率流出请求,流入请求速率任意,当流入的请求数累积到漏桶容量时,则新流入的请求被拒绝;

  • 限制对象不同

    • 令牌桶限制的是平均流入速率,允许突发请求,只要有令牌就可以处理,支持一次拿3个令牌,4个令牌;

    • 漏桶限制的是常量流出速率,即流出速率是一个固定常量值,比如都是1的速率流出,而不能一次是1,下次又是2,从而平滑突发流入速率;

  • 目的不同

    • 令牌桶允许一定程度的突发,能很好应对突然激增的流量

    • 漏桶主要目的是平滑流出速率;

滑动日志

  • 高级一点的滑动窗口算法,所以实现上和滑动窗口类似

  • 滑动日志限速算法需要记录请求的时间戳,通常使用 有序集合 来存储,我们可以在单个有序集合中跟踪用户在一个时间段内所有的请求

  • 假设我们要限制给定T时间内的请求不超过N,我们只需要存储最近T时间之内的请求日志,每当请求到来时判断最近T时间内的请求总数是否超过阈值。

优点与缺点

优点:

  • 能够避免突发流量,实现较为精准的限流

  • 更加灵活,能够支持更加复杂的限流策略 ,如多级限流,每分钟不超过100次,每小时不超过300次,每天不超过1000次

缺点:

  • 占用存储空间要高于其他限流算法

手写滑动日志的算法

属性

  • 每分钟限制请求数 PERMITS_PER_MINUTE

  • 日志计数器 requestLogCountMap

public class SlidingLogRateLimiter{// 每分钟限制请求数private static final long PERMITS_PER_MINUTE = 60;// 请求日志计数器, k-为请求的时间(秒),value当前时间的请求数量private final TreeMap<Long, Integer> requestLogCountMap = new TreeMap<>();public synchronized boolean tryAcquire() {// 最小时间粒度为slong currentTimestamp = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC);// 获取当前窗口的请求总数int currentWindowCount = getCurrentWindowCount(currentTimestamp);if (currentWindowCount >= PERMITS_PER_MINUTE) {return false;}// 请求成功,将当前请求日志加入到日志中requestLogCountMap.merge(currentTimestamp, 1, Integer::sum);return true;}/*** 统计当前时间窗口内的请求数*/private int getCurrentWindowCount(long currentTime) {// 计算出窗口的开始位置时间long startTime = currentTime - 59;// 遍历当前存储的计数器,删除无效的子窗口计数器,并累加当前窗口中的所有计数器之和return requestLogCountMap.entrySet().stream().filter(entry -> entry.getKey() >= startTime).mapToInt(Map.Entry::getValue).sum();}}

单机限流

单机限流针对的是单体架构应用。

Guava (详细)

Google Guava 自带的限流工具类 RateLimiter, 使用的是 令牌桶 限流算法模型,可以应对突发流量。使用非常简单,但是功能相对比较少。

除了最基本的令牌桶算法 (平滑突发限流) 实现之外,Guava 的RateLimiter还提供了 平滑预热限流 的算法实现。

  • 平滑突发限流就是按照指定的速率放令牌到桶里

  • 平滑预热限流会有一段预热时间,预热时间之内,速率会逐渐提升到配置的速率。

Guava组件&API

  • RateLimiter:限流器基类,定义限流器的创建、令牌的获取等操作。

  • SmoothRateLimiter:定义一种平滑的限流器,也是抽象类,继承RateLimiter。

  • SmoothBursty:普通的平滑限流器实现类,实现SmoothRateLimiter。以稳定的速率生成令牌,则会同时全部被获取到。比如令牌桶现有令牌数为5,这时连续进行10个请求,则前5个请求会全部直接通过,没有等待时间,之后5个请求则每隔200毫秒通过一次。

  • SmoothWarmingUp:预热的平滑限流器实现类,实现SmoothRateLimiter。随着请求量的增加,令牌生成速率会缓慢提升直到一个稳定的速率。比如令牌桶现有令牌数为5,这时连续进行10个请求,只会让第一个请求直接通过,之后的请求都会有等待时间,等待时间不断缩短,直到稳定在每隔200毫秒通过一次。这样,就会有一个预热的过程。

guava实现限流的主要类是RateLimiter,是限流器基类,定义限流器的创建、令牌的获取,常用方法如下:

  • create: 创建对象RateLimiter对象,参数主要为限制每秒通过限流器的请求数量,可传入TimeUnit设置时间单位。

  • acquire: 会阻塞等待限流,拿到令牌再执行,不常用。

  • tryAcquire: 非阻塞限流 ,不会等待,直接返回布尔值,业务依据布尔值进行处理,常用。

Guava使用

pom引入依赖

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

自定义限流注解

自定义一个限流用的注解,后面在需要限流的方法或接口上面只需添加该注解即可;

@Target(value = ElementType.METHOD)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface RateConfigAnno { String limitType();double limitCount() default 5d;
}

限流AOP类

通过AOP前置通知的方式拦截添加了上述自定义限流注解的方法,解析注解中的属性值,并以该属性值作为guava提供的限流参数,该类为整个实现的核心所在。

@Aspect
@Component
public class GuavaLimitAop {@Before("execution(@RateConfigAnno * *(..))")public void limit(JoinPoint joinPoint) {//1、获取当前的调用方法Method currentMethod = getCurrentMethod(joinPoint);if (Objects.isNull(currentMethod)) {return;}//2、从方法注解定义上获取限流的类型String limitType = currentMethod.getAnnotation(RateConfigAnno.class).limitType();double limitCount = currentMethod.getAnnotation(RateConfigAnno.class).limitCount();//使用guava的令牌桶算法获取一个令牌,获取不到先等待RateLimiter rateLimiter = RateLimitHelper.getRateLimiter(limitType, limitCount);boolean b = rateLimiter.tryAcquire();if (b) {System.out.println("获取到令牌");}else {HttpServletResponse resp = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();JSONObject jsonObject=new JSONObject();jsonObject.put("success",false);jsonObject.put("msg","限流中");try {output(resp, jsonObject.toJSONString());}catch (Exception e){logger.error("error,e:{}",e);}}}private Method getCurrentMethod(JoinPoint joinPoint) {Method[] methods = joinPoint.getTarget().getClass().getMethods();Method target = null;for (Method method : methods) {if (method.getName().equals(joinPoint.getSignature().getName())) {target = method;break;}}return target;}public void output(HttpServletResponse response, String msg) throws IOException {response.setContentType("application/json;charset=UTF-8");ServletOutputStream outputStream = null;try {outputStream = response.getOutputStream();outputStream.write(msg.getBytes("UTF-8"));} catch (IOException e) {e.printStackTrace();} finally {outputStream.flush();outputStream.close();}}
}

其中限流的核心API即为RateLimiter 这个对象,涉及到的RateLimitHelper类如下

public class RateLimitHelper {private RateLimitHelper(){}private static Map<String,RateLimiter> rateMap = new HashMap<>(); public static RateLimiter getRateLimiter(String limitType,double limitCount ){RateLimiter rateLimiter = rateMap.get(limitType);if(rateLimiter == null){rateLimiter = RateLimiter.create(limitCount);rateMap.put(limitType,rateLimiter);}return rateLimiter;}
}

使用注解

//localhost:8081/save
@GetMapping("/save")
@RateConfigAnno(limitType = "saveOrder",limitCount = 1)
public String save(){return "success";
}

Guava是如何实现令牌桶算法的?⭐

实现方法和传统的实现令牌桶算法不同

  • RateLimiter 速率器,通过预支将来的令牌来进行限制频控

  • 记录下一次的令牌产生时间并且动态的更新

其中几个重要概念:

  • maxPermits: 最大存储的令牌数,即令牌桶的大小

  • storedPermits: 已存储的令牌数 <=maxPermits, 当然这个是通过计算算出来的

  • nextFreeTicketMicros: 上次获取令牌时预支的最早能够再次获取令牌的时间

  • nowMicros: 当前系统时间

模拟令牌桶实现方法

public class SimpleLimiter2 {//当前令牌数量private long tokenNum = 0;// 令牌桶最大容量private long maxTokenNum = 3;// 下一次令牌产生的时间(单位纳秒)private long next = System.nanoTime();// 一秒 1毫秒 = 1000微秒  1微秒 = 1000纳秒private long speed = 1000_000_000;/*** 请求时间在下一令牌产生时间之后* 1、重新计算令牌桶中的令牌数(当前的令牌总数)* 2、将下一令牌产生时间重置为当前时间*/private void resync(long now){if (now > next){// 新产生的令牌数long newProduceNum = (now - next)/1000 * speed;// 当前令牌桶中的令牌数tokenNum = Math.min(maxTokenNum,newProduceNum+tokenNum);next = now;}}/*** 计算下一次令牌产生的时间(单位纳秒)*/private synchronized long reserve(long now){// 请求时间在下一次令牌产生之后 简单处理resync(now);// 能够获取令牌的时间long at = next;// 令牌桶能够提供的令牌数 只有两种情况// fb = 0 说明tokenNum=0 now <= next  令牌桶中没有令牌// fb = 1 说明tokenNum>1 now > next   令牌桶中有令牌long fb = Math.min(1,tokenNum);// nr=0那么已经消耗了一个令牌    nr=1 令牌桶中没有令牌还没消耗long nr = 1- fb;// 下一次令牌产生的时间next = next + nr*speed;// 重新计算令牌桶中的数量tokenNum -= fb;return at;}/*** 申请令牌*/public void acquire(){long now = System.nanoTime();// 令牌产生时间long reserve = reserve(now);long waitTime = Math.max(reserve - now, 0);if (waitTime>0){try {TimeUnit.NANOSECONDS.sleep(waitTime);} catch (InterruptedException e) {e.printStackTrace();}}}}

Bucket4j (简介)

  • Bucket4j 是一个非常不错的基于令牌/漏桶算法的限流库。

  • 相对于,Guava 的限流工具类来说,Bucket4j 提供的限流功能更加全面。不仅支持单机限流和分布式限流,还可以集成监控,搭配 Prometheus 和 Grafana 使用。

Resilience4j(简介)

  • Resilience4j 是一个轻量级的容错组件,其灵感来自于 Hystrix。

  • Spring 官方和 Netflix 都更推荐使用 Resilience4j 来做限流熔断(一般情况下,为了保证系统的高可用,项目的限流和熔断都是要一起做的。)

  • Resilience4j 不仅提供限流,还提供了熔断、负载保护、自动重试等保障系统高可用开箱即用的功能。并且,Resilience4j 的生态也更好,很多网关都使用 Resilience4j 来做限流熔断的。

  • 所以,在绝大部分场景下 Resilience4j 或许会是更好的选择。如果是一些比较简单的限流场景的话,Guava 或者 Bucket4j 也是不错的选择。

分布式限流

分布式限流针对的分布式/微服务应用架构应用,在这种架构下,单机限流就不适用了,因为会存在多种服务,并且一种服务也可能会被部署多份。

分布式限流常见的方案:

  • 借助中间件架限流:可以借助 Sentinel 或者使用 Redis 来自己实现对应的限流逻辑。

  • 网关层限流:比较常用的一种方案,直接在网关层把限流给安排上了。不过,通常网关层限流通常也需要借助到中间件/框架。就比如 Spring Cloud Gateway 的分布式限流实现RedisRateLimiter就是基于 Redis+Lua 来实现的,再比如 Spring Cloud Gateway 还可以整合 Sentinel 来做限流。

redis + lua

Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中

Lua脚本实现算法对比操作Redis实现算法的优点:

  • 减少网络开销:使用Lua脚本,无需向Redis 发送多次请求,执行一次即可,减少网络传输

  • 原子操作:Redis 将整个Lua脚本作为一个命令执行,原子,无需担心并发

  • 复用:Lua脚本一旦执行,会永久保存 Redis 中,,其他客户端可复用

limit.lua

local key = KEYS[1] --限流KEY 
local limit = tonumber(ARGV[1]) --限流大小 
local current = tonumber(redis.call('get', key) or "0") if current + 1 > limit then 
return 0 else redis.call("INCRBY", key,"1") redis.call("expire", key,"2") return current + 1 end

如果不想自己写 Lua 脚本的话,也可以直接利用 Redisson 中的 RRateLimiter 来实现分布式限流,其底层实现就是基于 Lua 代码。

Redisson 是一个开源的 Java 语言 Redis 客户端,提供了很多开箱即用的功能,比如 Java 中常用的数据结构实现、分布式锁、延迟队列等等。并且,Redisson 还支持 Redis 单机、Redis Sentinel、Redis Cluster 等多种部署架构。

Sentinel (简介)

  • alibaba开源的面向分布式服务架构的流量控制框架 Sentinel

  • 基于滑动窗口实现的,很好的解决固定窗口算法的临界问题。

  • Sentinel 是阿里巴巴提供的一种限流、熔断中间件,与RateLimiter相比,Sentinel提供了丰富的限流、熔断功能。它支持控制台配置限流、熔断规则,支持集群限流,并可以将相应服务调用情况可视化。

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

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

相关文章

react useRef用法

1&#xff0c;保存变量永远不丢失 import React, { useState,useRef } from react export default function App() { const [count,setcount] useState(0) var mycount useRef(0)//保存变量永远不丢失--useRef用的是闭包原理 return( <div> <button onClick{()>…

SocketError | Socket错误码一览表(每一种错误码的故障排查建议)

Socket错误码一览表 文章目录 Socket错误码一览表前言错误码表 前言 在软件开发和网络通信编程中&#xff0c;SocketError算是一个绕不开的坎。它可能因为各种原因而来&#xff0c;比如网络问题、用户搞错了、应用程序出错等等。本文整理一张SocketError排查建议表格就是为了帮…

蜘蛛蜂优化算法SWO求解不闭合MD-MTSP,可以修改旅行商个数及起点(提供MATLAB代码)

1、蜘蛛蜂优化算法SWO 蜘蛛蜂优化算法&#xff08;Spider wasp optimizer&#xff0c;SWO&#xff09;由Mohamed Abdel-Basset等人于2023年提出&#xff0c;该算法模型雌性蜘蛛蜂的狩猎、筑巢和交配行为&#xff0c;具有搜索速度快&#xff0c;求解精度高的优势。VRPTW&#x…

合泰杯开发板HT66F2390入门教程(点亮LED灯)——获得成就:点灯大师

前言 前不久报名了合泰杯竞赛项目&#xff0c;然后手上也是有一个HT66F2390的开发板&#xff0c;我就打算先从点灯开始&#xff0c;学习一个新的芯片第一步都是先成为点灯大师。 一开始&#xff0c;我在网上搜寻了许多的代码示例&#xff0c;希望能够顺利实现LED的控制。然而&…

基于事件触发机制的孤岛微电网二次电压与频率协同控制MATLAB仿真模型

微❤关注“电气仔推送”获得资料&#xff08;专享优惠&#xff09; 本模型质量非常高&#xff0c;运行效果完美。本模型为4机并联孤岛系统&#xff0c;在下垂控制的基础上加入二次控制&#xff0c;二次电压与频率协同控制策略利用事件触发的方法来减少控制器的更新次数。该方法…

C++ //练习 9.16 重写上一题的程序,比较一个list<int>中的元素和一个vector<int>中的元素。

C Primer&#xff08;第5版&#xff09; 练习 9.16 练习 9.16 重写上一题的程序&#xff0c;比较一个list中的元素和一个vector中的元素。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09; 工具&#xff1a;vim 代码块 /**********************************…

【刷题】leetcode 1544.整理字符串

刷题 1544.整理字符串思路一&#xff08;模拟栈速解版&#xff09;思路二 &#xff08;原地算法巧解版&#xff09;思路三&#xff08;C栈版&#xff09; Thanks♪(&#xff65;ω&#xff65;)&#xff89;谢谢阅读&#xff01;&#xff01;&#xff01;下一篇文章见&#xff…

数据结构与算法|线性结构

数据结构与算法|线性结构 第二章 线性结构2.1 多项式表示2.2 什么是线性表2.3 线性表的实现方式2.3.1 线性表的顺序存储实现2.3.2 线性表的链式存储实现1. 单链表实现2. 双链表实现 上篇&#xff1a;第一章、绪论 第二章 线性结构 线性结构是数据结构中最基础的&#xff0c;也…

Rider 2023:打造高效.NET项目的智能IDE,让开发更简单mac/win版

JetBrains Rider 2023激活版下载是一款专为.NET开发者打造的强大集成开发环境&#xff08;IDE&#xff09;。这款IDE提供了丰富的功能&#xff0c;旨在帮助开发者更快速、更高效地编写、调试和测试.NET应用程序。 Rider 2023 软件获取 Rider 2023在保持了其一贯的智能代码补全…

如何下载B站高清视频、音频到本地?

在B站上找到了喜欢的视频&#xff1f;想要将它保存到本地或者与朋友分享&#xff1f;本文将向您详细介绍一种简单而有效的方法&#xff0c;帮助我们轻松下载并导出B站视频&#xff0c;以便随时欣赏或分享。 一、如何下载并导出B站视频&#xff1f; 手机端/平板端 第一步&…

排序(9.17)

1.排序的概念及其运用 1.1排序的概念 排序 &#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。 稳定性 &#xff1a;假定在待排序的记录序列中&#xff0c;存在多个具有相同的关键字的记…

STM32通用定时器输入捕获

通用定时器输入捕获部分框图介绍 通用定时器输入捕获脉宽测量原理 要测量脉宽的高电平的时间&#xff1a;t2-t1&#xff08;脉宽下降沿时间点-脉宽上升沿时间点&#xff09; 假设&#xff1a;递增计数模式 ARR&#xff1a;自动重装载寄存器的值 CCRx1&#xff1a;t1时间点CCRx…