Spring Cloud Gateway 熔断降级

 

一转眼 2023 年就要过去了,今天查了一下冬至是哪天,原来是 22 号上个周五,也已经过去了。趁着还有一点时间,把 spring 的熔断和降级复习复习。

0x01 概念

服务降级:一般指在服务器压力剧增的时候,根据实际业务使用情况,对一些服务和页面有策略的不处理或者用一种简单的方式进行处理,从而释放服务器的资源以保证核心业务的正常高效运行。

服务降级是从整个系统的负荷情况出发和考虑的,为了预防某些功能出现负荷过载或者响应变慢的情况,在内部暂时舍弃一些非核心接口和数据的请求,而直接返回一个提前准备好的 fallback 信息。这样虽然提供的是一个有损的服务,但却可以保护了整个系统的稳定和可用性。

服务降级需要考虑:

  • 那些为核心服务,哪些不是
  • 降级策略
  • 自动/手动降级

服务熔断:是应对为服务雪崩效应的一种链路保护机制。当调用链中的某个服务不可用或者响应时间太长,会进行服务熔断,不再对该节点服务调用,快速返回错误的响应信息,当检测到该节点调用正常后,再恢复调用链路。

Spring Cloud 框架里,熔断机制是通过 Hystrix 实现的。Hystrix 会监控为服务之间的调用状况,当失败的调用到一定的阈值(默认 20次/5秒)就会启动熔断机制。

服务熔断需要考虑:

  • 如何判断服务变得不稳定
  • 如何探知服务是否恢复

服务降级和服务熔断的区别

  • 触发原因: 服务熔断是链路上摸个服务引起的,服务降级是从整体负载情况考虑的。
  • 管理目标: 服务熔断是一个框架层级的处理,服务降级是业务层级的处理。
  • 实现方式: 服务熔断通常自动恢复,服务降级通常人工控制。

0x02 示例

以项目中使用 spring cloud 版本为例:

    <properties>
        <spring.bood-version>3.0.12</spring.bood-version>
        <spring-cloud.version>2022.0.0</spring-cloud.version>
        <java.version>17</java.version>
    </properties>

Step 1, 添加依赖:

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
        </dependency>
    </dependencies>

Step 2, application.yml 配置

spring:
  cloud:
    gateway:
      routes:
        - id: web-flux-demo-2
          predicates:
            - Path=/demo/**
          uri: http://localhost:8080/
          filters:
            - StripPrefix=1
            # 降级配置
            - name: CircuitBreaker
              args:
                name: testOne
                # 降级接口地址
                fallbackUri: forward:/v1/fallback

Step 3, 在 Gateway 中实现一个熔断的 fallback 接口

@RestController
public class Call1FallbackController {

    @GetMapping("/v1/fallback")
    public Map<String, String> fallback() {

        Map<String, String> result = new HashMap<>();
        result.put("code", "error");
        result.put("msg", "What the fuck can I do!");
        return result;
    }
}

Step 4, 定制

package c.b.cheng.wfd2.config;

import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.timelimiter.TimeLimiterConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.circuitbreaker.resilience4j.ReactiveResilience4JCircuitBreakerFactory;
import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JConfigBuilder;
import org.springframework.cloud.client.circuitbreaker.Customizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.time.Duration;

/**
 * <p>
 * <strong>
 * Describe the function in one sentence.
 * </strong><br /><br />
 * As the title says.
 * </p>
 *
 * @author Cheng, Chao - 2023/12/26 00:17 <br />
 * @see Object
 * @since 1.0
 */
@Configuration
public class Resilience4JCircuitBreakerConfig {

    private static final Logger LOGGER = LoggerFactory.getLogger(Resilience4JCircuitBreakerConfig.class);

    @Bean
    public Customizer<ReactiveResilience4JCircuitBreakerFactory> defaultCustomizer() {
        LOGGER.info(">>>>>>>>> ");
        return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
                .timeLimiterConfig(
                        TimeLimiterConfig
                                .custom()
                                .timeoutDuration(Duration.ofSeconds(4))
                                .build())
                .circuitBreakerConfig(CircuitBreakerConfig.ofDefaults())
                .build());
    }
}

参考和抄袭: