目录
5.2 实现 WebMvcConfigurer 添加自定义拦截器
一 引入依赖
<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.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
二 配置文件
2.1 Redis 配置
application.yml 配置:
spring:
redis:
host: 127.0.0.1
database: 0
port: 6379
timeout: 1000
jedis:
pool:
max-active: 256
max-idle: 8
min-idle: 1
max-wait: 100
2.2 Lua 脚本文件
rateLimiter.lua 配置:
local key = "rate.limit:" .. KEYS[1] --限流KEY
local limit = tonumber(ARGV[1]) --限流大小
local current = tonumber(redis.call('get', key) or "0")
if current + 1 > limit then --如果超出限流大小
return 0
else --请求数+1,并设置2秒过期
redis.call("INCRBY", key,"1")
redis.call("expire", key,"2")
return current + 1
end
三 Bean 配置
package com.sb.springbootdistributedrateLimiter.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.scripting.support.ResourceScriptSource;
import java.io.Serializable;
@Configuration
public class CommentConfig {
/**
* 读取限流脚本
*
* @return
*/
@Bean
public DefaultRedisScript<Number> redisluaScript() {
DefaultRedisScript<Number> redisScript = new DefaultRedisScript<>();
redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("rateLimiter.lua")));
redisScript.setResultType(Number.class);
return redisScript;
}
/**
* RedisTemplate
*
* @return
*/
@Bean
public RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Serializable> template = new RedisTemplate<String, Serializable>();
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
四 限流注解
package com.sb.springbootdistributedrateLimiter.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributedRateLimiterAnnotation {
/**
* 限流唯一标示
*
* @return
*/
String key() default "";
/**
* 限流时间
*
* @return
*/
int time();
/**
* 限流次数
*
* @return
*/
int count();
}
五 拦截器限流
5.1 自定义拦截器,在拦截器中实现限流
DistributedRateLimiterInterceptor 类:
package com.sb.springbootdistributedrateLimiter.interceptor;
import com.sb.springbootdistributedrateLimiter.annotation.DistributedRateLimiterAnnotation;
import com.sb.springbootdistributedrateLimiter.utils.IpUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.Serializable;
import java.util.Collections;
import java.util.List;
@Slf4j
@Component
public class DistributedRateLimiterInterceptor implements HandlerInterceptor {
@Autowired
private RedisTemplate<String, Serializable> redisTemplate;
@Autowired
private DefaultRedisScript<Number> redisluaScript;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
DistributedRateLimiterAnnotation distributedRateLimiterAnnotation = handlerMethod.getMethod().getAnnotation(DistributedRateLimiterAnnotation.class);
if(distributedRateLimiterAnnotation == null) {
return true;
}
String ip = IpUtil.getIp(request);
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(ip).append("-")
.append(request.getRequestURL()).append("-")
.append(distributedRateLimiterAnnotation.key());
List<String> keys = Collections.singletonList(stringBuffer.toString());
Number number = redisTemplate.execute(redisluaScript, keys, distributedRateLimiterAnnotation.count(), distributedRateLimiterAnnotation.time());
if (number != null && number.intValue() != 0 && number.intValue() <= distributedRateLimiterAnnotation.count()) {
log.info("第{}次请求成功", number.toString());
return true;
} else {
log.info("限流中");
return false;
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
IpUtil 类:
package com.sb.springbootdistributedrateLimiter.utils;
import org.apache.commons.lang3.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class IpUtil {
/**
* 获取用户真实IP地址,不使用request.getRemoteAddr();的原因是有可能用户使用了代理软件方式避免真实IP地址,
* <p>
* 可是,如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值,究竟哪个才是真正的用户端的真实IP呢? 答案是取X-Forwarded-For中第一个非unknown的有效IP字符串。
* <p>
* 如:X-Forwarded-For:192.168.1.110, 192.168.1.120, 192.168.1.130, 192.168.1.100
* <p>
* 用户真实IP为: 192.168.1.110
*
* @param request
* @return
*/
public static String getIp(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (StringUtils.isNotEmpty(ip)) {
ip = ip.replaceAll(" ", "").replaceAll("unknown", "");
if (ip.indexOf(",") > 0) {
ip = ip.substring(0, ip.indexOf(","));
}
}
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.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
if (ip != null && isIP(ip)) {
return ip;
}
return null;
}
public static boolean isIP(String addr) {
if (addr.length() < 7 || addr.length() > 15 || "".equals(addr)) {
return false;
}
/**
* 判断IP格式和范围
*/
String rexp = "^(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])$";
Pattern pat = Pattern.compile(rexp);
Matcher mat = pat.matcher(addr);
boolean ipAddress = mat.find();
return ipAddress;
}
}
5.2 实现 WebMvcConfigurer 添加自定义拦截器
package com.sb.springbootdistributedrateLimiter.config;
import com.sb.springbootdistributedrateLimiter.interceptor.DistributedRateLimiterInterceptor;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;
@SpringBootConfiguration
public class WebConfiguration implements WebMvcConfigurer {
@Resource
private DistributedRateLimiterInterceptor distributedRateLimiterInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(distributedRateLimiterInterceptor).addPathPatterns("/**");
}
}
六 controller 类实现
package com.sb.springbootdistributedrateLimiter.controller;
import com.sb.springbootdistributedrateLimiter.annotation.DistributedRateLimiterAnnotation;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api")
public class RateLimiterController {
// 10 秒中,可以访问10次
@DistributedRateLimiterAnnotation(key = "test", time = 10, count = 10)
@RequestMapping(value = "/test", method = RequestMethod.GET)
public void rateLimiterTest() throws Exception {
//业务逻辑;
}
}
七 限流测试
通过 jmeter 发送 20 QPS 请求接口得到如下结果:
八 参考文档
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/9549.html