本篇讲网关上的限流
用开源项目spring-cloud-zuul-ratelimit 做网关上的限流 (项目github:https://github.com/marcosbarbero/ )
1,在网关项目里,引入限流组件的maven依赖:
2,在网关项目yml配置里,配限流相关配置
github也有相关配置说明:https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit
限流框架限流需要存一些信息,可以存在数据库里,也可以存在redis里,这里就用redis做实验,而且我看该组件的限流存储枚举类里,如果使用数据库保存限流信息,只能用JPA,不能用mybatis(可能歪果仁更喜欢JPA):
public enum RateLimitRepository { REDIS, CONSUL, JPA, BUCKET4J_JCACHE, BUCKET4J_HAZELCAST, BUCKET4J_IGNITE, BUCKET4J_INFINISPAN, }
限流yml配置:
限流的信息,在redis是有有效期的,过了有效期会自动清除,所以要想在redis看到限流的相关信息,这里时间窗口就设置为 10 秒(刚开始我设置的是1秒,redis死活看不到限流信息,浪费了十几分钟时间)
zuul: routes: #路由的配置是个Map,可以配置多个 token: #token结尾的请求,都转发到http://localhost:9090认证服务器地址 url: http://localhost:9090 order: #order结尾的请求,都转发到http://localhost:9060 顶单服务 url: http://localhost:9060 sensitive-headers: null #设置敏感头设置为空,Authorization等请求头的请求,都往后转发 ratelimit: key-prefix: rate enabled: true repository: REDIS default-policy-list: # ########### 如下的配置就是说:每1秒内不能超过2个请求,2个请求时间加起来不能超过1秒(quota)############ - limit: 2 #optional - request number limit per refresh interval window quota: 1 #optional - request time limit per refresh interval window (in seconds) refresh-interval: 10 #时间窗口 (in seconds) type: ##根据什么控制流量,可以组合使用,如url、httpmethod组合,就会把 /orders的get和post请求分开处理 - url - http_method #- user #根据用户控制需要Security支持,(一般不用) #- origin #根据客户端的ip控制
3,实验
1,分别启动 订单 、认证、网关 三个服务
2,从网关获取一个token:
OAuth2 password模式参数
网关client的username,password
3,拿token访问网关创建订单
手速点的快点,Http就会返回 429 状态码,表示过多的请求。
看redis,已经有限流的信息了:
生成的限流的key是: rate:order:/orders:POST rate是配置的限流信息的前缀,order是zuul转发的规则,/orders:POST 是配置的限流类型 url 和http_method的组合
当下一个请求过来的时候就会计算key,然后根据key去redis找,看当前的key已经过了多少个请求了,来判断这次请求能不能过。
大部分情况下,根据user 、origin、 url 、http_method已经满足需求了。有些特殊的场景,需要根据传过来的参数进行限流,比如有两种优惠券A、B,优惠券A业务简单,每秒能处理100个请求,优惠券B复杂,每秒能处理10个请求,此时自带的限流规则就不能满足需求了。限流归根揭底是根据key来限流的,所以此时就要自定义key的生成规则。
自定义key的生成规则:
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生成规则,自定义限流规则 */ @Component public class MyKeyGen extends DefaultRateLimitKeyGenerator { public MyKeyGen(RateLimitProperties properties, RateLimitUtils rateLimitUtils){ super(properties,rateLimitUtils); } @Override public String key(HttpServletRequest request, Route route, RateLimitProperties.Policy policy) { //可以从route拿出路由信息,自定义key生成规则:https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit#usage return super.key(request, route, policy); } }
重写错误处理:
package com.nb.security.filter; import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.repository.DefaultRateLimiterErrorHandler; import org.springframework.stereotype.Component; /** * 限流错误自定义处理 */ @Component public class MyRateLimitErrorHandler extends DefaultRateLimiterErrorHandler { //限流数据存时候报错了的处理,一般不覆盖 @Override public void handleSaveError(String key, Exception e) { super.handleSaveError(key, e); } //限流取数据报错的处理,一般不覆盖 @Override public void handleFetchError(String key, Exception e) { super.handleFetchError(key, e); } //限流错误处理,记日志等 @Override public void handleError(String msg, Exception e) { super.handleError(msg, e); } }
都在网关上做限流是有问题的
1,耦合
如上所述的根据不同的优惠券进行的限流,如果优惠券又多了种类型,就要重写网关的限流的代码,重写部署网关,这就是耦合,是不行的。服务和服务之间,以及服务和基础组件之间,一定要解耦。微服务场景下解耦是价值最大的事。一旦两个服务耦合在一块,一个变了,另一个也要跟着变。
2,限流的数量的问题
网关只能处理从整个微服务外边进来的请求,并不处理微服务之间的调用。如,有A B两个微服务,A还调用了B服务,A、B服务每秒处理的请求最大都是100个,网关限流 A 100请求/秒 ,B 100请求/秒。此时有100个A请求到网关,100个B请求也到了网关,都通过了网关,而此时,100个到A的请求又请求了100次B,导致B服务不可用!
所以:在网关上不要做细粒度的限流,没有用,因为很多服务之间的调用,都不走网关。网关层面只根据硬件设备的处理能力做一些限流,如服务的节点用的是tomcat,根据每个tomcat的资源配置,计算能处理的多少并发请求,根据这个去限流。具体的方法,跟业务逻辑的耦合,都不要发生在网关上的限流逻辑里。这就是上边配置里说的,限流类型 user最好不在网关上用。
本篇github : https://github.com/lhy1234/springcloud-security/tree/chapt-4-10-ratelimit 如果对你有一点帮助,点个小星星吧!谢谢
欢迎关注个人公众号一起交流学习: