当前位置:网站首页>两种白名单限流方案(redis lua限流,guava方案)
两种白名单限流方案(redis lua限流,guava方案)
2022-08-04 20:29:00 【百里东君~】
两种白名单限流方案
1、redis lua方案
创建注解类
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Limit {
// 资源名称,用于描述接口功能
String name() default "";
// 资源 key
String key() default "";
// key 前缀
String prefix() default "";
// 时间单位秒
int period();
// 限制单位时间访问的次数
int count();
// 限制类型
LimitType limitType() default LimitType.CUSTOMER;
}
编写AOP拦截类
@Slf4j
@Aspect
@Component
public class LimitAspect {
@Autowired
@Qualifier("jacksonRedisTemplate")
private RedisTemplate<String,Object> redisTemplate;
@Pointcut("@annotation(com.xx.common.annotation.Limit)")
public void pointcut() {
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
Limit limitAnnotation = method.getAnnotation(Limit.class);
LimitType limitType = limitAnnotation.limitType();
String name = limitAnnotation.name();
String key;
String ip = IpUtil.getIpAddr(request);
int limitPeriod = limitAnnotation.period();
int limitCount = limitAnnotation.count();
switch (limitType) {
case IP:
key = ip;
break;
case CUSTOMER:
key = limitAnnotation.key();
break;
default:
key = StringUtils.upperCase(method.getName());
}
ImmutableList<String> keys = ImmutableList.of(StringUtils.join(limitAnnotation.prefix() + "_", key, ip));
String luaScript = buildLuaScript();
RedisScript<Number> redisScript = new DefaultRedisScript<>(luaScript, Number.class);
Number count = redisTemplate.execute(redisScript, keys, limitCount, limitPeriod);
log.info("限流拦截IP:{} 第 {} 次访问key={},接口描述:{}", ip, count, keys, name);
if (count != null && count.intValue() <= limitCount) {
return point.proceed();
} else {
throw new LimitAccessException("接口访问超出频率限制");
}
}
/** * 限流脚本 * 调用的时候不超过阈值,则执行计算器自加。 * * @return lua脚本 */
private static String buildLuaScript() {
return "local c" +
"\nc = redis.call('get',KEYS[1])" +
"\nif c and tonumber(c) > tonumber(ARGV[1]) then" +
"\nreturn c;" +
"\nend" +
"\nc = redis.call('incr',KEYS[1])" +
"\nif tonumber(c) == 1 then" +
"\nredis.call('expire',KEYS[1],ARGV[2])" +
"\nend" +
"\nreturn c;";
}
}
枚举
public enum LimitType {
// 传统类型
CUSTOMER,
// 根据 IP 限制
IP;
}
获取IP工具类
@Slf4j
public class IpUtil {
private static final String UNKNOWN = "unknown";
private static final String LOCAL_IP = "127.0.0.1";
private static final Integer IP_LENGTH = 15;
protected IpUtil() {
}
/** * 获取 IP地址 * 使用 Nginx等反向代理软件, 则不能通过 request.getRemoteAddr()获取 IP地址 * 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址, * X-Forwarded-For中第一个非 unknown的有效IP字符串,则为真实IP地址 */
public static String getIpAddr(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
if (LOCAL_IP.equals(ip)) {
// 根据网卡取本机配置的IP
InetAddress inet = null;
try {
inet = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
log.error("获取ip错误:{}", e);
}
if (inet != null) {
ip = inet.getHostAddress();
}
}
}
if (ip != null && ip.length() > IP_LENGTH) {
if (ip.indexOf(StrUtil.COMMA) > 0) {
ip = ip.substring(0, ip.indexOf(","));
}
}
return "0:0:0:0:0:0:0:1".equals(ip) ? LOCAL_IP : ip;
}
}
2、guava方案
引入guava包
<!--guava 限流-->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.0.1-jre</version>
</dependency>
自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
int NOT_LIMITED = 0;
/** * qps */
double qps() default NOT_LIMITED;
/** * 超时时长 */
int timeout() default 0;
/** * 超时时间单位 */
TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
/** * 超时时间单位 */
LimitType limitType() default LimitType.CUSTOMER;
}
AOP拦截:
@Slf4j
@Aspect
@Component
public class RateLimitAspect {
/** * map */
private static final ConcurrentMap<String, RateLimiter> RATE_LIMITER_CACHE = new ConcurrentHashMap<>();
@Pointcut("@annotation(com.xx.common.annotation.RateLimit)")
public void pointcut() {
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
log.info("guava限流拦截到了{}方法...", point.getSignature().getName());
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
if (method.isAnnotationPresent(RateLimit.class)) {
//获取方法上的注解
RateLimit rateLimit = method.getAnnotation(RateLimit.class);
if (rateLimit != null && rateLimit.qps() > 0) {
double qps = rateLimit.qps();
if (RATE_LIMITER_CACHE.get(method.getName()) == null) {
// 初始化 QPS
RATE_LIMITER_CACHE.put(method.getName(), com.google.common.util.concurrent.RateLimiter.create(qps));
}
log.debug("【{}】的QPS设置为: {}", method.getName(), RATE_LIMITER_CACHE.get(method.getName()).getRate());
// 尝试获取令牌
if (RATE_LIMITER_CACHE.get(method.getName()) != null && !RATE_LIMITER_CACHE.get(method.getName()).tryAcquire(rateLimit.timeout(), rateLimit.timeUnit())) {
throw new LimitAccessException("接口访问超出频率限制");
}
return point.proceed();
}
}
return point.proceed();
}
}
边栏推荐
- 如何进行AI业务诊断,快速识别降本提效增长点?
- vs Code 运行一个本地WEB服务器
- [TypeScript] In-depth study of TypeScript enumeration
- jMeter Thread group 对应的 constant timer
- mysql的存储过程介绍、创建、案例、删除、查看「建议收藏」
- 多用户同时远程登录连接到一台服务器
- CAS :80750-24-9(脱硫生物素 NHS 酯)
- C语言基础[通俗易懂]
- Client Side Cache 和 Server Side Cache 的区别
- After encountering MapStruct, the conversion between PO, DTO and VO objects is no longer handwritten
猜你喜欢
随机推荐
[Academic related] Tsinghua professor persuaded to quit his Ph.D.:I have seen too many doctoral students have mental breakdowns, mental imbalances, physical collapses, and nothing!...
Web3时代的战争
vim clear last search highlighting
关于 SAP 电商云 Spartacus UI SSR 的 state transfer 问题
How to manually download and install SAP Fiori tools - Extension Pack for Visual Studio Code
win10 uwp 修改图片质量压缩图片
IIC驱动OLED
Chrome 开发者工具 performance 标签页的用法
SAP UI5 ensures that the control id is globally unique implementation method
C#移动OA办公系统源码(基于微信企业号)
多商户商城系统功能拆解22讲-平台端分销商品
[Awards for Essays] Autumn recruitment special training to create your exclusive product experience
密码学系列之:PEM和PKCS7,PKCS8,PKCS12
面试官:JVM运行时数据区包含哪几部分?作用是啥?
Apache服务器的配置[通俗易懂]
C语言基础[通俗易懂]
June To -.-- -..- -
手撕SparkSQL五大JOIN的底层机制
零知识证明——zkSNARK证明体系
Ant Group's time series database CeresDB is officially open source