Spring Cloud Zuul 微服务网关的 API 限流

API 限流

微服务开发中有时需要对 API 做限流保护,防止网络攻击,比如做一个短信验证码 API,限制客户端的请求速率能在一定程度上抵御短信轰炸攻击,降低损失。

微服务网关是每个请求的必经入口,非常适合做一些 API 限流、认证之类的操作,这里有一个基于 Zuul 微服务网关的 API 限流库:https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit

使用方法

比如我们要对 user-service 这个服务进行限流,限制每个请求源每分钟最多只能请求 10 次。

首先在项目中添加 spring-cloud-zuul-ratelimit 依赖:

1
2
3
4
5
<dependency>
<groupId>com.marcosbarbero.cloud</groupId>
<artifactId>spring-cloud-zuul-ratelimit</artifactId>
<version>1.5.0.RELEASE</version>
</dependency>

然后再添加如下配置即可:

1
2
3
4
5
6
7
8
9
10
11
12
zuul:
ratelimit:
enabled: true
behind-proxy: true
policy-list:
user-service:
- limit: 10
refresh-interval: 60
type:
- user
- origin
- url
  • 测试客户端如果 60s 内请求超过 10 次,服务端就抛出异常,一分钟后又可以正常请求

  • 某个 IP 的客户端被限流并不影响其他客户端,即 API 网关对每个客户端限流是相互独立的

限流数据存储

对 API 限流是基于 Zuul 过滤器完成的,默认情况下限流数据是记录在内存中的,实际上是用 ConcurrentHashMap 保存,当然也提供了多种存储方式,包括 Redis、Consul、Spring Data JPA,使用这三种存储方式要添加相关依赖。

然后再添加存储配置,比如使用 Redis 的配置:

1
2
3
zuul:
ratelimit:
repository: Redis

原理分析

限流拦截时机

限流过滤器是在请求被转发之前调用的

1
2
3
4
@Override
public String filterType() {
return "pre";
}

限流类型

限流类型主要包括 url、origin、user 三种

1
2
3
4
5
6
7
8
9
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 就拦截请求:

1
2
3
4
5
6
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 异常消息,达到拦截请求的效果。

示例代码

https://github.com/yunTerry/spring-cloud-netflix