限流、流量控制方案

参考资料

https://blog.csdn.net/xushiyu1996818/article/details/106764890/

https://zhuanlan.zhihu.com/p/60979444

https://blog.csdn.net/fubicheng208/article/details/106650146/

https://blog.csdn.net/weixin_38019299/article/details/120883690

我司用了 6 年的 Redis 分布式限流器,可以说是非常厉害了! (qq.com)

流量控制

流量控制在计算机领域称为过载保护。何为过载保护?所谓“过载”,即需求超过了负载能力;而“保护”则是指当“过载”发生了,采取必要的措施保护自己不受“伤害”。在计算机领域,尤其是分布式系统领域,“过载保护”是一个重要的概念。一个不具备“过载保护”功能的系统,是非常危险和脆弱的,很可能由于瞬间的压力激增,引起“雪崩效应”,导致系统的各个部分都同时崩溃,停止服务。这就好像在没有保险丝的保护下,电压突然变高,导致所有的电器都会被损坏一样,“过载保护”功能是系统的“保险丝”。

接口限流——redis + lua脚本

主要是借助redis做限流,通过aop或拦截器做限流,利用lua脚步是为了做到原子性。

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

相比Redis事务,Lua脚本的优点:

  • 减少网络开销:使用Lua脚本,无需向Redis 发送多次请求,执行一次即可,减少网络传输
  • 原子操作:Redis 将整个Lua脚本作为一个命令执行,原子,无需担心并发
  • 复用:Lua脚本一旦执行,会永久保存 Redis 中,,其他客户端可复用

Lua脚本大致逻辑如下:

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

限流常在网关这一层做,比如NginxOpenrestykongzuulSpring Cloud Gateway等,而像spring cloud - gateway网关限流底层实现原理,就是基于Redis + Lua,通过内置Lua限流脚本的方式。

