一个基于redis实现的接口限流方案,先说要实现的功能
- 可以限制指定的接口,在一定时间内,只能被请求N次,超过次数就返回异常信息
- 可以通过配置文件,或者管理后台,动态的修改限流配置
request_limit_config"/api2" : {"limit": 10, "time": 1, "timeUnit": "SECONDS"}
hash中的key就是请求的uri路径,value是一个对象 。通过3个属性,描述限制策略- limit 最多请求次数
- time 时间
- timeUnit 时间单位
request_limit:/api1
处理请求的时候,通过increment对该key进行 +1 操作,如果返回1,则表示是第一次请求,此时设置它的过期时间 。为限制策略中定义时间限制信息 。再通过命名的返回值,判断是否超出了限制 。increment 指令是线程安全的,不用担心并发的问题 。使用SpringBoot实现创建SpringBoot工程,添加spring-boot-starter-data-redis依赖,并且给出正确的配置 。
这里不做工程的创建,配置,以及其他额外代码的演示,仅仅给出关键的代码 。RedisKeys定义两个Key,限流用到的2个Key
public interface RedisKeys { /*** api的限制配置,hash key*/ String REQUEST_LIMIT_CONFIG = "request_limit_config";/*** api的请求的次数*/ String REQUEST_LIMIT = "request_limit";}
ObjectRedisTemplate为了提高hash value的序列化效率,自定义一个RedisTemplate的实现 。使用jdk的序列化,而不是json 。import org.springframework.data.redis.core.RedisTemplate;public class ObjectRedisTemplate extends RedisTemplate<String, Object> { }
RedisConfigration把自定义的ObjectRedisTemplate配置到IOCimport org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.connection.RedisConnectionFactory;import org.springframework.data.redis.serializer.RedisSerializer;import io.springboot.jwt.redis.ObjectRedisTemplate;@Configurationpublic class RedisConfiguration { @Bean public ObjectRedisTemplate objectRedisTemplate(@Autowired RedisConnectionFactory redisConnectionFactory) {ObjectRedisTemplate objectRedisTemplate = new ObjectRedisTemplate();objectRedisTemplate.setConnectionFactory(redisConnectionFactory);objectRedisTemplate.setKeySerializer(RedisSerializer.string());objectRedisTemplate.setValueSerializer(RedisSerializer.JAVA());// hash的key使用String序列化objectRedisTemplate.setHashKeySerializer(RedisSerializer.string());// hash的value使用jdk的序列化objectRedisTemplate.setHashValueSerializer(RedisSerializer.java());return objectRedisTemplate; }}
RequestLimitConfig用于描述限制策略的对象 。import java.io.Serializable;import java.util.concurrent.TimeUnit;public class RequestLimitConfig implements Serializable { /****/ private static final long serialVersionUID = 1101875328323558092L; // 最大请求次数 private long limit; // 时间 private long time; // 时间单位 private TimeUnit timeUnit; public RequestLimitConfig() {super(); } public RequestLimitConfig(long limit, long time, TimeUnit timeUnit) {super();this.limit = limit;this.time = time;this.timeUnit = timeUnit; } public long getLimit() {return limit; } public void setLimit(long limit) {this.limit = limit; } public long getTime() {return time; } public void setTime(long time) {this.time = time; } public TimeUnit getTimeUnit() {return timeUnit; } public void setTimeUnit(TimeUnit timeUnit) {this.timeUnit = timeUnit; } @Override public String toString() {return "RequestLimitConfig [limit=" + limit + ", time=" + time + ", timeUnit=" + timeUnit + "]"; }}
RequestLimitInterceptor通过拦截器,来完成限流的实现 。import java.nio.charset.StandardCharsets;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.MediaType;import org.springframework.util.StringUtils;import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;import io.springboot.jwt.redis.ObjectRedisTemplate;import io.springboot.jwt.redis.RedisKeys;import io.springboot.jwt.web.RequestLimitConfig;public class RequestLimitInterceptor extends HandlerInterceptorAdapter {private static final Logger LOGGER = LoggerFactory.getLogger(RequestLimitInterceptor.class);@Autowired private ObjectRedisTemplate objectRedisTemplate;@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {/*** 获取到请求的URI*/String contentPath = request.getContextPath();String uri = request.getRequestURI().toString();if (!StringUtils.isEmpty(contentPath) && !contentPath.equals("/")) {uri =uri.substring(uri.indexOf(contentPath) + contentPath.length());}LOGGER.info("uri={}",uri);/*** 尝试从hash中读取得到当前接口的限流配置*/RequestLimitConfig requestLimitConfig = (RequestLimitConfig) this.objectRedisTemplate.opsForHash().get(RedisKeys.REQUEST_LIMIT_CONFIG, uri);if (requestLimitConfig == null) {LOGGER.info("该uri={}没有限流配置", uri);return true;}String limitKey = RedisKeys.REQUEST_LIMIT + ":" + uri;/*** 当前接口的访问次数 +1*/long count = this.objectRedisTemplate.opsForValue().increment(limitKey);if (count == 1) {/*** 第一次请求,设置key的过期时间*/this.objectRedisTemplate.expire(limitKey, requestLimitConfig.getTime(), requestLimitConfig.getTimeUnit());LOGGER.info("设置过期时间:time={}, timeUnit={}", requestLimitConfig.getTime(), requestLimitConfig.getTimeUnit());}LOGGER.info("请求限制 。limit={}, count={}", requestLimitConfig.getLimit(), count);if (count > requestLimitConfig.getLimit()) {/*** 限定时间内,请求超出限制,响应客户端错误信息 。*/response.setContentType(MediaType.TEXT_PLAIN_VALUE);response.setCharacterEncoding(StandardCharsets.UTF_8.name());response.getWriter().write("服务器繁忙,稍后再试");return false;}return true; }}
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Vue 中如何从插槽中发出数据
- 受教育者在哪些方面依法享有平等权利?
- Apollo配置中心管理后台的详解
- 第13双眼睛恐怖歌曲完整版 第13双眼睛歌曲的恐怖在哪里
- 人类对火星的探索进展到了什么程度 人类探索火星的步伐正在加快
- 木星是气态巨行星,也是太阳系中最大的 矮行星是围绕大行星运动的天体吗
- HEX文件格式介绍
- 费米悖论提出,银河系大约有2500亿颗恒星 费米悖论之所以被称为悖论,是因为对外星人?
- 黑茶加陈皮的功效,香苏茶具有解毒祛暑理气化淤温胃和中的功效
- 黑茶存在的误区有哪些,黑茶能煮多长时间