API 是現代應用程式的支柱。當我第一次開始使用 Spring Boot 建立 API 時,我過於專注於提供功能,而忽略了一個關鍵方面:彈性。我經歷了慘痛的教訓才明白,API 能夠優雅地處理故障並適應不同條件的能力才是它真正可靠的原因。讓我向您介紹我一路上犯的一些錯誤以及我是如何糾正這些錯誤的。希望您能夠在自己的旅程中避免這些陷阱。
發生了什麼事:在我的一個早期專案中,我建立了一個 API,可以對第三方服務進行外部呼叫。我認為這些服務總是會快速回應,並且不會費心設定超時。一切看起來都很好,直到流量增加,第三方服務開始變慢。我的 API 將無限期掛起,等待回應。
影響: API 的反應能力急遽下降。相關服務開始出現故障,用戶面臨長時間的延遲,有些甚至遇到了可怕的 500 內部伺服器錯誤。
我是如何修復它的:那時我意識到超時配置的重要性。以下是我使用 Spring Boot 修復這個問題的方法:
@Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate(RestTemplateBuilder builder) { return builder .setConnectTimeout(Duration.ofSeconds(5)) .setReadTimeout(Duration.ofSeconds(5)) .additionalInterceptors(new RestTemplateLoggingInterceptor()) .build(); } // Custom interceptor to log request/response details @RequiredArgsConstructor public class RestTemplateLoggingInterceptor implements ClientHttpRequestInterceptor { private static final Logger log = LoggerFactory.getLogger(RestTemplateLoggingInterceptor.class); @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { long startTime = System.currentTimeMillis(); log.info("Making request to: {}", request.getURI()); ClientHttpResponse response = execution.execute(request, body); long duration = System.currentTimeMillis() - startTime; log.info("Request completed in {}ms with status: {}", duration, response.getStatusCode()); return response; } } }
此配置不僅設定適當的逾時,還包括日誌記錄以幫助監控外部服務效能。
發生了什麼事:曾經有一段時間,我們所依賴的內部服務宕機了幾個小時。我的 API 沒有很好地處理這種情況。相反,它不斷重試那些失敗的請求,為已經緊張的系統增加了更多的負載。
級聯故障是分散式系統中最具挑戰性的問題之一。當一項服務出現故障時,可能會產生骨牌效應,導致整個系統癱瘓。
影響:重複的重試使系統不堪重負,減慢了應用程式的其他部分並影響了所有用戶。
我是如何修復它的:就在那時我發現了斷路器模式。使用 Spring Cloud Resilience4j,我能夠打破這個循環。
@Configuration public class Resilience4jConfig { @Bean public CircuitBreakerConfig circuitBreakerConfig() { return CircuitBreakerConfig.custom() .failureRateThreshold(50) .waitDurationInOpenState(Duration.ofSeconds(60)) .permittedNumberOfCallsInHalfOpenState(2) .slidingWindowSize(2) .build(); } @Bean public RetryConfig retryConfig() { return RetryConfig.custom() .maxAttempts(3) .waitDuration(Duration.ofSeconds(2)) .build(); } } @Service @Slf4j public class ResilientService { private final CircuitBreaker circuitBreaker; private final RestTemplate restTemplate; public ResilientService(CircuitBreakerRegistry registry, RestTemplate restTemplate) { this.circuitBreaker = registry.circuitBreaker("internalService"); this.restTemplate = restTemplate; } @CircuitBreaker(name = "internalService", fallbackMethod = "fallbackResponse") @Retry(name = "internalService") public String callInternalService() { return restTemplate.getForObject("https://internal-service.com/data", String.class); } public String fallbackResponse(Exception ex) { log.warn("Circuit breaker activated, returning fallback response", ex); return new FallbackResponse("Service temporarily unavailable", getBackupData()).toJson(); } private Object getBackupData() { // Implement cache or default data strategy return new CachedDataService().getLatestValidData(); } }
這個簡單的添加可以防止我的 API 壓垮自身、內部服務或第三方服務,確保系統穩定性。
發生了什麼事: 早期,我沒有對錯誤處理投入太多考慮。我的 API 要么拋出一般錯誤(例如所有內容的 HTTP 500),要么在堆疊追蹤中暴露敏感的內部詳細資訊。
影響:使用者對出了什麼問題感到困惑,內部細節的暴露造成了潛在的安全風險。
我是如何修復它的:我決定使用 Spring 的 @ControllerAdvice 註解來集中處理錯誤。這就是我所做的:
@Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate(RestTemplateBuilder builder) { return builder .setConnectTimeout(Duration.ofSeconds(5)) .setReadTimeout(Duration.ofSeconds(5)) .additionalInterceptors(new RestTemplateLoggingInterceptor()) .build(); } // Custom interceptor to log request/response details @RequiredArgsConstructor public class RestTemplateLoggingInterceptor implements ClientHttpRequestInterceptor { private static final Logger log = LoggerFactory.getLogger(RestTemplateLoggingInterceptor.class); @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { long startTime = System.currentTimeMillis(); log.info("Making request to: {}", request.getURI()); ClientHttpResponse response = execution.execute(request, body); long duration = System.currentTimeMillis() - startTime; log.info("Request completed in {}ms with status: {}", duration, response.getStatusCode()); return response; } } }
這使得錯誤訊息清晰且安全,為使用者和開發人員提供協助。
發生了什麼事:在一個晴朗的日子,我們發起了一項促銷活動,我們的 API 流量猛增。雖然這對企業來說是個好消息,但一些用戶開始向 API 發送垃圾郵件請求,導致其他人資源匱乏。
影響:每個人的表現都下降了,我們收到了大量投訴。
我如何修復它:為了解決這個問題,我使用 Bucket4j 和 Redis 實現了速率限制。這是一個例子:
@Configuration public class Resilience4jConfig { @Bean public CircuitBreakerConfig circuitBreakerConfig() { return CircuitBreakerConfig.custom() .failureRateThreshold(50) .waitDurationInOpenState(Duration.ofSeconds(60)) .permittedNumberOfCallsInHalfOpenState(2) .slidingWindowSize(2) .build(); } @Bean public RetryConfig retryConfig() { return RetryConfig.custom() .maxAttempts(3) .waitDuration(Duration.ofSeconds(2)) .build(); } } @Service @Slf4j public class ResilientService { private final CircuitBreaker circuitBreaker; private final RestTemplate restTemplate; public ResilientService(CircuitBreakerRegistry registry, RestTemplate restTemplate) { this.circuitBreaker = registry.circuitBreaker("internalService"); this.restTemplate = restTemplate; } @CircuitBreaker(name = "internalService", fallbackMethod = "fallbackResponse") @Retry(name = "internalService") public String callInternalService() { return restTemplate.getForObject("https://internal-service.com/data", String.class); } public String fallbackResponse(Exception ex) { log.warn("Circuit breaker activated, returning fallback response", ex); return new FallbackResponse("Service temporarily unavailable", getBackupData()).toJson(); } private Object getBackupData() { // Implement cache or default data strategy return new CachedDataService().getLatestValidData(); } }
這確保了公平使用並保護 API 免受濫用。
發生了什麼事:每當生產中出現問題時,就像大海撈針一樣。我沒有適當的日誌記錄或指標,因此診斷問題花費的時間比應有的時間要長。
影響:故障排除變成了一場噩夢,延遲了問題解決並使用戶感到沮喪。
我是如何解決這個問題的:我添加了 Spring Boot Actuator 來進行健康檢查,並將 Prometheus 與 Grafana 整合起來以實現指標視覺化:
@RestControllerAdvice @Slf4j public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { @ExceptionHandler(HttpClientErrorException.class) public ResponseEntity<ErrorResponse> handleHttpClientError(HttpClientErrorException ex, WebRequest request) { log.error("Client error occurred", ex); ErrorResponse error = ErrorResponse.builder() .timestamp(LocalDateTime.now()) .status(ex.getStatusCode().value()) .message(sanitizeErrorMessage(ex.getMessage())) .path(((ServletWebRequest) request).getRequest().getRequestURI()) .build(); return ResponseEntity.status(ex.getStatusCode()).body(error); } @ExceptionHandler(Exception.class) public ResponseEntity<ErrorResponse> handleGeneralException(Exception ex, WebRequest request) { log.error("Unexpected error occurred", ex); ErrorResponse error = ErrorResponse.builder() .timestamp(LocalDateTime.now()) .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) .message("An unexpected error occurred. Please try again later.") .path(((ServletWebRequest) request).getRequest().getRequestURI()) .build(); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error); } private String sanitizeErrorMessage(String message) { // Remove sensitive information from error messages return message.replaceAll("(password|secret|key)=\[.*?\]", "=[REDACTED]"); } }
我還使用 ELK Stack(Elasticsearch、Logstash、Kibana)實作了結構化日誌記錄。這使得日誌更具可操作性。
建立彈性 API 是一個旅程,錯誤是這個過程的一部分。以下是我學到的主要經驗教訓:
這些變化改變了我進行 API 開發的方式。如果您遇到過類似的挑戰或有其他建議,我很想聽聽您的故事!
尾註:請記住,彈性不是您添加的功能,而是您從頭開始構建到系統中的特性。這些元件中的每一個在創建 API 方面都發揮著至關重要的作用,這些 API 不僅可以工作,而且可以在壓力下繼續可靠地工作。
以上是建立彈性 API:我犯的錯誤以及我如何克服這些錯誤的詳細內容。更多資訊請關注PHP中文網其他相關文章!