MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

微服务降级的常见场景与实现方式

2022-08-294.3k 阅读

微服务降级的常见场景

在微服务架构中,由于各个服务之间相互依赖,当某个服务出现故障或性能问题时,可能会导致整个系统的级联故障。微服务降级就是为了应对这种情况,通过牺牲部分非核心功能或服务的方式,确保核心服务的可用性和稳定性。以下是一些常见的需要进行微服务降级的场景。

高并发场景

  1. 流量洪峰 在一些特殊的时间节点,如电商的促销活动(双 11、618 等),系统会迎来大量用户的访问,形成流量洪峰。以一个电商系统为例,在促销活动开始的瞬间,商品详情页的访问量可能会在短时间内激增数倍甚至数十倍。假设平时商品详情页服务每秒能处理 1000 个请求,而在促销活动时,请求量可能达到每秒 10000 个。如果商品详情页服务没有进行合理的限流和降级处理,就可能因为过载而崩溃。一旦商品详情页服务不可用,不仅用户无法查看商品信息,后续依赖商品详情数据的下单、支付等核心服务也会受到影响。
  2. 突发热点事件 除了计划性的促销活动,一些突发的热点事件也会导致流量的瞬间爆发。例如,某个明星突然发布了一条与某品牌相关的微博,大量粉丝会瞬间涌入该品牌的官网或电商平台。这些突发流量可能会使相关的微服务不堪重负。比如该品牌官网的品牌介绍服务,平时可能只有少量的常规访问,但在热点事件发生后,请求量可能突然上升,若不进行降级,可能导致服务响应缓慢甚至宕机,进而影响整个网站的用户体验。

服务故障场景

  1. 依赖服务不可用 在微服务架构中,一个服务往往会依赖多个其他服务。例如,一个订单服务可能依赖库存服务来检查商品库存,依赖用户服务来获取用户信息。如果库存服务由于网络故障、服务器硬件故障等原因不可用,订单服务若继续尝试调用库存服务,就会出现大量的请求超时。在这种情况下,订单服务如果不进行降级处理,会导致自身资源被耗尽,最终也无法正常提供服务,影响整个下单流程。
  2. 服务性能下降 即使服务没有完全不可用,但如果其性能大幅下降,也可能需要进行降级。比如,一个搜索服务原本可以在 100 毫秒内返回搜索结果,但由于算法优化不当或数据量突然增大等原因,现在平均响应时间延长到了 1 秒。对于一些对响应时间要求较高的业务场景(如电商搜索,用户希望能快速得到搜索结果),这样的性能下降会严重影响用户体验。此时,为了保证整体系统的可用性,可能需要对搜索服务进行降级,比如降低搜索结果的精度或返回固定的默认结果。

网络问题场景

  1. 网络延迟 在分布式系统中,服务之间通过网络进行通信。当网络出现延迟时,服务调用的响应时间会变长。例如,两个位于不同数据中心的微服务之间进行通信,正常情况下网络延迟为 50 毫秒,但由于网络拥塞等原因,延迟上升到了 500 毫秒。对于一些实时性要求较高的业务,如在线游戏的对战服务,这样的网络延迟会导致游戏体验变差,玩家可能会感受到明显的卡顿。为了保证游戏的基本流畅性,可能需要对一些非关键的服务功能进行降级,如减少游戏中的特效展示等。
  2. 网络抖动 网络抖动表现为网络延迟的不稳定波动。例如,在移动网络环境下,信号强弱的变化会导致网络抖动。一个移动应用的后端微服务在与移动客户端进行数据交互时,可能会因为网络抖动出现频繁的短暂通信中断。如果微服务没有应对网络抖动的降级策略,就可能会不断尝试重连,消耗大量资源,最终导致服务不可用。比如,移动支付服务在网络抖动时,若不进行降级处理,可能会出现多次支付请求重复提交的问题,给用户和商家带来损失。

微服务降级的实现方式

实现微服务降级有多种方式,下面将从不同的技术层面和工具来详细介绍。

