简述:
Spring Cloud Zuul RateLimit项目Github地址:
https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit
该包实现了在Zuul对每个服务进行限流
微服务开发中有时需要对API做限流保护,防止网络攻击,比如做一个短信验证码API,限制客户端的请求速率能在一定程度上抵御短信轰炸攻击,降低损失。微服务网关是每个请求的必经入口,非常适合做一些API限流、认证之类的操作,本文介绍Zuul如何进行限流操作
个人建议:如果在网关做细粒度的限流,后面微服务业务变化的话网关也要跟着变,而且后面涉及到微服务之间的调用,这个网关限流做不了。所以在网关上不能做细粒度的限流,网关主要为服务器硬件设备的并发处理能力做限流,细粒度的限流还是交给专门的熔断限流微服务去处理,这样利于各微服务之间的解构和各团队的协同开发
一、Ratelimit相关配置介绍
1、限流策略
限流粒度/类型 | 说明 |
---|---|
Authenticated User | 使用经过身份验证的用户名或“匿名” |
Request Origin | 使用用户原始请求 |
URL | 使用下游服务的请求路径 |
ROLE | 使用经过身份验证的用户角色 |
Request method | 使用HTTP请求方法 |
Global configuration per service | 这个不验证请求Origin,Authenticated User或URI,要使用这个,请不要设置type |
2、可用的实现
存储类型 | 说明 |
---|---|
consul | 基于consul |
redis | 基于redis,使用时必须引入redis相关依赖 |
JPA | 基于SpringDataJPA,需要用到数据库 |
MEMORY | 基于本地内存,默认 |
BUKET4J | 使用一个Java编写的基于令牌桶算法的限流库 |
Bucket4j实现需要相关的bean @Qualifier("RateLimit"):
- JCache - javax.cache.Cache
- Hazelcast - com.hazelcast.core.IMap
- Ignite - org.apache.ignite.IgniteCache
- Infinispan - org.infinispan.functional.ReadWriteMap
3、常见的配置属性
属性名 | 值 | 默认值 |
---|---|---|
enabled | true、false | false |
behind-proxy | true、false | false |
add-response-header | true、false | false |
key-prefix | string | ${spring.application.name:rate-limit-application} |
repository | CONSUL、REDIS、JPA、BUCKET4J_JCACHE、BUCKET4J_HAZELCAST、BUCKET4J_INFINISPAN、BUCKET4J_IGNITE | - |
default-policy-list | list-of-policy | - |
policy-list | Map of Lists of Policy | - |
postFilterOrder | int | FilterConstants.SEND_RESPONSE_FILTER_ORDER - 10 |
preFilterOrder | int | FilterConstants.FORM_BODY_WRAPPER_FILTER_ORDER |
policy的相关属性
属性名 | 值 | 默认值 |
---|---|---|
limit | number of calls 请求数量 | - |
quota | time of calls 请求时间 | - |
refresh-interval | seconds 统计窗口刷新时间(单位:秒) | 60 |
type | 限流类型[ORIGIN,、USER,、URL、ROLE、HTTPMETHOD] | [] |
4、发生错误如何处理
@Component
@Slf4j
public class MyRateLimiterErrorHandler extends DefaultRateLimiterErrorHandler {
/**
* 往redis存储限流请求信息的时候报错的处理,此方法一般不用重写
* @param key
* @param e
*/
@Override
public void handleSaveError(String key, Exception e) {
super.handleSaveError(key, e);
}
/**
* 从redis取出限流请求信息的时候报错的处理,此方法一般不用重写
* @param key
* @param e
*/
@Override
public void handleFetchError(String key, Exception e) {
super.handleFetchError(key, e);
}
/**
* 请求发生限流了,或者限流过程中发生错误了的处理
* 对限流进行日志记录,返回限流的信息等,方便后期分析日志排查问题,这里就简单处理打印日志
* @param msg
* @param e
*/
@Override
public void handleError(String msg, Exception e) {
log.error("限流信息msg={},错误信息e={}",e);
super.handleError(msg, e);
}
}
二、搭建Zuul结合Ratelimit服务
1、导入依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.10.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<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>
<version>2.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 高版本redis的lettuce需要commons-pool2 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.7.0</version>
</dependency>
<dependencyManagement>
<dependencies>
<!--整合Spring Cloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2、启动类标注解
@SpringBootApplication
@EnableEurekaClient
@EnableZuulProxy
public class ZuulRatelimitApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulRatelimitApplication.class, args);
}
}
3、配置文件
server:
port: 9070
zuul:
routes:
#这里没有使用服务发现组件,所以将服务服务地址写死
token:
url: http://localhost:9090
order:
url: http://localhost:9060
#zuul.sensitiveHeaders设置为空,代表不过滤任何Header信息,Header会向下转发
sensitive-headers:
ratelimit:
enabled: true
repository: redis #对应存储类型(用来存储统计信息)
behind-proxy: true #代理之后
default-policy-list: #可选 - 针对所有的路由配置的策略,除非特别配置了policies
- limit: 10 #可选 - 每个刷新时间窗口对应的请求数量限制
quota: 1000 #可选- 每个刷新时间窗口对应的请求时间限制(秒)
refresh-interval: 60 # 刷新时间窗口的时间,默认值 (秒)
type: #可选 限流方式
- url
- httpmethod
- origin
policy-list: #可选 - 针对所有的路由配置的策略,除非特别配置了policies
token: #特定的路由
- limit: 2 #可选 - 每个刷新时间窗口对应的请求数量限制
quota: 1 #可选- 每个刷新时间窗口对应的请求时间限制(秒)
refresh-interval: 3 # 刷新时间窗口的时间,默认值 (秒)
type: #可选 限流方式
- url
- httpmethod
- origin
spring:
redis:
database: 0
host: 127.0.0.1
jedis:
pool:
#最大连接数据库连接数,设 0 为没有限制
max-active: 8
#最大等待连接中的数量,设 0 为没有限制
max-idle: 8
#最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。
max-wait: -1ms
#最小等待连接中的数量,设 0 为没有限制
min-idle: 0
lettuce:
pool:
max-active: 8
max-idle: 8
max-wait: -1ms
min-idle: 0
shutdown-timeout: 100ms
password:
port: 6379
application:
name: gateway
4、启动后进行访问
由于我们配置的是一秒只允许3个请求,当我们超过时,会抛出过多请求异常
5、自定义Key策略
如果希望自己控制key的策略,可以通过自定义RateLimitKeyGenerator的实现来增加自己的策略逻辑。
实例:
根据请求上的参数来对请求进行限流。比如有一个请求是http://localhost:9070/api-a//hello2?name=kevin,对相同的name值进行限流。我们设置了1分钟内,限流10次,那么如果1分钟内,name是kevin的请求超过10次,就会发生限流。
自定义RateLimitKeyGenerator的实现:
@Component
public class MyKeyGenerator extends DefaultRateLimitKeyGenerator {
public MyKeyGenerator(RateLimitProperties properties, RateLimitUtils rateLimitUtils) {
super(properties, rateLimitUtils);
}
//在这个方法里面写限流逻辑
@Override
public String key(HttpServletRequest request, Route route, RateLimitProperties.Policy policy) {
return super.key(request, route, policy);
}
}
到此本文就结束啦!
参考: