Spring cloud入门-11:服务降级-Hystrix

本文介绍了在分布式系统中面临的雪崩效应问题,并详细讲解了Hystrix如何通过服务降级来防止这种情况。通过构建服务提供模块8001和消费模块80,演示了Hystrix的使用,包括异常处理、超时配置和服务降级方法。同时,展示了在服务调用超时或异常时,消费端如何返回备选响应,确保系统稳定。
摘要由CSDN通过智能技术生成

1、Hystrix入门介绍

1.1 分布式系统面临的问题

  前面已经构建了erureka集群(7001,7002)、服务提供模块集群(8001,8002)、和消费服务模块(80)的简单demo。
  但是在实际的分布式系统中,一般不是和demo这样简单的调用依赖关系,而是多级的服务调用关系,但是这些服务之间的相互调用在某些时候不可避免地会出现失败。
  比如微服务A调用微服务B和微服务C,微服务B又调用微服务D,微服务C又调用微服务E。如果微服务D的调用时间过长或者已经不可用了。此时,A调用C,C调用D,在D这里调用时间长或不可用的话,会导致该服务其实一致没有返回但是在高流量的情况下,微服务A还会继续接收到流量,此时前面的服务都还没有处理完响应因此对微服务A的持续调用就会占用越来越多的系统资源,导致延迟增加,备份队列,线程和其他系统资源紧张,从而导致整个系统发生更多的级联故障,进而引起系统崩溃,发生所谓的“雪崩效应”

1.2 Introduction to Hystrix

  针对上面分布式系统中遇到的问题,本节介绍Hystrix。首先访问官网,看下介绍:https://github.com/Netflix/Hystrix
  在这里插入图片描述
  Hystrix 是一个用于处理分布式系统的延迟(latency)和容错(tolerance)的开源库,旨在隔离对远程系统、服务和第 3 方库的访问点,停止级联故障(cascading failure),使故障不可避免的复杂分布式系统具有弹性(enable resilience)。
  也就是当某个服务发生故障后,Hystrix可以向调用方返回一个预期的、可处理的备选响应(FallBack),而不是长时间等待或者抛出调用方法无法处理的异常从而保证服务调用方的线程不会长时间地占用,从而避免故障在分布式系统中的蔓延,乃至雪崩

2、构建服务提供模块8001

  还是和之前一样,构建注册进eureka集群的服务提供模块8001。
  服务降级主要在以下4种情况下会发生:

  • 程序运行异常
  • 超时
  • 服务熔断触发服务降级
  • 线程池/信号量打满导致服务降级

2.1 业务类新增异常返回

  在服务提供模块(8001)中的查看订单的业务逻辑中,如果id为小于等于0,那么就抛出异常。
  在这里插入图片描述

package com.example.springcloud.controller;

import com.example.springcloud.entities.CommonResult;
import com.example.springcloud.entities.Payment;
import com.example.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

@RestController
@Slf4j
public class PaymentController {
    @Resource
    private PaymentService paymentService;

    @Value("${server.port}")
    private String serverPort;

    @PostMapping(value = "/payment/create")
    public CommonResult create(@RequestBody Payment payment) {
        int result = paymentService.create(payment);
        log.info("插入结果" + result);

        if (result > 0) {
            return new CommonResult(200, "插入数据库成功, serverPort: " + serverPort, result);
        } else {
            return new CommonResult(500, "插入数据失败, serverPort: " + serverPort);
        }
    }

    @GetMapping(value = "/payment/get/{id}")
    public CommonResult getPaymentById(@PathVariable("id") Long id) throws Exception{
        if (id<=0) {
            throw new Exception("订单编号必须大于0");
        }

        Payment payment = paymentService.getPaymentById(id);
        log.info("读取订单: " + payment);

        if (payment==null) {
            return new CommonResult(500, "未查询到该订单,查询id: " + id);
        } else {
            return new CommonResult(200, "查询成功, serverPort: " + serverPort, payment);
        }
    }

    @GetMapping(value = "/payment/feign/timeout")
    public String PaymentFeignTimeout() {
        // 模拟较长时间的业务处理,时间为3秒
        int timeOut = 3;
        try {
            TimeUnit.SECONDS.sleep(timeOut);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "长时间业务处理:" + serverPort;
    }
}

3、构建服务消费模块80

3.1 建module

  构建一个包含服务降级hystrix的服务消费模块:cloud-consumer-order-feign-hystrix-80。改模块的环境构建可参考cloud-consumer-order-feign-80工程。
在这里插入图片描述

3.2 改pom

依赖包新增hystrix:spring-cloud-starter-netflix-hystrix。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2021</artifactId>
        <groupId>org.example.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-consumer-order-feign-hystrix-80</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <!-- 导入 eureka client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>2.2.2.RELEASE</version>
        </dependency>
        <!-- 导入公共包-->
        <dependency>
            <groupId>org.example.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!-- spring boot web 依赖模块:web, actuator-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
            <version>1.14.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

3.3 写yml

在配置文件中开启feign-hystrix属性。

server.port=8080

spring.application.name=cloud-order-service

# 添加Eureka Client配置
# 表示是否将自己注册进Eureka Server,默认为true
eureka.client.register-with-eureka=true
# 表示是否从Eureka Server抓取已有的注册信息,默认为true。
# 该配置在单节点时无所谓,集群配置时必须设置为true,才能配合ribbon使用负载均衡
eureka.client.fetchRegistry=true
#eureka.client.service-url.defaultZone=http://localhost:7001/eureka  # 单机版
eureka.client.service-url.defaultZone=http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka  # 集群版

# 建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ribbon.CoonecTimeout=5000
# 建立连接后从服务器读取到可用资源所用的时间
ribbon.ReadTimeout=5000

# 指定feign日志以什么级别监控哪个接口
logging.level.com.example.springcloud.service.PaymentFeignService=debug

# 开启feign-hystrix属性
feign.hystrix.enabled=true

3.4 主启动

启动类上加上启动Hystrix的注解:@EnableHystrix

package com.example.springcloud;

import cn.hutool.db.sql.Order;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients
@EnableHystrix
public class OrderHystrixMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderHystrixMain80.class, args);
    }
}

3.5 业务类

  构建openfeign支付服务接口,直接复制服务提供模块的controller即可。
  两个接口:
  1)getPaymentById:根据id查询订单
  2)PaymentFeignTimeout:长时间业务处理接口,业务处理时间为3秒

package com.example.springcloud.service;

import com.example.springcloud.entities.CommonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@Component
@FeignClient(value = "cloud-payment-service")
public interface PaymentHystrixService {
    @GetMapping(value = "/payment/get/{id}")
    public CommonResult getPaymentById(@PathVariable("id") Long id);

    @GetMapping(value = "/payment/feign/timeout")
    public String PaymentFeignTimeout();
}

  构建订单服务controller,复制PaymentHystrixService即可,接口路径上添加/consumer表示是服务消费接口。
  针对服务端可能出现的长时间调用或者服务异常的情况,给每个接口添加对应的服务降级方法。
  当服务2秒后未返回,或者服务发生异常,80服务接口直接调用备选方法(fallback method)返回

package com.example.springcloud.controller;

import com.example.springcloud.entities.CommonResult;
import com.example.springcloud.service.PaymentHystrixService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@Slf4j
public class OrderHystrixController {
    @Resource
    private PaymentHystrixService paymentHystrixService;

    @GetMapping(value = "/consumer/payment/get/{id}")
    @HystrixCommand(
            fallbackMethod = "getPaymentByIdFallbackMethod",
            commandProperties = {
                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000")
            }
    )
    public CommonResult getPaymentById(@PathVariable("id") Long id) {
        return paymentHystrixService.getPaymentById(id);
    }

    @GetMapping(value = "/consumer/payment/feign/timeout")
    @HystrixCommand(
            fallbackMethod = "PaymentFeignTimeoutFallbackMethod",
            commandProperties = {
                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000")
            }
    )
    public String PaymentFeignTimeout() {
        return paymentHystrixService.PaymentFeignTimeout();
    }

    // getPaymentById接口的服务降级方法
    public CommonResult getPaymentByIdFallbackMethod(@PathVariable("id") Long id) {
        String msg = "getPaymentById接口繁忙,请稍后再试,当前处理线程:" + Thread.currentThread().getName();
        return new CommonResult(500, msg);
    }

    // PaymentFeignTimeout接口的服务降级方法
    public String PaymentFeignTimeoutFallbackMethod() {
        return "PaymentFeignTimeout接口繁忙,请稍后再试,当前处理线程:" + Thread.currentThread().getName();
    }
}

3.6 测试

3.6.1 测试查看订单接口:getPaymentById

首先测试没有异常的情况,也就是id大于0的情况:http://localhost:8080/consumer/payment/get/3
在这里插入图片描述
  然后测试有异常的情况,也就是id小于等于0的情况:http://localhost:8080/consumer/payment/get/-3。
  可以看到当服务提供接口发生异常的时候,80接口直接返回备选方法的返回结果,而不会返回服务提供接口的异常返回信息。
在这里插入图片描述

3.6.2 测试长时间业务处理接口:PaymentFeignTimeout

  由于在8001接口中,我们设置的长时间业务处理有3秒。而在服务消费模块80的controller这里设置的超时时间为2s。因此调用消费服务模块的接口一定会返回备选方法的返回值。
  服务消费接口:http://localhost:8080/consumer/payment/feign/timeout
  在这里插入图片描述
  根据上面的测试,我们可以得到,当服务异常或者超时的情况下,可以发生服务降级,也就是返回该服务的备选方法的结果,避免程序发生级故障,导致系统崩溃。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值