Rumah > Java > javaTutorial > Membina API Berdaya Tahan: Kesilapan yang Saya Buat dan Cara Saya Mengatasinya

Membina API Berdaya Tahan: Kesilapan yang Saya Buat dan Cara Saya Mengatasinya

Mary-Kate Olsen
Lepaskan: 2025-01-04 15:48:40
asal
620 orang telah melayarinya

Building Resilient APIs: Mistakes I Made and How I Overcame Them

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.

Kesilapan 1: Mengabaikan Konfigurasi Tamat Masa

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;
        }
    }
}
Salin selepas log masuk
Salin selepas log masuk

Konfigurasi ini bukan sahaja menetapkan tamat masa yang sesuai tetapi juga termasuk pengelogan untuk membantu memantau prestasi perkhidmatan luaran.

Kesilapan 2: Tidak Melaksanakan Pemutus Litar

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();
    }
}
Salin selepas log masuk
Salin selepas log masuk

Tambahan mudah ini menghalang API saya daripada mengatasi dirinya sendiri, perkhidmatan dalaman atau perkhidmatan pihak ketiga, memastikan kestabilan sistem.

Kesilapan 3: Pengendalian Ralat yang Lemah

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;
        }
    }
}
Salin selepas log masuk
Salin selepas log masuk

Ini menjadikan mesej ralat jelas dan selamat, membantu pengguna dan pembangun.

Kesilapan 4: Mengabaikan Had Kadar

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();
    }
}
Salin selepas log masuk
Salin selepas log masuk

Ini memastikan penggunaan yang adil dan melindungi API daripada penyalahgunaan.

Kesilapan 5: Mengabaikan Kebolehmerhatian

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]");
    }
}
Salin selepas log masuk

Saya juga melaksanakan pengelogan berstruktur menggunakan ELK Stack (Elasticsearch, Logstash, Kibana). Ini menjadikan log lebih mudah diambil tindakan.

Bawa pulang

Membina API yang berdaya tahan adalah satu perjalanan dan kesilapan adalah sebahagian daripada proses. Berikut ialah pengajaran utama yang saya pelajari:

  1. Sentiasa konfigurasi tamat masa untuk panggilan luaran.
  2. Gunakan pemutus litar untuk mengelakkan kegagalan lata.
  3. Memusatkan pengendalian ralat untuk menjadikannya jelas dan selamat.
  4. Laksanakan pengehadan kadar untuk mengurus lonjakan trafik.

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!

sumber:dev.to
Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Artikel terbaru oleh pengarang
Tutorial Popular
Lagi>
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan