简述: 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,要使用这个,请不要设置type2、可用的实现
存储类型 说明 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_ORDERpolicy的相关属性
属性名 值 默认值 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: gateway4、启动后进行访问 由于我们配置的是一秒只允许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); } }参考: