高并发系统的限流

高并发系统中,限流是保护系统的重要手段之一,包括缓存、降级和限流。本文主要探讨限流,介绍常用的限流算法如漏桶和令牌桶算法,对比其特点。并讨论了Guava的RateLimiter(令牌桶实现)、Semaphore(计数信号量)等限流工具类的使用,以及如何结合Redis实现更复杂的限流策略,如Redis+Lua计数器算法和Redis-list令牌桶算法。限流工具的选择和使用取决于系统需求,例如RateLimiter适用于平滑限流,Semaphore用于控制并发线程数。
摘要由CSDN通过智能技术生成

在开发高并发系统时,有三把利器用来保护系统:缓存、降级和限流

  • 缓存的目的是提升系统访问速度和增大系统能处理的容量,可谓是抗高并发流量的银弹;
  • 降级是当服务出问题或者影响到核心流程的性能则需要暂时屏蔽掉,待高峰或者问题解决后再打开;
  • 限流:而有些场景并不能用缓存和降级来解决,比如稀缺资源(秒杀、抢购)、写服务(如评论、下单)、频繁的复杂查询(评论的最后几页),因此需有一种手段来限制这些场景的并发/请求量,就是限流

​ 限流的目的是通过对并发访问/请求进行限速或者一个时间窗口内的的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务(定向到错误页或告知资源没有了)、排队或等待(比如秒杀、评论、下单)、降级(返回兜底数据或默认数据,如商品详情页库存默认有货)。

常用的限流算法

漏桶算法

漏桶算法主要目的是控制数据注入到网络的速率,平滑网络上的突发流量。漏桶算法提供了一种机制,通过它,突发流量可以被整形以便为网络提供一个稳定的流量。描述如下:

  • 一个固定容量的漏桶,按照常量固定速率流出水滴;
  • 如果桶是空的,则不需流出水滴;
  • 可以以任意速率流入水滴到漏桶;
  • 如果流入水滴超出了桶的容量,则流入的水滴溢出了(被丢弃),而漏桶容量是不变的。

在这里插入图片描述

​ 以固定速率消费请求,漏桶容量固定,每次用户请求都得放入桶中,桶满则拒绝请求或等待。达到平滑请求的效果。

令牌桶算法

​ 对于很多应用场景来说,除了要求能够限制数据的平均传输速率外,还要求允许某种程度的突发传输。这时候漏桶算法可能就不合适了,令牌桶算法更为适合。令牌桶算法则是一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌。桶中存放的令牌数有最大上限,超出之后就被丢弃或者拒绝。当流量或者网络请求到达时,每个请求都要获取一个令牌,如果能够获取到,则直接处理,并且令牌桶删除一个令牌。如果获取不到,该请求就要被限流,要么直接丢弃,要么在缓冲区等待。

在这里插入图片描述

对比

  • 令牌桶是按照固定的速度往桶里添加令牌,请求是否被处理需要看桶中的令牌是否足够,当令牌数量减到零时则拒绝请求或者排队等候;漏桶则是按照固定的速度处理亲故,请求流入的速度是任意的,当流入的请求累积超过桶的容量时,拒绝新流入的请求。
  • 令牌桶限制的是平均流入的速度,允许突发请求,只要有令牌就可以处理,支持一次获取多个令牌;流通限制的是请求流出的速率,即流出的速率是一个固定值,从而平滑突发流量。
  • 令牌桶允许一定量的突发,而漏桶主要目的是平滑流出速率。

计数器算法:在周期内累加访问次数,当达到设定的限流值时,触发限流策略。下一个周期开始时,进行清零,重新计数。常用于QPS限流和统计总访问量,对于秒级以上的时间周期来说,会存在临界问题(假设一个周期<1min>的访问量限制在100,然而在第一个周期的最后5秒和下一个周期的开始5秒时间段内,分别涌入100的访问量,虽然没有超过每个周期的限制量,但是整体上10秒内已达到200的访问量,已远远超过服务器的负载能力)。

限流工具类

RateLimiter

​ Guava是google提供的java扩展类库,其中的限流工具类RateLimiter采用的就是令牌桶算法。RateLimiter 从概念上来讲,速率限制器会在可配置的速率下分配许可证,如果必要的话,每个acquire() 会阻塞当前线程直到许可证可用后获取该许可证,一旦获取到许可证,不需要再释放许可证。通俗的讲RateLimiter会按照一定的频率往桶里扔令牌,线程拿到令牌才能执行,比如你希望自己的应用程序QPS不要超过1000,那么RateLimiter设置1000的速率后,就会每秒往桶里扔1000个令牌。

简单应用
public class HelloController {
   
  private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
  private static final RateLimiter rateLimiter = RateLimiter.create(5);//每秒放进5个令牌
  public String SayHello() {
   
    //tryAcquire尝试获取permit,默认超时时间是0,意思是拿不到就立即返回false
    if (rateLimiter.tryAcquire()) {
   //一次获取一个令牌
      System.out.println("hello " + sdf.format(new Date()));
      try {
   
        Thread.sleep(500);
      } catch (InterruptedException e) {
   
        e.printStackTrace();
      }
    } else {
   
      System.out.println("hello limit.");
    }
    return "hello";
  }
  public String SayHi() {
   
    //acquire拿不到就等待,拿到为止
    rateLimiter.acquire(5);//一次获取五个令牌
    System.out.println("hi " 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值