在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流
缓存 缓存的目的是提升系统访问速度和增大系统处理容量
降级 降级是当服务出现问题或者影响到核心流程时,需要暂时屏蔽掉,待高峰或者问题解决后再打开
限流 限流的目的是通过对并发访问/请求进行限速,或者对一个时间窗口内的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待、降级等处理
常用的限流算法
漏桶算法
漏桶算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率。
令牌桶算法
对于很多应用场景来说,除了要求能够限制数据的平均传输速率外,还要求允许某种程度的突发传输。这时候漏桶算法可能就不合适了,令牌桶算法更为适合。如图所示,令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。
RateLimiter使用
Google开源工具包Guava提供了限流工具类RateLimiter,该类基于令牌桶算法实现流量限制,使用十分方便,而且十分高效。
首先简单介绍下RateLimiter的使用
public void testAcquire() {
//创建一个限流器,参数代表每秒生成的令牌数
RateLimiter limiter = RateLimiter.create(1);
for(int i = 1; i < 10; i = i + 2 ) {
//以阻塞的方式获取令牌
double waitTime = limiter.acquire(i);
//来设置等待超时时间的方式获取令牌,如果超timeout为0,则代表非阻塞,获取不到立即返回。
//limiter.tryAcquire(int permits, long timeout, TimeUnit unit);
System.out.println("cutTime=" + System.currentTimeMillis() + " acq:" + i + " waitTime:" + waitTime);
}
}
输出结果
cutTime=1535439657427 acq:1 waitTime:0.0
cutTime=1535439658431 acq:3 waitTime:0.997045
cutTime=1535439661429 acq:5 waitTime:2.993028
cutTime=1535439666426 acq:7 waitTime:4.995625
cutTime=1535439673426 acq:9 waitTime:6.999223
通过上面的铺垫了解,下面正试开始用Zuul网关结合Guava实现对订单接口的流量限制
Zuul 网关服务项目的环境搭建,
可参考博文 [技术陈旧] SpringCloud-Zuul网关项目 环境搭建和整合Eureka访问_springcloud创建一个zuul网关服务,实现通过网关访问eureka client中的接口-CSDN博客
项目核心代码,Zuul过滤器代码
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.http.HttpStatus;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
/**
* 订单限流
*/
@Component
public class OrderRateLimiterFilter extends ZuulFilter {
//创建一个限流器,参数代表每秒生成的令牌数
private static final RateLimiter RATE_LIMITER = RateLimiter.create(500);
/**
* 过滤器类型
* org.springframework.cloud.netflix.zuul.filters.support.FilterConstants 这个类
* pre:在请求被路由(转发)之前调用
* route:在路由(请求)转发时被调用
* error:服务网关发生异常时被调用
* post:在路由(转发)请求后调用
* @return
*/
@Override
public String filterType() {
return PRE_TYPE;
}
/**
* 过滤器顺序,越小越先执行
* @return
*/
@Override
public int filterOrder() {
return -4;
}
/**
* 过滤器是否生效
* @return
*/
@Override
public boolean shouldFilter() {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
//只对订单接口限流,进行拦截,就会进入下面的 run方法中
if ("/apigateway/order/api/v1/order/save".equalsIgnoreCase(request.getRequestURI())){
return true;
}
//不拦截,放行
return false;
}
@Override
public Object run() throws ZuulException {
//获取上下文
RequestContext requestContext = RequestContext.getCurrentContext();
//尝试获取令牌
if(!RATE_LIMITER.tryAcquire()){
//停止访问,并返回出错的消息
requestContext.setSendZuulResponse(false);
requestContext.setResponseStatusCode(HttpStatus.TOO_MANY_REQUESTS.value());
}
//正常的话,继续向下走
return null;
}
}