系统限流要求
- 系统总并发数限制,如设置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)
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论