Spring Boot使用Redisson實(shí)現(xiàn)滑動(dòng)窗口限流的項(xiàng)目實(shí)踐
一、背景
在一些業(yè)務(wù)場(chǎng)景中,我們可能會(huì)提供一些API接口來(lái)給用戶使用,這些接口是不需要認(rèn)證的。比如:忘記密碼,重置密碼的接口,這些接口用戶可以隨意調(diào)用。為了防止這些接口被攻擊者惡意頻繁調(diào)用,消耗我們的系統(tǒng)資源,通常我們會(huì)對(duì)這些接口做限流保護(hù),否則可能會(huì)導(dǎo)致我們的服務(wù)器的宕機(jī)。
其實(shí)限流可以認(rèn)為是服務(wù)降級(jí)的一種,限流通過限制請(qǐng)求的流量以達(dá)到保護(hù)系統(tǒng)的目的。今天我們一起來(lái)討論一下在Redis緩存中怎么通過Redisson實(shí)現(xiàn)滑動(dòng)窗口限流。
二、滑動(dòng)窗口限流算法
1. 原理
我們先來(lái)了解一下滑動(dòng)窗口限流的實(shí)現(xiàn)原理,話不多說,先上圖。
從上圖我們可以得知,有一個(gè)時(shí)間窗口隨著時(shí)間軸在向右移動(dòng),我們可以記錄這個(gè)時(shí)間窗口內(nèi)的請(qǐng)求次數(shù),當(dāng)超過我們?cè)试S的閾值時(shí),則進(jìn)行限流不能繼續(xù)進(jìn)行請(qǐng)求。
2. 具體實(shí)現(xiàn)步驟
主要實(shí)現(xiàn)步驟如下:
- 我們需要定義一個(gè)時(shí)間窗口,例如窗口大小為1分鐘。
- 每次有請(qǐng)求時(shí),使用redis中的zset將這條請(qǐng)求記錄下來(lái)。 充分利用zset這個(gè)數(shù)據(jù)結(jié)果的特點(diǎn),zet一條記錄由兩個(gè)成員(score和member)來(lái)表示,并且會(huì)按照score進(jìn)行排序。方便我們后面進(jìn)行刪除滑動(dòng)后窗口之前的請(qǐng)求記錄,和統(tǒng)計(jì)當(dāng)前窗口內(nèi)請(qǐng)求總數(shù)。
- 通過ZREMRANGEBYSCORE命令,刪除zset中當(dāng)前窗口之前的請(qǐng)求記錄。
- 通過ZCARD命令,統(tǒng)計(jì)當(dāng)前窗口內(nèi)的請(qǐng)求數(shù)量。
所以,我們使用滑動(dòng)窗口的思想是,只保留當(dāng)前時(shí)間窗口類的請(qǐng)求記錄,而丟棄當(dāng)前窗口之外的記錄。當(dāng)下次有請(qǐng)求進(jìn)來(lái)時(shí),我們只需要判斷當(dāng)前窗口內(nèi)的請(qǐng)求是否超過閾值就可以了。未超過則放行,超過則限流。
3. 偽代碼
根據(jù)以上步驟我們寫了一段偽代碼,如果考慮并發(fā)場(chǎng)景則需要考慮使用Lua腳本。
public boolean allowRequest(String requestKey) { // 定義一個(gè)時(shí)間窗口為1分鐘 long windowSize = Duration.ofMinutes(1).toSeconds(); // 定義時(shí)間窗口內(nèi)請(qǐng)求閾值為100 long limit = 100; // 當(dāng)前時(shí)間戳 long currentTime = System.currentTimeMillis(); // 窗口開始時(shí)間為當(dāng)前時(shí)間戳 - 窗口大小 long windowStart = currentTime - windowSize * 1000; // 刪除當(dāng)前窗口開始之前的所有數(shù)據(jù) Jedis.zremrangeByScore(requestKey, "0", String.valueOf(windowStart)); // 計(jì)算當(dāng)前窗口內(nèi)的請(qǐng)求總數(shù) long count = Jedis.zcard(requestKey); if (count < limit) { // 如果允許訪問,則將當(dāng)前請(qǐng)求加到窗口內(nèi) Jedis.zadd(requestKey, currentTime, String.valueOf(currentTime)); return true; } return false; } }
三、怎么使用Redisson進(jìn)行滑動(dòng)窗口限流
在redisson中已經(jīng)為我們實(shí)現(xiàn)好了滑動(dòng)窗口限流,通過redissonClient拿到限流器后,配置好時(shí)間窗口和限流速率就能直接使用了。實(shí)現(xiàn)原理和上面我們的偽代碼是一樣的,只是它將這一部分封裝好了,我們拿到后開箱即用。直接上代碼:
import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import org.redisson.api.RRateLimiter; import org.redisson.api.RateIntervalUnit; import org.redisson.api.RateType; import org.redisson.api.RedissonClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.web.filter.OncePerRequestFilter; public class RateLimiterFilter extends OncePerRequestFilter { private final Logger log = LoggerFactory.getLogger(RateLimiterFilter.class); private static final String RATE_LIMIT_KEY = "rateLimit:yourApiKey"; private static final int MAX_REQUESTS_PER_MINUTE = 10; private final RedissonClient redissonClient; public RateLimiterFilter(RedissonClient redissonClient) { this.redissonClient = redissonClient; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { RRateLimiter rateLimiter = redissonClient.getRateLimiter(RATE_LIMIT_KEY); // rateLimiter.trySetRate就是設(shè)置限流參數(shù),RateType有兩種,OVERALL是全局限流 ,PER_CLIENT是單Client限流(可以認(rèn)為就是單機(jī)限流),這里我們只討論全局模式。 // 而后面三個(gè)參數(shù)的作用就是設(shè)置在多長(zhǎng)時(shí)間窗口內(nèi)(rateInterval+IntervalUnit),許可總量不超過多少(rate) // 上面代碼中我設(shè)置的值就是1分鐘內(nèi)總許可數(shù)不超過10個(gè) rateLimiter.trySetRate(RateType.OVERALL, MAX_REQUESTS_PER_MINUTE, 1, RateIntervalUnit.MINUTES); // 調(diào)用rateLimiter的tryAcquire()或者acquire()方法即可獲取許可 if (!rateLimiter.tryAcquire()) { String path = request.getRequestURI().substring(request.getContextPath().length()); log.error("當(dāng)前請(qǐng)求觸發(fā)限流策略,請(qǐng)求: {} 已經(jīng)被限流.", path); response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value()); response.getWriter().write("Too many requests!"); return; } filterChain.doFilter(request, response); } }
四、滑動(dòng)窗口限流的優(yōu)勢(shì)
滑動(dòng)窗口限流是一種流量控制策略,用于控制在一定時(shí)間內(nèi)的請(qǐng)求頻率。它的主要優(yōu)點(diǎn)是可以在單位時(shí)間內(nèi)平滑的控制流量,而不是簡(jiǎn)單的設(shè)置固定的請(qǐng)求數(shù)或速率。這使得系統(tǒng)可以更靈活的應(yīng)對(duì)突發(fā)流量或峰值流量,而不會(huì)因?yàn)楣潭ㄋ俾实南拗贫速M(fèi)或降低系統(tǒng)性能。
這種限流算法可以在分布式系統(tǒng)、API服務(wù)等各種場(chǎng)景中使用,以確保系統(tǒng)的穩(wěn)定性和可用性,防止過多的請(qǐng)求或惡意請(qǐng)求對(duì)系統(tǒng)造成負(fù)擔(dān)或崩潰。
到此這篇關(guān)于Spring Boot使用Redisson實(shí)現(xiàn)滑動(dòng)窗口限流的項(xiàng)目實(shí)踐的文章就介紹到這了,更多相關(guān)SpringBoot Redisson滑動(dòng)窗口限流內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java動(dòng)態(tài)批量生成logback日志文件的示例
本文主要介紹了Java動(dòng)態(tài)批量生成logback日志文件的示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2025-04-04SpringCloud客戶端報(bào)錯(cuò):- was unable to send&nb
這篇文章主要介紹了SpringCloud客戶端報(bào)錯(cuò):- was unable to send heartbeat!的問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-05-05Java的JDBC中Statement與CallableStatement對(duì)象實(shí)例
這篇文章主要介紹了Java的JDBC中Statement與CallableStatement對(duì)象實(shí)例,JDBC是Java編程中用于操作數(shù)據(jù)庫(kù)的API,需要的朋友可以參考下2015-12-12java 數(shù)據(jù)結(jié)構(gòu)單鏈表的實(shí)現(xiàn)
這篇文章主要介紹了java 數(shù)據(jù)結(jié)構(gòu)單鏈表的實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下2017-07-07NoHttpResponseException問題排查解決記錄分析
這篇文章主要為大家介紹了NoHttpResponseException問題排查解決記錄分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08WebUploader實(shí)現(xiàn)圖片上傳功能
這篇文章主要為大家詳細(xì)介紹了WebUploader實(shí)現(xiàn)圖片上傳功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-03-03RocketMQ中的消費(fèi)模式和消費(fèi)策略詳解
這篇文章主要介紹了RocketMQ中的消費(fèi)模式和消費(fèi)策略詳解,RocketMQ 是基于發(fā)布訂閱模型的消息中間件,所謂的發(fā)布訂閱就是說,consumer 訂閱了 broker 上的某個(gè) topic,當(dāng) producer 發(fā)布消息到 broker 上的該 topic 時(shí),consumer 就能收到該條消息,需要的朋友可以參考下2023-10-10Java實(shí)現(xiàn)提取Word文檔表格數(shù)據(jù)
使用Java實(shí)現(xiàn)Word文檔表格數(shù)據(jù)的提取,可以確保數(shù)據(jù)處理的一致性和準(zhǔn)確性,同時(shí)大大減少所需的時(shí)間和成本,下面我們來(lái)看看具體實(shí)現(xiàn)方法吧2025-01-01