单机限流实战

一、 限流算法介绍

  • 漏桶算法

该算法关键是漏桶取出请求的速率是固定的,请求过多(流入)超过容量时会排队或者拒绝。所以处理的请求的速度是固定的,面对大量的突发请求,请求会排队按照固定的速率处理
在这里插入图片描述

  • 令牌桶算法

该算法的核心是在创建令牌桶的时候,指定固定的速率生成令牌可以应对突发流量。同时能够使用所有的请求平均分布到时间段区间内。
和漏桶不一样,令牌桶有以下特点:

  1. 固定的速率放入令牌
  2. 正常情况下,桶内会保持一定数量的令牌(桶大小),满则丢弃令牌。当请求到达时取令牌,取到则可以执行。
  3. 桶空了取不到则等待或直接丢弃(尝试获取返回false,可以自定义处理)

令牌桶

二、 令牌桶算法实现-guava RateLimiter

他山之石可以攻玉:令牌桶在限流框架中广泛应用,guava工具包中RateLimiter提供令牌桶算法的实现。
支持两种方式:

  1. 创建一个应对突发流量的令牌桶SmoothBursty

public static RateLimiter create(double permitsPerSecond)

  1. 创建一个可以预热的令牌桶SmoothWarmingUp,可以指定预热时间

public static RateLimiter create(double permitsPerSecond, long
warmupPeriod, TimeUnit unit)

调用的详细API了可以参考:http://ifeve.com/guava-ratelimiter/

springboot应用中代码示例

package shen.example.limit;


import com.google.common.util.concurrent.RateLimiter;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class RateLimiterController {

    //设置令牌桶的生成令牌的速率 1qps
    RateLimiter rateLimiter = RateLimiter.create(1);

    @GetMapping("/index")
    public String doGet(){
        if(rateLimiter.tryAcquire()){
            return "success: 请求正确处理";
        }else {
            return "error:系统操作太频繁,请重试";
        }
    }

}

发送请求频率低的情况:
在这里插入图片描述
发送请求频率高的情况:

在这里插入图片描述

我们可以根据不同的资源设置不同RateLimiter,令牌生成频率设置不一样。

public static Map<String,RateLimiter> resourceRateLimiters = new ConcurrentHashMap<>();

三、 限流框架封装

使用AOP+注解很容易实现封装,实现低侵入性的限流框架。

  • maven需要引入的依赖

    aop和guava依赖:
    <!--aop-->
    <dependency> 			
       <groupId>org.springframework.boot</groupId> 			
       <artifactId>spring-boot-starter-aop</artifactId> 
       <version>2.2.6.RELEASE</version>		
    </dependency>
    
    <!--guava-->
    <dependency> 			
  	   <groupId>com.google.guava</groupId>
  	   <artifactId>guava</artifactId> 			
  	   <version>29.0-jre</version>
    </dependency>
  • 定义注解MyRateLimiter
package shen.example.limit;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;

/**
 * 基于令牌桶实现的限流器
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRateLimiter {

    /**
     * 资源id
     * @return
     */
    String resourceId() default "";

    /**
     * 限制请求频率
     * 即令牌生成频率
     * @return
     */
    double qps() default 1.0d;

    /**
     * 预热时间
     * 不大于0时采用  SmoothBursty
     * 大于0时 采用   moothWarmingUp
     * @return
     */
    long warmupPeriod() default 0L;

    /**
     * 预热时间单位
     * warmupPeriod > 0
     * @return
     */
    TimeUnit timeUnit() default TimeUnit.SECONDS;

}

  • aop定义切面和处理注解
package shen.example.limit.aop;

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.springframework.stereotype.Component;
import shen.example.limit.MyRateLimiter;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Component
@Aspect
public class RateLimiterAspect {

    public  Map<String, RateLimiter> resourceRateLimiters = new ConcurrentHashMap<>();

    /**
     * aop round 处理注解MyRateLimiter
     * @param pjp
     * @param myRateLimiter
     */
    @Around("@annotation(myRateLimiter)")
    public Object rateLimiterRound(ProceedingJoinPoint pjp, MyRateLimiter myRateLimiter) throws Throwable {
        //限流处理
        long warmupPeriod = myRateLimiter.warmupPeriod();
        double qps = myRateLimiter.qps();
        RateLimiter rateLimiter = null;
        //SmoothBursty
        if(warmupPeriod <= 0 ){
            rateLimiter =  RateLimiter.create(qps);
            //SmoothWarmingUp
        } else {
            rateLimiter =  RateLimiter.create(qps,warmupPeriod,myRateLimiter.timeUnit());
        }
        //获取令牌桶
        RateLimiter limiter = resourceRateLimiters.putIfAbsent(myRateLimiter.resourceId(),rateLimiter);
        if(limiter == null ){
            limiter = rateLimiter;
        }
        //尝试获取令牌
        if(limiter.tryAcquire()){
            return pjp.proceed();
        }else {
            return "error:请求太频繁,稍后再试";
        }
    }
}

  • 业务代码中使用注解实现限流功能
    直接使用注解即可实现功能,指定资源id/qps/令牌桶预热
package shen.example.limit;


import com.google.common.util.concurrent.RateLimiter;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class RateLimiterController {

    //设置令牌桶的生成令牌的速率 1qps
    RateLimiter rateLimiter = RateLimiter.create(1);

    @MyRateLimiter(resourceId = "index",qps = 2.0d)//接口限流注解
    @GetMapping("/index")
    public String doGet(){
        //do something
        return "success: 请求正确处理";
    }
}

同样使用上面的限流功能,而且可复用。

四、 总结

本文简单介绍了目前主要的限流算法:漏桶和令牌桶算法,在不同场景使用。然后使用guava实现的令牌桶实现了一个简单限流例子。最后利用AOP+注解进行封装实现低侵入性限流框架。希望以上内容对需要了解限流和进行限流开发的朋友有帮助,以后有机会再详细介绍分布式限流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值