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限流策略
概要 在大数据量高并发访问时,经常会出现服务或接口面对暴涨的请求而不可用的情况,甚至引发连锁反映导致整个系统崩溃.此时你需要使用的技术手段之一就是限流,当请求达到一定的并发数或速率,就进行等待.排队. ...
随机推荐
- freeswitch拨打分机号源代码跟踪
概述 freeswitch是一款非常好用的开源VOIP软交换平台. 之前我们有介绍过使用fs拨打分机号的方法,其中代码流程是比较复杂的,所以单独开一章介绍. fs拨打分机号,是使用send_dtmf接 ...
- arcgis中nodata设为0及其小技巧
一.arcgis中nodata设为0 两个栅格进行叠加,有时会有一部分没有数据,即用identify点击该区域,Value为NoDat a,而不是像其他非空区域一样有值. 此时注意nodata区域要赋 ...
- appium实现简单的功能测试
实现思路 思路: 1.获取capabilities信息 2.启动app(包含安装过程) 3.检查是否安装成功 4.卸载app 5.检查是否卸载成功 6.执行×3 from time import sl ...
- 选择结构-扩展if-else语句和练习用if语句实现考试成绩划分
判断语句3--if..else if...else if语句第三种格式: if...else if ...else if (判断条件1) { 执行语句1; } else if (判断条件2) { 执行 ...
- atcoder ABC 232 B~E题解
B 模拟,水题 #include<bits/stdc++.h> using namespace std; char s1[100005],s2[100005]; int a1[100005 ...
- Linux一些错误总结
1.cannot verify <mydomainname> certificate, issued by '/C=US/O=Let's Encrypt/CN=R3': 解决1:wget ...
- kubernetes之资源限制及QOS服务质量
1.什么是资源限制? 1.1在kubernetes集群中,为了使得系统能够稳定的运行,通常会对Pod的资源使用量进行限制.在kubernetes集群中,如果有一个程序出现异常,并且占用大量的系统资源, ...
- springboot的@ConditionalOnClass注解
大家好,我是"良工说技术". 今天给大家带来的是springboot中的@ConditionalOnClass注解的用法.上次的@ConditionalOnBean注解还记得吗? ...
- vant自动上传图片/文件
vant自动上传文件/图片 vant上传图片与elementUI有所不同,没有自动上传功能,所以与后端进行接口对接的时候可以在after-read中将文件进行上传 html页面 <!-- 上传图 ...
- 2019国家集训队论文《整点计数》命题报告 学习笔记/Min25
\(2019\)国家集训队论文<整点计数>命题报告 学习笔记/\(Min25\) 补了个大坑 看了看提交记录,发现\(hz\)的\(xdm\)早过了... 前置知识,\(HAOI\)< ...