常见分布式限流方案

限流分类

容器限流

比如我们在服务器端通过限流算法实现限流,此项也是我们本文介绍的重点。

实现方案:
进入tomcat的conf/server.xml文件配置中,然后找到如下代码:

maxThreads就是tomcat的最大线程数,当请求的并发大于此值,请求就会排队执行。
此值默认为 150(Tomcat 版本 8.5.42),每开启一个线程需要耗用 1MB 的 JVM 内存空间用于作为线程栈之用,Windows 每个进程中的线程数不允许超过 2000,Linux 每个进程中的线程数不允许超过 1000。
上面的请求限制太过于苛刻,对于单位时间内无法执行的请求直接拒绝。真实情况应该用ip继续细分而不是只统计单位时间内请求次数。我们可以使用burst配置,代码如下:
burst=4 表示每个 IP 最多允许4个突发请求.这意味着500ms内一个ip发起6次请求,第一个请求立即执行,接下来4个请求进入队列等待执行,最后一个请求直接拒绝。
$binary_remote_addr和$server_name。
其中 limit_conn perip 10 表示限制单个 IP 同时最多能持有 10 个连接;limit_conn perserver 100 表示 server 同时能处理并发连接的总数为 100 个。

实现方案:
所谓的滑动时间算法指的是以当前时间为截止时间,往前取一定的时间,比如往前取 60s 的时间,在这 60s 之内运行最大的访问数为 100,此时算法的执行逻辑为,先清除 60s 之前的所有请求记录,再计算当前集合内请求数量是否大于设定的最大请求数 100,如果大于则执行限流拒绝策略,否则插入本次请求记录并返回可以正常执行的标识给客户端。
滑动时间窗口如下图所示:

 

<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.3.0</version>
</dependency>

 

import redis.clients.jedis.Jedis;

public class RedisLimit {
    // Redis 操作客户端
    static Jedis jedis = new Jedis("127.0.0.1", 6379);

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 15; i++) {
            boolean res = isPeriodLimiting("java", 3, 10);
            if (res) {
                System.out.println("正常执行请求:" + i);
            } else {
                System.out.println("被限流:" + i);
            }
        }
        // 休眠 4s
        Thread.sleep(4000);
        // 超过最大执行时间之后,再从发起请求
        boolean res = isPeriodLimiting("java", 3, 10);
        if (res) {
            System.out.println("休眠后,正常执行请求");
        } else {
            System.out.println("休眠后,被限流");
        }
    }

    /**
     * 限流方法(滑动时间算法)
     * @param key      限流标识
     * @param period   限流时间范围(单位:秒)
     * @param maxCount 最大运行访问次数
     * @return
     */
    private static boolean isPeriodLimiting(String key, int period, int maxCount) {
        long nowTs = System.currentTimeMillis(); // 当前时间戳
        // 删除非时间段内的请求数据(清除老访问数据,比如 period=60 时,标识清除 60s 以前的请求记录)
        jedis.zremrangeByScore(key, 0, nowTs - period * 1000);
        long currCount = jedis.zcard(key); // 当前请求次数
        if (currCount >= maxCount) {
            // 超过最大请求次数,执行限流
            return false;
        }
        // 未达到最大请求数,正常执行业务
        jedis.zadd(key, nowTs, "" + nowTs); // 请求记录 +1
        return true;
    }
}

时间窗口算法的缺点:在一定范围内,比如 60s 内只能有 10 个请求,当第一秒时就到达了 10 个请求,那么剩下的 59s 只能把所有的请求都给拒绝掉。

漏洞算法的实现步骤是,先声明一个队列用来保存请求,这个队列相当于漏斗,当队列容量满了之后就放弃新来的请求,然后重新声明一个线程定期从任务队列中获取一个或多个任务进行执行,这样就实现了漏桶算法。

6.服务端使用令牌算法借助Google 的 guava 包来实现限流
注意事项:Redis 实现的限流方案可用于分布式系统,而 guava 实现的限流只能应用于单机环境。

 

 

 

 

 

上一篇:Redis


下一篇:jedis 与 redission 实现分布式锁