系统限流要求
- 系统总并发数限制,如设置1000,表示该系统接口每秒可以请求1000次
- 自定义系统接口请求并发数,也可以不加限流设置,如设置100,表示每秒可以请求100次该接口
- 指定接口ip请求并发数,如设置1,表示每秒该ip可以请求1次该接口
实现思路
- 每秒系统总并发数限流实现,可以使用拦截器或过滤器,来处理系统总并发数限流的实现
- 自定义系统接口请求并发数和指定接口ip请求并发数的实现,可以使用自定义注解和切面,来处理自定义系统接口请求并发数的实现
- 可以使用redisson rratelimiter组件实现具体限流逻辑
- 自定义业务异常类,当请求数超出请求限制时,来打断业务
核心代码
1.接口限流注解
package com.ocean.angel.tool.annotation; import java.lang.annotation.*; /** * 接口限流注解 */ @retention(retentionpolicy.runtime) @target(elementtype.method) public @interface apilimiting { // 接口请求限制数 int apirequestlimit() default 200; // 接口请求ip限制数 int apiiplimit() default 1; }
2.接口限流切面
package com.ocean.angel.tool.aspect; import com.ocean.angel.tool.annotation.apilimiting; import com.ocean.angel.tool.constant.apilimitingtypeenum; import com.ocean.angel.tool.constant.resultcode; import com.ocean.angel.tool.dto.apilimitingdata; import com.ocean.angel.tool.exception.businessexception; import com.ocean.angel.tool.util.ratelimiterkeyutil; import lombok.extern.slf4j.slf4j; import org.aspectj.lang.joinpoint; import org.aspectj.lang.annotation.aspect; import org.aspectj.lang.annotation.before; import org.aspectj.lang.annotation.pointcut; import org.aspectj.lang.reflect.methodsignature; import org.redisson.api.rratelimiter; import org.redisson.api.rateintervalunit; import org.redisson.api.ratetype; import org.redisson.api.redissonclient; import org.springframework.stereotype.component; import javax.annotation.resource; import java.lang.reflect.method; /** * 接口限流切面 */ @slf4j @aspect @component public class apilimitingaspect { @resource private redissonclient redissonclient; @pointcut("@annotation(com.ocean.angel.tool.annotation.apilimiting)") public void apilimitingaspect() {} @before(value = "apilimitingaspect()") public void apilimiting(joinpoint joinpoint) { apilimitingdata apilimitingdata = getapilimitdata(joinpoint); ratelimiterhandler(redissonclient, apilimitingdata); } /** * api 限流逻辑处理 */ private void ratelimiterhandler(redissonclient redissonclient, apilimitingdata apilimitingdata) { if(apilimitingdata.getapiiplimit() > 0) { // 获取rratelimiter实例 rratelimiter ratelimiter = redissonclient.getratelimiter(getratelimiterkey(apilimitingdata, apilimitingtypeenum.api_ip_limit)); // rratelimiter初始化 if(!ratelimiterkeyutil.contains(getratelimiterkey(apilimitingdata, apilimitingtypeenum.api_ip_limit))) { ratelimiter.trysetrate(ratetype.overall, apilimitingdata.getapiiplimit(), 1, rateintervalunit.seconds); } // 超出接口请求ip限流设置,打断业务 if (!ratelimiter.tryacquire()) { log.info("接口{}超出ip请求限制, 时间:{}",apilimitingdata.getmethodname(), system.currenttimemillis()); throw new businessexception(resultcode.beyond_rate_limit); } } if(apilimitingdata.getapirequestlimit() > 0) { rratelimiter ratelimiter = redissonclient.getratelimiter(getratelimiterkey(apilimitingdata, apilimitingtypeenum.api_request_limit)); if(!ratelimiterkeyutil.contains(getratelimiterkey(apilimitingdata, apilimitingtypeenum.api_request_limit))) { ratelimiter.trysetrate(ratetype.overall, apilimitingdata.getapirequestlimit(), 1, rateintervalunit.seconds); } // 超出接口请求限流设置,打断业务 if (!ratelimiter.tryacquire()) { log.info("接口{}超出请求限制, 时间:{}",apilimitingdata.getmethodname(), system.currenttimemillis()); throw new businessexception(resultcode.beyond_rate_limit); } } } /** * 组装apilimitingdata */ private apilimitingdata getapilimitdata(joinpoint joinpoint) { apilimitingdata apilimitingdata = new apilimitingdata(); methodsignature signature = (methodsignature) joinpoint.getsignature(); method method = signature.getmethod(); apilimitingdata.setmethodname(method.getname()); apilimiting apilimiting = method.getannotation(apilimiting.class); apilimitingdata.setapirequestlimit(apilimiting.apirequestlimit()); apilimitingdata.setapiiplimit(apilimiting.apiiplimit()); return apilimitingdata; } /** * ratelimiter key */ private string getratelimiterkey(apilimitingdata apilimitingdata, apilimitingtypeenum apilimitingtypeenum) { return apilimitingdata.getmethodname() + "_" + apilimitingtypeenum.getcode(); } }
3.系统接口限流拦截器
package com.ocean.angel.tool.interceptor; import com.ocean.angel.tool.constant.resultcode; import com.ocean.angel.tool.exception.businessexception; import com.ocean.angel.tool.util.ratelimiterkeyutil; import lombok.extern.slf4j.slf4j; import org.redisson.api.rratelimiter; import org.redisson.api.rateintervalunit; import org.redisson.api.ratetype; import org.redisson.api.redissonclient; import org.springframework.web.servlet.handlerinterceptor; import javax.servlet.http.httpservletrequest; import javax.servlet.http.httpservletresponse; @slf4j public class apilimitinginterceptor implements handlerinterceptor { private final static string api_total_limit = "apitotallimit"; // 系统每秒请求总数,30表示每秒最多处理30个请求 private final static int api_total_limit_number = 30; private final redissonclient redissonclient; public apilimitinginterceptor(redissonclient redissonclient) { this.redissonclient = redissonclient; } @override public boolean prehandle(httpservletrequest request, httpservletresponse response, object handler) throws exception { rratelimiter ratelimiter = redissonclient.getratelimiter(api_total_limit); if(!ratelimiterkeyutil.contains(api_total_limit)) { ratelimiter.trysetrate(ratetype.overall, api_total_limit_number, 1, rateintervalunit.seconds); } // 超出系统接口总请求数限制,打断业务 if (!ratelimiter.tryacquire()) { log.info("超出系统接口总请求数限制, 时间:{}", system.currenttimemillis()); throw new businessexception(resultcode.beyond_rate_limit); } return true; } }
4.接口自定义注解配置
@apilimiting(apirequestlimit = 5, apiiplimit = 1) @getmapping("/limited/resource") public resultbean<?> limitedresource() { return resultbean.success(); }
限流方案演示
下载源代码,github源码连接
修改application.yml和redission.yml,关于redis的相关配置
启动项目,调用http://localhost:8090/test/limited/resource接口,截图如下:
保持项目启动状态,运行com.ocean.angel.tool.applicationtests.contextloads()方法,截图如下:
使用指南
修改系统总请求数限制
调整系统接口限流参数
本文使用redisson rratelimiter组件实现具体限流逻辑,小伙伴们可以自己去手写具体限流功能(可以参考redission的限流相关的数据结构)
注意:
小伙伴们如果修改系统限流的配置,需要先删除redis里面的限流数据(如上图),不然修改不会生效。
本文使用以1秒为单位进行系统并发数控制,小伙伴可以根据需要自己去修改,如下:
ratelimiter.trysetrate(ratetype.overall, apilimitingdata.getapiiplimit(), 1, rateintervalunit.seconds)
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论