我们的程序对请求的处理能力是有限的,一旦请求多到超出它的处理极限就会崩溃。为了不出现最坏的崩溃情况,只能耽误一下大家的访问。限流是保证系统高可用的重要手段!!!由于互联网公司的流量巨大,系统上线会做一个流量峰值的评估,尤其是像各种秒杀促销活动,为了保证系统不被巨大的流量压垮,会在系统流量到达一定阈值时,拒绝掉一部分流量。
    分布式微服务架构,一个业务操作往往涉及多个微服务,即调用链。当调用链的下游服务由于网络或者硬件问题导致长时间的无响应或者不可用,那上游服务就应该停止尝试或者直接返回不要卡死在这里。限流会导致用户在短时间内(这个时间段是毫秒级的)系统不可用,一般我们衡量系统处理能力的指标是每秒查询率(QPS,Queries-per-second)或者每秒事务处理量(TPS,Transaction Per Second),假设系统每秒的流量阈值是1000,理论上一秒内有第1001个请求进来时,那么这个请求就会被限流。 :::info 计数器算法 ::: Java可以通过原子类计数器AtomicIntegerSemaphore信号量来做简单的限流。

    1. // 限流的个数
    2. private int maxCount = 10;
    3. // 指定的时间内
    4. private long interval = 60;
    5. // 原子类计数器
    6. private AtomicInteger atomicInteger = new AtomicInteger(0);
    7. // 起始时间
    8. private long startTime = System.currentTimeMillis();
    9. public boolean limit(int maxCount, int interval) {
    10. atomicInteger.addAndGet(1);
    11. if (atomicInteger.get() == 1) {
    12. startTime = System.currentTimeMillis();
    13. atomicInteger.addAndGet(1);
    14. return true;
    15. }
    16. // 超过了间隔时间,直接重新开始计数
    17. if (System.currentTimeMillis() - startTime > interval * 1000) {
    18. startTime = System.currentTimeMillis();
    19. atomicInteger.set(1);
    20. return true;
    21. }
    22. // 还在间隔时间内,check有没有超过限流的个数
    23. if (atomicInteger.get() > maxCount) {
    24. return false;
    25. }
    26. return true;
    27. }

    :::info 网关限流 ::: 限流常在网关这一层做,像SpringCloudGateway限流的原理,就是基于Redis和内置Lua的限流脚本
    限流器算法 - 图1
    Lua脚本和 MySQL数据库的存储过程比较相似,他们执行一组命令,所有命令的执行要么全部成功或者失败,以此达到原子性。也可以把Lua脚本理解为,一段具有业务逻辑的代码块。而Lua本身就是一种编程语言,虽然redis 官方没有直接提供限流相应的API,但却支持了 Lua 脚本的功能,可以使用它实现复杂的令牌桶或漏桶算法,也是分布式系统中实现限流的主要方式之一。
    Lua脚本大致逻辑如下:

    • 通过KEYS[1] 获取传入的key参数
    • 通过ARGV[1]获取传入的limit参数
    • redis.call方法,从缓存中get与key相关的值,如果为null那么就返回0
    • 接着判断缓存中记录的数值是否会大于限制大小,如果超出表示该被限流,返回0
    • 如果未超过,那么该key的缓存值+1,并设置过期时间为1秒钟以后,并返回缓存值+1 ```java — 获取调用脚本时传入的第一个key值(用作限流的 key) local key = KEYS[1] — 获取调用脚本时传入的第一个参数值(限流大小) local limit = tonumber(ARGV[1])

    — 获取当前流量大小 local curentLimit = tonumber(redis.call(‘get’, key) or “0”)

    — 是否超出限流 if curentLimit + 1 > limit then — 返回(拒绝) return 0 else — 没有超出 value + 1 redis.call(“INCRBY”, key, 1) — 设置过期时间 redis.call(“EXPIRE”, key, 2) — 返回(放行) return 1 end ```