Based on
spring-cloud-zuul-ratelimit-core-2.4.0.RELEASE.jar
.
0. Directory
1 Introduction
As a gateway, current limiting is naturally one of the basic functions.
This article tries to explain how to realize the current limiting requirement based on the zuul1.x version.
2. SpringBoot related
This section mainly introduces the AutoConfig provided for Zuul-RateLimiter under the SpringBoot framework, as well as actuator-related functions.
2.1 Function of AutoConfigRateLimitAutoConfiguration
The automatic configuration function of Zuul-RateLimiter is RateLimitAutoConfiguration
completed by .
RateLimitAutoConfiguration
There are mainly three types of beans registered in:
- Two Filters for implementing current limiting logic -
RateLimitPreFilter
andRateLimitPostFilter
. We will discuss this specifically in the next subsection. - A cache class bean used to store current-limited state data. Zuul-RateLimiter provides Redis, JPA and other methods.
- Some other auxiliary beans. For example, the implementation class responsible for caching Key generation
RateLimitKeyGenerator
, etc.
2.2 actuator function
This function is actually provided by the zuul body spring-cloud-netflix-core-2.2.5.RELEASE.jar . For the completeness of this article, it is added here.
The key type is RoutesEndpoint
, and there are three restful interfaces finally provided:
URL | illustrate | Remark |
---|---|---|
/routes | Get the successfully registered routing rule set in zuul. | GET requests. |
/routes/details | Verbose version of the /routes method above. | GET requests. |
/routes/reset | Can be used to reset registered routing rule sets. ( use with caution ) | POST requests . The outside world can RoutesRefreshedEvent dynamically update the existing routing rules at runtime through the response. |
3. Core logic processing type: RateLimitPreFilter
/RateLimitPostFilter
These two classes are the key to the implementation of current limiting logic.
3.1 RateLimitPreFilter
(core logic)
This class is responsible for the main logic of current limiting logic judgment.
According to the interpretation of the key points of spring-cloud-netflix-zuul source code analysis , as the ZuulFilter implementation class, RateLimitPreFilter
the four base class methods implemented are:
base class method | Remark |
---|---|
filterType() | The type is "pre", which conforms to the current limiting logic. |
filterOrder() | The default value is -1. zuul.ratelimit.preFilterOrder It can be set by external configuration items . |
shouldFilter() | defined in the base class AbstractRateLimitFilter . |
run() | Collect data and determine whether the current limit is effective or not. The main logic implementation point. Special interpretation below. |
// --------------------------------- RateLimitPreFilter.run()
@Override
public Object run() {
final RequestContext ctx = RequestContext.getCurrentContext();
final HttpServletResponse response = ctx.getResponse();
final HttpServletRequest request = ctx.getRequest();
// 数据项Route : 用户自定义的 route规则, 例如projectB路由到 projectB系统
// 以下配置文件所对应的是 ZuulRoute
/*
# ZuulProperties
zuul:
routes:
projectB:
path: /projectB/**
url: http://127.0.0.1:801/projectB/
*/
final Route route = route(request);
// 数据项RateLimitProperties.Policy 代表用户自定义的限流规则
// policy(route, request) 筛选出匹配当前请求的 限流策略 ———— 对应 RateLimitProperties.Policy 类
policy(route, request).forEach(policy -> {
Map<String, String> responseHeaders = Maps.newHashMap();
// 筛选, 生成出key.
// 该key将作为本次请求的唯一标识, 相关类型: MatchType, RateLimitType(这是个枚举, 列举了限流方式, 例如基于url, 基于http-method, 基于用户的等等)
final String key = rateLimitKeyGenerator.key(request, route, policy);
// 数据项Rate, 代表针对当前请求所计算出的, 实时限流状况, 其中包含: 当前时间窗口内, 针对该请求还能访问多少次; 当前时间窗口内, 针对该请求允许处理总时长还剩余多少等等, 详见本类注释
final Rate rate = rateLimiter.consume(policy, key, null);
// 根据配置, 写入响应头
final Long limit = policy.getLimit();
final Long remaining = rate.getRemaining();
if (limit != null) {
responseHeaders.put(HEADER_LIMIT, String.valueOf(limit));
responseHeaders.put(HEADER_REMAINING, String.valueOf(Math.max(remaining, 0)));
}
final Duration quota = policy.getQuota();
// Quota: 定额;配额;限额;指标;
final Long remainingQuota = rate.getRemainingQuota();
if (quota != null) {
// 这个写入值. 将在 RateLimitPostFilter.getRequestStartTime() 方法中被使用, 用于统计最终本次请求的处理总耗时
request.setAttribute(REQUEST_START_TIME, System.currentTimeMillis());
responseHeaders.put(HEADER_QUOTA, String.valueOf(quota.getSeconds()));
responseHeaders.put(HEADER_REMAINING_QUOTA, String.valueOf(MILLISECONDS.toSeconds(Math.max(remainingQuota, 0))));
}
responseHeaders.put(HEADER_RESET, String.valueOf(rate.getReset()));
// 是否将限流信息写入到响应头部; 默认为true,
if (properties.isAddResponseHeaders()) {
final String httpHeaderKey = key.replaceAll("[^A-Za-z0-9-.]", "_").replaceAll("__", "_");
for (Map.Entry<String, String> headersEntry : responseHeaders.entrySet()) {
String header = VERBOSE.equals(properties.getResponseHeaders()) ? headersEntry.getKey() + "-" + httpHeaderKey : headersEntry.getKey();
response.setHeader(header, headersEntry.getValue());
}
}
// 判断限流是否生效, 生效则直接抛出异常......
if ((limit != null && remaining < 0) || (quota != null && remainingQuota < 0)) {
// 设置响应信息
ctx.setResponseStatusCode(HttpStatus.TOO_MANY_REQUESTS.value());
ctx.put(RATE_LIMIT_EXCEEDED, "true");
ctx.setSendZuulResponse(false);
// 基于Spring Event实现的事件驱动扩展接口. 外界可以通过响应RateLimitExceededEvent来执行自定义逻辑.
eventPublisher.publishEvent(new RateLimitExceededEvent(this, policy, rateLimitUtils.getRemoteAddress(request)));
throw new RateLimitExceededException();
}
});
return null;
}
3.2 RateLimitPostFilter
It is much simpler than RateLimitPreFilter
implementing logic . RateLimitPostFilter
The main responsibility is to calculate the Remaining Quota (remaining time quota) and Remaining Limit (remaining current limiting times) of the route corresponding to this visit based on the preset configuration items before the end of the current request, which will be used as the data basis for the next calculation of current limiting.
base class method | Remark |
---|---|
filterType() | The type is "post". |
filterOrder() | The default is 990 ( SendResponseFilter execute on). zuul.ratelimit.postFilterOrder It can be set by external configuration items . |
shouldFilter() | Override the base class AbstractRateLimitFilter method. |
run() | A special introduction is given below. |
// ========================= RateLimitPostFilter.run()
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
Route route = route(request);
// 对本次请求对应路由的限流策略的实时执行情况, 基于本次请求结果进行更新. 用作下一次本路由限流策略计算的数据基础.
policy(route, request).forEach(policy -> {
long requestTime = System.currentTimeMillis() - getRequestStartTime();
String key = rateLimitKeyGenerator.key(request, route, policy);
// RateLimiter实现. 常用的有RedisRateLimiter, ConsulRateLimiter等
rateLimiter.consume(policy, key, requestTime > 0 ? requestTime : 1);
});
return null;
}
Notice:
ZuulServlet / ZuulServletFilter
The logic disclosed in reveals that the "post" zuulFilter will definitely be executed, which realizes the logical closed loop of current limiting judgment.
4. Data items
Some literal confusing types are defined in zuul-rateLimiter, which are specifically listed here.
type | illustrate |
---|---|
ZuulProperties.ZuulRoute |
When the user-defined Route configuration information in the configuration file is read into the memory by SpringBoot, it is carried by this class instance. Therefore, the field attributes defined inside the class correspond to the configuration item zuul.routes in our configuration file one by one. |
Route |
1. Used in conjunction with RouteLocator, the ZuulProperties.ZuulRoute memory instance generated by reading the configuration file above must not be used directly. We still need to do some processing, and the information carrying after processing is done by Route. 2. Route SimpleRouteLocator.getRoute(ZuulRoute route, String path) Responsible for converting ZuulRoute to Route. |
RateLimitProperties.Policy |
Host the traffic limiting policy configured by the user in the configuration file. |
Rate |
1. The comments on the class are rich enough. 2. It is used to express the current limit of a request at a certain point in time: a. How many times can it be executed within the time window? b. How long is the allowed time for the number of executions remaining in the time window? c. How long is the time window? |
5. Configuration items
List some common configuration items and their meanings.
zuul:
routes:
# 以下读取配置到内存, 数据为: ZuulRoute{id='manager', path='/manager/**', serviceId='project3', url='null', stripPrefix=true, retryable=null, sensitiveHeaders=[ ], customSensitiveHeaders=false }
# 走的构造函数是: ZuulRoute(String text)
project3: /manager/**
# 以下读取配置到内存, 数据为: ZuulRoute{id='project2', path='/projectB/M/**', serviceId='null', url='http://127.0.0.1:801/projectB/M?k=r8O8WtvCRhlzRo48W1sqvw==', stripPrefix=true, retryable=null, sensitiveHeaders=[], customSensitiveHeaders=false, }
project2:
path: /projectB/M/**
url: http://127.0.0.1:801/projectB/M?k=r8O8WtvCRhlzRo48W1sqvw==
projectB:
path: /projectB/**
url: http://127.0.0.1:801/projectB/
ratelimit:
enabled: true
behindProxy: false
responseHeaders: VERBOSE
# 上面的Route 和这里的 Police 之间的匹配关系, 是通过 routeId 来关联的; 相关源码参见: AbstractRateLimitFilter.policy(...)
policyList:
test:
-
refreshInterval: 60
# Request number limit per refresh interval window. (每个窗口期间, 允许访问的次数; 例如这里是每60秒访问3次)
limit: 3
# Request time limit per refresh interval window (in seconds). (每个窗口期间, 所有正常响应请求的处理总时长; 例如这里是60秒内, 5次请求的总耗时不能超过1秒)
# 上面这两个条件, 有一个不满足, 限流即生效
quota: 1
breakOnMatch: true
# 对应 RateLimitProperties.Policy.MatchType 内部类, 其中:
# 1. 其中的字段type 对应下面配置的user, origin, url; 类型为: com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.properties.RateLimitType
# 2. 其中的字段matcher 对应下面配置的/callPost3, 所以针对下面的user, origin而言, matcher为null, 而这将影响 MatchType.apply(...)中的执行 - 如果为空, 则代表匹配, 即所有的请求都适应本Police
type:
- user
- origin
- url
# 上面的 policyList 未匹配时候, 将全部匹配这个
defaultPolicyList:
-
refreshInterval: 60
limit: 3
quota: 1000
breakOnMatch: true
type:
- user # 登录用户名进行区分,也包括匿名用户
- origin # 客户端IP地址区分
- url
keyPrefix:
repository: REDIS # REDIS, CONSUL, JPA, BUCKET4J_JCACHE
postFilterOrder: 990
preFilterOrder: -1
6. Extension points
Summarize some extensible interface types provided by zuul-ratelimiter to facilitate possible future customization requirements.
type | application location | Remark |
---|---|---|
RateLimitExceededEvent |
RateLimitPreFilter.run() trigger in |
Based on Spring Events. |
RateLimitExceededException |
RateLimitPreFilter.run() trigger in |
Based on global exception handling. |
RateLimiterErrorHandler |
RateLimiter used in each implementation class |
Based on a custom implementation class. |
RoutesRefreshedEvent |
RoutesEndpoint trigger in |
Based on Spring Events. |