为了防止网关过量负载请求,很多时候会要根据实际的情况对接口进行限流。zuul本身是没有提供限流的功能的,但是你也可以根据它提供的filter自己去做限流,当然也可以使用人家已经写好了的限流组件去集成,因为资源有限,自研成本会很高,所以还是采取集成别人的功能来做,然后根据看一下人家的代码和自身业务做结合去达到最终的目的。
需求功能
- 全局的限流
- 特定URL限流、希望是可配置。
- 有些业务复杂,耗费资源的需要单独配置限流规则。
- 可以做个类似于防重提交的功能
- 比如1秒内某个接口只能点击一次。
开源限流工具
spring-cloud-zuul-ratelimit
基于Zuul的filter开发的限流功能,可以结合本地缓存、redis、consul、mysql等做数据存储。
配置简单、使用方便
接下来我们先集成该工具
maven配置
<dependency>
<groupId>com.marcosbarbero.cloud</groupId>
<artifactId>spring-cloud-zuul-ratelimit</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>
<!-- 如果项目中有redis的话可以直接使用,我们应用中用到了redis所以直接以它为存储媒介 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring-cloud-zuul-ratelimit的最新版本是2.4.X,为什么这里用到是2.2.7。这是因为我们sc的版本比较低是1.5的,导致版本匹配不上,查阅了各个版本发现2.2.7比较兼容,如果大家应用的时候发现配置不生效,很有可能是版本不兼容导致的,我这边不兼容的原因是RateLimitProperties中有的注解使用版本过高,导致配置不生效,大家可以根据实际情况做调试查看。
yaml配置
zuul:
ratelimit:
key-prefix: your-prefix # 缓存key的名称定义前缀。
enabled: true
repository: REDIS
behind-proxy: true
add-response-headers: true
default-policy-list: # 可选,默认的全局配置,如果根据规则查询不到配置则应用这个定义的配置
- limit: 10 #可选,时间窗口内最多访问次数
quota: 1000 # 可选时间窗口单位是秒
refresh-interval: 60 # 默认的刷新时间窗口的间隔。
type: # 可选 规则类型
- user
- origin # IP
- url
- http_method # HTTP方法 可以组合
policy-list: # 具体的规则定义
myServiceId: # SpringCloud的服务编号
- limit: 10 # optional - request number limit per refresh interval window
quota: 1000 # optional - request time limit per refresh interval window (in seconds)
refresh-interval: 60 #default value (in seconds)
# 以下是参考的规则案例 URL是采用左侧前缀方式
type: #optional
- user
- origin
- url
- type: #optional value for each type
- user=anonymous
- origin=somemachine.com
- url=/api #url prefix
- role=user
- http_method=get #case insensitive
- type:
- url_pattern=/api/*/payment
这里需要说明的是:
- limit : 1
- quota : 10
- refresh-interval : 60
三个组合在一起的意思就是 : 在10秒内如果访问次数超过1次,则会被限流,一旦限流需要在60秒之后才会被解封
根据以上配置全局默认配置限流的功能已经OK了,特定的URL配置其实也是有的,但是需要每次在配置文件中定义,网关本身是所有流量的入口,如果每次配置都需要改配置文件然后再重启无疑是痛苦的。
我们如果阅读过相关的源码会发现,特定的URL是放在了RateLimitProperties
的policyList
集合中,然而这个集合是个Map对象,key是服务编号,value是特定的URL规则,可以是多个。
我之前想做一个功能就是将所有服务的URL进行统一管理,然后业务同学根据接口的需求自己定义接口访问频率(注解),接口开发完成之后,需要限流的URL数据同步到Mysql表中,最终同步到Redis缓存里面,网关则根据缓存里面的频率规则直接应用。
不过目前功能没有实现完全,后续如果有需要的可以留言,完成了会贴代码。
这里提供一些实现思路:
- 实现ApplicationRunner接口,在应用启动的时候回调run方法。
-
- 负责同步Mysql的路由数据到redis中
-
- 实现一个间隔刷新路由的线程
-
如果上文有什么不对的地方欢迎指正讨论,共同进步。