赞
踩
目录
Redis(Remote Dictionary Server)是一个开源的高性能键值对存储数据库。它支持多种数据结构,包括字符串(String)、哈希(Hash)、列表(List)、集合(Set)、有序集合(Sorted Set)等。Redis的特点包括:
内存存储:Redis将数据存储在内存中,因此读写速度非常快,适用于对性能有较高要求的场景。
持久化:Redis支持持久化将内存中的数据保存到硬盘上,以便在服务器重启后能够恢复数据。
数据结构多样:Redis不仅仅支持简单的键值对存储,还支持丰富的数据结构,例如列表、集合、有序集合等,使其具备更多的功能和用途。
高并发:Redis是单线程模型,通过使用异步I/O和非阻塞I/O来支持高并发。
多语言支持:Redis支持多种编程语言的客户端,如Java、Python、C#等,便于开发人员在不同平台上使用。
发布/订阅:Redis支持发布/订阅模式,允许客户端订阅一个或多个频道并接收对应频道的消息。
事务支持:Redis支持事务,可以在一个事务中执行多个命令,并保证这些命令的原子性。
由于Redis具有高性能、灵活的数据结构和丰富的功能,它被广泛用于缓存、消息队列、计数器、实时排行榜、会话管理等多种应用场景。
需求:针对相同IP,60s的接口请求次数不能超过10000次
接口限流是为了保护系统和服务,防止因为过多的请求而导致系统过载、性能下降甚至崩溃。以下是进行接口限流的几个主要原因:
防止恶意攻击:接口限流可以防止恶意用户或者攻击者通过大量的请求来攻击系统,保护系统的稳定性和安全性。
保护系统资源:对于一些计算密集型或者资源消耗较大的接口,限制请求的频率可以避免服务器资源被过度消耗,保障其他正常请求的处理。
避免雪崩效应:当某个服务不可用或者响应时间过长时,如果没有限流措施,大量请求可能会涌入后端,导致更多的请求失败,产生雪崩效应。
提升系统性能:限流可以控制并发请求数,避免过多的请求导致服务器负载过高,从而提升系统的整体性能和响应速度。
提供公平资源分配:通过限流,可以实现对不同用户或者不同服务请求的公平分配,避免某些请求占用过多资源而影响其他请求。
综上所述,进行接口限流是保护系统和提升性能的重要手段,对于高并发的系统尤为重要。通过合理设置限流策略,可以有效地平衡资源利用和系统稳定性,提供更好的用户体验。
当用户在第一次访问该接口时,向Redis中设置一个包含了用户IP和接口方法名的key,value的值初始化为1(表示第一次访问当前接口),同时设置该key的过期时间(60秒),只要此Redis的key没有过期,每次访问都将value的值自增1次,用户每次访问接口前,先从Redis中拿到当前接口访问次数,如果发现访问次数大于规定的次数(超过10000次),则向用户返回接口访问失败的标识。
1、添加Redis依赖:首先在pom.xml
文件中添加Spring Data Redis依赖
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-redis</artifactId>
- </dependency>
2、 配置Redis连接信息:在application.properties
或application.yml
中配置Redis的连接信息,包括主机、端口、密码等。
3、创建限流拦截器:在项目中创建一个限流拦截器,用于对用户IP进行接口限流。拦截器可以实现HandlerInterceptor
接口,并重写preHandle
方法进行限流逻辑。
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.concurrent.TimeUnit; public class RateLimitInterceptor implements HandlerInterceptor { @Autowired private RedisTemplate<String, String> redisTemplate; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String ipAddress = getIpAddress(request); String uri = request.getRequestURI().replace("/","_"); String key = "apiVisits:" + uri + ":" + ipAddress; // 判断是否已经达到限流次数 String value = redisTemplate.opsForValue().get(key); // key 不存在,则是第一次请求设置过期时间 if(StringUtils.isBlank(value)){ redisTemplate.opsForValue().increment(key, 1); redisTemplate.expire(key, time, TimeUnit.SECONDS); return true; } if (value != null && Integer.parseInt(value) > 10) { response.setStatus(HttpServletResponse.SC_TOO_MANY_REQUESTS); return false; } // 未达到限流次数,自增 redisTemplate.opsForValue().increment(key, 1); return true; } private String getIpAddress(HttpServletRequest request) { // 从请求头或代理头中获取真实IP地址 String ipAddress = request.getHeader("X-Forwarded-For"); if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("Proxy-Client-IP"); } if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("WL-Proxy-Client-IP"); } if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getRemoteAddr(); } return ipAddress; } }
4、注册拦截器:在配置类中注册自定义的限流拦截器。
- import org.springframework.context.annotation.Configuration;
- import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
- import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
-
- @Configuration
- public class WebMvcConfig implements WebMvcConfigurer {
-
- @Autowired
- private RateLimitInterceptor rateLimitInterceptor;
-
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- registry.addInterceptor(rateLimitInterceptor).addPathPatterns("/**");
- }
- }
以注解+切面的方式实现,将需要进行限流的API加上注解即可
1、创建注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface CurrentLimiting { /** * 缓存key */ String key() default "apiVisits:"; /** * 限流时间,单位秒 */ int time() default 5; /** * 限流次数 */ int count() default 10; }
2、创建AOP切面
- @Slf4j
- @Aspect
- @Component
- @RequiredArgsConstructor
- public class CurrentLimitingAspect {
-
- private final RedisTemplate redisTemplate;
-
- /**
- * 带有注解的方法之前执行
- */
- @SuppressWarnings("unchecked")
- @Before("@annotation(currentLimiting)")
- public void doBefore(JoinPoint point, CurrentLimiting currentLimiting) throws Throwable {
- int time = currentLimiting.time();
- int count = currentLimiting.count();
- // 将接口方法和用户IP构建Redis的key
- String key = getCurrentLimitingKey(currentLimiting.key(), point);
-
- // 判断是否已经达到限流次数
- String value = redisTemplate.opsForValue().get(key);
- if (value != null && Integer.parseInt(value) > count) {
- log.error("接口限流,key:{},count:{},currentCount:{}", key, count, value);
- throw new RuntimeException("访问过于频繁,请稍后再试!");
- }
- // 未达到限流次数,自增
- redisTemplate.opsForValue().increment(key, 1);
- // key 不存在,则是第一次请求设置过期时间
- if(StringUtils.isBlank(value)){
- redisTemplate.expire(key, time, TimeUnit.SECONDS);
- }
- }
-
- /**
- * 组装 redis 的 key
- */
- private String getCurrentLimitingKey(String prefixKey,JoinPoint point) {
- StringBuilder sb = new StringBuilder(prefixKey);
- ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
- HttpServletRequest request = attributes.getRequest();
- sb.append( Utils.getIpAddress(request) );
-
- MethodSignature signature = (MethodSignature) point.getSignature();
- Method method = signature.getMethod();
- Class<?> targetClass = method.getDeclaringClass();
- return sb.append("_").append( targetClass.getName() )
- .append("_").append(method.getName()).toString();
- }
- }

