SpringBoot中使用Redis對接口進行限流的實現(xiàn)
一個基于Redis實現(xiàn)的接口限流方案,先說要實現(xiàn)的功能
- 可以限制指定的接口,在一定時間內(nèi),只能被請求N次,超過次數(shù)就返回異常信息
- 可以通過配置文件,或者管理后臺,動態(tài)的修改限流配置
實現(xiàn)的思路
使用 Hash 存儲接口的限流配置
request_limit_config "/api2" : {"limit": 10, "time": 1, "timeUnit": "SECONDS"}
hash中的key就是請求的uri路徑,value是一個對象。通過3個屬性,描述限制策略
- limit 最多請求次數(shù)
- time 時間
- timeUnit 時間單位
使用普通kv,存儲api的請求次數(shù)
request_limit:/api 1
處理請求的時候,通過increment對該key進行 +1 操作,如果返回1,則表示是第一次請求,此時設(shè)置它的過期時間。為限制策略中定義時間限制信息。再通過命名的返回值,判斷是否超出了限制。
increment 指令是線程安全的,不用擔心并發(fā)的問題。
使用SpringBoot實現(xiàn)
創(chuàng)建SpringBoot工程,添加
spring-boot-starter-data-redis依賴,并且給出正確的配置。
這里不做工程的創(chuàng)建,配置,以及其他額外代碼的演示,僅僅給出關(guān)鍵的代碼。
RedisKeys
定義兩個Key,限流用到的2個Key
public interface RedisKeys { /** * api的限制配置,hash key */ String REQUEST_LIMIT_CONFIG = "request_limit_config"; /** * api的請求的次數(shù) */ String REQUEST_LIMIT = "request_limit"; }
ObjectRedisTemplate
為了提高hash value的序列化效率,自定義一個RedisTemplate的實現(xiàn)。使用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; @Configuration public 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; // 最大請求次數(shù) 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
通過攔截器,來完成限流的實現(xiàn)。
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; /** * 當前接口的訪問次數(shù) +1 */ long count = this.objectRedisTemplate.opsForValue().increment(limitKey); if (count == 1) { /** * 第一次請求,設(shè)置key的過期時間 */ this.objectRedisTemplate.expire(limitKey, requestLimitConfig.getTime(), requestLimitConfig.getTimeUnit()); LOGGER.info("設(shè)置過期時間:time={}, timeUnit={}", requestLimitConfig.getTime(), requestLimitConfig.getTimeUnit()); } LOGGER.info("請求限制。limit={}, count={}", requestLimitConfig.getLimit(), count); if (count > requestLimitConfig.getLimit()) { /** * 限定時間內(nèi),請求超出限制,響應(yīng)客戶端錯誤信息。 */ response.setContentType(MediaType.TEXT_PLAIN_VALUE); response.setCharacterEncoding(StandardCharsets.UTF_8.name()); response.getWriter().write("服務(wù)器繁忙,稍后再試"); 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; @Configuration public class WebMvcConfiguration implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(this.requestLimitInterceptor()) .addPathPatterns("/test"); } @Bean public RequestLimitInterceptor requestLimitInterceptor() { return new RequestLimitInterceptor(); } }
通過@Test測試,初始化一個限流配置
@Autowired private ObjectRedisTemplate objectRedisTemplate; @Test public void test () { // 3秒內(nèi),只能請求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中
這就會導(dǎo)致上面的代碼有問題,因為這里采用的是根據(jù)URI來完成的限流操作。檢索不同ID的用戶,會導(dǎo)致URI不同。 解決辦法我認為也很簡單。那就不要使用URI,可以通過 自定義注解,方式,不同的接口,定義不同的唯一標識。在攔截器中獲取到注解,讀取到唯一的編碼,代替原來的URI,即可。
到此這篇關(guān)于SpingBoot中使用Redis對接口進行限流的實現(xiàn)的文章就介紹到這了,更多相關(guān)SpingBoot Redis接口限流內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Springboot+Redis實現(xiàn)API接口防刷限流的項目實踐
- SpringBoot整合Redis并且用Redis實現(xiàn)限流的方法 附Redis解壓包
- 基于SpringBoot+Redis實現(xiàn)一個簡單的限流器
- SpringBoot使用Redis對用戶IP進行接口限流的項目實踐
- SpringBoot使用Redis對用戶IP進行接口限流的示例詳解
- SpringBoot Redis用注釋實現(xiàn)接口限流詳解
- 使用SpringBoot?+?Redis?實現(xiàn)接口限流的方式
- springboot+redis 實現(xiàn)分布式限流令牌桶的示例代碼
- SpringBoot整合redis實現(xiàn)計數(shù)器限流的示例
相關(guān)文章
Spring boot 總結(jié)之跨域處理cors的方法
本篇文章主要介紹了Spring boot 總結(jié)之跨域處理cors的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-02-02Spring?Boot?中的?@HystrixCommand?注解原理及使用方法
通過使用 @HystrixCommand 注解,我們可以輕松地實現(xiàn)對方法的隔離和監(jiān)控,從而提高系統(tǒng)的可靠性和穩(wěn)定性,本文介紹了Spring Boot 中的@HystrixCommand注解是什么,其原理以及如何使用,感興趣的朋友跟隨小編一起看看吧2023-07-07Java實現(xiàn)warcraft?java版游戲的示例代碼
致敬經(jīng)典的warcraft,《warcraft?java版》是一款即時戰(zhàn)略題材單機游戲,采用魔獸原味風格和機制。本文將用java語言實現(xiàn),采用了swing技術(shù)進行了界面化處理,感興趣的可以了解一下2022-09-09使用MyBatis-Plus實現(xiàn)聯(lián)表查詢分頁的示例代碼
本文主要講述了如何在SpringBoot項目中使用MyBatis-Plus的分頁插件,通過這個示例,可以學(xué)會如何利用MyBatis-Plus進行高效的分頁查詢,感興趣的可以了解一下2024-10-10