


Créer des API résilientes : les erreurs que j'ai commises et comment je les ai surmontées
Les API sont l'épine dorsale des applications modernes. Lorsque j’ai commencé à créer des API avec Spring Boot, j’étais tellement concentré sur la fourniture de fonctionnalités que j’ai négligé un aspect crucial : la résilience. J’ai appris à mes dépens que la capacité d’une API à gérer les pannes avec élégance et à s’adapter à différentes conditions est ce qui la rend vraiment fiable. Laissez-moi vous expliquer quelques erreurs que j'ai commises en cours de route et comment je les ai corrigées. J'espère que vous pourrez éviter ces pièges au cours de votre propre voyage.
Erreur 1 : ignorer les configurations de délai d'attente
Que s'est-il passé : Dans l'un de mes premiers projets, j'ai construit une API qui effectuait des appels externes vers des services tiers. J’ai supposé que ces services répondraient toujours rapidement et ne prenaient pas la peine de définir des délais d’attente. Tout semblait bien jusqu'à ce que le trafic augmente et que les services tiers commencent à ralentir. Mon API se bloquerait indéfiniment, en attendant une réponse.
Impact : La réactivité de l’API a plongé. Les services dépendants ont commencé à échouer et les utilisateurs ont été confrontés à de longs retards : certains ont même eu la redoutable erreur de serveur interne 500.
Comment je l'ai résolu : C'est à ce moment-là que j'ai réalisé l'importance des configurations de délai d'attente. Voici comment je l'ai corrigé avec 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; } } }
Cette configuration définit non seulement des délais d'attente appropriés, mais inclut également la journalisation pour aider à surveiller les performances des services externes.
Erreur 2 : ne pas mettre en œuvre de disjoncteurs
Que s'est-il passé : Il fut un temps où notre service interne sur lequel nous comptions était tombé en panne pendant plusieurs heures. Mon API n’a pas géré la situation avec élégance. Au lieu de cela, il réessayait sans cesse les requêtes qui échouaient, ajoutant ainsi une charge supplémentaire au système déjà sollicité.
Les pannes en cascade sont l'un des problèmes les plus difficiles dans les systèmes distribués. Lorsqu'un service tombe en panne, cela peut créer un effet domino qui fait tomber l'ensemble du système.
Impact : Les tentatives répétées ont submergé le système, ralentissant d'autres parties de l'application et affectant tous les utilisateurs.
Comment je l'ai réparé : C'est à ce moment-là que j'ai découvert le modèle du disjoncteur. Grâce à Spring Cloud Resilience4j, j'ai pu briser le cycle.
@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(); } }
Ce simple ajout a empêché mon API de se submerger, le service interne ou le service tiers, garantissant ainsi la stabilité du système.
Erreur 3 : faible gestion des erreurs
Que s'est-il passé : Au début, je n'ai pas beaucoup réfléchi à la gestion des erreurs. Mon API a soit généré des erreurs génériques (comme HTTP 500 pour tout), soit exposé des détails internes sensibles dans les traces de pile.
Impact : Les utilisateurs ne comprenaient pas ce qui n'allait pas, et la divulgation de détails internes créait des risques de sécurité potentiels.
Comment je l'ai corrigé : J'ai décidé de centraliser la gestion des erreurs à l'aide de l'annotation @ControllerAdvice de Spring. Voici ce que j'ai fait :
@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; } } }
Cela rend les messages d'erreur clairs et sécurisés, aidant à la fois les utilisateurs et les développeurs.
Erreur 4 : négliger la limitation du taux
Que s'est-il passé : Un beau jour, nous avons lancé une campagne promotionnelle et le trafic vers notre API est monté en flèche. Bien qu'il s'agisse d'une excellente nouvelle pour l'entreprise, certains utilisateurs ont commencé à envoyer des requêtes à l'API, privant ainsi les autres de ressources.
Impact : Performances dégradées pour tout le monde, et nous avons reçu un flot de plaintes.
Comment je l'ai résolu : Pour gérer cela, j'ai implémenté une limitation de débit à l'aide de Bucket4j avec Redis. Voici un exemple :
@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(); } }
Cela garantissait une utilisation équitable et protégeait l'API contre les abus.
Erreur 5 : négliger l'observabilité
Ce qui s'est passé : Chaque fois que quelque chose n'allait pas dans la production, c'était comme chercher une aiguille dans une botte de foin. Je n'avais pas mis en place de journalisation ou de métriques appropriées, donc le diagnostic des problèmes a pris beaucoup plus de temps que prévu.
Impact : Le dépannage est devenu un cauchemar, retardant la résolution des problèmes et frustrant les utilisateurs.
Comment je l'ai corrigé : J'ai ajouté Spring Boot Actuator pour les contrôles de santé et intégré Prometheus à Grafana pour la visualisation des métriques :
@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]"); } }
J'ai également implémenté une journalisation structurée à l'aide de la pile ELK (Elasticsearch, Logstash, Kibana). Cela a rendu les journaux beaucoup plus exploitables.
Points à retenir
Construire des API résilientes est un voyage, et les erreurs font partie du processus. Voici les principales leçons que j'ai apprises :
- Toujours configurer des délais d'attente pour les appels externes.
- Utilisez des disjoncteurs pour éviter les pannes en cascade.
- Centralisez la gestion des erreurs pour la rendre claire et sécurisée.
- Mettre en œuvre une limitation du débit pour gérer les pics de trafic.
Ces changements ont transformé ma façon d'aborder le développement d'API. Si vous avez été confronté à des défis similaires ou si vous avez d’autres conseils, j’aimerais entendre vos histoires !
Remarque de fin : n'oubliez pas que la résilience n'est pas une fonctionnalité que vous ajoutez, c'est une caractéristique que vous intégrez à votre système à partir de zéro. Chacun de ces composants joue un rôle crucial dans la création d'API qui non seulement fonctionnent, mais continuent de fonctionner de manière fiable sous pression.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Outils d'IA chauds

