上面介绍的 spring-cloud-zuul-ratelimit 限流均是通过某个 KEY 进行限流,如基于请求方法、请求 URL 地址、用户等等信息进行限流。
自定义限流是通过实现 RateLimitKeyGenerator 接口,创建自己的 KEY,然后 spring-cloud-zuul-ratelimit 基于该 KEY 进行限流,如:你可以将某个请求参数作为 KEY。
RateLimitKeyGenerator接口是 spring-cloud-zuul-ratelimit 中的一个重要接口,主要用于生成限流的键(key)。
接口源码如下:
package com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config; import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.properties.RateLimitProperties; import org.springframework.cloud.netflix.zuul.filters.Route; import javax.servlet.http.HttpServletRequest; /** * Key generator for rate limit control. * * @author Liel Chayoun */ public interface RateLimitKeyGenerator { /** * Returns a key based on {@link HttpServletRequest}, {@link Route} and * {@link RateLimitProperties.Policy} * * @param request The {@link HttpServletRequest} * @param route The {@link Route} * @param policy The {@link RateLimitProperties.Policy} * @return Generated key */ String key(HttpServletRequest request, Route route, RateLimitProperties.Policy policy); }
该接口只有一个方法 key(RequestContext context),它接收一个 RequestContext 对象作为参数,该对象包含了当前请求的上下文信息。其目的是根据请求的上下文生成一个唯一的键,这个键将用于在限流存储中进行请求计数和限流控制。
spring-cloud-zuul-ratelimit 提供了该接口的默认实现 DefaultRateLimitKeyGenerator 类。
DefaultRateLimitKeyGenerator 是 RateLimitKeyGenerator 接口的默认实现,主要负责生成用于限流的唯一键(key)。这个键在限流操作中起着关键作用,因为它决定了限流的范围和粒度。通过不同的键可以对不同类型的请求或用户进行精确的限流控制。
DefaultRateLimitKeyGenerator 主要功能:
生成限流键:根据不同的请求属性和配置的限流策略,DefaultRateLimitKeyGenerator 会生成一个唯一的键。这个键会被存储在存储库(如 Redis)中,用于记录请求的限流信息,例如请求的次数、时间等。
与限流策略关联:它与不同的限流策略(如基于用户、IP、URL 等)紧密相关。根据不同的策略,生成的键将有所不同,以确保限流规则的准确性和有效性。
源码如下:
package com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.support; import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.RateLimitKeyGenerator; import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.RateLimitUtils; import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.properties.RateLimitProperties; import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.properties.RateLimitProperties.Policy; import org.apache.commons.lang3.StringUtils; import org.springframework.cloud.netflix.zuul.filters.Route; import javax.servlet.http.HttpServletRequest; import java.util.StringJoiner; /** * Default KeyGenerator implementation. * * @author roxspring (github user) * @author Marcos Barbero * @author Liel Chayoun */ public class DefaultRateLimitKeyGenerator implements RateLimitKeyGenerator { private final RateLimitProperties properties; private final RateLimitUtils rateLimitUtils; public DefaultRateLimitKeyGenerator(RateLimitProperties properties, RateLimitUtils rateLimitUtils) { this.properties = properties; this.rateLimitUtils = rateLimitUtils; } @Override public String key(final HttpServletRequest request, final Route route, final Policy policy) { // KEY 的多个部分使用 “:” 进行拼接 final StringJoiner joiner = new StringJoiner(":"); // 前缀:@Value("${spring.application.name:rate-limit-application}") // 应用名称 joiner.add(properties.getKeyPrefix()); // 路由IP if (route != null) { joiner.add(route.getId()); } // 匹配的类型 policy.getType().forEach(matchType -> { // 调用 key() 方法,生成 key String key = matchType.key(request, route, rateLimitUtils); if (StringUtils.isNotEmpty(key)) { joiner.add(key); } }); return joiner.toString(); } }
policy.getType() 源码:
public static class Policy { //... public List<MatchType> getType() { return type; } //... }
MatchType 类源码:
public static class MatchType { @Valid @NotNull private RateLimitType type; private String matcher; //... public String key(HttpServletRequest request, Route route, RateLimitUtils rateLimitUtils) { return type.key(request, route, rateLimitUtils, matcher) + (StringUtils.isEmpty(matcher) ? StringUtils.EMPTY : (":" + matcher)); } //... }
RateLimitType 类源码:
public enum RateLimitType { /** * Rate limit policy considering the user's origin. */ ORIGIN { @Override 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)); } return matcher.equals(rateLimitUtils.getRemoteAddress(request)); } @Override public String key(HttpServletRequest request, Route route, RateLimitUtils rateLimitUtils, String matcher) { // 生成KEY return rateLimitUtils.getRemoteAddress(request); } }, /** * Rate limit policy considering the authenticated user. */ USER { //... }, /** * Rate limit policy considering the request path to the downstream service. */ URL { //... }, /** * Rate limit policy considering the authenticated user's role. */ ROLE { //... }, /** * @deprecated See {@link #HTTP_METHOD} */ @Deprecated HTTPMETHOD { //... }, /** * Rate limit policy considering the HTTP request method. */ HTTP_METHOD { //... }, /** * Rate limit policy considering an URL Pattern */ URL_PATTERN { //... }; public abstract boolean apply(HttpServletRequest request, Route route, RateLimitUtils rateLimitUtils, String matcher); public abstract String key(HttpServletRequest request, Route route, RateLimitUtils rateLimitUtils, String matcher); /** * Helper method to validate specific cases per type. * * @param matcher The type matcher * @return The default behavior will always return true. */ public boolean isValid(String matcher) { return true; } }
当一个请求到达 Zuul 网关,DefaultRateLimitKeyGenerator会根据当前的限流策略和请求的各种属性开始生成键。
它会从请求中提取所需的信息,例如,使用 HttpServletRequest 来获取 IP 地址、请求的 URL、用户信息(如果有)等。
对于不同的策略,会调用不同的逻辑来生成相应的键,确保键的唯一性和符合限流策略的要求。
生成的键将用于后续的限流操作,包括从存储库中读取限流数据、更新限流数据和判断是否超过限流阈值。
下面通过继承 DefaultRateLimitKeyGenerator 类,重写 key() 方法实现自定义限流 KEY,实现限流。
(1)在 application.yml 或 application.properties 文件中配置限流类型。以下是一个配置示例:
zuul: ratelimit: enabled: true default-policy: limit: 10 quota: 100 refresh-interval: 60 type: - user
上述配置中,limit 表示每个用户的限流次数,quota 表示每个用户的配额,refresh-interval 表示刷新间隔(以秒为单位)。
(2)自定义限流 Key 生成器:
package com.hxstrive.hystrix_demo.custom; import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.RateLimitUtils; import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.properties.RateLimitProperties; import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.support.DefaultRateLimitKeyGenerator; import org.springframework.cloud.netflix.zuul.filters.Route; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; /** * 自定义限流 key 生成器 * @author hxstrive.com */ @Component public class RatelimitKeyGenerator extends DefaultRateLimitKeyGenerator { public RatelimitKeyGenerator(RateLimitProperties properties, RateLimitUtils rateLimitUtils) { super(properties, rateLimitUtils); } /** * 限流逻辑 * @param request 请求对象 * @param route 路由对象 * @param policy 策略对象 * @return 限流 key */ @Override public String key(HttpServletRequest request, Route route, RateLimitProperties.Policy policy) { System.out.println("自定义限流 key 生成器,id=" + request.getParameter("id")); // 对请求中相同 id 参数的请求进行限制 return super.key(request, route, policy) + ":" + request.getParameter("id"); } }
重启 Zuul 服务,短时间内连续访问 http://localhost:9000/api/product/product/get?id=1 地址,将输出如下错误信息:
后端日志信息:
从上图可知,成功触发了限流,并且也调用了自定义的限流 KEY 生成器。
点击下载/查看本教程相关资料或者源代码。