搜索
首页Javajava教程构建弹性 API:我犯的错误以及我如何克服这些错误

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

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;
        }
    }
}

此配置不仅设置适当的超时,还包括日志记录以帮助监控外部服务性能。

错误2:没有实施断路器

发生了什么:曾经有一段时间,我们所依赖的内部服务宕机了几个小时。我的 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 压垮自身、内部服务或第三方服务,确保系统稳定性。

错误 3:错误处理能力弱

发生了什么: 早期,我没有对错误处理投入太多考虑。我的 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 免遭滥用。

错误5:忽视可观察性

发生了什么:每当生产中出现问题时,就像大海捞针一样。我没有适当的日志记录或指标,因此诊断问题花费的时间比应有的时间要长。

影响:故障排除变成了一场噩梦,延迟了问题解决并使用户感到沮丧。

我是如何解决这个问题的:我添加了 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]");
    }
}
</errorresponse></errorresponse>

我还使用 ELK Stack(Elasticsearch、Logstash、Kibana)实现了结构化日志记录。这使得日志更具可操作性。

要点

构建弹性 API 是一个旅程,错误是这个过程的一部分。以下是我学到的主要经验教训:

  1. 始终为外部调用配置超时。
  2. 使用断路器来防止级联故障。
  3. 集中错误处理,使其清晰且安全。
  4. 实施速率限制以管理流量峰值。

这些变化改变了我进行 API 开发的方式。如果您遇到过类似的挑战或有其他建议,我很想听听您的故事!

尾注:请记住,弹性不是您添加的功能,而是您从头开始构建到系统中的特性。这些组件中的每一个在创建 API 方面都发挥着至关重要的作用,这些 API 不仅可以工作,而且可以在压力下继续可靠地工作。

以上是构建弹性 API:我犯的错误以及我如何克服这些错误的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
如何将Maven或Gradle用于高级Java项目管理,构建自动化和依赖性解决方案?如何将Maven或Gradle用于高级Java项目管理,构建自动化和依赖性解决方案?Mar 17, 2025 pm 05:46 PM

本文讨论了使用Maven和Gradle进行Java项目管理,构建自动化和依赖性解决方案,以比较其方法和优化策略。

如何使用适当的版本控制和依赖项管理创建和使用自定义Java库(JAR文件)?如何使用适当的版本控制和依赖项管理创建和使用自定义Java库(JAR文件)?Mar 17, 2025 pm 05:45 PM

本文使用Maven和Gradle之类的工具讨论了具有适当的版本控制和依赖关系管理的自定义Java库(JAR文件)的创建和使用。

如何使用咖啡因或Guava Cache等库在Java应用程序中实现多层缓存?如何使用咖啡因或Guava Cache等库在Java应用程序中实现多层缓存?Mar 17, 2025 pm 05:44 PM

本文讨论了使用咖啡因和Guava缓存在Java中实施多层缓存以提高应用程序性能。它涵盖设置,集成和绩效优势,以及配置和驱逐政策管理最佳PRA

如何将JPA(Java持久性API)用于具有高级功能(例如缓存和懒惰加载)的对象相关映射?如何将JPA(Java持久性API)用于具有高级功能(例如缓存和懒惰加载)的对象相关映射?Mar 17, 2025 pm 05:43 PM

本文讨论了使用JPA进行对象相关映射,并具有高级功能,例如缓存和懒惰加载。它涵盖了设置,实体映射和优化性能的最佳实践,同时突出潜在的陷阱。[159个字符]

Java的类负载机制如何起作用,包括不同的类载荷及其委托模型?Java的类负载机制如何起作用,包括不同的类载荷及其委托模型?Mar 17, 2025 pm 05:35 PM

Java的类上载涉及使用带有引导,扩展程序和应用程序类负载器的分层系统加载,链接和初始化类。父代授权模型确保首先加载核心类别,从而影响自定义类LOA

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
1 个月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
1 个月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
1 个月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.聊天命令以及如何使用它们
1 个月前By尊渡假赌尊渡假赌尊渡假赌

热工具

MinGW - 适用于 Windows 的极简 GNU

MinGW - 适用于 Windows 的极简 GNU

这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。

EditPlus 中文破解版

EditPlus 中文破解版

体积小,语法高亮,不支持代码提示功能

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境