令牌桶算法
令牌桶算法是网络流量整形(Traffic Shaping)和限流(Rate Limiting)中最常使用的一种算法,它可用于控制发送到网络上数据的数量并允许突发数据的发送。
从某种意义上来说,令牌桶算法是对漏桶算法的一种改进,主要在于令牌桶算法能够在限制调用的平均速率的同时还允许一定程度的突发调用,令牌桶算法的实现原理:
整个的过程是这样的:
- 系统以恒定的速率产生令牌,然后将令牌放入令牌桶中
- 令牌桶有一个容量,当令牌桶满了的时候,再向其中放入的令牌就会被丢弃
- 每次一个请求过来,需要从令牌桶中获取一个令牌,假设有令牌,那么提供服务;假设没有令牌,那么拒绝服务
那么,我们再看一下,为什么令牌桶算法可以防止一定程度的突发流量呢?可以这么理解,假设我们想要的速率是1000QPS,那么往桶中放令牌的速度就是1000个/s,假设第1秒只有800个请求,那意味着第2秒可以容许1200个请求,这就是一定程度突发流量的意思,反之我们看漏桶算法,第一秒只有800个请求,那么全部放过,第二秒这1200个请求将会被打回200个。
注意上面多次提到一定程度这四个字,这也是我认为令牌桶算法最需要注意的一个点。假设还是1000QPS的速率,那么5秒钟放1000个令牌,第1秒钟800个请求过来,第2~4秒没有请求,那么按照令牌桶算法,第5秒钟可以接受4200个请求,但是实际上这已经远远超出了系统的承载能力,因此使用令牌桶算法特别注意设置桶中令牌的上限即可。
总而言之,作为对漏桶算法的改进,令牌桶算法在限流场景下被使用更加广泛。
1. 引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
2.每引入新的依赖,一般都需要在启动类的增加注解,需要引入@EnableZuulProxy 注解
3.yml文件的配置:
配置为SimpleHostRoutingFilter的方式
server:
port: 9100
zuul:
routes:
zuul-service:
path: /forword1/**
url: http://localhost:9100/myController
4. 应用Guava的RateLimiter的RateLimiter
import com.google.common.util.concurrent.RateLimiter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
@Component
public class LimitFilter extends ZuulFilter {
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
return -10;
}
@Override
public boolean shouldFilter() {
return true;
}
// 50 qps(1秒 50个 请求 Query Per Second 每秒查询量)
private static final RateLimiter RATE_LIMITER = RateLimiter.create(50);
@Override
public Object run() throws ZuulException {
RequestContext currentContext = RequestContext.getCurrentContext();
//acquire是阻塞的且会一直等待到获取令牌为止,它有一个返回值为double型,意思是从阻塞开始到获取到令牌的等待时间,单位为秒
// tryAcquire是另外一个方法,它可以指定超时时间,返回值为boolean型,即假设线程等待了指定时间后仍然没有获取到令牌,那么就会返回给客户端false,客户端根据自身情况是打回给前台错误还是定时重试
if (RATE_LIMITER.tryAcquire()){
System.out.println("通过");
return null;
}else {
// 被流控的逻辑
System.out.println("被限流了");
currentContext.setSendZuulResponse(false);
currentContext.setResponseStatusCode(HttpStatus.TOO_MANY_REQUESTS.value());
}
return null;
}
}
5.便于测试,在本服务中(网关)增加 controller,实际应用中zuul充当网关,请求都会被打到网关上
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyController {
@GetMapping("/myController")
public String myForward(){
System.out.println("我被调用了");
return "my controller : ";
}
}
测试:
请求地址:localhost:9100/forword1/ 该地址会被转发到myController接口,若被限流,则不会向后转发
这种限流方式,适合各微服务能扛的流量均衡,若各个微服务,流量差异较大,则用ribbon来设置负载均衡,网关可以根据权重来做负载