一個高併發系統中不得不面臨的一個方面流量,過大的流量可能導致接口不可用,甚至可能拖慢整個服務,最終導致整改服務不可用。因此,當系統流量增大到一定程度時,就需要考慮如何限流了。
一、限流算法
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;
}
閱讀更多 加班狗的微博 的文章