實際場景案例
假設你正在構建一個電子商務平臺,該平臺每天會有大量的使用者在特定的時間段(如促銷時)同時下單。這些訂單請求會同時涌入伺服器,如果沒有有效的流量控制,伺服器可能會被瞬時過載,導致崩潰或響應延遲。爲了避免這種情況,你決定使用“削峰填谷”策略,即限制同一時間內允許處理的請求數(例如100個),而多餘的請求則會被放入佇列等待處理。
爲了確保使用者體驗,前端需要處理這種情況下的請求超時或重試邏輯,防止使用者感受到系統卡頓。同時,後端也需要有相應的機制來控制請求流量並避免高峰期間的資源過載。
Step 1: 設計方案思路
請求限制: 每次允許最多100個請求同時進入系統,超過這個數量的請求將被放入等待佇列。
超時處理: 如果請求等待的時間超過一定的閾值(例如5秒),則應返回給前端相應的超時錯誤。
前端響應策略: 前端在收到超時錯誤後,可以透過彈出提示框讓使用者知道當前系統繁忙,或者進行重試。
後端實現: 使用Spring Boot實現請求限制和佇列管理。
Step 2: 後端實現思路(基於Spring Boot)
RateLimiter的使用: 使用限流演算法來限制併發請求數,常用的工具如Guava的
RateLimiter
或使用Semaphore
。請求佇列: 多餘的請求將被放入佇列,等待前面的請求被處理。
超時處理: 使用
@ControllerAdvice
統一處理超時異常,返回超時錯誤。
程式碼實現
1. 引入依賴
首先,確保你在pom.xml
中引入了必要的依賴:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> </dependency>
2. 具體程式碼實現
package com.example.ratelimiter; import com.google.common.util.concurrent.RateLimiter; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.concurrent.*; @RestController @RequestMapping("/order") public class OrderController { // 使用Guava RateLimiter來限制每秒100個請求 private final RateLimiter rateLimiter = RateLimiter.create(100); // 這裏表示每秒最多處理100個請求 // 阻塞佇列,儲存多餘的請求 private final BlockingQueue<Callable<String>> requestQueue = new LinkedBlockingQueue<>(1000); @GetMapping("/place") public String placeOrder() throws Exception { if (rateLimiter.tryAcquire()) { // 如果限流器允許請求,直接處理 return processOrder(); } else { // 如果請求超過了限制,則將請求放入佇列 Future<String> future = handleInQueue(); return future.get(5, TimeUnit.SECONDS); // 設定5秒超時,超時則丟擲異常 } } // 模擬訂單處理邏輯 private String processOrder() throws InterruptedException { // 模擬訂單處理時間 Thread.sleep(2000); return "訂單處理成功!"; } // 將請求放入佇列 private Future<String> handleInQueue() throws InterruptedException { Callable<String> task = this::processOrder; if (!requestQueue.offer(task, 100, TimeUnit.MILLISECONDS)) { throw new RejectedExecutionException("伺服器繁忙,請稍後再試!"); } // 使用執行緒池非同步處理佇列中的請求 ExecutorService executorService = Executors.newFixedThreadPool(10); return executorService.submit(task); } }
3. 異常處理
爲了統一處理請求超時或者拒絕的情況,我們可以透過@ControllerAdvice
來實現。
package com.example.ratelimiter; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import java.util.concurrent.TimeoutException; @ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(TimeoutException.class) @ResponseStatus(HttpStatus.REQUEST_TIMEOUT) public String handleTimeoutException(TimeoutException ex) { return "請求超時,請稍後再試"; } @ExceptionHandler(RejectedExecutionException.class) @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE) public String handleRejectedExecutionException(RejectedExecutionException ex) { return "伺服器繁忙,請稍後再試"; } }
Step 3: 前端處理
在前端,可以捕獲請求超時或者服務不可用的響應,進行相應的處理。
async function placeOrder() { try { const response = await fetch("/order/place"); if (!response.ok) { if (response.status === 503) { alert("伺服器繁忙,請稍後再試"); } else if (response.status === 408) { alert("請求超時,請稍後再試"); } } else { const result = await response.text(); console.log(result); } } catch (error) { console.error("請求失敗", error); } }
Step 4: 思考總結
削峰填谷的實際場景: 在電商、搶票等需要高併發的場景下,非常適合使用限流策略來避免系統崩潰。透過將多餘的請求放入佇列,系統可以在承載能力範圍內平穩處理請求。
超時處理的重要性: 前端和後端都需要考慮超時的處理,以提升使用者體驗。請求如果等待時間過長,使用者體驗會大大下降,因此應及時返回錯誤提示或進行重試。
擴充套件思路: 限流方式可以靈活變換,比如根據使用者的優先順序、IP 地址等做不同的限流策略。
透過這種方式,我們可以有效控制系統的負載,確保服務的穩定性。