API ialah tulang belakang aplikasi moden. Apabila saya mula membina API dengan Spring Boot, saya sangat tertumpu pada penyampaian ciri sehingga saya terlepas pandang satu aspek penting: daya tahan. Saya belajar dengan susah payah bahawa keupayaan API untuk menangani kegagalan dengan anggun dan menyesuaikan diri dengan keadaan yang berbeza adalah yang menjadikannya benar-benar boleh dipercayai. Izinkan saya membawa anda melalui beberapa kesilapan yang saya lakukan sepanjang perjalanan dan cara saya membetulkannya. Mudah-mudahan, anda boleh mengelakkan perangkap ini dalam perjalanan anda sendiri.
Perkara yang Berlaku: Dalam salah satu projek awal saya, saya membina API yang membuat panggilan luaran kepada perkhidmatan pihak ketiga. Saya menganggap perkhidmatan tersebut akan sentiasa bertindak balas dengan cepat dan tidak mengganggu menetapkan tamat masa. Semuanya kelihatan baik sehingga trafik meningkat, dan perkhidmatan pihak ketiga mula perlahan. API saya hanya akan tergantung selama-lamanya, menunggu jawapan.
Impak: Responsif API semakin merosot. Perkhidmatan bergantung mula gagal, dan pengguna menghadapi kelewatan yang lama—malah ada yang mendapat Ralat Pelayan Dalaman 500 yang digeruni.
Cara Saya Membetulkannya: Ketika itulah saya menyedari kepentingan konfigurasi tamat masa. Begini cara saya membetulkannya menggunakan 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; } } }
Konfigurasi ini bukan sahaja menetapkan tamat masa yang sesuai tetapi juga termasuk pengelogan untuk membantu memantau prestasi perkhidmatan luaran.
Apa Yang Berlaku: Ada masanya perkhidmatan dalaman kami yang kami harapkan merosot selama beberapa jam. API saya tidak mengendalikan keadaan dengan baik. Sebaliknya, ia terus mencuba semula permintaan yang gagal itu, menambah lebih banyak beban pada sistem yang sudah tertekan.
Kegagalan berlatarkan adalah salah satu masalah yang paling mencabar dalam sistem teragih. Apabila satu perkhidmatan gagal, ia boleh mencipta kesan domino yang menjatuhkan keseluruhan sistem.
Kesan: Percubaan semula berulang mengatasi sistem, memperlahankan bahagian lain aplikasi dan menjejaskan semua pengguna.
Cara Saya Membetulkannya: Ketika itulah saya menemui corak pemutus litar. Menggunakan Spring Cloud Resilience4j, saya dapat memecahkan kitaran.
@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(); } }
Tambahan mudah ini menghalang API saya daripada mengatasi dirinya sendiri, perkhidmatan dalaman atau perkhidmatan pihak ketiga, memastikan kestabilan sistem.
Apa yang Berlaku: Pada awalnya, saya tidak terlalu memikirkan tentang pengendalian ralat. API saya sama ada melemparkan ralat generik (seperti HTTP 500 untuk segala-galanya) atau mendedahkan butiran dalaman sensitif dalam surih tindanan.
Impak: Pengguna keliru tentang perkara yang salah dan pendedahan butiran dalaman mewujudkan potensi risiko keselamatan.
Cara Saya Membetulkannya: Saya memutuskan untuk memusatkan pengendalian ralat menggunakan anotasi @ControllerAdvice Spring. Inilah yang saya lakukan:
@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; } } }
Ini menjadikan mesej ralat jelas dan selamat, membantu pengguna dan pembangun.
Perkara yang Berlaku: Pada suatu hari yang baik, kami melancarkan kempen promosi dan trafik ke API kami meningkat. Walaupun ini merupakan berita baik untuk perniagaan, sesetengah pengguna mula menghantar spam kepada API dengan permintaan, menyebabkan orang lain kelaparan sumber.
Impak: Prestasi merosot untuk semua orang, dan kami menerima banyak aduan.
Cara Saya Membetulkannya: Untuk mengendalikan perkara ini, saya melaksanakan pengehadan kadar menggunakan Bucket4j dengan Redis. Berikut ialah contoh:
@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(); } }
Ini memastikan penggunaan yang adil dan melindungi API daripada penyalahgunaan.
Apa yang Berlaku: Setiap kali berlaku masalah dalam pengeluaran, ia seperti mencari jarum dalam timbunan jerami. Saya tidak mempunyai pengelogan atau metrik yang betul, jadi mendiagnosis isu mengambil masa yang lebih lama daripada yang sepatutnya.
Impak: Penyelesaian masalah menjadi mimpi ngeri, menangguhkan penyelesaian isu dan mengecewakan pengguna.
Cara Saya Membetulkannya: Saya menambahkan Spring Boot Actuator untuk pemeriksaan kesihatan dan menyepadukan Prometheus dengan Grafana untuk visualisasi metrik:
@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]"); } }
Saya juga melaksanakan pengelogan berstruktur menggunakan ELK Stack (Elasticsearch, Logstash, Kibana). Ini menjadikan log lebih mudah diambil tindakan.
Membina API yang berdaya tahan adalah satu perjalanan dan kesilapan adalah sebahagian daripada proses. Berikut ialah pengajaran utama yang saya pelajari:
Perubahan ini mengubah cara saya mendekati pembangunan API. Jika anda pernah menghadapi cabaran yang sama atau mempunyai petua lain, saya ingin mendengar cerita anda!
Nota Akhir: Ingat bahawa daya tahan bukanlah ciri yang anda tambah—ia adalah ciri yang anda bina ke dalam sistem anda dari bawah. Setiap komponen ini memainkan peranan penting dalam mencipta API yang bukan sahaja berfungsi tetapi terus berfungsi dengan pasti di bawah tekanan.
Atas ialah kandungan terperinci Membina API Berdaya Tahan: Kesilapan yang Saya Buat dan Cara Saya Mengatasinya. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!