当在10:00访问接口,这个时候向Reids写入一条数据访问次数为1,在10:59的时候突然访问了9999次,然后redis过期,在11:00访问了9999次,这样出现的问题就是在10:59到11:00之间访问了9999+9999次。故以固定时间段的方式进行限流可能会不起作用,会存在Reids过期的临界点内造成大量的用户访问。
由于方案一的时间是固定的,我们可以把固定的时间段改成动态的,也就是在用户每次访问接口时,记录当前用户访问的时间点(时间戳),并计算前一分钟内用户访问该接口的总次数。如果总次数大于限流次数,则不允许用户访问该接口。这样就能保证在任意时刻用户的访问次数不会超过10000次。
1、创建注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface CurrentLimiting { /** * 缓存key */ String key() default "apiVisits:"; /** * 限流时间,单位秒 */ int time() default 5; /** * 限流次数 */ int count() default 10; }
2、创建AOP切面
- @Slf4j
- @Aspect
- @Component
- @RequiredArgsConstructor
- public class CurrentLimitingAspect {
-
- private final RedisTemplate redisTemplate;
-
- /**
- * 带有注解的方法之前执行
- */
- @SuppressWarnings("unchecked")
- @Before("@annotation(currentLimiting)")
- public void doBefore(JoinPoint point, CurrentLimiting currentLimiting) throws Throwable {
- int time = currentLimiting.time();
- int count = currentLimiting.count();
- // 将接口方法和用户IP构建Redis的key
- String key = getCurrentLimitingKey(currentLimiting.key(), point);
-
- // 使用Zset的 score 设置成用户访问接口的时间戳
- ZSetOperations zSetOperations = redisTemplate.opsForZSet();
-
- // 当前时间戳
- long currentTime = System.currentTimeMillis();
- zSetOperations.add(key, currentTime, currentTime);
-
- // 设置过期时间防止key不消失
- redisTemplate.expire(key, time, TimeUnit.SECONDS);
-
- // 移除 time 秒之前的访问记录,动态时间段
- zSetOperations.removeRangeByScore(key, 0, currentTime - time * 1000);
-
- // 获得当前时间窗口内的访问记录数
- Long currentCount = zSetOperations.zCard(key);
- // 限流判断
- if (currentCount > count) {
- log.error("接口限流,key:{},count:{},currentCount:{}", key, count, currentCount);
- throw new RuntimeException("访问过于频繁,请稍后再试!");
- }
- }
-
- /**
- * 组装 redis 的 key
- */
- private String getCurrentLimitingKey(String prefixKey,JoinPoint point) {
- StringBuilder sb = new StringBuilder(prefixKey);
- ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
- HttpServletRequest request = attributes.getRequest();
- sb.append( Utils.getIpAddress(request) );
-
- MethodSignature signature = (MethodSignature) point.getSignature();
- Method method = signature.getMethod();
- Class<?> targetClass = method.getDeclaringClass();
- return sb.append("_").append( targetClass.getName() )
- .append("_").append(method.getName()).toString();
- }
- }

赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。