spring boot使用经验分享(七)限流

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

一、限流算法

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> limitRedisTemplate;/<string>

@Autowired

public LimitInterceptor(RedisTemplate<string> limitRedisTemplate) {/<string>

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);/<string>

try {

RedisScript<number> redisScript = new DefaultRedisScript<>(buildLuaScript(), Number.class);/<number>

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>

// 每秒只发出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>

配置(nacos)

spring.cloud.sentinel.transport.port=8765

spring.cloud.sentinel.transport.dashboard=127.0.0.1:8090

spring.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;

}


spring boot使用经验分享(七)限流


分享到:


相關文章: