一个高并发系统中不得不面临的一个方面流量,过大的流量可能导致接口不可用,甚至可能拖慢整个服务,最终导致整改服务不可用。因此,当系统流量增大到一定程度时,就需要考虑如何限流了。

一、限流算法

1)计数器

通过限制总并发数来限流。

假如我们需要限制一个接口一分钟内只能请求100次,首先设置一个一分钟重置请求次数的计数器counter,当接口接到一个请求后,counter就自动加1。如果counter的值大于100的话,就拦截该请求。

2)漏桶算法

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

漏桶可以看作是一个带有常量服务时间的单服务器队列,如果漏桶为空,则不需要流出水滴,如果漏桶溢出,那么水滴会被溢出丢弃。

3)令牌桶算法

有一个固定容量的桶,桶里存放着令牌(token)。桶一开始是空的,token以一个固定的速度往桶里填充,直到达到桶的容量,多余的令牌将会被丢弃。每当一个请求过来时,就会尝试从桶里移除一个令牌,如果没有令牌的话,请求无法通过。

二、限流实现

1)redis+lua

通过redis


@Inherited
@Documented
@Target({ElementType.FIELD, ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLimit {

    int count() default 100;

    int second() default 1;

    String key() default "";
}


@Aspect
@Configuration
public class LimitInterceptor {

    private final RedisTemplate<String, Serializable> limitRedisTemplate;

    @Autowired
    public LimitInterceptor(RedisTemplate<String, Serializable> limitRedisTemplate) {
        this.limitRedisTemplate = limitRedisTemplate;
    }

    @Around("execution(public * *(..)) && @annotation(com.test.annotation.AccessLimit)")
    public Object interceptor(ProceedingJoinPoint pjp) {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        AccessLimit limitAnnotation = method.getAnnotation(AccessLimit.class);
        int limitSecond = limitAnnotation.second();
        int limitCount = limitAnnotation.count();
        String key = limitAnnotation.key();
        ImmutableList<String> keys = ImmutableList.of(key);
        try {
            RedisScript<Number> redisScript = new DefaultRedisScript<>(buildLuaScript(), Number.class);
            Number count = limitRedisTemplate.execute(redisScript, keys, limitCount, limitSecond);
            if (count != null && count.intValue() <= limitCount) {
                return pjp.proceed();
            } else {
                throw new RuntimeException("Access limit");
            }
        } catch (Throwable e) {
            if (e instanceof RuntimeException) {
                throw new RuntimeException(e.getLocalizedMessage());
            }
            throw new RuntimeException("server exception");
        }
    }

    public String buildLuaScript() {
        StringBuilder sb = new StringBuilder();
        //定义c
        sb.append("local c");
        //获取redis中的值
        sb.append("\nc = redis.call('get',KEYS[1])");
        //如果调用不超过最大值
        sb.append("\nif c and tonumber(c) > tonumber(ARGV[1]) then");
        //直接返回
        sb.append("\n return c;");
        //结束
        sb.append("\nend");
        //访问次数加一
        sb.append("\nc = redis.call('incr',KEYS[1])");
        //如果是第一次调用
        sb.append("\nif tonumber(c) == 1 then");
        //设置对应值的过期设置
        sb.append("\nredis.call('expire',KEYS[1],ARGV[2])");
        //结束
        sb.append("\nend");
        //返回
        sb.append("\nreturn c;");

        return sb.toString();
    }

}


2)Guava RateLimiter

<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>25.1-jre</version>
</dependency>
// 每秒只发出10个令牌,此处是单进程服务的限流,内部采用令牌捅算法实现
private static RateLimiter rateLimiter = RateLimiter.create(10.0);
public  Object around(ProceedingJoinPoint joinPoint) { 
         Boolean flag = rateLimiter.tryAcquire();
         Object obj = null;
         try {
             if(flag){
                 obj = joinPoint.proceed();
             }
         } catch (Throwable e) {
             e.printStackTrace();
         } 
         return obj;
     }

3)Sentinel

Sentinel是阿里巴巴开源的限流器熔断器,并且带有可视化操作界面。支持流量控制、熔断降级、系统负载保护等多种功能。

引入

<dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
     <version>0.2.0.RELEASE</version>
 </dependency>

配置(nacos)

spring.cloud.sentinel.transport.port=8765
 spring.cloud.sentinel.transport.dashboard=127.0.0.1:8090spring.cloud.sentinel.datasource.ds.nacos.server-addr=127.0.0.1:8848
 #nacos中存储规则的dataId
 spring.cloud.sentinel.datasource.ds.nacos.dataId=product-flow-rules
 #nacos中存储规则的groupId
 spring.cloud.sentinel.datasource.ds.nacos.groupId=SENTINEL_GROUP
 #定义存储的规则类型
 spring.cloud.sentinel.datasource.ds.nacos.rule-type=flow

实现

@SentinelResource("createProduct")
 @RequestMapping(value = "/createProduct", method = RequestMethod.POST)
 @ResponseBody
 public String createProduct(@RequestBody String product) {
   return null;
 }