Redisson多策略注解限流
限流:使用Redisson的RRateLimiter进行限流
多策略:map+函数式接口优化if判断
自定义注解
/**
* aop限流注解
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface RedisLimit {
String prefix() default "rateLimit:";
//限流唯一标示
String key() default "";
//限流单位时间(单位为s)
int time() default 1;
//单位时间内限制的访问次数
int count();
//限流类型
LimitType type() default LimitType.CUSTOM;
}
定义限流类型
public enum LimitType {
/**
* 自定义key
*/
CUSTOM,
/**
* 请求者IP
*/
IP,
/**
* 方法级别限流
* key = ClassName+MethodName
*/
METHOD,
/**
* 参数级别限流
* key = ClassName+MethodName+Params
*/
PARAMS,
/**
* 用户级别限流
* key = ClassName+MethodName+Params+UserId
*/
USER,
/**
* 根据request的uri限流
* key = Request_uri
*/
REQUEST_URI,
/**
* 对requesturi+userId限流
* key = Request_uri+UserId
*/
REQUESTURI_USERID,
/**
* 对userId限流
* key = userId
*/
SINGLEUSER,
/**
* 对方法限流
* key = ClassName+MethodName
*/
SINGLEMETHOD,
/**
* 对uri+params限流
* key = uri+params
*/
REQUEST_URI_PARAMS,
/**
* 对uri+params+userId限流
* key = uri+params+userId
*/
REQUEST_URI_PARAMS_USERID;
}
生成key的工具类
根据类型生成锁的对象(key)的工具类,使用map+函数式接口优化if,其中BaseContext是一个获取用户唯一标识userId的工具类
@Component
public class ProceedingJoinPointUtil {
@Autowired
private HttpServletRequest request;
private Map<LimitType, Function<ProceedingJoinPoint,String>> functionMap = new HashMap<>(9);
@PostConstruct
void initMap(){
//初始化策略
functionMap.put(LimitType.METHOD, this::getMethodTypeKey);
functionMap.put(LimitType.PARAMS, this::getParamsTypeKey);
functionMap.put(LimitType.USER, this::getUserTypeKey);
functionMap.put(LimitType.REQUEST_URI,proceedingJoinPoint ->
request.getRequestURI());
functionMap.put(LimitType.REQUESTURI_USERID, proceedingJoinPoint ->
request.getRequestURI()+BaseContext.getUserId());
functionMap.put(LimitType.REQUEST_URI_PARAMS,proceedingJoinPoint ->
request.getRequestURI()+getParams(proceedingJoinPoint));
functionMap.put(LimitType.REQUEST_URI_PARAMS_USERID,proceedingJoinPoint ->
request.getRequestURI()+getParams(proceedingJoinPoint)+BaseContext.getUserId());
functionMap.put(LimitType.SINGLEUSER,(proceedingJoinPoint)->
String.valueOf(BaseContext.getUserId()));
functionMap.put(LimitType.SINGLEMETHOD,(proceedingJoinPoint -> {
StringBuilder sb = new StringBuilder();
appendMthodName(proceedingJoinPoint,sb);
return sb.toString();
}));
}
public Object getKey(ProceedingJoinPoint joinPoint, RedisLimit redisLimit) {
//根据限制类型生成key
Object generateKey = "";
//自定义
if(redisLimit.type() != LimitType.CUSTOM){
generateKey = generateKey(redisLimit.type(), joinPoint);
}else {
//非自定义
generateKey = redisLimit.key();
}
return generateKey;
}
/**
* 根据LimitType生成key
* @param type
* @param joinPoint
* @return
*/
private Object generateKey(LimitType type , ProceedingJoinPoint joinPoint) {
Function function = functionMap.get(type);
Object result = function.apply(joinPoint);
return result;
}
/**
* 方法级别
* key = ClassName+MethodName
* @param joinPoint
* @return
*/
private String getMethodTypeKey(ProceedingJoinPoint joinPoint){
StringBuilder sb = new StringBuilder();
appendMthodName(joinPoint, sb);
return sb.toString();
}
/**
* 参数级别
* key = ClassName+MethodName+Params
* @param joinPoint
* @return
*/
private String getParamsTypeKey(ProceedingJoinPoint joinPoint){
StringBuilder sb = new StringBuilder();
appendMthodName(joinPoint, sb);
appendParams(joinPoint, sb);
return sb.toString();
}
/**
* 用户级别
* key = ClassName+MethodName+Params+UserId
*/
private String getUserTypeKey(ProceedingJoinPoint joinPoint){
StringBuilder sb = new StringBuilder();
appendMthodName(joinPoint, sb);
appendParams(joinPoint, sb);
//获取userId
appendUserId(sb);
return sb.toString();
}
/**
* StringBuilder添加类名和方法名
* @param joinPoint
* @param sb
*/
private void appendMthodName(ProceedingJoinPoint joinPoint, StringBuilder sb) {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
sb.append(joinPoint.getTarget().getClass().getName())//类名
.append(method.getName());//方法名
}
/**
* StringBuilder添加方法参数值
* @param joinPoint
* @param sb
*/
private void appendParams(ProceedingJoinPoint joinPoint, StringBuilder sb) {
for (Object o : joinPoint.getArgs()) {
sb.append(o.toString());
}
}
private String getParams(ProceedingJoinPoint joinPoint) {
StringBuilder sb = new StringBuilder();
for (Object o : joinPoint.getArgs()) {
if(o instanceof MultipartFile){
try {
ImageTypeCheck.getImgHeightAndWidth(((MultipartFile) o).getInputStream());
} catch (IOException e) {
throw new BusinessException("MultipartFile输入流获取失败,source:ProceedingJoinPointUtils.149",USER_PRINCIPAL_EMAIL);
}
}else {
sb.append(o.toString());
}
}
return sb.toString();
}
/**
* StringBuilder添加UserId
* @param sb
*/
private void appendUserId(StringBuilder sb) {
sb.append(BaseContext.getUserId());
}
}
定义aop具体逻辑
@Aspect
@Component
@Slf4j
public class RedisLimitAspect {
@Autowired
private RedissonClient redissonClient;
@Autowired
private ProceedingJoinPointUtil proceedingJoinPointUtil;
@Pointcut("@annotation(com.cat.www.aop.limit.anno.RedisLimit)")
private void pointCut() {
}
@Around("pointCut() && @annotation(redisLimit)")
private Object around(ProceedingJoinPoint joinPoint, RedisLimit redisLimit) {
Object generateKey = proceedingJoinPointUtil.getKey(joinPoint, redisLimit);
//redis key
String key = redisLimit.prefix() +generateKey.toString();
//声明一个限流器
RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);
//设置速率,time秒中产生count个令牌
rateLimiter.trySetRate(RateType.OVERALL, redisLimit.count(), redisLimit.time(), RateIntervalUnit.SECONDS);
// 试图获取一个令牌,获取到返回true
boolean tryAcquire = rateLimiter.tryAcquire();
if (!tryAcquire) {
return new ResultData<>().FAILED().setResultIns("访问过于频繁");
}
Object obj = null;
try {
obj = joinPoint.proceed();
} catch (Throwable e) {
throw new RuntimeException();
}
return obj;
}
}
Redisson多策略注解限流的更多相关文章
- Sentinel限流示例:编码和注解限流
一.Sentinel 是什么? 随着微服务的流行,服务和服务之间的稳定性变得越来越重要.Sentinel 以流量为切入点,从流量控制.熔断降级.系统负载保护等多个维度保护服务的稳定性. Sentine ...
- Sentinel: 使用注解限流
在前面我们对Sentinel做了一个详细的介绍,可以手动的通过Sentinel提供的SphU类来保护资源.这种做法不好的地方在于每个需要限制的地方都得写代码,从 0.1.1 版本开始,Sentinel ...
- 基于令牌桶算法实现的SpringBoot分布式无锁限流插件
本文档不会是最新的,最新的请看Github! 1.简介 基于令牌桶算法和漏桶算法实现的纳秒级分布式无锁限流插件,完美嵌入SpringBoot.SpringCloud应用,支持接口限流.方法限流.系统限 ...
- 从SpringBoot构建十万博文聊聊限流特技
前言 在开发十万博客系统的的过程中,前面主要分享了爬虫.缓存穿透以及文章阅读量计数等等.爬虫的目的就是解决十万+问题:缓存穿透是为了保护后端数据库查询服务:计数服务解决了接近真实阅读数以及数据库服务的 ...
- 基于Redis的限流系统的设计
本文讲述基于Redis的限流系统的设计,主要会谈及限流系统中限流策略这个功能的设计:在实现方面,算法使用的是令牌桶算法来,访问Redis使用lua脚本. 1.概念 In computer netw ...
- 使用AOP和Semaphore对项目中具体的某一个接口进行限流
整体思路: 一 具体接口,可以自定义一个注解,配置限流量,然后对需要限流的方法加上注解即可! 二 容器初始化的时候扫描所有所有controller,并找出需要限流的接口方法,获取对应的限流量 三 使用 ...
- Spring Cloud Gateway 扩展支持动态限流
之前分享过 一篇 <Spring Cloud Gateway 原生的接口限流该怎么玩>, 核心是依赖Spring Cloud Gateway 默认提供的限流过滤器来实现 原生Request ...
- 这个注解一次搞定限流与熔断降级:@SentinelResource
在之前的<使用Sentinel实现接口限流>一文中,我们仅依靠引入Spring Cloud Alibaba对Sentinel的整合封装spring-cloud-starter-alibab ...
- Java限流策略
概要 在大数据量高并发访问时,经常会出现服务或接口面对暴涨的请求而不可用的情况,甚至引发连锁反映导致整个系统崩溃.此时你需要使用的技术手段之一就是限流,当请求达到一定的并发数或速率,就进行等待.排队. ...
随机推荐
- C++ 练气期之二维数组与矩阵运算
1. 前言 C++中的一维数组可以存储线性结构的数据,二维数组可以存储平面结构的数据.如班上所有学生的各科目成绩就有二个维度,学生姓名维度和科目成绩维度. 这样的表格数据可以使用二维数组进行存储. 当 ...
- Django【执行查询】(二)
官方Django3.2 文档:https://docs.djangoproject.com/en/3.2/topics/db/queries/ 本文大部分内容参考官方3.2版本文档撰写,仅供学习使用 ...
- P3480 [POI2009]KAM-Pebbles 题解
题目链接 首先,这道题看上去就是个博弈论,很显然的 \(Nim\) 游戏. 因为每一个的取法都和它的上一位有关. 有一种非常显然的转换方式 :我们把这若干堆石子从前向后做一个差分 . 我们记 \(a_ ...
- Image-Text Matching
重要性和意义: Image-text matching has received a large amount of interest since it associates different mo ...
- java通过注解顺序通过映射导出excel
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.ann ...
- 可视化查询(sp_helptext)——快速查询包含指定字符串的存储过程(附源码)
前言 在开发中,随着业务逻辑的调整,修改存储过程是必不可免的. 那怎么定位到需要修改的存储过程呢?一个一个的点开查询?存储过程少的话还行,一旦存储过程过多,这样是很浪费时间的,一个不注意还会遗漏掉. ...
- 【FAQ】应用内支付服务无法拉起支付页面常见原因分析和解决方法
华为应用内支付服务(In-App Purchases)通过简便的接入流程为用户提供良好的应用内支付体验,然而在实际接入过程中,有一些开发者反馈测试时会无法正常拉起支付页面,下文将详细分析问题出现的5种 ...
- CF665B Shopping
CF665B Shopping 题目描述 Ayush is a cashier at the shopping center. Recently his department has started ...
- js实现两种99乘法表
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- Python3的单元测试模块Mock与性能测试模块CProfile
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_92 我们知道写完了代码需要自己跑一跑进行测试,一个写好的程序如果连测试都没有就上到生产环境是不敢想象的,这么做的人不是太自信就是太 ...