基于SpringBoot+Redis實(shí)現(xiàn)一個(gè)簡(jiǎn)單的限流器
1.基礎(chǔ)介紹
1.1. 限流場(chǎng)景
假設(shè)我們有一個(gè)API接口,需要限制每個(gè)用戶(hù)在一段時(shí)間內(nèi)的請(qǐng)求頻率。比如每秒只允許請(qǐng)求100次等等的業(yè)務(wù)需求。
1.2. 實(shí)現(xiàn)限流邏輯:
使用Redis的計(jì)數(shù)器功能可以實(shí)現(xiàn)基于時(shí)間窗口的限流算法。通過(guò)在Redis中存儲(chǔ)請(qǐng)求計(jì)數(shù)器和過(guò)期時(shí)間,可以控制單位時(shí)間內(nèi)的請(qǐng)求頻率。在需要進(jìn)行限流的接口或方法中,使用Redis的原子操作(如INCR和EXPIRE)來(lái)增加計(jì)數(shù)器并設(shè)置過(guò)期時(shí)間。
在每個(gè)請(qǐng)求到達(dá)時(shí),檢查計(jì)數(shù)器的值是否超過(guò)設(shè)定的閾值,如果超過(guò)則拒絕請(qǐng)求,否則允許請(qǐng)求繼續(xù)執(zhí)行。
2.步驟
2.1. 引入依賴(lài)
<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ù)器的密碼(如果有的話(huà))。
- spring.redis.database:Redis數(shù)據(jù)庫(kù)的索引,默認(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表示無(wú)限等待。
如果 使用的是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請(qǐng)根據(jù)您的實(shí)際Redis服務(wù)器配置進(jìn)行調(diào)整,并根據(jù)需要添加其他相關(guān)配置,如超時(shí)設(shè)置、SSL配置等。
2.3. 核心源碼
- 實(shí)現(xiàn)請(qǐng)求限流過(guò)濾器:
創(chuàng)建一個(gè)實(shí)現(xiàn)javax.servlet.Filter接口的請(qǐng)求限流過(guò)濾器。在過(guò)濾器中,使用Redis的計(jì)數(shù)器功能來(lái)實(shí)現(xiàn)請(qǐng)求限流邏輯。
示例中,RequestLimitFilter是一個(gè)實(shí)現(xiàn)了javax.servlet.Filter接口的請(qǐng)求限流過(guò)濾器。它使用Redis的計(jì)數(shù)器功能來(lái)實(shí)現(xiàn)請(qǐng)求限流邏輯。每個(gè)請(qǐng)求到達(dá)時(shí),根據(jù)客戶(hù)端的IP地址作為Redis的鍵,增加計(jì)數(shù)器的值并設(shè)置過(guò)期時(shí)間為指定的時(shí)間窗口。如果計(jì)數(shù)器超過(guò)了設(shè)定的閾值(這里是100),則返回HTTP 429 Too Many Requests響應(yīng)
示例中使用的是RedisTemplate<String, String>來(lái)操作Redis, 可以根據(jù)需要調(diào)整為適合您的數(shù)據(jù)類(lèi)型和操作方式的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; // 請(qǐng)求限制數(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("請(qǐng)求頻率超過(guò)限制,請(qǐng)稍后再試!");
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; // 請(qǐng)求限制數(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("請(qǐng)求頻率超過(guò)限制,請(qǐng)稍后再試!");
}
return;
}
chain.doFilter(request, response);
}
private String getClientIpAddress(HttpServletRequest request) {
...
return ipAddress;
}
}再優(yōu)化一下加入布隆過(guò)濾器
使用布隆過(guò)濾器減少對(duì)Redis的訪問(wèn):布隆過(guò)濾器是一種高效的概率數(shù)據(jù)結(jié)構(gòu),可以用于快速判斷元素是否存在于集合中。在限制請(qǐng)求頻率時(shí),可以使用布隆過(guò)濾器來(lái)減少對(duì)Redis的訪問(wèn)。只有在布隆過(guò)濾器判斷請(qǐng)求不是重復(fù)請(qǐng)求時(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; // 請(qǐng)求限制數(shù)量
private static final long TIME_WINDOW = 60; // 時(shí)間窗口(單位:秒)
private BloomFilter<String> bloomFilter;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化布隆過(guò)濾器
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)) {
// 布隆過(guò)濾器判斷可能是重復(fù)請(qǐng)求,直接放行
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)建的鍵,避免無(wú)限增長(zhǎng)
redisTemplate.delete(key);
}
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
try (PrintWriter writer = httpResponse.getWriter()) {
writer.write("請(qǐng)求頻率超過(guò)限制,請(qǐng)稍后再試!");
}
return;
}
bloomFilter.put(ipAddress); // 將IP地址添加到布隆過(guò)濾器
chain.doFilter(request, response);
}
@Override
public void destroy() {
// 清理資源,如關(guān)閉Redis連接等
}
private String getClientIpAddress(HttpServletRequest request) {
// 獲取客戶(hù)端IP地址的邏輯
// ...
}
}在上述代碼中,我們引入了布隆過(guò)濾器來(lái)減少對(duì)Redis的訪問(wèn)。如果布隆過(guò)濾器判斷請(qǐng)求可能是重復(fù)請(qǐng)求,則直接放行,無(wú)需進(jìn)行Redis操作。同時(shí),我們還添加了對(duì)Redis操作異常的處理,并在限流超過(guò)閾值時(shí)刪除新創(chuàng)建的鍵,以避免無(wú)限增長(zhǎng)。請(qǐng)根據(jù)實(shí)際情況進(jìn)行適當(dāng)調(diào)整和完善。
- 注冊(cè)過(guò)濾器:
在Spring Boot應(yīng)用程序的配置類(lèi)中注冊(cè)過(guò)濾器,以便它能夠在請(qǐng)求處理過(guò)程中生效。
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private RequestLimitFilter requestLimitFilter;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(requestLimitFilter);
}
}通過(guò)將過(guò)濾器添加到addInterceptors方法中,它將被注冊(cè)為Spring Boot應(yīng)用程序的全局過(guò)濾器,并在請(qǐng)求到達(dá)時(shí)執(zhí)行限流邏輯。
3.總結(jié)
其實(shí)上面我們寫(xiě)完的還是有問(wèn)題的
- 如果系統(tǒng)部署在多個(gè)節(jié)點(diǎn)上,可以考慮使用分布式限流算法,如令牌桶算法或漏桶算法。這些算法可以在分布式環(huán)境中平衡請(qǐng)求的處理,并保證全局的請(qǐng)求限制。
- 將請(qǐng)求限流的參數(shù),如請(qǐng)求限制數(shù)量和時(shí)間窗口,配置為可動(dòng)態(tài)調(diào)整的參數(shù)。可以使用注解或配置文件來(lái)管理這些參數(shù),以便在運(yùn)行時(shí)進(jìn)行調(diào)整,而無(wú)需重新編譯代碼。
到此這篇關(guān)于基于SpringBoot+Redis實(shí)現(xiàn)一個(gè)簡(jiǎn)單的限流器的文章就介紹到這了,更多相關(guān)SpringBoot+Redis實(shí)現(xiàn)限流器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- springboot+redis 實(shí)現(xiàn)分布式限流令牌桶的示例代碼
- 使用SpringBoot?+?Redis?實(shí)現(xiàn)接口限流的方式
- SpringBoot整合Redis并且用Redis實(shí)現(xiàn)限流的方法 附Redis解壓包
- SpringBoot中使用Redis對(duì)接口進(jìn)行限流的實(shí)現(xiàn)
- SpringBoot使用Redis對(duì)用戶(hù)IP進(jìn)行接口限流的示例詳解
- Springboot使用redis實(shí)現(xiàn)接口Api限流的實(shí)例
- SpringBoot使用Redis對(duì)用戶(hù)IP進(jìn)行接口限流的項(xiàng)目實(shí)踐
- Springboot+Redis實(shí)現(xiàn)API接口限流的示例代碼
- Springboot使用redis實(shí)現(xiàn)接口Api限流的示例代碼
- SpringBoot使用Redis進(jìn)行限流功能實(shí)現(xiàn)
相關(guān)文章
Java模擬單鏈表和雙端鏈表數(shù)據(jù)結(jié)構(gòu)的實(shí)例講解
這篇文章主要介紹了Java模擬單鏈表和雙端鏈表數(shù)據(jù)結(jié)構(gòu)的實(shí)例,注意這里的雙端鏈表不是雙向鏈表,是在單鏈表的基礎(chǔ)上保存有對(duì)最后一個(gè)鏈接點(diǎn)的引用,需要的朋友可以參考下2016-04-04
java可以作為第一門(mén)編程語(yǔ)言學(xué)習(xí)嗎
在本篇內(nèi)容里小編給JAVA零基礎(chǔ)的網(wǎng)友分享一篇關(guān)于java可以作為第一門(mén)編程語(yǔ)言學(xué)習(xí)嗎的文章,有興趣的朋友們可以參考下。2020-11-11
Springboot文件上傳出現(xiàn)找不到指定系統(tǒng)路徑的解決
這篇文章主要介紹了Springboot文件上傳出現(xiàn)找不到指定系統(tǒng)路徑的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
詳解Spring AOP 攔截器的基本實(shí)現(xiàn)
本篇文章主要介紹了詳解Spring AOP 攔截器的基本實(shí)現(xiàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-03-03
SpringBoot前后端分離實(shí)現(xiàn)驗(yàn)證碼操作
驗(yàn)證碼的功能是防止非法用戶(hù)惡意去訪問(wèn)登錄接口而設(shè)置的一個(gè)功能,今天我們就來(lái)看看在前后端分離的項(xiàng)目中,SpringBoot是如何提供服務(wù)的2022-05-05
淺析Spring IOC 依賴(lài)查找你需要知道的幾種方式
這篇文章主要介紹了淺析Spring IOC 依賴(lài)查找的幾種方式,Spring是Java面試中最??嫉?,學(xué)Java的小伙伴快來(lái)看看吧2021-08-08
Spring整合Quartz Job以及Spring Task的實(shí)現(xiàn)方法
下面小編就為大家分享一篇Spring整合Quartz Job以及Spring Task的實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2017-12-12

