思想:
使用redis自增保存接口并发量,然后对并发量校验,控制某段时间内的并发量。
pom 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.9</version>
</dependency>
boot3.0版本默认配置redis application.properties
# Redis数据库索引(默认为0)
spring.data.redis.database=0
# Redis服务器地址
spring.data.redis.host=localhost
# Redis服务器连接端口
spring.data.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.data.redis.password=
# 连接池最大连接数(使用负值表示没有限制) 默认 8
spring.data.redis.lettuce.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
spring.data.redis.lettuce.pool.max-wait=-1
# 连接池中的最大空闲连接 默认 8
spring.data.redis.lettuce.pool.max-idle=8
# 连接池中的最小空闲连接 默认 0
spring.data.redis.lettuce.pool.min-idle=0
Redis工具类
自己定义
import cn.hutool.extra.spring.SpringUtil;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import java.util.concurrent.TimeUnit;
/**
* @Classname RedisUtil
* @Author hygge
* @Date 2023/2/11 20:42
* @Description redis基本命令封装
*/
public class RedisUtil {
public static StringRedisTemplate getRedisTemplate() {
return SpringUtil.getBean("stringRedisTemplate");
}
/**
* 根据key获取value
* @param key 键
* @return value
*/
public static String getString(String key){
return getRedisTemplate().opsForValue().get(key);
}
/**
* 根据key删除value
* @param key 键
*/
public static void delete(String key){
getRedisTemplate().delete(key);
}
/**
* 根据key,缓存value
* @param key 键
* @param value 值
*/
public static void set(String key, String value) {
getRedisTemplate().opsForValue().set(key, value);
}
/**
* 根据key,缓存value,有效时间times 毫秒值
* @param key 键
* @param value 值
* @param times 毫秒值
*/
public static void set(String key, String value, Long times) {
getRedisTemplate().opsForValue().set(key, value, times, TimeUnit.MILLISECONDS);
}
/**
* 根据key,进行自增+1
* @param key
* @return
*/
public static Long increment(String key){
return getRedisTemplate().opsForValue().increment(key);
}
/**
* <pre>自增并设置过期key</pre>
* @param key
* @param time 时间秒
* @param interCurrent 控制并发量
* @return
*/
public static boolean checkLimit(String key, Integer time, Integer interCurrent){
String limitScript = """
if ( redis.call('exists', KEYS[1]) == 0 ) then
redis.call('incr', KEYS[1])
redis.call('expire', KEYS[1], ARGV[1] )
return 1
else
if ( redis.call('incr', KEYS[1]) > tonumber(ARGV[2])) then
return 0
end
return 1
end
""";
RedisScript<Long> defaultRedisScript = RedisScript.of(limitScript, Long.class);
Long result = getStringRedisTemplate().execute(defaultRedisScript, List.of(key), time.toString(), interCurrent.toString());
return result == 0;
}
}
编码形式使用
import com.example.hygge.result.R;
import com.example.hygge.utils.RedisUtil;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Classname TestController
* @Author hygge
* @Date 2023/3/19 8:53
* @Description
*/
@RequestMapping("test")
@RestController
public class LimitController {
private static final Logger log = LoggerFactory.getLogger(LimitController.class);
/**
* 限流接口测试
* @return
*/
@GetMapping("/limitting")
public R limitting(HttpServletRequest request){
if (RedisUtil.checkLimit(request.getRequestURI(), 60, 10)){
log.info("某段时间内并发大于3进行限流,并发数:{}" , RedisUtil.getString(request.getRequestURI()));
return R.fail("服务器繁忙,请稍后重试");
}
return R.ok();
}
}
注解形式使用
注解
import java.lang.annotation.*;
/**
* @Classname Limitting
* @Author hygge
* @Date 2023/3/19 10:51
* @Description 自定义限流注解
*/
@Target({ElementType.PARAMETER, ElementType.METHOD}) // 注解放置目标位置,METHOD可作用在方法上
@Retention(RetentionPolicy.RUNTIME) // 注解执行阶段
@Documented // 生成文档
public @interface CurrentLimiting {
/**
* 默认可以没有,则使用controller uri路径
*/
String key() default "";
/**
* 默认并发数量
*/
int interCurrent() default 10;
/**
* 限制时间
*/
int timeSecond() default 30;
}
切面类
import cn.hutool.core.util.ObjectUtil;
import com.example.hygge.result.R;
import com.example.hygge.utils.RedisUtil;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.lang.reflect.Method;
/**
* @Classname CurrentLimiting
* @Author hygge
* @Date 2023/3/19 11:02
* @Description 限流注解注解切面类
*/
@Aspect
@Component
public class CurrentLimiting {
private static final Logger log = LoggerFactory.getLogger(CurrentLimiting.class);
/**
* <pre>定义切点</pre>
* Controller层切点 注解拦截
*/
@Pointcut("@annotation(com.example.hygge.annotation.CurrentLimiting)")
public void currentLimitings() {
}
/**
* 环绕通知拦截controller
* @param joinPoint
*/
@Around(value = "currentLimitings()")
public Object around(ProceedingJoinPoint joinPoint) {
Object resObj = null;
// 从切面值入点获取植入点方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取切入点方法
Method method = signature.getMethod();
// 获取注解
com.example.hygge.annotation.CurrentLimiting annotation = method.getAnnotation(com.example.hygge.annotation.CurrentLimiting.class);
// 获取当前请求
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.getRequestAttributes())).getRequest();
// 限流key
String key = ObjectUtil.isNotEmpty(annotation.key()) ? annotation.key() : request.getRequestURI();
if (RedisUtil.checkLimit(key, annotation.timeSecond(), annotation.interCurrent())){
log.info("超出接口承受并发,当前并发量>>>>>>>>{}", RedisUtil.getString(key));
resObj = R.fail("服务器繁忙,请稍后重试");
}else {
// 执行原方法
try {
resObj = joinPoint.proceed(joinPoint.getArgs());
} catch (Throwable e) {
e.printStackTrace();
log.error("方法执行异常!");
}
}
return resObj;
}
测试接口
/**
* 注解限流接口测试
* @return
*/
@CurrentLimiting(interCurrent = 5)
@GetMapping("/limit")
public R limit(){
log.info("当前并发量正常》》》》》");
return R.ok();
}