API は最新のアプリケーションのバックボーンです。 Spring Boot を使用して API の構築を始めたとき、私は機能を提供することに集中しすぎて、復元力という 1 つの重要な側面を見落としていました。 API が障害を適切に処理し、さまざまな状況に適応できる能力こそが API を真に信頼できるものにするということを、私は苦労して学びました。その過程で私が犯したいくつかの間違いと、それをどのように修正したかを説明しましょう。うまくいけば、あなた自身の旅でこれらの落とし穴を避けることができます。
何が起こったか: 私の初期のプロジェクトの 1 つで、サードパーティ サービスへの外部呼び出しを行う 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 は状況を適切に処理できませんでした。代わりに、失敗したリクエストを再試行し続け、すでにストレスがかかっているシステムにさらなる負荷を加えました。
カスケード障害は、分散システムにおける最も困難な問題の 1 つです。 1 つのサービスに障害が発生すると、システム全体がダウンするドミノ効果が発生する可能性があります。
影響: 繰り返される再試行によりシステムに負荷がかかり、アプリケーションの他の部分の速度が低下し、すべてのユーザーに影響を及ぼします。
私がそれを解決した方法: そのとき、私はサーキットブレーカーのパターンを発見しました。 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 にリクエストをスパム送信し始め、他のユーザーのリソースが枯渇してしまいました。
影響: すべてのパフォーマンスが低下し、苦情が殺到しました。
修正方法: これに対処するために、Redis で Bucket4j を使用してレート制限を実装しました。以下に例を示します:
@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 スタック (Elasticsearch、Logstash、Kibana) を使用して構造化ログも実装しました。これにより、ログがはるかに実用的になりました。
回復力のある API を構築するのは長い旅であり、間違いはプロセスの一部です。私が学んだ重要な教訓は次のとおりです:
これらの変更により、API 開発への取り組み方が変わりました。同じような課題に直面したことがある方、または他のヒントをお持ちの方がいらっしゃいましたら、ぜひお話をお聞かせください!
終了注記: 復元力はユーザーが追加する機能ではなく、システムに最初から組み込む特性であることに注意してください。これらのコンポーネントはそれぞれ、動作するだけでなくストレス下でも確実に動作し続ける API を作成する上で重要な役割を果たします。
以上が回復力のある API の構築: 私が犯した間違いとそれをどのように克服したかの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。