Java面試之限流的實現(xiàn)方式小結(jié)
限流是指在各種應(yīng)用場景中,通過技術(shù)和策略手段對數(shù)據(jù)流量、請求頻率或資源消耗進(jìn)行有計劃的限制,以避免系統(tǒng)負(fù)載過高、性能下降甚至崩潰的情況發(fā)生。限流的目標(biāo)在于維護系統(tǒng)的穩(wěn)定性和可用性,并確保服務(wù)質(zhì)量。
使用限流的好處有以下幾個:
- 保護系統(tǒng)穩(wěn)定性:過多的并發(fā)請求可能導(dǎo)致服務(wù)器內(nèi)存耗盡、CPU 使用率飽和,從而引發(fā)系統(tǒng)響應(yīng)慢、無法正常服務(wù)的問題。
- 防止資源濫用:確保有限的服務(wù)資源被合理公平地分配給所有用戶,防止個別用戶或惡意程序過度消耗資源。
- 優(yōu)化用戶體驗:對于網(wǎng)站和應(yīng)用程序而言,如果任由高并發(fā)導(dǎo)致響應(yīng)速度變慢,會影響所有用戶的正常使用體驗。
- 保障安全:在網(wǎng)絡(luò)層面,限流有助于防范 DoS/DDoS 攻擊,降低系統(tǒng)遭受惡意攻擊的風(fēng)險。
- 運維成本控制:合理的限流措施可以幫助企業(yè)減少不必要的硬件投入,節(jié)省運營成本。
在 Java 中,限流的實現(xiàn)方式有很多種,例如以下這些:
- 單機限流:使用 JUC 下的 Semaphore 限流,或一些常用的框架,例如 Google 的 Guava 框架進(jìn)行限流,但這種限流方式都是基于 JVM 層面的內(nèi)存級別的單臺機器限流。
- 網(wǎng)關(guān)層限流:單機限流往往不適用于分布式系統(tǒng),而分布式系統(tǒng)可以在網(wǎng)關(guān)層限流,如 Spring Cloud Gateway 通過 Sentinel、Hystrix 對整個集群進(jìn)行限流。
- Nginx 限流:通常在網(wǎng)關(guān)層的上游,我們會使用 Nginx 一起來配合使用,也就是用戶請求會先到 Nginx(或 Nginx 集群),然后再將請求轉(zhuǎn)發(fā)給網(wǎng)關(guān),網(wǎng)關(guān)再調(diào)用其他的微服務(wù),從而實現(xiàn)整個流程的請求調(diào)用,因此 Nginx 限流也是分布式系統(tǒng)中常用的限流手段。
它們限流的具體實現(xiàn)如下。
1.單機限流
JVM 層面多線程級別的限流可以使用 JUC 下的 Semaphore,具體使用示例如下:
import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; public class SemaphoreExample { private final Semaphore semaphore = new Semaphore(5); // 只允許5個線程同時訪問 public void accessResource() { try { semaphore.acquire(); // 獲取許可,如果當(dāng)前許可數(shù)不足,則會阻塞 System.out.println(Thread.currentThread().getName() + "獲得了許可,正在訪問資源..."); // 模擬訪問資源的時間消耗 Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "訪問資源結(jié)束,釋放許可..."); } catch (InterruptedException e) { Thread.currentThread().interrupt(); e.printStackTrace(); } finally { semaphore.release(); // 訪問結(jié)束后釋放許可 } } public static void main(String[] args) { SemaphoreExample example = new SemaphoreExample(); for (int i = 0; i < 10; i++) { new Thread(() -> example.accessResource()).start(); } } }
想要實現(xiàn)更平滑的單機限流,可以考慮 Google 提供的 Guava 框架,它的使用示例如下。
首先在 pom.xml 添加 guava 引用,配置如下:
<!-- https://mvnrepository.com/artifact/com.google.guava/guava --> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>28.2-jre</version> </dependency>
具體實現(xiàn)代碼如下:
import com.google.common.util.concurrent.RateLimiter; import java.time.Instant; /** * Guava 實現(xiàn)限流 */ public class RateLimiterExample { public static void main(String[] args) { // 每秒產(chǎn)生 10 個令牌(每 100 ms 產(chǎn)生一個) RateLimiter rt = RateLimiter.create(10); for (int i = 0; i < 11; i++) { new Thread(() -> { // 獲取 1 個令牌,獲取到令牌就執(zhí)行,否則就阻塞等待 rt.acquire(); System.out.println("正常執(zhí)行方法,ts:" + Instant.now()); }).start(); } } }
2.網(wǎng)關(guān)層限流
在 Spring Cloud Gateway 網(wǎng)關(guān)層限流,可以借助 Sentinel 等限流框架來實現(xiàn),它的實現(xiàn)步驟如下。
首先,在 pom.xml 中添加 Gateway 和 Sentinel 相關(guān)依賴,如下所示:
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId> </dependency>
配置限流相關(guān)的規(guī)則,如下示例所示:
spring: application: name: gate-way-blog cloud: sentinel: transport: dashboard: localhost:18080 scg: # 配置限流之后,響應(yīng)內(nèi)容 fallback: # 兩種模式,一種是 response 返回文字提示信息, # 另一種是 redirect 重定向跳轉(zhuǎn),不過配置 redirect 也要配置對應(yīng)的跳轉(zhuǎn)的 uri mode: response # 響應(yīng)的狀態(tài) response-status: 200 # 響應(yīng)體 response-body: '{"code": -10,"message": "被熔斷或限流!"}'
最后在 Sentinel 控制臺配置網(wǎng)關(guān)的限流設(shè)置即可,當(dāng)然也可以使用 Nacos 作為數(shù)據(jù)源,兩者選擇配置其中一個即可。
3.Nginx 限流
Nginx 提供了兩種限流手段:
- 通過控制速率來實現(xiàn)限流。
- 通過控制并發(fā)連接數(shù)來實現(xiàn)限流。
我們一個一個來看。
3.1 控制速率實現(xiàn)限流
我們需要使用 limit_req_zone 用來限制單位時間內(nèi)的請求數(shù),即速率限制,示例配置如下:
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s; server { location / { limit_req zone=mylimit; } }
以上配置表示,限制每個 IP 訪問的速度為 2r/s,因為 Nginx 的限流統(tǒng)計是基于毫秒的,我們設(shè)置的速度是 2r/s,轉(zhuǎn)換一下就是 500ms 內(nèi)單個 IP 只允許通過 1 個請求,從 501ms 開始才允許通過第 2 個請求。
我們使用單 IP 在 10ms 內(nèi)發(fā)并發(fā)送了 6 個請求的執(zhí)行結(jié)果如下:
從以上結(jié)果可以看出他的執(zhí)行符合我們的預(yù)期,只有 1 個執(zhí)行成功了,其他的 5 個被拒絕了(第 2 個在 501ms 才會被正常執(zhí)行)。
速率限制升級版
上面的速率控制雖然很精準(zhǔn)但是應(yīng)用于真實環(huán)境未免太苛刻了,真實情況下我們應(yīng)該控制一個 IP 單位總時間內(nèi)的總訪問次數(shù),而不是像上面那么精確但毫秒,我們可以使用 burst 關(guān)鍵字開啟此設(shè)置,示例配置如下:
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s; server { location / { limit_req zone=mylimit burst=4; } }
burst=4 表示每個 IP 最多允許4個突發(fā)請求,如果單個 IP 在 10ms 內(nèi)發(fā)送 6 次請求的結(jié)果如下:
從以上結(jié)果可以看出,有 1 個請求被立即處理了,4 個請求被放到 burst 隊列里排隊執(zhí)行了,另外 1 個請求被拒絕了。
3.2 控制并發(fā)數(shù)實現(xiàn)限流
利用 limit_conn_zone 和 limit_conn 兩個指令即可控制并發(fā)數(shù),示例配置如下:
limit_conn_zone $binary_remote_addr zone=perip:10m; limit_conn_zone $server_name zone=perserver:10m; server { ... limit_conn perip 10; limit_conn perserver 100; }
其中 limit_conn perip 10 表示限制單個 IP 同時最多能持有 10 個連接;limit_conn perserver 100 表示 server 同時能處理并發(fā)連接的總數(shù)為 100 個。
小貼士:只有當(dāng) request header 被后端處理后,這個連接才進(jìn)行計數(shù)。
課后思考
Semaphore 限流和 Guava 限流有什么區(qū)別?Sentinel 和 Nginx 限流有什么不足?應(yīng)該如何避免?
到此這篇關(guān)于Java面試之限流的實現(xiàn)方式小結(jié)的文章就介紹到這了,更多相關(guān)Java限流內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
javaWeb實現(xiàn)學(xué)生信息管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了javaWeb實現(xiàn)學(xué)生信息管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-01-01java策略枚舉:消除在項目里大批量使用if-else的優(yōu)雅姿勢
這篇文章主要給大家介紹了關(guān)于Java徹底消滅if-else的8種方案,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2021-06-06Java Arrays.sort()如何實現(xiàn)對int類型數(shù)組倒序排序
這篇文章主要介紹了Java Arrays.sort()如何實現(xiàn)對int類型數(shù)組倒序排序問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-08-08Java注解@Transactional事務(wù)類內(nèi)調(diào)用不生效問題及解決辦法
這篇文章主要介紹了Java注解@Transactional事務(wù)類內(nèi)調(diào)用不生效問題及解決辦法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05Springboot+Redis實現(xiàn)API接口防刷限流的項目實踐
本文主要介紹了Springboot+Redis實現(xiàn)API接口防刷限流的項目實踐,通過限流可以讓系統(tǒng)維持在一個相對穩(wěn)定的狀態(tài),為更多的客戶提供服務(wù),具有一定的參考價值,感興趣的可以了解一下2024-07-07如何通過java將doc文件轉(zhuǎn)換為docx文件詳解
在數(shù)字化時代文檔處理成為了我們?nèi)粘9ぷ骱蛯W(xué)習(xí)中不可或缺的一部分,其中doc和docx作為兩種常見的文檔格式,各自具有不同的特點和優(yōu)勢,這篇文章主要給大家介紹了關(guān)于如何通過java將doc文件轉(zhuǎn)換為docx文件的相關(guān)資料,需要的朋友可以參考下2024-07-07