在平常項目中為了防止一些沒有token訪問的API被大量無限的調用,需要對一些服務進行API限流。就好比拿一些注冊或者發驗證碼的一些接口,如果被惡意無限的調用,多少會造成一些費用的產生,發短信或者郵件都是一些第三方接口,次數越多,當然費用也就越多了,嚴重的直接導致服務崩潰。spring cloud api-gateway中引入限流的配置還是必須的。
引入依賴
在pom文件中引入Zuul RateLimit的依賴
<dependency>
<groupId>com.marcosbarbero.cloud</groupId>
<artifactId>spring-cloud-zuul-ratelimit</artifactId>
<version>1.3.2.RELEASE</version>
</dependency>
配置信息
更詳細的配置解讀下面有寫,這里只是簡單配置一下,以下這個配置就可以對服務進行限流了
zuul:
routes: 你的路由配置
test:
path:
serviceId:
ratelimit:
enabled: true
policies:
test: 路由名
limit: 限制次數
refresh-interval: 刷新時間
type: 類型
RateLimit源碼簡單分析
本地讓自己的一個服務配置為一分鍾內該服務的API只能訪問十次,超過十次,網關就會報錯
zuul:
routes:
test:
path: /api/test/**
serviceId: hscf-cloud-test-9457
ratelimit:
enabled: true
policies:
test:
limit: 10
refresh-interval: 60
type: origin 限流方式
下面通過源碼簡要分析一下
RateLimit類是繼承ZuulFilter,內中的變量不難看出就是我們在yml文件中配置的屬性值。RateLimit內中的部分源碼,filterType為“pre”表示在每一個API訪問之前進行攔截,LIMIT_HEADER,REMAINING_HEADER,RESET_HEADER這三個變量應該就是獲取我們配置的訪問次數,還有記錄該時間內剩余的訪問次數。
public class RateLimitFilter extends ZuulFilter {
public static final String LIMIT_HEADER = "X-RateLimit-Limit";
public static final String REMAINING_HEADER = "X-RateLimit-Remaining";
public static final String RESET_HEADER = "X-RateLimit-Reset";
public String filterType() {
return "pre";
}
public int filterOrder() {
return -1;
}
public boolean shouldFilter() {
return this.properties.isEnabled() && this.policy(this.route()).isPresent();
}
主體邏輯run()中進行判斷。先通過this.policy(route).ifPresent((policy)判斷policy配置信息是否存在,存在的話會讀
取到當前的限制值,還剩余的限制值,最終判斷剩余的限制值是否小於0,小於0的話就會報出太多請求的異常
TOO_MANY_REQUESTS(429, "Too Many Requests")
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletResponse response = ctx.getResponse();
HttpServletRequest request = ctx.getRequest();
Route route = this.route();
this.policy(route).ifPresent((policy) -> {
String key = this.rateLimitKeyGenerator.key(request, route, policy);
Rate rate = this.rateLimiter.consume(policy, key);
response.setHeader("X-RateLimit-Limit", policy.getLimit().toString());
response.setHeader("X-RateLimit-Remaining", String.valueOf(Math.max(rate.getRemaining().longValue(), 0L)));
response.setHeader("X-RateLimit-Reset", rate.getReset().toString());
if(rate.getRemaining().longValue() < 0L) {
ctx.setResponseStatusCode(HttpStatus.TOO_MANY_REQUESTS.value());
ctx.put("rateLimitExceeded", "true");
throw new ZuulRuntimeException(new ZuulException(HttpStatus.TOO_MANY_REQUESTS.toString(), HttpStatus.TOO_MANY_REQUESTS.value(), (String)null));
}
});
return null;
}
。。。。。。
控制台的異常信息,異常code為429,也就是太多請求的異常:TOO_MANY_REQUESTS(429, "Too Many Requests")
RateLimit詳細的配置信息解讀
zuul:
ratelimit:
key-prefix: your-prefix #對應用來標識請求的key的前綴
enabled: true
repository: REDIS #對應存儲類型(用來存儲統計信息)
behind-proxy: true #代理之后
default-policy: #可選 - 針對所有的路由配置的策略,除非特別配置了policies
limit: 10 #可選 - 每個刷新時間窗口對應的請求數量限制
quota: 1000 #可選- 每個刷新時間窗口對應的請求時間限制(秒)
refresh-interval: 60 # 刷新時間窗口的時間,默認值 (秒)
type: #可選 限流方式
- user
- origin
- url
policies:
myServiceId: #特定的路由
limit: 10 #可選- 每個刷新時間窗口對應的請求數量限制
quota: 1000 #可選- 每個刷新時間窗口對應的請求時間限制(秒)
refresh-interval: 60 # 刷新時間窗口的時間,默認值 (秒)
type: #可選 限流方式
- user
- origin
- url
- url類型的限流就是通過請求路徑區分
- origin是通過客戶端IP地址區分
- user是通過登錄用戶名進行區分,也包括匿名用戶
目前只是將網關中的限流方式使用連起來,源碼中的原理知道了一下,后續可以繼續進行擴展,可以針對請求上的參數進行請求攔截限流