Undresser.AI Undress
Application basée sur l'IA pour créer des photos de nu réalistes

AI Clothes Remover
Outil d'IA en ligne pour supprimer les vêtements des photos.

Undress AI Tool
Images de déshabillage gratuites

Clothoff.io
Dissolvant de vêtements AI

Video Face Swap
Échangez les visages dans n'importe quelle vidéo sans effort grâce à notre outil d'échange de visage AI entièrement gratuit !

Article chaud

Outils chauds

Bloc-notes++7.3.1
Éditeur de code facile à utiliser et gratuit

SublimeText3 version chinoise
Version chinoise, très simple à utiliser

Envoyer Studio 13.0.1
Puissant environnement de développement intégré PHP

Dreamweaver CS6
Outils de développement Web visuel

SublimeText3 version Mac
Logiciel d'édition de code au niveau de Dieu (SublimeText3)

Sujets chauds











Dépannage et solutions au logiciel de sécurité de l'entreprise qui fait que certaines applications ne fonctionnent pas correctement. De nombreuses entreprises déploieront des logiciels de sécurité afin d'assurer la sécurité des réseaux internes. ...

Solutions pour convertir les noms en nombres pour implémenter le tri dans de nombreux scénarios d'applications, les utilisateurs peuvent avoir besoin de trier en groupe, en particulier en un ...

Le traitement de la cartographie des champs dans l'amarrage du système rencontre souvent un problème difficile lors de l'exécution d'amarrage du système: comment cartographier efficacement les champs d'interface du système a ...

Lorsque vous utilisez MyBatis-Plus ou d'autres cadres ORM pour les opérations de base de données, il est souvent nécessaire de construire des conditions de requête en fonction du nom d'attribut de la classe d'entité. Si vous manuellement à chaque fois ...

Commencez le printemps à l'aide de la version IntelliJideaultimate ...

Conversion des objets et des tableaux Java: Discussion approfondie des risques et des méthodes correctes de la conversion de type de distribution De nombreux débutants Java rencontreront la conversion d'un objet en un tableau ...

Explication détaillée de la conception des tables SKU et SPU sur les plates-formes de commerce électronique Cet article discutera des problèmes de conception de la base de données de SKU et SPU dans les plateformes de commerce électronique, en particulier comment gérer les ventes définies par l'utilisateur ...

Comment la solution de mise en cache Redis réalise-t-elle les exigences de la liste de classement des produits? Pendant le processus de développement, nous devons souvent faire face aux exigences des classements, comme l'affichage d'un ...
