-
对请求的目标URL进行限流(例如:某个URL每分钟只允许调用多少次)
-
对客户端的访问IP进行限流(例如:某个IP每分钟只允许请求多少次)
-
对某些特定用户或者用户组进行限流(例如:非VIP用户限制每分钟只允许调用100次某个API等)
-
多维度混合的限流。此时,就需要实现一些限流规则的编排机制。与、或、非等关系。
介绍
spring-cloud-zuul-ratelimit是和zuul整合提供分布式限流策略的扩展,只需在yaml中配置几行配置,就可使应用支持限流
<dependency>
<groupId>com.marcosbarbero.cloud</groupId>
<artifactId>spring-cloud-zuul-ratelimit</artifactId>
<version>1.3.4.RELEASE</version></dependency>
支持的限流粒度
-
服务粒度 (默认配置,当前服务模块的限流控制)
-
用户粒度 (详细说明,见文末总结)
-
ORIGIN粒度 (用户请求的origin作为粒度控制)
-
接口粒度 (请求接口的地址作为粒度控制)
-
以上粒度自由组合,又可以支持多种情况。
-
如果还不够,自定义RateLimitKeyGenerator实现。
// 默认实现
public String key(final HttpServletRequest request, final Route route, final RateLimitProperties.Policy policy) {
final List<Type> types = policy.getType();
final StringJoiner joiner = new StringJoiner(":");
joiner.add(properties.getKeyPrefix());
if (route != null) {
joiner.add(route.getId());
}
if (!types.isEmpty()) {
if (types.contains(Type.URL) && route != null) {
joiner.add(route.getPath());
}
if (types.contains(Type.ORIGIN)) {
joiner.add(getRemoteAddr(request));
}
// 这个结合文末总结。
if (types.contains(Type.USER)) {
joiner.add(request.getUserPrincipal() != null ? request.getUserPrincipal().getName() : ANONYMOUS_USER);
}
}
return joiner.toString();
}
支持的存储方式
-
InMemoryRateLimiter - 使用 ConcurrentHashMap作为数据存储
-
ConsulRateLimiter - 使用 Consul 作为数据存储
-
RedisRateLimiter - 使用 Redis 作为数据存储
-
SpringDataRateLimiter - 使用 数据库 作为数据存储
对API限流是基于Zuul过滤器完成的,默认情况下限流数据是记录在内存中的,实际上是用ConcurrentHashMap保存,当然也提供了多种存储方式,包括Redis、Consul、Spring Data JPA,使用这三种存储方式要添加相关依赖。
限流配置
-
limit 单位时间内允许访问的个数
-
quota 单位时间内允许访问的总时间(统计每次请求的时间综合)
-
refresh-interval 单位时间设置
zuul:
ratelimit:
key-prefix: your-prefix
enabled: true
repository: REDIS
behind-proxy: true
policies:
myServiceId:
limit: 10
quota: 20
refresh-interval: 30
type:
- user
- origin
- url
以上配置意思是:30秒内允许10个访问,并且要求总请求时间小于20秒,某个IP的客户端被限流并不影响其他客户端,即API网关对每个客户端限流是相互独立的
效果展示
yaml配置:
zuul:
ratelimit:
key-prefix: pig-ratelimite
enabled: true
repository: REDIS
behind-proxy: true
policies:
pig-admin-service:
limit: 2
quota: 1
refresh-interval: 3
动态图 ↓↓↓↓↓
Redis 中数据结构 注意红色字体
原理分析
限流拦截时机
限流过滤器是在请求被转发之前调用的
@Override
public String filterType() {
return "pre";
}
限流类型
限流类型主要包括url、origin、user三种
if (types.contains(URL)) {
joiner.add(route.getPath());
}
if (types.contains(ORIGIN)) {
joiner.add(getRemoteAddr(request));
}
if (types.contains(USER)) {
joiner.add(request.getUserPrincipal() != null ? request.getUserPrincipal().getName() : ANONYMOUS);
}
url类型的限流就是通过请求路径区分
origin是通过客户端IP地址区分
user是通过授权用户进行区分,也包括匿名用户
可以多个限流类型结合使用
如果不配置限流类型,就不做以上区分
拦截限流请求
在过滤器的run方法中判断请求剩余次数,小于0就拦截请求:
if (rate.getRemaining() < 0) {
ctx.setResponseStatusCode(TOO_MANY_REQUESTS.value());
ctx.put("rateLimitExceeded", "true");
throw new ZuulRuntimeException(new ZuulException(TOO_MANY_REQUESTS.toString(),
TOO_MANY_REQUESTS.value(), null));
}
可以看到,单位时间内剩余请求次数小于0时抛出ZuulRuntimeException,直接返回客户端TOO_MANY_REQUESTS异常消息,达到拦截请求的效果。
总结
-
可以使用Spring Boot Actuator 提供的服务状态,动态设置限流开关
-
源码可以参考:https://gitee.com/log4j/pig
-
用户限流的实现:如果你的项目整合 Shiro 或者 Spring Security 安全框架,那么会自动维护request域UserPrincipal,如果是自己的框架,请登录成功后维护request域UserPrincipal,才能使用用户粒度的限流。未登录默认是:anonymous
快来成为我的朋友或合作伙伴,一起交流,一起进步!
QQ群:961179337
微信:lixiang6153
邮箱:lixx2048@163.com
公众号:IT技术快餐
更多资料等你来拿!