Spring Boot使用Redisson實(shí)現(xiàn)滑動窗口限流的項(xiàng)目實(shí)踐
一、背景
在一些業(yè)務(wù)場景中,我們可能會提供一些API接口來給用戶使用,這些接口是不需要認(rèn)證的。比如:忘記密碼,重置密碼的接口,這些接口用戶可以隨意調(diào)用。為了防止這些接口被攻擊者惡意頻繁調(diào)用,消耗我們的系統(tǒng)資源,通常我們會對這些接口做限流保護(hù),否則可能會導(dǎo)致我們的服務(wù)器的宕機(jī)。
其實(shí)限流可以認(rèn)為是服務(wù)降級的一種,限流通過限制請求的流量以達(dá)到保護(hù)系統(tǒng)的目的。今天我們一起來討論一下在Redis緩存中怎么通過Redisson實(shí)現(xiàn)滑動窗口限流。
二、滑動窗口限流算法
1. 原理
我們先來了解一下滑動窗口限流的實(shí)現(xiàn)原理,話不多說,先上圖。

從上圖我們可以得知,有一個(gè)時(shí)間窗口隨著時(shí)間軸在向右移動,我們可以記錄這個(gè)時(shí)間窗口內(nèi)的請求次數(shù),當(dāng)超過我們允許的閾值時(shí),則進(jìn)行限流不能繼續(xù)進(jìn)行請求。
2. 具體實(shí)現(xiàn)步驟
主要實(shí)現(xiàn)步驟如下:
- 我們需要定義一個(gè)時(shí)間窗口,例如窗口大小為1分鐘。
- 每次有請求時(shí),使用redis中的zset將這條請求記錄下來。 充分利用zset這個(gè)數(shù)據(jù)結(jié)果的特點(diǎn),zet一條記錄由兩個(gè)成員(score和member)來表示,并且會按照score進(jìn)行排序。方便我們后面進(jìn)行刪除滑動后窗口之前的請求記錄,和統(tǒng)計(jì)當(dāng)前窗口內(nèi)請求總數(shù)。
- 通過ZREMRANGEBYSCORE命令,刪除zset中當(dāng)前窗口之前的請求記錄。
- 通過ZCARD命令,統(tǒng)計(jì)當(dāng)前窗口內(nèi)的請求數(shù)量。
所以,我們使用滑動窗口的思想是,只保留當(dāng)前時(shí)間窗口類的請求記錄,而丟棄當(dāng)前窗口之外的記錄。當(dāng)下次有請求進(jìn)來時(shí),我們只需要判斷當(dāng)前窗口內(nèi)的請求是否超過閾值就可以了。未超過則放行,超過則限流。
3. 偽代碼
根據(jù)以上步驟我們寫了一段偽代碼,如果考慮并發(fā)場景則需要考慮使用Lua腳本。
public boolean allowRequest(String requestKey) {
// 定義一個(gè)時(shí)間窗口為1分鐘
long windowSize = Duration.ofMinutes(1).toSeconds();
// 定義時(shí)間窗口內(nèi)請求閾值為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)的請求總數(shù)
long count = Jedis.zcard(requestKey);
if (count < limit) {
// 如果允許訪問,則將當(dāng)前請求加到窗口內(nèi)
Jedis.zadd(requestKey, currentTime, String.valueOf(currentTime));
return true;
}
return false;
}
}三、怎么使用Redisson進(jìn)行滑動窗口限流
在redisson中已經(jīng)為我們實(shí)現(xiàn)好了滑動窗口限流,通過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è)置在多長時(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)前請求觸發(fā)限流策略,請求: {} 已經(jīng)被限流.", path);
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.getWriter().write("Too many requests!");
return;
}
filterChain.doFilter(request, response);
}
}
四、滑動窗口限流的優(yōu)勢
滑動窗口限流是一種流量控制策略,用于控制在一定時(shí)間內(nèi)的請求頻率。它的主要優(yōu)點(diǎn)是可以在單位時(shí)間內(nèi)平滑的控制流量,而不是簡單的設(shè)置固定的請求數(shù)或速率。這使得系統(tǒng)可以更靈活的應(yīng)對突發(fā)流量或峰值流量,而不會因?yàn)楣潭ㄋ俾实南拗贫速M(fèi)或降低系統(tǒng)性能。
這種限流算法可以在分布式系統(tǒng)、API服務(wù)等各種場景中使用,以確保系統(tǒng)的穩(wěn)定性和可用性,防止過多的請求或惡意請求對系統(tǒng)造成負(fù)擔(dān)或崩潰。
到此這篇關(guān)于Spring Boot使用Redisson實(shí)現(xiàn)滑動窗口限流的項(xiàng)目實(shí)踐的文章就介紹到這了,更多相關(guān)SpringBoot Redisson滑動窗口限流內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實(shí)現(xiàn)轉(zhuǎn)換圖片格式的示例代碼
在日常的軟件開發(fā)中,處理圖像文件是一項(xiàng)常見任務(wù),這篇文章將實(shí)現(xiàn)實(shí)現(xiàn)一個(gè)簡單的Java程序,用于將一種圖片格式轉(zhuǎn)換為另一種格式,需要的可以了解下2025-02-02
java后臺實(shí)現(xiàn)支付寶對賬功能的示例代碼
這篇文章主要介紹了java后臺實(shí)現(xiàn)支付寶對賬功能的示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-08-08
mybatis+springboot中使用mysql的實(shí)例
在軟件開發(fā)中,數(shù)據(jù)庫的引入是必不可少的,這里來展現(xiàn)一下通過mybatis框架在springboot中使用mysql,具有一定的參考價(jià)值,感興趣的可以了解一下2021-07-07
SpringBoot中JPA實(shí)現(xiàn)Sort排序的三種方式小結(jié)
這篇文章主要介紹了SpringBoot中JPA實(shí)現(xiàn)Sort排序的三種方式小結(jié),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11

