Java - 深入四大限流算法:原理、实现与应用

文章目录

  • Pre
  • 概述
  • 简单计数器
    • 原理
    • 实现
    • 测试
    • 优缺点
  • 滑动窗口算法
    • 原理
    • 实现
    • 测试
    • 优缺点
  • 漏桶算法
    • 原理
    • 实现
    • 测试
    • 优缺点
  • 令牌桶算法
    • 原理
    • 实现
    • 测试
    • 优缺点
  • 小结

在这里插入图片描述

Pre

深入理解分布式技术 - 限流

并发编程-25 高并发处理手段之消息队列思路 + 应用拆分思路 + 应用限流思路

SpringBoot - 优雅的实现【流控】

Spring Boot - 利用Resilience4j-RateLimiter进行流量控制和服务降级

MQ - 19 安全_限流方案的设计

每日一博 - 闲聊“突发流量”的应对之道


概述

限流算法是一种在分布式系统中广泛使用的技术,用于控制对系统资源的访问速率,以保护系统免受恶意攻击或突发流量导致的过载。

在实际的业务场景中,接口限流策略的应用非常广泛,以下是一些典型的场景:

  1. API 网关限流:在微服务架构中,API 网关通常是系统对外的唯一入口,需要限制单个用户或IP在一定时间内的请求次数,以保护后端服务不受恶意请求或突发流量的冲击。
  2. 分布式系统中的服务限流:在分布式系统中,各个服务之间可能会有调用关系,通过限流可以控制服务间的调用频率,避免服务间因为调用过于频繁而造成的服务过载。
  3. 微服务间的接口限流:微服务架构中,服务间通过接口进行通信,对接口进行限流可以保证服务之间的通信不会因为过量的请求而变得不可用。
  4. 营销活动限流:在开展营销活动时,为了防止活动页面被恶意刷票或访问量过大而崩溃,需要对接口进行限流,确保活动能够在可控的范围内进行。
  5. 用户高频操作限流:对于用户频繁操作的接口,如登录、发帖、评论等,需要限制用户在短时间内完成的操作次数,防止用户恶意操作或频繁操作导致系统资源耗尽。
  6. 秒杀活动限流:在秒杀活动中,为了防止用户在短时间内提交过多的秒杀请求,导致系统无法处理,需要对参与秒杀的接口进行限流。
  7. 后端服务保护限流:对于一些敏感操作或计算密集型的后端服务,通过限流可以避免因请求过多而使得服务响应变慢或崩溃。
  8. 防止分布式拒绝服务攻击:在遭受分布式拒绝服务(DDoS)攻击时,限流策略能够有效地减轻攻击带来的压力,确保关键业务的稳定性。
  9. 用户体验优化:对于一些需要高响应速度的服务,通过限流可以避免过多的请求积压,确保用户能够获得良好的服务体验。

在这里插入图片描述

简单计数器

原理

简单计数器是一种最基础的限流算法,它的实现原理相对直观。

简单计数器的工作原理如下:

  1. 时间窗口设定:首先设定一个固定的时间窗口,比如1分钟。
  2. 计数器初始化:在每个时间窗口开始时,将计数器重置为0。
  3. 请求到达:每当一个请求到达时,计数器加1。
  4. 判断与拒绝:如果在时间窗口内计数器的值达到了设定的阈值,比如1000,则后续的请求会被拒绝,直到当前时间窗口结束,计数器被重置。

在这里插入图片描述


实现

package com.artisan.counter;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CounterRateLimit {


    /**
     * 请求数量
     *
     * @return
     */
    int maxRequest();


    /**
     * 时间窗口, 单位秒
     *
     * @return
     */
    int timeWindow();


}

package com.artisan.counter;

import org.aspectj.lang.ProceedingJoinPoint; 
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */

@Aspect
@Component
public class CounterRateLimitAspect {

    // 存储每个方法对应的请求次数
    private Map<String, AtomicInteger> REQUEST_COUNT = new ConcurrentHashMap<>();

    // 存储每个方法的时间戳
    private Map<String, Long> REQUEST_TIMESTAMP = new ConcurrentHashMap<>();

    /**
     *
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("@annotation(com.artisan.counter.CounterRateLimit)")
    public Object rateLimit(ProceedingJoinPoint joinPoint) throws Throwable {

        // 获取注解信息
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        CounterRateLimit annotation = method.getAnnotation(CounterRateLimit.class);

        // 获取注解的参数
        int maxRequest = annotation.maxRequest();
        long timeWindowInMillis = TimeUnit.SECONDS.toMillis(annotation.timeWindow());

        // 获取方法名
        String methodName = method.toString();

        // 初始化计数器和时间戳
        AtomicInteger count = REQUEST_COUNT.computeIfAbsent(methodName, x -> new AtomicInteger(0));
        long startTime = REQUEST_TIMESTAMP.computeIfAbsent(methodName, x -> System.currentTimeMillis());

        // 获取当前时间
        long currentTimeMillis = System.currentTimeMillis();

        // 判断: 如果当前时间超出时间窗口,则重置
        if (currentTimeMillis - startTime > timeWindowInMillis) {
            count.set(0);
            REQUEST_TIMESTAMP.put(methodName, currentTimeMillis);
        }

        // 原子的增加计数器并检查其值
        if (count.incrementAndGet() > maxRequest) {
            // 如果超出最大请求次数,递减计数器,并报错
            count.decrementAndGet();
            throw new RuntimeException("Too many requests, please try again later.");
        }

        // 方法原执行
        return joinPoint.proceed();
    }
}
    
package com.artisan.counter;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
@RestController
public class CounterRateLimitController {

    /**
     * 一秒一次
     *
     * @return
     */
    @GetMapping("/counter")
    @CounterRateLimit(maxRequest = 2, timeWindow = 2)
    public ResponseEntity counter() {
        System.out.println("Request Coming Coming....");
        return ResponseEntity.ok("Artisan OK");
    }
}
    

测试

启动项目, 访问接口 http://localhost:8080/counter

在这里插入图片描述


优缺点

简单计数器算法的优点是实现简单,但缺点也很明显:

  • 边界问题:由于计数器是在时间窗口结束时重置的,如果系统的请求量非常大,可能会出现时间窗口临界点的问题,即在窗口即将结束时请求量激增,而在窗口开始时请求量较少,导致系统资源不能被有效利用。

  • 突增流量处理能力不足:无法有效处理突增的流量,因为它的限制是固定的,不能根据系统的实时负载进行调整。
    为了解决简单计数器的这些问题,可以采用更为复杂的限流算法,如滑动窗口计数器、漏桶算法、令牌桶算法等。这些算法能够更加平滑和有效地控制请求速率,提高系统的稳定性和可靠性。

在这里插入图片描述

在示例中,有一个使用了 @CounterRateLimit 注解的 counter 方法。根据注解的参数,这个方法在2秒钟的时间窗口内只能被调用2次。 如果在 2 秒内有更多的调用,那么这些额外的调用将被限流,并返回错误信息

在这里插入图片描述

假设1min一个时间段,每个时间段内最多100个请求。有一种极端情况,当10:00:58这个时刻100个请求一起过来,到达阈值;当10:01:02这个时刻100个请求又一起过来,到达阈值。这种情况就会导致在短短的4s内已经处理完了200个请求,而其他所有的时间都在限流中。


滑动窗口算法

原理

滑动窗口算法是实现限流的一种常用方法,它通过维护一个时间窗口来控制单位时间内请求的数量,从而保护系统免受突发流量或恶意攻击的影响。其核心原理是统计时间窗口内的请求次数,并根据预设的阈值来决定是否允许新的请求通过。

在这里插入图片描述
从图上可以看到时间创建是一种滑动的方式前进, 滑动窗口限流策略能够显著减少临界问题的影响,但并不能完全消除它。滑动窗口通过跟踪和限制在一个连续的时间窗口内的请求来工作。与简单的计数器方法不同,它不是在窗口结束时突然重置计数器,而是根据时间的推移逐渐地移除窗口中的旧请求,添加新的请求。

举个例子:假设时间窗口为10s,请求限制为3,第一次请求在10:00:00发起,第二次在10:00:05发起,第三次10:00:11发起,那么计数器策略的下一个窗口开始时间是10:00:11,而滑动窗口是10:00:05。所以这也是滑动窗口为什么可以减少临界问题的影响,但并不能完全消除它的原因。

在这里插入图片描述


实现

package com.artisan.slidingwindow;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SlidingWindowRateLimit {


    /**
     * 请求数量
     *
     * @return
     */
    int maxRequest();


    /**
     * 时间窗口, 单位秒
     *
     * @return
     */
    int timeWindow();

}

package com.artisan.slidingwindow;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */

@Aspect
@Component
public class SlidingWindowRateLimitAspect {

    /**
     * 使用 ConcurrentHashMap 保存每个方法的请求时间戳队列
     */
    private final ConcurrentHashMap<String, ConcurrentLinkedQueue<Long>> REQUEST_TIMES_MAP = new ConcurrentHashMap<>();


    @Around("@annotation(com.artisan.slidingwindow.SlidingWindowRateLimit)")
    public Object rateLimit(ProceedingJoinPoint joinPoint) throws Throwable {
        
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        SlidingWindowRateLimit rateLimit = method.getAnnotation(SlidingWindowRateLimit.class);
        
        // 允许的最大请求数
        int requests = rateLimit.maxRequest();
        // 滑动窗口的大小(秒)
        int timeWindow = rateLimit.timeWindow();

        // 获取方法名称字符串
        String methodName = method.toString();
        // 如果不存在当前方法的请求时间戳队列,则初始化一个新的队列
        ConcurrentLinkedQueue<Long> requestTimes = REQUEST_TIMES_MAP.computeIfAbsent(methodName,
                k -> new ConcurrentLinkedQueue<>());

        // 当前时间
        long currentTime = System.currentTimeMillis();
        // 计算时间窗口的开始时间戳
        long thresholdTime = currentTime - TimeUnit.SECONDS.toMillis(timeWindow);

        // 这一段代码是滑动窗口限流算法中的关键部分,其功能是移除当前滑动窗口之前的请求时间戳。这样做是为了确保窗口内只保留最近时间段内的请求记录。
        // requestTimes.isEmpty() 是检查队列是否为空的条件。如果队列为空,则意味着没有任何请求记录,不需要进行移除操作。
        // requestTimes.peek() < thresholdTime 是检查队列头部的时间戳是否早于滑动窗口的开始时间。如果是,说明这个时间戳已经不在当前的时间窗口内,应当被移除。
        while (!requestTimes.isEmpty() && requestTimes.peek() < thresholdTime) {
            // 移除队列头部的过期时间戳
            requestTimes.poll();
        }

        // 检查当前时间窗口内的请求次数是否超过限制
        if (requestTimes.size() < requests) {
            // 未超过限制,记录当前请求时间
            requestTimes.add(currentTime);
            return joinPoint.proceed();
        } else {
            // 超过限制,抛出限流异常
            throw new RuntimeException("Too many requests, please try again later.");
        }
    }


}
    
package com.artisan.slidingwindow;

import com.artisan.leakybucket.LeakyBucketRateLimit;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
@RestController
public class SlidingWindowController {

    @GetMapping("/slidingWindow")
    @SlidingWindowRateLimit(maxRequest = 2, timeWindow = 2)
    public ResponseEntity slidingWindow() {
        return ResponseEntity.ok("artisan  slidingWindow ");
    }
}
    

测试

在这里插入图片描述


优缺点

滑动窗口算法的优点是它能够比较平滑地控制流量,允许一定程度的突发流量,同时又能够限制平均流量。相比于固定窗口算法,滑动窗口算法能够更精确地控制单位时间内的请求量,因为它考虑了时间窗口内请求的分布情况,而不仅仅是在窗口的开始和结束时刻的请求量。

滑动窗口算法的变种有很多,如基于令牌桶和漏桶的算法,这些算法在滑动窗口的基础上增加了更为复杂的令牌生成和消耗机制,以实现更精细的流量控制。


漏桶算法

原理

在这里插入图片描述
在Leaky Bucket算法中,容器有一个固定的容量,类似于漏桶的容量。数据以固定的速率进入容器,如果容器满了,则多余的数据会溢出。容器中的数据会以恒定的速率从底部流出,类似于漏桶中的水滴。如果容器中的数据不足以满足流出速率,则会等待直到有足够的数据可供流出。这样就实现了对数据流的平滑控制。

