Spring Boot 实现限流注解

关注我,回复关键字“spring”免费领取Spring学习资料

来源:https://www.cnblogs.com/atwood-pan/p/17418814.html

在高并发系统中,保护系统的三种方式分别为:缓存,降级和限流。

限流的目的是通过对并发访问请求进行限速或者一个时间窗口内的的请求数量进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待

1、限流类型枚举类

    

/**

* 限流类型

* @author ss_419

*/

public enum LimitType {

/**

* 默认的限流策略,针对某一个接口进行限流

*/

DEFAULT,

/**

* 针对某一个IP进行限流

*/

IP

}

2、自定义限流注解

    

/**

* @author ss_419

*/

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)

public @interface RateLimiter {

/**

* 限流的 key,主要是指前缀

* @return

*/

String key() default "rate_limit:";

/**

* 在时间窗内的限流次数

* @return

*/

int count() default 100;

/**

* 限流类型

* @return

*/

LimitType limitType() default LimitType.DEFAULT;

/**

* 限流时间窗

* @return

*/

int time() default 60;

}

欢迎关注公众号:SpringForAll社区(spring4all.com),专注分享关于Spring的一切!回复“加群”还可加入Spring技术交流群!

3、限流lua脚本

1、由于我们使用 Redis 进行限流,我们需要引入 Redis 的 maven 依赖,同时需要引入 aop 的依赖

    

<!-- aop依赖 -->

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-aop</artifactId>

</dependency>

<!-- redis依赖 -->

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-data-redis</artifactId>

</dependency>

2、配置redis以及lua脚本

    

@Configuration

public class RedisConfig {

@Bean

RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory factory) {

RedisTemplate<Object, Object> template = new RedisTemplate<>();

template.setConnectionFactory(factory);

Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

template.setKeySerializer(jackson2JsonRedisSerializer);

template.setValueSerializer(jackson2JsonRedisSerializer);

template.setHashKeySerializer(jackson2JsonRedisSerializer);

template.setHashValueSerializer(jackson2JsonRedisSerializer);

return template;

}

/**

* 读取lua脚本

* @return

*/

@Bean

DefaultRedisScript<Long> limitScript() {

DefaultRedisScript<Long> script = new DefaultRedisScript<>();

script.setResultType(Long.class);

script.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/limit.lua")));

return script;

}

}

通过 Lua 脚本,根据 Redis 中缓存的键值判断限流时间(也是 key 的过期时间)内,访问次数是否超出了限流次数,没超出则访问次数 +1,返回 true,超出了则返回 false。
limit.lua:

    

local key = KEYS[1]

local time = tonumber(ARGV[1])

local count = tonumber(ARGV[2])

local current = redis.call('get', key)

if current and tonumber(current) > count then

return tonumber(current)

end

current = redis.call('incr', key)

if tonumber(current) == 1 then

redis.call('expire', key, time)

end

return tonumber(current)

4、限流切面处理类

1、使用我们刚刚的 Lua 脚本判断是否超出了限流次数,超出了限流次数后返回一个自定义异常,然后在全局异常中去捕捉异常,返回 JSON 数据。

2、根据注解参数,判断限流类型,拼接缓存 key 值

    

package org.pp.ratelimiter.aspectj;

import org.aspectj.lang.JoinPoint;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Before;

import org.aspectj.lang.reflect.MethodSignature;

import org.pp.ratelimiter.annotation.RateLimiter;

import org.pp.ratelimiter.enums.LimitType;

import org.pp.ratelimiter.exception.RateLimitException;

import org.pp.ratelimiter.utils.IpUtils;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.data.redis.core.RedisTemplate;

import org.springframework.data.redis.core.script.RedisScript;

import org.springframework.stereotype.Component;

import org.springframework.web.context.request.RequestContextHolder;

import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;

import java.util.Collections;

@Aspect

@Component

