SpringBoot实现接口限流(Redis版本)
[notice]随着前后端分离架构的流⾏,前端页面与后端接口的调⽤关系越来越复杂,后端服务的稳定性越来越重要。在遇到突发的请求量激增,恶意的⽤户访问,亦或请求频率过⾼给下游服务带来较⼤压⼒时,我们常常需要通过缓存、限流、负载均衡等多种⽅式保证后端服务的稳定性。其中,限流是不可或缺的⼀环。[/notice]
1. redis maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2. redis yaml配置
redis:
host: 192.168.1.1
port: 6379
password:
jedis:
pool:
min-idle: 0
max-idle: 8
max-active: 80
max-wait: 30000
timeout: 3000
3. RedisConfig配置
package com.dzy.tonguecollectionback.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* @Author 。。。源
* @Email apple_dzy@163.com
* @Blog https://www.findmyfun.cn
* @Date 2022/6/6 17:22
* @Version 1.0
*/
@Configuration
public class RedisConfig {
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
4. WebConfig配置
package com.dzy.tonguecollectionback.config;
import com.dzy.tonguecollectionback.component.RequestLimitInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @Author 。。。源
* @Email apple_dzy@163.com
* @Blog https://www.findmyfun.cn
* @Date 2022/11/14 15:24
* @Version 1.0
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
private final RequestLimitInterceptor requestLimitInterceptor;
public WebConfig(RequestLimitInterceptor requestLimitInterceptor) {
this.requestLimitInterceptor = requestLimitInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(requestLimitInterceptor)
//拦截所有请求路径
.addPathPatterns("/**")
//再设置 放开哪些路径
// .excludePathPatterns("/static/**","/auth/login")
;
}
}
5. RequestLimit自定义注解
package com.dzy.tonguecollectionback.annotation;
import java.lang.annotation.*;
/**
* @Author 。。。源
* @Email apple_dzy@163.com
* @Blog https://www.findmyfun.cn
* @Date 2022/11/14 15:25
* @Version 1.0
*/
@Inherited
@Documented
@Target({ElementType.FIELD, ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestLimit {
/**
* 时间内 秒为单位
*/
int second() default 10;
/**
* 允许访问次数
*/
int maxCount() default 5;
//默认效果 : 10秒内 对于使用该注解的接口,只能总请求访问数 不能大于 5次
}
6. RequestLimitInterceptor拦截过滤器
package com.dzy.tonguecollectionback.component;
import com.alibaba.fastjson.JSONObject;
import com.dzy.tonguecollectionback.annotation.RequestLimit;
import com.dzy.tonguecollectionback.utils.IPUtils;
import com.dzy.tonguecollectionback.utils.R;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.JsonObject;
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.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
/**
* @Author 。。。源
* @Email apple_dzy@163.com
* @Blog https://www.findmyfun.cn
* @Date 2022/11/14 15:26
* @Version 1.0
*/
@Component
public class RequestLimitInterceptor implements HandlerInterceptor {
private static Logger logger = LoggerFactory.getLogger(RequestLimitInterceptor.class);
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
try {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 获取RequestLimit注解
RequestLimit requestLimit = handlerMethod.getMethodAnnotation(RequestLimit.class);
if (null == requestLimit) {
return true;
}
//限制的时间范围
int seconds = requestLimit.second();
//时间内的 最大次数
int maxCount = requestLimit.maxCount();
String ipAddr = IPUtils.getIpAddr(request);
// 存储key
String key = ipAddr + ":" + request.getContextPath() + ":" + request.getServletPath();
// 已经访问的次数
Integer count = (Integer) redisTemplate.opsForValue().get(key);
if (count == null) {
count = 0;
}
if (count == 0 || count == -1) {
count = 1;
logger.info("来源ip:{} 请求接口:{} 并发数:{}", ipAddr, request.getServletPath(), count);
redisTemplate.opsForValue().set(key, count, seconds, TimeUnit.SECONDS);
return true;
}
if (count < maxCount) {
logger.info("来源ip:{} 请求接口:{} 并发数:{}", ipAddr, request.getServletPath(), count + 1);
redisTemplate.opsForValue().increment(key);
return true;
}
logger.info("来源ip:{} 请求接口:{} 请求过于频繁", ipAddr, request.getServletPath());
returnData(response);
return false;
}
return true;
} catch (Exception e) {
logger.warn("异常请求 请求接口:{} 异常信息:{}", request.getServletPath(), e);
}
return true;
}
public void returnData(HttpServletResponse response) throws IOException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
//这里传提示语可以改成自己项目的返回数据封装的类
response.getWriter().println(new JSONObject(R.error().put("msg", "请求过于频繁请稍后再试")).toJSONString());
}
}
当前页面是本站的「Google AMP」版。查看和发表评论请点击:完整版 »