在这里插入图片描述


实现

package com.artisan.leakybucket;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface LeakyBucketRateLimit {

    /**
     * 桶的容量
     *
     * @return
     */
    int capacity();

    /**
     * 漏斗的速率,单位通常是秒
     *
     * @return
     */
    int leakRate();
}

package com.artisan.leakybucket;


import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
@Aspect
@Component
public class LeakyBucketRateLimitAspect {


    @Around("@annotation(com.artisan.leakybucket.LeakyBucketRateLimit)")
    public Object rateLimit(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        LeakyBucketRateLimit leakyBucketRateLimit = method.getAnnotation(LeakyBucketRateLimit.class);

        int capacity = leakyBucketRateLimit.capacity();
        int leakRate = leakyBucketRateLimit.leakRate();

        // 方法签名作为唯一标识
        String methodKey = method.toString();

        LeakyBucketLimiter limiter = LeakyBucketLimiter.createLimiter(methodKey, capacity, leakRate);
        if (!limiter.tryAcquire()) {
            // 超过限制,抛出限流异常
            throw new RuntimeException("Too many requests, please try again later.");
        }

        return joinPoint.proceed();
    }
}
    
package com.artisan.leakybucket;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
public class LeakyBucketLimiter {
    /**
     * 桶的容量
     */
    private final int capacity;
    /**
     * 漏桶的漏出速率,单位时间内漏出水的数量
     */
    private final int leakRate;
    /**
     * 当前桶中的水量
     */
    private volatile int water = 0;
    /**
     * 上次漏水的时间
     */
    private volatile long lastLeakTime = System.currentTimeMillis();

    /**
     * 漏桶容器
     */
    private static final ConcurrentHashMap<String, LeakyBucketLimiter> LIMITER_MAP = new ConcurrentHashMap<>();

    /**
     * 静态工厂方法,确保相同的方法使用相同的漏桶实例
     *
     * @param methodKey 方法名
     * @param capacity
     * @param leakRate
     * @return
     */
    public static LeakyBucketLimiter createLimiter(String methodKey, int capacity, int leakRate) {
        return LIMITER_MAP.computeIfAbsent(methodKey, k -> new LeakyBucketLimiter(capacity, leakRate));
    }

    private LeakyBucketLimiter(int capacity, int leakRate) {
        this.capacity = capacity;
        this.leakRate = leakRate;
    }

    /**
     * 尝试获取许可(try to acquire a permit),如果获取成功返回true,否则返回false
     *
     * @return
     */
    public boolean tryAcquire() {
        long currentTime = System.currentTimeMillis();
        synchronized (this) {
            // 计算上次漏水到当前时间的时间间隔
            long leakDuration = currentTime - lastLeakTime;
            // 如果时间间隔大于等于1秒,表示漏桶已经漏出一定数量的水
            if (leakDuration >= TimeUnit.SECONDS.toMillis(1)) {
                // 计算漏出的水量
                long leakQuantity = leakDuration / TimeUnit.SECONDS.toMillis(1) * leakRate;
                // 漏桶漏出水后,更新桶中的水量,但不能低于0
                water = (int) Math.max(0, water - leakQuantity);
                lastLeakTime = currentTime;
            }
            // 判断桶中的水量是否小于容量,如果是则可以继续添加水(相当于获取到令牌)
            if (water < capacity) {
                water++;
                return true;
            }
        }
        // 如果桶满,则获取令牌失败
        return false;
    }
}
    
package com.artisan.leakybucket;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
@RestController
public class LeakBucketController {

    /**
     * 
     *
     * @return
     */
    @GetMapping("/leakyBucket")
    @LeakyBucketRateLimit(capacity = 10, leakRate = 2)
    public ResponseEntity leakyBucket() {
        return ResponseEntity.ok("leakyBucket test ok!");
    }
}
    

测试

在这里插入图片描述


优缺点

漏桶算法和令牌桶算法最明显的区别是令牌桶算法允许流量一定程度的突发。因为默认的令牌桶算法,取走token是不需要耗费时间的,也就是说,假设桶内有100个token时,那么可以瞬间允许100个请求通过。