public class RateLimiterAspect {

private static final Logger logger = LoggerFactory.getLogger(RateLimiterAspect.class);

@Autowired

RedisTemplate<Object, Object> redisTemplate;

@Autowired

RedisScript<Long> redisScript;

@Before("@annotation(rateLimiter)")

public void before(JoinPoint jp, RateLimiter rateLimiter) throws RateLimitException {

int time = rateLimiter.time();

int count = rateLimiter.count();

String combineKey = getCombineKey(rateLimiter, jp);

try {

Long number = redisTemplate.execute(redisScript, Collections.singletonList(combineKey), time, count);

if (number == null || number.intValue() > count) {

//超过限流阈值

logger.info("当前接口以达到最大限流次数");

throw new RateLimitException("访问过于频繁,请稍后访问");

}

logger.info("一个时间窗内请求次数:{},当前请求次数:{},缓存的 key 为 {}", count, number, combineKey);

} catch (Exception e) {

throw e;

}

}

/**

* 这个 key 其实就是接口调用次数缓存在 redis 的 key

* rate_limit:11.11.11.11-org.javaboy.ratelimit.controller.HelloController-hello

* rate_limit:org.javaboy.ratelimit.controller.HelloController-hello

* @param rateLimiter

* @param jp

* @return

*/

private String getCombineKey(RateLimiter rateLimiter, JoinPoint jp) {

StringBuffer key = new StringBuffer(rateLimiter.key());

if (rateLimiter.limitType() == LimitType.IP) {

key.append(IpUtils.getIpAddr(((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest()))

.append("-");

}

MethodSignature signature = (MethodSignature) jp.getSignature();

Method method = signature.getMethod();

key.append(method.getDeclaringClass().getName())

.append("-")

.append(method.getName());

return key.toString();

}

}

5、使用与测试

    

@RestController

public class HelloController {

/**

* 限流 10 秒之内,这个接口可以访问3次

* @return

*/

@GetMapping("/hello")

@RateLimiter(time = 10,count = 3)

public Map<String, Object> hello() {

Map<String, Object> map = new HashMap<>();

map.put("status", 200);

map.put("message", "Hello RateLimiter");

return map;

}

}

十秒之内访问次数超过3次就会报异常

 

redis中的数据,每一次访问都加1

当访问次数超过3,则进行限流操作

给大家推荐我们团队开发的Chrome插件:YouTube中文配音(https://www.youtube-dubbing.com/)。如果您跟我们一样,热爱看国外的视频学习前沿知识或者其他内容,该插件可以很好的帮助您讲外语视频一键转化为中文视频!


END



Java 21 分代 ZGC 和虚拟线程上手体验
什么是正向代理?什么是反向代理?区别是?
Spring Boot虚拟线程的性能还不如Webflux?
Spring官方出品的状态机框架,简单又优雅!
高质量交流群,关注:SpringForAll,回复关键词:加群

相关推荐

  • 工作 8 年的同事尽然不懂 isEmpty 和 isBlank 的区别
  • 我看不懂程序员男朋友的聊天记录
  • 浅析Redis大Key
  • 基于模式挖掘的可靠性治理探索与实践
  • 40+专家齐聚共谋数据未来,StarRocks Summit 2023 议程公布!更多精彩议题等你探索...
  • 苹果发布M3系列芯片,采用3nm工艺、支持 “动态缓存” 技术
  • 重磅!阿里发布AI编码助手:通义灵码,兼容 VS Code、IDEA等主流编程工具
  • [译]CSS Animations 与 CSS Transitions
  • 5K Star大厂微软开源的图标集合,丰富、精致、统一
  • AI编程,详细比较GitHub Copilot对比Amazon CodeWhisperer
  • 【美团】异步加载 JS 脚本时,async 与 defer 有何区别
  • 人工智能将如何增强平台工程和DevEx?
  • C++“中年危机”有救了!C++之父新动作!
  • 蔡崇信、周靖人、王坚、王小川指路大模型
  • AI 编程时代已至,大模型如何助力开发者打造新质生产力?
  • 在鹅厂晋级了!
  • 四季酒店里的低调招商会
  • 23日开抢!最高可领11111元京东红包
  • Java 8 腰斩!Java 11 和 Java 17 使用率超过 Java 8
  • 拜登政府发布新的AI行政命令,称确保AI将为美国人服务