基于SpringBoot+Redis實(shí)現(xiàn)一個(gè)簡單的限流器
1.基礎(chǔ)介紹
1.1. 限流場景
假設(shè)我們有一個(gè)API接口,需要限制每個(gè)用戶在一段時(shí)間內(nèi)的請求頻率。比如每秒只允許請求100次等等的業(yè)務(wù)需求。
1.2. 實(shí)現(xiàn)限流邏輯:
使用Redis的計(jì)數(shù)器功能可以實(shí)現(xiàn)基于時(shí)間窗口的限流算法。通過在Redis中存儲(chǔ)請求計(jì)數(shù)器和過期時(shí)間,可以控制單位時(shí)間內(nèi)的請求頻率。在需要進(jìn)行限流的接口或方法中,使用Redis的原子操作(如INCR和EXPIRE)來增加計(jì)數(shù)器并設(shè)置過期時(shí)間。
在每個(gè)請求到達(dá)時(shí),檢查計(jì)數(shù)器的值是否超過設(shè)定的閾值,如果超過則拒絕請求,否則允許請求繼續(xù)執(zhí)行。
2.步驟
2.1. 引入依賴
<dependencies> <!-- Spring Data Redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> </dependencies>
2.2. 配置文件
# Redis連接配置 spring.redis.host=127.0.0.1 spring.redis.port=6379 spring.redis.password=your_password spring.redis.database=0 # Redis連接池配置 spring.redis.jedis.pool.max-active=50 spring.redis.jedis.pool.max-idle=10 spring.redis.jedis.pool.min-idle=5 spring.redis.jedis.pool.max-wait=-1
在上面的配置中,您可以根據(jù)實(shí)際情況修改以下屬性:
- spring.redis.host:Redis服務(wù)器的主機(jī)名或IP地址。
- spring.redis.port:Redis服務(wù)器的端口號(hào)。
- spring.redis.password:Redis服務(wù)器的密碼(如果有的話)。
- spring.redis.database:Redis數(shù)據(jù)庫的索引,默認(rèn)為0。
另外,您還可以配置Redis連接池的屬性,以控制連接池的行為。在示例配置中,設(shè)置了以下連接池屬性:
- spring.redis.jedis.pool.max-active:連接池中的最大活動(dòng)連接數(shù)。
- spring.redis.jedis.pool.max-idle:連接池中的最大空閑連接數(shù)。
- spring.redis.jedis.pool.min-idle:連接池中的最小空閑連接數(shù)。
- spring.redis.jedis.pool.max-wait:從連接池獲取連接的最大等待時(shí)間(毫秒),-1表示無限等待。
如果 使用的是YAML格式的配置文件(application.yml
),可以將上述配置轉(zhuǎn)換為相應(yīng)的格式:
spring: redis: host: 127.0.0.1 port: 6379 password: your_password database: 0 redis.jedis.pool: max-active: 50 max-idle: 10 min-idle: 5 max-wait: -1
請根據(jù)您的實(shí)際Redis服務(wù)器配置進(jìn)行調(diào)整,并根據(jù)需要添加其他相關(guān)配置,如超時(shí)設(shè)置、SSL配置等。
2.3. 核心源碼
- 實(shí)現(xiàn)請求限流過濾器:
創(chuàng)建一個(gè)實(shí)現(xiàn)javax.servlet.Filter接口的請求限流過濾器。在過濾器中,使用Redis的計(jì)數(shù)器功能來實(shí)現(xiàn)請求限流邏輯。
示例中,RequestLimitFilter是一個(gè)實(shí)現(xiàn)了javax.servlet.Filter接口的請求限流過濾器。它使用Redis的計(jì)數(shù)器功能來實(shí)現(xiàn)請求限流邏輯。每個(gè)請求到達(dá)時(shí),根據(jù)客戶端的IP地址作為Redis的鍵,增加計(jì)數(shù)器的值并設(shè)置過期時(shí)間為指定的時(shí)間窗口。如果計(jì)數(shù)器超過了設(shè)定的閾值(這里是100),則返回HTTP 429 Too Many Requests響應(yīng)
示例中使用的是RedisTemplate<String, String>
來操作Redis, 可以根據(jù)需要調(diào)整為適合您的數(shù)據(jù)類型和操作方式的RedisTemplate。
@Component public class RequestLimitFilter implements Filter { @Autowired private RedisTemplate<String, String> redisTemplate; private static final String REQUEST_LIMIT_PREFIX = "requestLimit:"; private static final long REQUEST_LIMIT = 100; // 請求限制數(shù)量 private static final long TIME_WINDOW = 60; // 時(shí)間窗口(單位:秒) @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; String ipAddress = getClientIpAddress(httpRequest); String key = REQUEST_LIMIT_PREFIX + ipAddress; Long counter = redisTemplate.opsForValue().increment(key, 1); if (counter == 1) { redisTemplate.expire(key, TIME_WINDOW, TimeUnit.SECONDS); } if (counter > REQUEST_LIMIT) { HttpServletResponse httpResponse = (HttpServletResponse) response; httpResponse.setStatus(HttpStatus.TOO_MANY_REQUESTS.value()); httpResponse.getWriter().write("請求頻率超過限制,請稍后再試!"); return; } chain.doFilter(request, response); } private String getClientIpAddress(HttpServletRequest request) { String ipAddress = request.getHeader("X-Forwarded-For"); if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("Proxy-Client-IP"); } if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("WL-Proxy-Client-IP"); } if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getRemoteAddr(); } return ipAddress; } }
優(yōu)化后
public class RequestLimitFilter implements Filter { @Autowired private RedisTemplate<String, String> redisTemplate; private static final String REQUEST_LIMIT_PREFIX = "requestLimit:"; private static final long REQUEST_LIMIT = 100; // 請求限制數(shù)量 private static final long TIME_WINDOW = 60; // 時(shí)間窗口(單位:秒) @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; String ipAddress = getClientIpAddress(httpRequest); String key = REQUEST_LIMIT_PREFIX + ipAddress; Long counter = redisTemplate.opsForValue().increment(key, 1); if (counter == 1) { redisTemplate.expire(key, TIME_WINDOW, TimeUnit.SECONDS); } if (counter > REQUEST_LIMIT) { HttpServletResponse httpResponse = (HttpServletResponse) response; httpResponse.setStatus(HttpStatus.TOO_MANY_REQUESTS.value()); try (PrintWriter writer = httpResponse.getWriter()) { writer.write("請求頻率超過限制,請稍后再試!"); } return; } chain.doFilter(request, response); } private String getClientIpAddress(HttpServletRequest request) { ... return ipAddress; } }
再優(yōu)化一下加入布隆過濾器
使用布隆過濾器減少對(duì)Redis的訪問:布隆過濾器是一種高效的概率數(shù)據(jù)結(jié)構(gòu),可以用于快速判斷元素是否存在于集合中。在限制請求頻率時(shí),可以使用布隆過濾器來減少對(duì)Redis的訪問。只有在布隆過濾器判斷請求不是重復(fù)請求時(shí),才進(jìn)行Redis操作。
public class RequestLimitFilter implements Filter { @Autowired private RedisTemplate<String, String> redisTemplate; private static final String REQUEST_LIMIT_PREFIX = "requestLimit:"; private static final long REQUEST_LIMIT = 100; // 請求限制數(shù)量 private static final long TIME_WINDOW = 60; // 時(shí)間窗口(單位:秒) private BloomFilter<String> bloomFilter; @Override public void init(FilterConfig filterConfig) throws ServletException { // 初始化布隆過濾器 int expectedInsertions = 1000; // 預(yù)期插入數(shù)量 double falsePositiveProbability = 0.01; // 誤判率 bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), expectedInsertions, falsePositiveProbability); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; String ipAddress = getClientIpAddress(httpRequest); if (bloomFilter.mightContain(ipAddress)) { // 布隆過濾器判斷可能是重復(fù)請求,直接放行 chain.doFilter(request, response); return; } String key = REQUEST_LIMIT_PREFIX + ipAddress; Long counter; boolean isNewKey = false; try { counter = redisTemplate.opsForValue().increment(key, 1); if (counter == 1) { redisTemplate.expire(key, TIME_WINDOW, TimeUnit.SECONDS); isNewKey = true; } } catch (Exception e) { // 處理Redis操作異常 // 可以選擇記錄日志或采取適當(dāng)?shù)奶幚泶胧? e.printStackTrace(); chain.doFilter(request, response); return; } if (counter > REQUEST_LIMIT) { if (isNewKey) { // 刪除新創(chuàng)建的鍵,避免無限增長 redisTemplate.delete(key); } HttpServletResponse httpResponse = (HttpServletResponse) response; httpResponse.setStatus(HttpStatus.TOO_MANY_REQUESTS.value()); try (PrintWriter writer = httpResponse.getWriter()) { writer.write("請求頻率超過限制,請稍后再試!"); } return; } bloomFilter.put(ipAddress); // 將IP地址添加到布隆過濾器 chain.doFilter(request, response); } @Override public void destroy() { // 清理資源,如關(guān)閉Redis連接等 } private String getClientIpAddress(HttpServletRequest request) { // 獲取客戶端IP地址的邏輯 // ... } }
在上述代碼中,我們引入了布隆過濾器來減少對(duì)Redis的訪問。如果布隆過濾器判斷請求可能是重復(fù)請求,則直接放行,無需進(jìn)行Redis操作。同時(shí),我們還添加了對(duì)Redis操作異常的處理,并在限流超過閾值時(shí)刪除新創(chuàng)建的鍵,以避免無限增長。請根據(jù)實(shí)際情況進(jìn)行適當(dāng)調(diào)整和完善。
- 注冊過濾器:
在Spring Boot應(yīng)用程序的配置類中注冊過濾器,以便它能夠在請求處理過程中生效。
@Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private RequestLimitFilter requestLimitFilter; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(requestLimitFilter); } }
通過將過濾器添加到addInterceptors
方法中,它將被注冊為Spring Boot應(yīng)用程序的全局過濾器,并在請求到達(dá)時(shí)執(zhí)行限流邏輯。
3.總結(jié)
其實(shí)上面我們寫完的還是有問題的
- 如果系統(tǒng)部署在多個(gè)節(jié)點(diǎn)上,可以考慮使用分布式限流算法,如令牌桶算法或漏桶算法。這些算法可以在分布式環(huán)境中平衡請求的處理,并保證全局的請求限制。
- 將請求限流的參數(shù),如請求限制數(shù)量和時(shí)間窗口,配置為可動(dòng)態(tài)調(diào)整的參數(shù)。可以使用注解或配置文件來管理這些參數(shù),以便在運(yùn)行時(shí)進(jìn)行調(diào)整,而無需重新編譯代碼。
到此這篇關(guān)于基于SpringBoot+Redis實(shí)現(xiàn)一個(gè)簡單的限流器的文章就介紹到這了,更多相關(guān)SpringBoot+Redis實(shí)現(xiàn)限流器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Springboot+Redis實(shí)現(xiàn)API接口防刷限流的項(xiàng)目實(shí)踐
- SpringBoot整合Redis并且用Redis實(shí)現(xiàn)限流的方法 附Redis解壓包
- SpringBoot使用Redis對(duì)用戶IP進(jìn)行接口限流的項(xiàng)目實(shí)踐
- SpringBoot使用Redis對(duì)用戶IP進(jìn)行接口限流的示例詳解
- SpringBoot Redis用注釋實(shí)現(xiàn)接口限流詳解
- 使用SpringBoot?+?Redis?實(shí)現(xiàn)接口限流的方式
- SpringBoot中使用Redis對(duì)接口進(jìn)行限流的實(shí)現(xiàn)
- springboot+redis 實(shí)現(xiàn)分布式限流令牌桶的示例代碼
- SpringBoot整合redis實(shí)現(xiàn)計(jì)數(shù)器限流的示例
相關(guān)文章
idea中解決maven包沖突的問題(maven helper)
這篇文章主要介紹了idea中解決maven包沖突的問題(maven helper),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-12-12SpringBoot?AOP統(tǒng)一處理Web請求日志的示例代碼
springboot有很多方法處理日志,例如攔截器,aop切面,service中代碼記錄等,下面這篇文章主要給大家介紹了關(guān)于SpringBoot?AOP統(tǒng)一處理Web請求日志的相關(guān)資料,需要的朋友可以參考下2023-02-02java LRU(Least Recently Used )詳解及實(shí)例代碼
這篇文章主要介紹了java LRU(Least Recently Used )詳解及實(shí)例代碼的相關(guān)資料,Java里面實(shí)現(xiàn)LRU緩存通常有兩種選擇,一種是使用LinkedHashMap,一種是自己設(shè)計(jì)數(shù)據(jù)結(jié)構(gòu),使用鏈表+HashMap,需要的朋友可以參考下2016-11-11解決Mybatis中mapper.xml文件update,delete及insert返回值問題
這篇文章主要介紹了解決Mybatis中mapper.xml文件update,delete及insert返回值問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-11-11java8中定時(shí)任務(wù)最佳實(shí)現(xiàn)方式(實(shí)現(xiàn)原理)
這篇文章主要介紹了java8中定時(shí)任務(wù)最佳實(shí)現(xiàn)方式,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2024-12-12