令牌桶算法由于实现简单,且允许某些流量的突发,对用户友好,所以被业界采用地较多。当然我们需要具体情况具体分析,只有最合适的算法,没有最优的算法。


令牌桶算法

使用Guava自带的RateLimiter实现

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

原理

在这里插入图片描述

令牌桶算法通过一个形象的比喻来描述:想象有一个桶,桶里装有一定数量的令牌。系统会以固定的速率向桶中添加令牌,而每个数据包在发送前都需要从桶中获取一个令牌。如果桶中有足够的令牌,数据包就可以立即发送;如果桶中没有令牌,那么数据包就需要等待,直到桶中有足够的令牌为止。

关键参数:

  • 令牌生成速率:令牌被添加到桶中的速率,通常以每秒多少个令牌来表示。
  • 桶容量:桶中可以存放的最大令牌数,如果桶已满,新添加的令牌会被丢弃或忽略。
  • 初始令牌数:桶在开始时的令牌数量。

算法流程:

  1. 令牌添加:以固定的速率向桶中添加令牌,通常这个速率对应了网络接口的带宽限制。
  2. 请求处理
    • 当一个数据包到达时,系统会检查桶中是否有足够的令牌。
    • 如果有足够的令牌,就从桶中移除相应数量的令牌,并且允许数据包通过。
    • 如果桶中没有足够的令牌,数据包将被标记为等待或者被丢弃。
  3. 桶满处理:如果桶已满,新添加的令牌可能会被丢弃或计数超出桶容量的令牌数量,以允许临时突发流量的处理。

在这里插入图片描述


实现

package com.artisan.tokenbucket;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface TokenBucketRateLimit {
    /**
     * 产生令牌的速率(xx 个/秒)
     *
     * @return
     */
    double permitsPerSecond();
}
package com.artisan.tokenbucket;

import com.google.common.util.concurrent.RateLimiter;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */

@Aspect
@Component
public class TokenBucketRateLimitAspect {

    // 使用ConcurrentHashMap来存储每个方法的限流器
    private final ConcurrentHashMap<String, RateLimiter> limiters = new ConcurrentHashMap<>();

    // 环绕通知,用于在方法执行前后添加限流逻辑
    @Around("@annotation(com.artisan.tokenbucket.TokenBucketRateLimit)")
    public Object rateLimit(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取方法签名,用于获取方法信息
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 根据方法签名获取方法对象
        Method method = signature.getMethod();
        // 从方法对象中获取限流注解
        TokenBucketRateLimit rateLimit = method.getAnnotation(TokenBucketRateLimit.class);

        // 获取注解中定义的每秒令牌数
        double permitsPerSecond = rateLimit.permitsPerSecond();

        // 获取方法名,作为限流器的唯一标识
        String methodName = method.toString();
        // 如果限流器缓存中没有该方法的限流器,则创建一个新的
        RateLimiter rateLimiter = limiters.computeIfAbsent(methodName, k -> RateLimiter.create(permitsPerSecond));

        // 尝试获取令牌,如果可以获取,则继续执行方法
        if (rateLimiter.tryAcquire()) {
            return joinPoint.proceed();
        } else {
            // 如果无法获取令牌,则抛出异常,告知用户请求过于频繁
            throw new RuntimeException("Too many requests, please try again later.");
        }
    }
}

    
package com.artisan.tokenbucket;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
@RestController
public class TokenBucketController {

    @GetMapping("/tokenBucket")
    @TokenBucketRateLimit(permitsPerSecond = 0.5)
    public ResponseEntity tokenBucket() {
        return ResponseEntity.ok("artisan token bucket");
    }
}
    

测试

在这里插入图片描述


优缺点

  • 平滑流量:通过控制令牌生成的速率,可以平滑网络流量,避免突发流量导致的网络拥塞。
  • 灵活性:可以应对一定的突发流量,因为桶可以暂时存储超过平均流量的令牌。
  • 易于实现:算法相对简单,易于在硬件或软件中实现。

小结