基于代码层面的实现

  1. 手动编写降级逻辑 在代码中直接编写降级逻辑是一种较为基础的实现方式。以 Java 语言为例,假设我们有一个订单服务,依赖库存服务来检查商品库存。
public class OrderService {
    private InventoryService inventoryService;

    public OrderService(InventoryService inventoryService) {
        this.inventoryService = inventoryService;
    }

    public boolean placeOrder(Order order) {
        try {
            // 调用库存服务检查库存
            boolean hasStock = inventoryService.checkStock(order.getProductId(), order.getQuantity());
            if (hasStock) {
                // 下单逻辑
                return true;
            } else {
                return false;
            }
        } catch (Exception e) {
            // 库存服务调用失败,进行降级处理
            System.out.println("库存服务调用失败,采用默认库存充足处理");
            // 这里简单返回默认库存充足
            return true;
        }
    }
}

在上述代码中,当调用库存服务 checkStock 方法出现异常时,捕获异常并进行降级处理,直接返回默认的库存充足状态。这种方式简单直接,但对于复杂的业务逻辑和多个依赖服务,代码会变得冗长且难以维护。 2. 使用设计模式实现降级 可以利用设计模式中的代理模式来实现降级。以动态代理为例,假设我们有一个用户服务接口 UserService 及其实现类 UserServiceImpl,在客户端调用用户服务时,通过代理类来实现降级逻辑。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class UserServiceProxy implements InvocationHandler {
    private Object target;

    public UserServiceProxy(Object target) {
        this.target = target;
    }

    public Object getProxy() {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            return method.invoke(target, args);
        } catch (Exception e) {
            // 服务调用失败,进行降级处理
            System.out.println("用户服务调用失败,返回默认用户信息");
            // 这里返回默认用户信息
            return new User("defaultUser", "defaultEmail");
        }
    }
}

在使用时,可以这样创建代理对象:

public class Main {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        UserServiceProxy proxy = new UserServiceProxy(userService);
        UserService proxyService = (UserService) proxy.getProxy();
        User user = proxyService.getUserById(1);
        System.out.println(user);
    }
}

通过代理模式,将服务调用的异常处理和降级逻辑集中在代理类中,使得业务代码和降级代码分离,提高了代码的可维护性和可扩展性。

基于框架层面的实现

  1. Hystrix Hystrix 是 Netflix 开源的一款用于处理分布式系统的延迟和容错的库。它通过添加延迟容忍和容错逻辑来控制这些分布式系统之间的交互。 引入依赖:在基于 Maven 的 Java 项目中,添加 Hystrix 依赖。
<dependency>
    <groupId>com.netflix.hystrix</groupId>
    <artifactId>hystrix-core</artifactId>
    <version>1.5.18</version>
</dependency>

使用方式:以一个简单的商品价格查询服务为例。

import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;

public class ProductPriceCommand extends HystrixCommand<Double> {
    private Long productId;

    public ProductPriceCommand(Long productId) {
        super(HystrixCommandGroupKey.Factory.asKey("ProductGroup"));
        this.productId = productId;
    }

    @Override
    protected Double run() throws Exception {
        // 实际调用商品价格服务
        return getProductPriceFromService(productId);
    }

    @Override
    protected Double getFallback() {
        // 降级处理,返回默认价格
        System.out.println("商品价格服务调用失败,返回默认价格");
        return 0.0;
    }

    private Double getProductPriceFromService(Long productId) {
        // 模拟调用外部服务获取商品价格
        return 100.0;
    }
}

在使用时:

public class Main {
    public static void main(String[] args) {
        ProductPriceCommand command = new ProductPriceCommand(1L);
        Double price = command.execute();
        System.out.println("商品价格: " + price);
    }
}

Hystrix 提供了熔断器模式,当服务调用失败次数达到一定阈值时,熔断器会打开,后续请求直接进入降级逻辑,不再尝试调用实际服务,从而避免大量无效的调用导致系统资源耗尽。同时,Hystrix 还提供了线程隔离、信号量隔离等机制来保证系统的稳定性。 2. Resilience4j Resilience4j 是一个轻量级的容错库,旨在为 Java 8 和函数式编程提供弹性。它提供了熔断器、限流、重试等功能。 引入依赖:同样在 Maven 项目中添加依赖。

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-circuitbreaker</artifactId>
    <version>1.7.1</version>
