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使用經驗分享(七)限流


分享到:


相關文章: