因为访问的url是无状态验证的,需要对访问进行限流,由于正在使用zuul 网关转发,准备使用zuul-ratelimit进行限流。
1》配置文件如下:
zuul.ignored-services = *
zuul.routes.test.path = /mirco/**
zuul.routes.test.serviceId = mirco-service-1
zuul.ratelimit.enabled = true # 使能配置
zuul.ratelimit.repository = REDIS # 使用redis存储计数
zuul.ratelimit.behind-proxy = true # 是否启动代理
zuul.ratelimit.add-response-headers = true #是否增加响应头
# 通过实例配置覆盖默认配置,注意这里的`test`需要和网关对应路由的`test`关联
zuul.ratelimit.policy-list.test[0].limit = 2
zuul.ratelimit.policy-list.test[0].quota = 1000
zuul.ratelimit.policy-list.test[0].refresh-interval = 5
zuul.ratelimit.policy-list.test[0].type[2] = url
zuul.ratelimit.policy-list.test[0].type[0] = user
zuul.ratelimit.policy-list.test[0].type[1] = origin
2> pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>com.marcosbarbero.cloud</groupId>
<artifactId>spring-cloud-zuul-ratelimit</artifactId>
</dependency>
注意版本使用的是2.3.0RELAESE
限流的配置也挺简单,只需要简单的几行配置就可以起到限流的作用。
经过简单的配置,我们就实现了限流。
接下来我们就看源码中 RateLimitProperties.java
@Validated
@RefreshScope
@ConfigurationProperties("zuul.ratelimit")
public class RateLimitProperties {
public static final String PREFIX = "zuul.ratelimit";
@Valid
@NotNull
@Policies
@NestedConfigurationProperty
private List<RateLimitProperties.Policy> defaultPolicyList = Lists.newArrayList();
@Valid
@NotNull
@Policies
@NestedConfigurationProperty
private Map<String, List<RateLimitProperties.Policy>> policyList = Maps.newHashMap();
private boolean behindProxy;
private boolean enabled;
@NotNull
private ResponseHeadersVerbosity responseHeaders;
@NotNull
@Value("${spring.application.name:rate-limit-application}")
private String keyPrefix;
@NotNull
private RateLimitRepository repository;
private int postFilterOrder;
private int preFilterOrder;
//to-do
}
从源码可以看出,我们的配置文件应该如何配置了吧 。
这里定义了@ConfigurationProperties("zuul.ratelimit") 我们取配置的数据的前缀。
private List<RateLimitProperties.Policy> defaultPolicyList = Lists.newArrayList();
@NestedConfigurationProperty
private Map<String, List<RateLimitProperties.Policy>> policyList = Maps.newHashMap();
我们可以看到里面有这两行代码,里面有个默认的配置。
那默认的配置具体什么含义呢,我猜测是只要配置了默认了限流规则,则所有的通过zuul转发的都会被限制。
public List<RateLimitProperties.Policy> getPolicies(String key) {
return (List)this.policyList.getOrDefault(key, this.defaultPolicyList);
}
从上面的代码可以看出,获取策略时,如果没有指定某一个服务的策略,则会使用默认的策略代替。
提到策略,我看下策略的源代码
public static class Policy {
@NotNull
@DurationUnit(ChronoUnit.SECONDS)
private Duration refreshInterval = Duration.ofSeconds(60L);
private Long limit;
@DurationUnit(ChronoUnit.SECONDS)
private Duration quota;
private boolean breakOnMatch;
@Valid
@NotNull
@NestedConfigurationProperty
private List<RateLimitProperties.Policy.MatchType> type = Lists.newArrayList();
//to-do
我们可以到其中的属性字段正式我们配置的。
这里的quota 的含义是 在 60s 的时间内访问limit次数的最大延迟不超过quota设定的时间。
public static class MatchType {
@Valid
@NotNull
private RateLimitType type;
private String matcher;
public MatchType(@Valid @NotNull RateLimitType type, String matcher) {
this.type = type;
this.matcher = matcher;
}
public boolean apply(HttpServletRequest request, Route route, RateLimitUtils rateLimitUtils) {
return StringUtils.isEmpty(this.matcher) || this.type.apply(request, route, rateLimitUtils, this.matcher);
}
这里定义匹配类型
public enum RateLimitType {
ORIGIN {
public boolean apply(HttpServletRequest request, Route route, RateLimitUtils rateLimitUtils, String matcher) {
if (matcher.contains("/")) {
SubnetUtils subnetUtils = new SubnetUtils(matcher);
return subnetUtils.getInfo().isInRange(rateLimitUtils.getRemoteAddress(request));
} else {
return matcher.equals(rateLimitUtils.getRemoteAddress(request));
}
}
public String key(HttpServletRequest request, Route route, RateLimitUtils rateLimitUtils, String matcher) {
return rateLimitUtils.getRemoteAddress(request);
}
},
USER {
public boolean apply(HttpServletRequest request, Route route, RateLimitUtils rateLimitUtils, String matcher) {
return matcher.equals(rateLimitUtils.getUser(request));
}
public String key(HttpServletRequest request, Route route, RateLimitUtils rateLimitUtils, String matcher) {
return rateLimitUtils.getUser(request);
}
},
URL {
public boolean apply(HttpServletRequest request, Route route, RateLimitUtils rateLimitUtils, String matcher) {
return route == null || route.getPath().startsWith(matcher);
}
public String key(HttpServletRequest request, Route route, RateLimitUtils rateLimitUtils, String matcher) {
return (String)Optional.ofNullable(route).map(Route::getPath).orElse("");
}
},
ROLE {
public boolean apply(HttpServletRequest request, Route route, RateLimitUtils rateLimitUtils, String matcher) {
return rateLimitUtils.getUserRoles().contains(matcher.toUpperCase());
}
public String key(HttpServletRequest request, Route route, RateLimitUtils rateLimitUtils, String matcher) {
return matcher;
}
public boolean isValid(String matcher) {
return StringUtils.isNotEmpty(matcher);
}
},
/** @deprecated */
@Deprecated
HTTPMETHOD {
public boolean apply(HttpServletRequest request, Route route, RateLimitUtils rateLimitUtils, String matcher) {
return HTTP_METHOD.apply(request, route, rateLimitUtils, matcher);
}
public String key(HttpServletRequest request, Route route, RateLimitUtils rateLimitUtils, String matcher) {
return HTTP_METHOD.key(request, route, rateLimitUtils, matcher);
}
},
HTTP_METHOD {
public boolean apply(HttpServletRequest request, Route route, RateLimitUtils rateLimitUtils, String matcher) {
return request.getMethod().equalsIgnoreCase(matcher);
}
public String key(HttpServletRequest request, Route route, RateLimitUtils rateLimitUtils, String matcher) {
return StringUtils.isEmpty(matcher) ? request.getMethod() : "http-method";
}
},
URL_PATTERN {
public boolean apply(HttpServletRequest request, Route route, RateLimitUtils rateLimitUtils, String matcher) {
return (new AntPathMatcher()).match(matcher.toLowerCase(), request.getRequestURI().toLowerCase());
}
public String key(HttpServletRequest request, Route route, RateLimitUtils rateLimitUtils, String matcher) {
return matcher;
}
public boolean isValid(String matcher) {
return StringUtils.isNotEmpty(matcher);
}
};
目前zuul-ratelimit 支持这7种限流类型。
接下来针对这几种限流类型进行源码级一一解析。