在实施接口限流策略时,应根据具体的业务场景和系统需求,选择合适的限流算法和实现方式,同时注意限流策略对用户体验的影响,做到既能保护系统稳定运行,又不会对合法用户造成过多的困扰。

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

(上) C语言中的语句分类及分支语句:if语句、switch语句介绍

目录 前言 一、语句的分类 1. 空语句 2. 表达式语句 3. 函数调用语句 4. 复合语句 5. 控制语句 二、分支语句 1. if语句 (1) if的使用 (2) else的使用 (3) 分支中包含多条语句 (4) 嵌套if (5) 悬空else问题 2. switch语句 (1) if语句和switch语句的对比 (2) s…

摇臂MG995舵机模块实战教程

简介 舵机也叫伺服电机&#xff0c;最早用于船舶上实现其转向功能&#xff0c;由于可以通过程序连续控制其转角&#xff0c;因而被广泛应用智能小车以实现转向以及机器人各类关节运动中。舵机&#xff08;英文叫Servo&#xff09;&#xff1a;它由直流电机、减速齿轮组、传感器…

计算机网络——面试问题

1 从输⼊ URL 到⻚⾯展示到底发⽣了什么&#xff1f; 1. 先检查浏览器缓存⾥是否有缓存该资源&#xff0c;如果有直接返回&#xff1b;如果没有进⼊下⼀ 步⽹络请求。 2. ⽹络请求前&#xff0c;进⾏ DNS 解析 &#xff0c;以获取请求域名的 IP地址 。 3. 浏览器与服务器…

Sqoop与Kafka的集成:实时数据导入

将Sqoop与Kafka集成是实现实时数据导入和流处理的关键步骤之一。Sqoop用于将数据从关系型数据库导入到Hadoop生态系统中&#xff0c;而Kafka则用于数据流的传输和处理。本文将深入探讨如何使用Sqoop与Kafka集成&#xff0c;提供详细的步骤、示例代码和最佳实践&#xff0c;以确…

Git与GitHub零基础教学

大家好&#xff0c;我是星恒&#xff0c;这个帖子给大家分享的是git和github的全套教程&#xff0c;包含github初始&#xff0c;git常用命令以及基本使用&#xff0c;git的ssh链接github&#xff0c;github使用token登录&#xff0c;github和idea的配合&#xff0c;一些平时常用…

适合初学者的 机器学习 资料合集(可快速下载)

AI时代已经来临&#xff0c;机器学习成为了当今的热潮。但是&#xff0c;很多人在面对机器学习时却不知道如何开始学习。 今天&#xff0c;我为大家推荐几个适合初学者的机器学习开源项目&#xff0c;帮助大家更好地了解和掌握机器学习的知识。这些项目都是开源的&#xff0c;…

EtherNet/IP开发:C++开发CIP源代码

① 介绍一下CIP CIP是一种考虑到自动化行业而设计的通用协议。然而&#xff0c;由于其开放性&#xff0c;它可以并且已经应用于更多的领域。CIP网络库包含若干卷&#xff1a; 第1卷介绍了适用于所有网络自适应的CIP的常见方面。本卷包含通用对象库和设备配置文件库&#xff0…

信息安全的脆弱性及常见安全攻击

目录 信息安全概述信息安全现状及挑战传统安全防护逐步失效 安全风险能见度不足看不清资产看不见新型威胁看不见内网潜藏风险 常见的网络安全术语信息安全的脆弱性及常见安全攻击网络环境的开放性协议栈的脆弱性及常见攻击常见安全风险 协议栈自身的脆弱性网络的基本攻击模式 链…

Dubbo的几个序列化方式

欢迎订阅专栏&#xff0c;会分享Dubbo里面相关的技术实现 这篇文章就不详细的介绍每种序列化方式的实现细节&#xff0c;大家可以自行去问度娘&#xff0c;我也会找一些资料。需要注意的是&#xff0c;这个先后顺序不表示性能优越 ObjectInput、ObjectOutput 这两是Dubbo序列…

Linux_清理docker磁盘占用

文章目录 前言一、docker system 命令1. docker system df&#xff08;本文重点使用&#xff09;2. docker system prune&#xff08;本文重点使用&#xff09;3. docker system info4. docker system events 二、开始清理三、单独清理Build Cache四、单独清理未被使用的网络 前…