核心代码

    public boolean acquire(String key) {
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<Long>();
        String lua = "local key = KEYS[1]" +
                " local period = ARGV[1]" +
                " local limit= ARGV[2]" +
                " local times = redis.call('incr',key)" +
                " if times == 1 then" +
                " redis.call('expire',KEYS[1], period)" +
                " end" +
                " if times > tonumber(limit) then" +
                " return 0" +
                " end" +
                " return 1";
        redisScript.setScriptText(lua);
        redisScript.setResultType(Long.class);
        //表示1s 内最多访问3次
        //key [key ...],被操作的key,可以多个,在lua脚本中通过KEYS[1], KEYS[2]获取
        //arg [arg ...],参数,可以多个,在lua脚本中通过ARGV[1], ARGV[2]获取。
        //0: 超出限制,else:正常请求
        Long count = (Long) stringRedisTemplate.execute(redisScript, Arrays.asList(key), "1", "3");
        System.err.println(System.currentTimeMillis() + "<>" + count);
        //0:超出范围
        return count == 0;

JAVA SDK限流

原理看这篇:流量控制算法总结_xushiyu1996818的博客-CSDN博客_流量控制

令牌桶算法

https://zhuanlan.zhihu.com/p/60979444

令牌桶算法的原理也比较简单,我们可以理解成医院的挂号看病,只有拿到号以后才可以进行诊病。

系统会维护一个令牌(token)桶,以一个恒定的速度往桶里放入令牌(token),这时如果有请求进来想要被处理,则需要先从桶里获取一个令牌(token),当桶里没有令牌(token)可取时,则该请求将被拒绝服务。令牌桶算法通过控制桶的容量、发放令牌的速率,来达到对请求的限制。

生成令牌的速度是恒定的,而请求去拿令牌是没有速度限制的。这意味,面对瞬时大流量,该算法可以在短时间内请求拿到大量令牌,而且拿令牌的过程并不是消耗很大的事情

在这里插入图片描述
在这里插入图片描述

实现方式: RateLimiter

  • RateLimiter在并发环境下使用是安全的:它将限制所有线程调用的总速率。注意,它不保证公平调用。Rate limiter(直译为:速度限制器)经常被用来限制一些物理或者逻辑资源的访问速率。这和java.util.concurrent.Semaphore正好形成对照。
  • 一个RateLimiter主要定义了发放permits的速率。如果没有额外的配置,permits将以固定的速度分配,单位是每秒多少permits。默认情况下,Permits将会被稳定的平缓的发放。
  • 可以配置一个RateLimiter有一个预热期,在此期间permits的发放速度每秒稳步增长直到到达稳定的速率。

核心代码

<dependency>
  <groupId>com.google.guava</groupId>
  <artifactId>guava</artifactId>
  <version>26.0-jre</version>
  <!-- or, for Android: -->
  <version>26.0-android</version>
</dependency>

//一秒创建3个令牌
    RateLimiter rateLimiter = RateLimiter.create(3.0);

    public void query() throws InterruptedException {
       //尝试获取令牌
        if (rateLimiter.tryAcquire()) {
            System.err.println(System.currentTimeMillis() + "   业务执行成功!");
            TimeUnit.MILLISECONDS.sleep(200);
        } else {
            TimeUnit.MILLISECONDS.sleep(200);
            System.err.println("************ " + System.currentTimeMillis() + "   业务执行失败!");
        }
    }

自己实现:

long timeStamp=getNowTime(); 
int capacity; // 桶的容量 
int rate ;//令牌放入速度
 int tokens;//当前水量  
 
bool control() {
   //先执行添加令牌的操作
   long  now = getNowTime();
   tokens = max(capacity, tokens+ (now - timeStamp)*rate); 
   timeStamp = now;   //令牌已用完,拒绝访问
 
   if(tokens<1){
     return false;
   }else{//还有令牌,领取令牌
     tokens--;
     retun true;
   }
 } 

漏桶算法

漏桶算法思路很简单,我们把水比作是请求,漏桶比作是系统处理能力极限,水先进入到漏桶里,漏桶里的水按一定速率流出,当流出的速率小于流入的速率时,由于漏桶容量有限,后续进入的水直接溢出(拒绝请求),以此实现限流。

在这里插入图片描述
在这里插入图片描述

漏桶的出水速度是恒定的,那么意味着如果瞬时大流量的话,将有大部分请求被丢弃掉(也就是所谓的溢出)。

漏桶算法没有标准的sdk提供实现,可以自己去实现一个漏桶算法。

long timeStamp = getNowTime(); 
int capacity = 10000;// 桶的容量
int rate = 1;//水漏出的速度 
int water = 100;//当前水量  
 
public static bool control() {   
    //先执行漏水,因为rate是固定的,所以可以认为“时间间隔*rate”即为漏出的水量
    long  now = getNowTime();
    water = Math.max(0, water - (now - timeStamp) * rate);
    timeStamp = now;
 
    if (water < capacity) { // 水还未满,加水
        water ++; 
        return true; 
    } else { 
        return false;//水满,拒绝加水
   } 
} 

注意:这里的timeStamp是上一次执行操作的时间,这次操作,要先执行漏水,漏水量是now-timeStamp 相关的函数

该算法很好的解决了时间边界处理不够平滑的问题,因为在每次请求进桶前都将执行“漏水”的操作,再无边界问题。

但是对于很多场景来说,除了要求能够限制数据的平均传输速率外,还要求允许某种程度的突发传输。这时候漏桶算法可能就不合适了,令牌桶算法更为适合

二者区别

两者主要区别在于“漏桶算法”能够强行限制数据的传输速率,而“令牌桶算法”在能够限制数据的平均传输速率外,还允许某种程度的突发传输。

在“令牌桶算法”中,只要令牌桶中存在令牌,那么就允许突发地传输数据直到达到用户配置的上限,所以它适合于具有突发特性的流量。

分布式限流——阿里Sentinel

官网:Sentinel · alibaba/spring-cloud-alibaba Wiki · GitHub

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

在这里插入图片描述

  • docker部署—> 导包 —> 配置
  • 想要给某个方法限流,就在方法上加注解(@SentinelResource(value = "brand_list" ,blockHandlerClass = {SentinelUtil.class}, blockHander = handerException );
  • 其中value为资源名、后面的blockHandlerClass是流量超了之后的处理方式、规则所在的类、 blockHander是方法名,叫兜底方法
  • 兜底方法必须与原方法的参数(多一个异常)、返回值类型必须一样
  • 如果从控制台配置限流规则,则是存在了内存中,一旦重启就没了;所以使用nacos的配置中心

今日推歌

----《秘密》 张震岳

也许在你的心中早就已经有人进去
或许你不曾接受真正的爱真诚的情
遗忘吧过去的事
不要再怀疑
我彷佛可以听见你的心跳你的声音
不要只有在梦中才能看你才能靠近
我可以慢慢的等
直到你离去

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星回昭以烂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值