</dependency>

使用方式:以一个订单创建服务为例。

import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
import io.vavr.CheckedFunction0;
import io.vavr.control.Try;

public class OrderCreationService {
    private final CircuitBreaker circuitBreaker;

    public OrderCreationService() {
        CircuitBreakerConfig config = CircuitBreakerConfig.custom()
               .failureRateThreshold(50)
               .waitDurationInOpenState(Duration.ofSeconds(10))
               .build();
        CircuitBreakerRegistry registry = CircuitBreakerRegistry.of(config);
        circuitBreaker = registry.circuitBreaker("orderCreation");
    }

    public boolean createOrder(Order order) {
        CheckedFunction0<Boolean> decoratedCall = CircuitBreaker.decorateCheckedSupplier(circuitBreaker, () -> {
            // 实际创建订单逻辑
            return createOrderInDatabase(order);
        });
        return Try.of(decoratedCall).recover(t -> {
            // 降级处理,记录日志并返回失败
            System.out.println("订单创建服务调用失败,进行降级处理");
            return false;
        }).get();
    }

    private boolean createOrderInDatabase(Order order) {
        // 模拟数据库操作创建订单
        return true;
    }
}

Resilience4j 的配置相对灵活,通过设置 failureRateThreshold 等参数可以调整熔断器的行为。它与 Java 8 的函数式编程风格结合紧密,代码简洁明了。

基于服务网格层面的实现

  1. Istio Istio 是一个开源的服务网格,它提供了流量管理、安全和可观察性等功能,也支持微服务降级。在 Istio 中,可以通过编写 DestinationRule 和 VirtualService 来实现降级。 DestinationRule 配置
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: product-service
spec:
  host: product-service
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 100
    outlierDetection:
      consecutiveErrors: 5
      interval: 10s
      baseEjectionTime: 30s
      maxEjectionPercent: 10

上述配置定义了对 product - service 的连接池设置和异常检测规则。当连续出现 5 次错误时,将该实例从负载均衡池中移除 30 秒,最大移除比例为 10%。 VirtualService 配置

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: product-service
spec:
  hosts:
  - product-service
  http:
  - route:
    - destination:
        host: product-service
        subset: v1
    fault:
      abort:
        httpStatus: 503
        percentage:
          value: 10

此配置表示在访问 product - service 时,有 10% 的请求会被故意返回 503 状态码,模拟服务故障,触发客户端的降级逻辑。通过 Istio 的这些配置,可以在不修改应用代码的情况下实现微服务的降级和容错处理,实现了服务治理的集中化和标准化。 2. Linkerd Linkerd 也是一个服务网格,它专注于提供轻量级的服务间通信和可靠性保障。在 Linkerd 中,可以通过配置路由规则来实现降级。例如,假设我们有一个用户服务 user - service 和一个订单服务 order - service,订单服务依赖用户服务。可以通过以下方式在 Linkerd 中配置降级: 首先,定义一个 ServiceProfile 来描述用户服务的预期行为。

apiVersion: linkerd.io/v1alpha2
kind: ServiceProfile
metadata:
  name: user-service
spec:
  routes:
  - condition:
      method: GET
      pathRegex: /users/.*
    responseClassifier:
      status:
        - "200-299: Success"
        - "400-499: ClientError"
        - "500-599: ServerError"

然后,在订单服务的配置中,可以根据用户服务的状态来进行降级。例如,如果用户服务返回 500 错误的比例超过一定阈值,订单服务可以采取降级措施,如返回默认用户信息。具体的实现需要结合 Linkerd 的 API 和工具来动态调整路由和行为,实现对依赖服务的容错和降级处理。

通过上述从代码层面、框架层面和服务网格层面的多种实现方式,可以有效地应对微服务架构中的各种降级场景,保障系统的高可用性和稳定性。在实际应用中,需要根据项目的具体需求、技术栈和规模等因素,选择合适的降级实现方式。