SpingBoot中怎么利用Redis对接口限流
这期内容当中小编将会给大家带来有关SpingBoot中怎么利用Redis对接口限流,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。
实现的思路
使用 Hash 存储接口的限流配置
request_limit_config "/api2" : {"limit": 10, "time": 1, "timeUnit": "SECONDS"}
hash中的key就是请求的uri路径,value是一个对象。通过3个属性,描述限制策略
limit 最多请求次数
time 时间
timeUnit 时间单位
使用普通kv,存储api的请求次数
request_limit:/api 1
处理请求的时候,通过increment对该key进行 +1 操作,如果返回1,则表示是第一次请求,此时设置它的过期时间。为限制策略中定义时间限制信息。再通过命名的返回值,判断是否超出了限制。
increment 指令是线程安全的,不用担心并发的问题。
使用SpringBoot实现
创建SpringBoot工程,添加
spring-boot-starter-data-redis依赖,并且给出正确的配置。
这里不做工程的创建,配置,以及其他额外代码的演示,仅仅给出关键的代码。
RedisKeys
定义两个Key,限流用到的2个Key
public interface RedisKeys { String REQUEST_LIMIT_CONFIG = "request_limit_config"; 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配置到IOC
import 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 { 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); 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; long count = this.objectRedisTemplate.opsForValue().increment(limitKey); if (count == 1) { 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; }}
Controller
一个用于测试的接口类
import java.util.Collections;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController@RequestMapping("/test")public class TestController { @GetMapping public Object test () { return Collections.singletonMap("success", true); }}
WebMvcConfigration
拦截器的配置
import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import io.springboot.jwt.web.interceptor.RequestLimitInterceptor;@Configurationpublic class WebMvcConfiguration implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(this.requestLimitInterceptor()) .addPathPatterns("/test"); } @Bean public RequestLimitInterceptor requestLimitInterceptor() { return new RequestLimitInterceptor(); }}
通过@Test测试,初始化一个限流配置
@Autowiredprivate ObjectRedisTemplate objectRedisTemplate;@Testpublic void test () { // 3秒内,只能请求2次 RequestLimitConfig requestLimitConfig = new RequestLimitConfig(2, 3, TimeUnit.SECONDS); // 限制的uri是 /test this.objectRedisTemplate.opsForHash().put(RedisKeys.REQUEST_LIMIT_CONFIG, "/test", requestLimitConfig);}
使用浏览器演示
最后一些问题
怎么灵活的配置
都写到这个份儿上了,如果熟悉Redis以及客户端,我想提供一个“限流管理”接口的并不是难事儿。
针对指定的用户限流
这里演示的方法是,针对接口的限流。有时候,也有一些特殊的需求,需要“针对不同”的用户来做限流。打个比方。针对A用户,允许有他1分钟请求20次接口,针对B用户,允许他1分钟请求10次接口。 这个其实也简单,只需要修改一下上面的两个限制key,在key中添加用户的唯一标识(例如:ID)
request_limit_config "/api2:{userId}" : {"limit": 10, "time": 1, "timeUnit": "SECONDS"}
request_limit:{userId}:/api 1
在拦截器中获取到用户的ID,加上用户ID进行检索和判断,就可以完成针对用户的限流。
Restful 接口的问题
@GetMapping("/user/{id}") // restful的检索接口,往往把ID信息放在了URI中
这就会导致上面的代码有问题,因为这里采用的是根据URI来完成的限流操作。检索不同ID的用户,会导致URI不同。 解决办法我认为也很简单。那就不要使用URI,可以通过 自定义注解,方式,不同的接口,定义不同的唯一标识。在拦截器中获取到注解,读取到唯一的编码,代替原来的URI,即可。
上述就是小编为大家分享的SpingBoot中怎么利用Redis对接口限流了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注编程网行业资讯频道。
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341