限流:使用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多策略注解限流的更多相关文章

  1. Sentinel限流示例:编码和注解限流

    一.Sentinel 是什么? 随着微服务的流行,服务和服务之间的稳定性变得越来越重要.Sentinel 以流量为切入点,从流量控制.熔断降级.系统负载保护等多个维度保护服务的稳定性. Sentine ...

  2. Sentinel: 使用注解限流

    在前面我们对Sentinel做了一个详细的介绍,可以手动的通过Sentinel提供的SphU类来保护资源.这种做法不好的地方在于每个需要限制的地方都得写代码,从 0.1.1 版本开始,Sentinel ...

  3. 基于令牌桶算法实现的SpringBoot分布式无锁限流插件

    本文档不会是最新的,最新的请看Github! 1.简介 基于令牌桶算法和漏桶算法实现的纳秒级分布式无锁限流插件,完美嵌入SpringBoot.SpringCloud应用,支持接口限流.方法限流.系统限 ...

  4. 从SpringBoot构建十万博文聊聊限流特技

    前言 在开发十万博客系统的的过程中,前面主要分享了爬虫.缓存穿透以及文章阅读量计数等等.爬虫的目的就是解决十万+问题:缓存穿透是为了保护后端数据库查询服务:计数服务解决了接近真实阅读数以及数据库服务的 ...

  5. 基于Redis的限流系统的设计

    本文讲述基于Redis的限流系统的设计,主要会谈及限流系统中限流策略这个功能的设计:在实现方面,算法使用的是令牌桶算法来,访问Redis使用lua脚本.   1.概念 In computer netw ...

  6. 使用AOP和Semaphore对项目中具体的某一个接口进行限流

    整体思路: 一 具体接口,可以自定义一个注解,配置限流量,然后对需要限流的方法加上注解即可! 二 容器初始化的时候扫描所有所有controller,并找出需要限流的接口方法,获取对应的限流量 三 使用 ...

  7. Spring Cloud Gateway 扩展支持动态限流

    之前分享过 一篇 <Spring Cloud Gateway 原生的接口限流该怎么玩>, 核心是依赖Spring Cloud Gateway 默认提供的限流过滤器来实现 原生Request ...

  8. 这个注解一次搞定限流与熔断降级:@SentinelResource

    在之前的<使用Sentinel实现接口限流>一文中,我们仅依靠引入Spring Cloud Alibaba对Sentinel的整合封装spring-cloud-starter-alibab ...

  9. Java限流策略

    概要 在大数据量高并发访问时,经常会出现服务或接口面对暴涨的请求而不可用的情况,甚至引发连锁反映导致整个系统崩溃.此时你需要使用的技术手段之一就是限流,当请求达到一定的并发数或速率,就进行等待.排队. ...

随机推荐

  1. freeswitch拨打分机号源代码跟踪

    概述 freeswitch是一款非常好用的开源VOIP软交换平台. 之前我们有介绍过使用fs拨打分机号的方法,其中代码流程是比较复杂的,所以单独开一章介绍. fs拨打分机号,是使用send_dtmf接 ...

  2. arcgis中nodata设为0及其小技巧

    一.arcgis中nodata设为0 两个栅格进行叠加,有时会有一部分没有数据,即用identify点击该区域,Value为NoDat a,而不是像其他非空区域一样有值. 此时注意nodata区域要赋 ...

  3. appium实现简单的功能测试

    实现思路 思路: 1.获取capabilities信息 2.启动app(包含安装过程) 3.检查是否安装成功 4.卸载app 5.检查是否卸载成功 6.执行×3 from time import sl ...

  4. 选择结构-扩展if-else语句和练习用if语句实现考试成绩划分

    判断语句3--if..else if...else if语句第三种格式: if...else if ...else if (判断条件1) { 执行语句1; } else if (判断条件2) { 执行 ...

  5. atcoder ABC 232 B~E题解

    B 模拟,水题 #include<bits/stdc++.h> using namespace std; char s1[100005],s2[100005]; int a1[100005 ...

  6. Linux一些错误总结

    1.cannot verify <mydomainname> certificate, issued by '/C=US/O=Let's Encrypt/CN=R3': 解决1:wget ...

  7. kubernetes之资源限制及QOS服务质量

    1.什么是资源限制? 1.1在kubernetes集群中,为了使得系统能够稳定的运行,通常会对Pod的资源使用量进行限制.在kubernetes集群中,如果有一个程序出现异常,并且占用大量的系统资源, ...

  8. springboot的@ConditionalOnClass注解

    大家好,我是"良工说技术". 今天给大家带来的是springboot中的@ConditionalOnClass注解的用法.上次的@ConditionalOnBean注解还记得吗? ...

  9. vant自动上传图片/文件

    vant自动上传文件/图片 vant上传图片与elementUI有所不同,没有自动上传功能,所以与后端进行接口对接的时候可以在after-read中将文件进行上传 html页面 <!-- 上传图 ...

  10. 2019国家集训队论文《整点计数》命题报告 学习笔记/Min25

    \(2019\)国家集训队论文<整点计数>命题报告 学习笔记/\(Min25\) 补了个大坑 看了看提交记录,发现\(hz\)的\(xdm\)早过了... 前置知识,\(HAOI\)< ...