上面介绍的 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 生成器。
点击下载/查看本教程相关资料或者源代码。