如何理解 GO 语言的接口 - 鸭子模型

个人认为&#xff0c;要理解 Go 的接口&#xff0c;一定先了解下鸭子模型。 鸭子模型 那什么鸭子模型&#xff1f; 鸭子模型的解释&#xff0c;通常会用了一个非常有趣的例子&#xff0c;一个东西究竟是不是鸭子&#xff0c;取决于它的能力。游泳起来像鸭子、叫起来也像鸭子…

【Emgu CV教程】5.6、几何变换之LinearPolar()极坐标变换

LinearPolar()线性极坐标转换函数用于将图像从笛卡尔坐标系转换为极坐标系&#xff0c;太难懂了&#xff0c;还是简单的说吧 笛卡尔坐标系就是平面直角坐标系&#xff0c;用X轴、Y轴表示的图像&#xff0c;最常用的表示方式&#xff0c;比如灰度图Point(360,100) 230&#xff…

数据结构与算法教程,数据结构C语言版教程!(第五部分、数组和广义表详解)四

第五部分、数组和广义表详解 数组和广义表&#xff0c;都用于存储逻辑关系为“一对一”的数据。 数组存储结构&#xff0c;99% 的编程语言都包含的存储结构&#xff0c;用于存储不可再分的单一数据&#xff1b;而广义表不同&#xff0c;它还可以存储子广义表。 本章重点从矩阵…

【工具使用】Keil5软件使用-基础使用篇

一、概述 本文面向未接触过Keil的新手&#xff0c;如果是职场老手可跳过此篇。为了快速上手&#xff0c;本文会跳过很多细节及解释&#xff0c;如需要了解原理&#xff0c;请移步进阶篇。 二、 软件介绍 Keil提供了包括C编译器、宏汇编、链接器、库管理和一个功能强大的仿真调…

【Git不走弯路】(二)提交与分支的本质

1. 前言 提交与分支是Git中两个基本对象&#xff0c;对初学者而言需要花些时间理解。正如我们之前所说&#xff0c;计算机中很多新概念是新瓶装旧酒。计算机技术来源于需求&#xff0c;服务于需求&#xff0c;需求是计算机技术的出发点和落脚点。梳理清楚工程实践中&#xff0…

【开源】基于JAVA的停车场收费系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 停车位模块2.2 车辆模块2.3 停车收费模块2.4 IC卡模块2.5 IC卡挂失模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 停车场表3.2.2 车辆表3.2.3 停车收费表3.2.4 IC 卡表3.2.5 IC 卡挂失表 四、系统实现五、核心代码…

2023.1.21 关于 Redis 主从复制详解

目录 引言 单点问题 分布式系统 ​​​​​​​​​​​​​​主从模式 配置 Redis 主从结构 断开主从关系 切换主从关系 补充知识点一 只读 网络延迟 拓扑结构 一主一从 一主多从 树形主从结构 主从复制的基本流程 数据同步 replicationid offset pzync 运…

transdata笔记:手机数据处理

1 mobile_stay_duration 每个停留点白天和夜间的持续时间 transbigdata.mobile_stay_duration(staydata, col[stime, etime], start_hour8, end_hour20) 1.1 主要参数 staydata停留数据&#xff08;每一行是一条数据&#xff09;col 列名&#xff0c;顺序为[‘starttime’,…

终极解决Flutter项目运行ios项目报错Without CocoaPods, plugins will not work on iOS or macOS.

前言 最近在开发Flutter项目&#xff0c;运行ios环境的时候报错没有CocoaPods&#xff0c;安卓环境可以正常运行&#xff0c;当时一脸懵逼&#xff0c;网上搜索了一下&#xff0c;有给我讲原理的&#xff0c;还有让我安装这插件那插件的&#xff0c;最终把电脑搞得卡死&#x…

代码随想录算法训练DAY25|回溯2

算法训练DAY25|回溯2 216.组合总和III 力扣题目链接 找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数&#xff0c;并且每种组合中不存在重复的数字。 说明&#xff1a; 所有数字都是正整数。 解集不能包含重复的组合。 示例 1: 输入: k 3, n …