SpringBoot實(shí)現(xiàn)API接口限流
1. 簡(jiǎn)介
訪問(wèn)速率限制是一種API訪問(wèn)限制的策略。它限制客戶端在一定時(shí)間內(nèi)調(diào)用 API 的次數(shù)。這有助于保護(hù)應(yīng)用程序接口,防止無(wú)意或惡意的過(guò)度使用。
速率限制通常是通過(guò)跟蹤 IP 地址或更具體的業(yè)務(wù)方式(如 API 密鑰或訪問(wèn)令牌等方式)來(lái)應(yīng)用于 API 的。作為 API 開(kāi)發(fā)人員,當(dāng)客戶端達(dá)到限制時(shí),我們有幾種選擇:
- 請(qǐng)求排隊(duì),直到剩余時(shí)間結(jié)束(這也是最常用的方式)
- 拒絕請(qǐng)求(HTTP 429 請(qǐng)求過(guò)多)
本篇文章將介紹一款開(kāi)源的組件Bucket4j,該組件提供了強(qiáng)大的限流功能。基于基于令牌桶算法。既可用于獨(dú)立的 JVM 應(yīng)用程序,也可用于集群環(huán)境。它還通過(guò) JCache(JSR107)規(guī)范支持內(nèi)存或分布式緩存。
令牌桶算法
假設(shè)我們有一個(gè) "桶",其容量被定義為可容納的令牌數(shù)量。每當(dāng)消費(fèi)者想要訪問(wèn) API 端點(diǎn)時(shí),就必須從桶中獲取一個(gè)令牌。如果有令牌,我們就會(huì)從數(shù)據(jù)桶中移除令牌,并接受請(qǐng)求。反之,如果程序桶中沒(méi)有令牌,我們就會(huì)拒絕請(qǐng)求。
在請(qǐng)求消耗令牌(token)的同時(shí),我們也在以某種固定的速度補(bǔ)充令牌。
考慮一個(gè)速率限制為每分鐘 100 個(gè)請(qǐng)求的應(yīng)用程序接口。我們可以創(chuàng)建一個(gè)容量為 100 的水桶,每分鐘填充 100 個(gè)令牌。如果我們?cè)谝环昼妰?nèi)收到 70 個(gè)請(qǐng)求,少于可用令牌的數(shù)量,那么在下一分鐘開(kāi)始時(shí),我們只需再添加 30 個(gè)令牌,就能使水桶達(dá)到容量。另一方面,如果我們?cè)?40 秒內(nèi)用完了所有令牌,我們將等待 20 秒來(lái)重新裝滿令牌桶。
接下來(lái)將詳細(xì)介紹在Spring Boot中如何使用Bucket4j實(shí)現(xiàn)限流。
2. 實(shí)戰(zhàn)案例
2.1 環(huán)境準(zhǔn)備
引入依賴
<dependency> <groupId>com.giffing.bucket4j.spring.boot.starter</groupId> <artifactId>bucket4j-spring-boot-starter</artifactId> <version>0.12.7</version> </dependency> <dependency> <groupId>com.bucket4j</groupId> <artifactId>bucket4j-redis</artifactId> <version>8.10.1</version> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-core</artifactId> </dependency> 1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.
接下來(lái)的案例是基于redis的,所以引入了jedis。你也可以是lettuce或者是redisson但是這2個(gè)貌似需要是webflux環(huán)境。
jedis配置
@Bean
public JedisPool jedisPool(
@Value("${spring.data.redis.port}") Integer port,
@Value("${spring.data.redis.host}") String host,
@Value("${spring.data.redis.password}") String password,
@Value("${spring.data.redis.database}") Integer database
) {
// buildPoolConfig方法自己進(jìn)行配置吧
final JedisPoolConfig poolConfig = buildPoolConfig();
return new JedisPool(poolConfig, host, port, 60000, password, database);
}
1.2.3.4.5.6.7.8.9.10.11.
以上基礎(chǔ)環(huán)境就準(zhǔn)備好了,接下來(lái)就可以進(jìn)行規(guī)則配置。而規(guī)則的配置可以基于2中方式,基于配置文件和基于注解(AOP)。
定義接口
@RestController
@RequestMapping("/products")
public class ProductController {
@GetMapping("/{id}")
public Product getProduct(@PathVariable Integer id) {
return new Product(id, "商品 - " + id, BigDecimal.valueOf(new Random().nextDouble(1000))) ;
}
}
1.2.3.4.5.6.7.8.9.10.
接下來(lái)我將基于上面的接口進(jìn)行限流的配置。
2.2 基于配置文件
基于配置文件的規(guī)則配置底層實(shí)現(xiàn)是通過(guò)Filter。
bucket4j:
cache-to-use: redis-jedis
filter-config-caching-enabled: true
filters:
- cache-name: product_cache_name
id: product_filter
# 配置請(qǐng)求url的規(guī)則;這里底層是通過(guò)正則表達(dá)式進(jìn)行匹配的
url: /products/.*
rate-limits:
-
#這里的cache-key非常關(guān)鍵;用于區(qū)分不同請(qǐng)求的情況;
#比如,這里我會(huì)根據(jù)不同的請(qǐng)求id來(lái)現(xiàn)在訪問(wèn)速率
#這里可以寫(xiě)spel表達(dá)式,這里調(diào)用的是HttpServletRequest#getParameter方法
cache-key: getParameter("id")
bandwidths:
#配置桶的容量
- capacity: 2
# 時(shí)間
time: 30
# 單位
unit: seconds
# 填充速度;這會(huì)每隔30秒進(jìn)行填充
refill-speed: interval
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.
修改默認(rèn)的限流提示
bucket4j:
filters:
- cache-name: product_cache_name
http-content-type: 'application/json;charset=utf-8'
http-response-body: '{"code": -1, "message": "請(qǐng)求太快了"}'
1.2.3.4.5.
注意:你必須同時(shí)要設(shè)置content-type設(shè)置字符編碼,否則會(huì)亂碼。
條件放行
你也可以通過(guò)如下屬性進(jìn)行有條件的放行;
bucket4j:
filters:
- cache-name: product_cache_name
rate-limits:
-
skip-condition: 'getParameter("id").equals("6")'
1.2.3.4.5.6.
當(dāng)請(qǐng)求id的值為6時(shí)則跳過(guò)規(guī)則,直接方向。
以上是基于配置文件規(guī)則的應(yīng)用,它還有很多其它的配置屬性,詳細(xì)查看官方文檔
https://github.com/MarcGiffing/bucket4j-spring-boot-starter
接下來(lái)介紹基于注解的方式。
2.3 基于注解
通過(guò)"@RateLimiting"注解,AOP 可以攔截目標(biāo)方法。這樣,你就可以全面訪問(wèn)方法參數(shù),輕松定義速率限制鍵或有條件地跳過(guò)速率限制。
配置文件中配置規(guī)則
bucket4j:
methods:
- name: storage_rate #在代碼中會(huì)通過(guò)該名稱引用
cache-name: storage_cache_name
rate-limit:
bandwidths:
- capacity: 2
time: 30
unit: seconds
refill-speed: interval
1.2.3.4.5.6.7.8.9.10.
接口注解,配置限流
@RateLimiting(
name = "storage_rate",
cacheKey = "'storage-' + #id",
skipCondition = "#name eq 'admin'",
ratePerMethod = true,
fallbackMethodName = "getStorageFallback"
)
@GetMapping("/{id}")
public R<Storage> getStorage(@PathVariable Integer id, String name) {
return R.success(new Storage(id, "SP001 - " + id, new Random().nextInt(10000))) ;
}
// 回退方法的簽名必須與業(yè)務(wù)方法一致
public R<Storage> getStorageFallback(Integer id, String name) {
return R.failure(String.format("請(qǐng)求id=%d,name=%s被限流", id, name)) ;
}
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.
skipCondition:該屬性定義了如果請(qǐng)求的name的值為admin則跳過(guò),不限流。
@RateLimiting注解還可以應(yīng)用到類中,這樣該類中的所有方法都會(huì)被限流,如下示例:
@Service
@RateLimiting(
name = "storage_rate",
cacheKey = "getName",
ratePerMethod = false
)
public class StorageService {
public Storage queryStorageById(Integer id) {
return new Storage(id, "SP001 - " + id, new Random().nextInt(10000)) ;
}
@IgnoreRateLimiting
public List<Storage> queryStorages() {
return List.of(
new Storage(1, "SP001 - " + 1, new Random().nextInt(10000)),
new Storage(2, "SP002 - " + 2, new Random().nextInt(10000)),
new Storage(3, "SP003 - " + 3, new Random().nextInt(10000))
) ;
}
}
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.
上面代碼queryStorageById會(huì)被限流,而queryStorages方法被@IgnoreRateLimiting注解標(biāo)準(zhǔn),所以不會(huì)被限流。
到此這篇關(guān)于SpringBoot實(shí)現(xiàn)API接口限流的文章就介紹到這了,更多相關(guān)SpringBoot API接口限流內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Struts2學(xué)習(xí)筆記(9)-Result配置全局結(jié)果集
這篇文章主要介紹Struts2中使用Result配置全局結(jié)果集的方法,希望能給大家做一個(gè)參考。2016-06-06
java網(wǎng)上圖書(shū)商城(5)購(gòu)物車模塊2
這篇文章主要為大家詳細(xì)介紹了java網(wǎng)上圖書(shū)商城,購(gòu)物車模塊第二篇,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12
通過(guò)Java 程序獲取Word中指定圖片的坐標(biāo)位置
本文介紹通過(guò)Java程序獲取Word文檔中指定圖片的坐標(biāo)位置,程序運(yùn)行環(huán)境是jdk1.8開(kāi)發(fā)環(huán)境idea,通過(guò)java程序代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧2021-05-05
springboot 如何解決static調(diào)用service為null
這篇文章主要介紹了springboot 如何解決static調(diào)用service為null的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06
java通過(guò)釘釘機(jī)器人發(fā)消息的實(shí)現(xiàn)示例
本文主要介紹了java通過(guò)釘釘機(jī)器人發(fā)消息的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-09-09
springboot自動(dòng)重啟的簡(jiǎn)單方法
Springboot提供了熱部署的方式,當(dāng)發(fā)現(xiàn)任何類發(fā)生了改變,馬上通過(guò)JVM類加載的方式,加載最新的類到虛擬機(jī)中。這篇文章主要介紹了springboot自動(dòng)重啟的實(shí)現(xiàn)方法,需要的朋友可以參考下2018-04-04
java基于Socket做一個(gè)簡(jiǎn)單下載器
這篇文章主要為大家詳細(xì)介紹了java如何基于Socket制作一個(gè)簡(jiǎn)單下載器,感興趣的小伙伴們可以參考一下2016-08-08
從底層源碼深入分析Spring的IoC容器的實(shí)現(xiàn)原理
IoC容器負(fù)責(zé)管理對(duì)象的生命周期和依賴關(guān)系,大大簡(jiǎn)化了應(yīng)用程序的開(kāi)發(fā)和維,我們這篇文章將會(huì)從底層源碼的角度深入分析Spring的IoC容器實(shí)現(xiàn),探索它的工作原理和關(guān)鍵組件,需要的朋友可以參考下2023-07-07
Java本地方法(JNA)詳解及常見(jiàn)問(wèn)題
JNA(Java?Native?Access)是一個(gè)開(kāi)源Java框架,用于無(wú)需編寫(xiě)JNI代碼即可動(dòng)態(tài)訪問(wèn)本地系統(tǒng)庫(kù)如Windows的dll,它允許Java程序直接調(diào)用本地方法,這篇文章主要介紹了Java本地方法(JNA)詳解及常見(jiàn)問(wèn)題,需要的朋友可以參考下2024-09-09

