springcloud动力节点-04Hystrix

发布时间 2023-12-28 20:13:23作者: 爵岚

Spring Cloud Hystrix

1.前言

1.1 什么是服务雪崩

 

 

 

服务雪崩的本质:线程没有及时回收。
不管是调用成功还是失败,只要线程可以及时回收,就可以解决服务雪崩

1.2 服务雪崩怎么解决

1.2.1 修改调用的超时时长(不推荐)

将服务间的调用超时时长改小,这样就可以让线程及时回收,保证服务可用
优点:非常简单,也可以有效的解决服务雪崩
缺点:不够灵活,有的服务需要更长的时间去处理(写库,整理数据)

1.2.2 设置拦截器

 

 

2.Spring Cloud Hystrix 简介

熔断器,也叫断路器!(正常情况下 断路器是关的 只有出了问题才打开)用来保护微服务不雪崩的方法。思想和我们上面画的拦截器一样。
Hystrix 是 Netflix 公司开源的一个项目,它提供了熔断器功能,能够阻止分布式系统中出现联动故障。Hystrix 是通过隔离服务的访问点阻止联动故障的,并提供了故障的解决方案,从而提高了整个分布式系统的弹性。
微博 弹性云扩容Docker K8s

3.Hystrix 快速入门

当有服务调用的时候,才会出现服务雪崩,所以 Hystrix 常和 OpenFeign,Ribbon 一起出现

3.1 在 OpenFeign 中使用 Hystrix(重点)

3.1.1 启动 provider-order-service: 8761/eureka

3.1.1.1 先创建 rent-car-service,选择依赖: web \ eureka client 

 

3.1.1.2 rent-car-service 修改配置文件

# 应用服务 WEB 访问端口
server:
  port: 8080
spring:
  application:
    name: rent-car-service
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka
  instance:
    hostname: localhost
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}
server: # 源文档
    port: 8082
spring:
    application:
        name: provider-order-service
eureka:
    client:
        service-url:
        defaultZone: http://localhost:8761/eureka
    instance:
        instance-id: ${spring.application.name}:${server.port}
        prefer-ip-address: true        

3.1.1.3 rent-car-service 修改启动类增加一个访问接口

package com.tongda.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class RentCarController {

    @GetMapping("rent")
    public String rent() {
        return "租车成功";
    }
} 

3.1.1.4 rent-car-service 启动测试访问

  

3.1.2 新建 customer-service:Web \ eureka client \ Openfeign \ hystrix

添加依赖

<!-- 熔断机制:https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-hystrix -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
            <version>2.2.10.RELEASE</version>
        </dependency>

3.1.2.1 CustomerController

package com.tongda.controller;

import com.tongda.feign.CustomerRentFeign;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CustomerController {

    @Autowired
    private CustomerRentFeign customerRentFeign;

    @GetMapping("customerRent")
    public String CustomerRent(){
        System.out.println("客户前来租车");
        // 远程调用RPC
        String rent = customerRentFeign.rent();
        return rent;
    }
}

Feign中CustomerRentFeign接口 

package com.tongda.feign;

import com.tongda.feign.hystrix.CustomerRentFeignHystrix;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

// 有实现类备选方案时:fallback=类名.class
// 需要指定熔断的类
@FeignClient(value = "rent-car-service",fallback = CustomerRentFeignHystrix.class)
public interface CustomerRentFeign {

    // 调用ren-car-service
    @GetMapping("rent")
    public String rent();

}

hystrix接口实现:备选方案@FeignClient(value = "rent-car-service",fallback = CustomerRentFeignHystrix.class)

package com.tongda.feign.hystrix;

import com.tongda.feign.CustomerRentFeign;
import org.springframework.stereotype.Component;

/**
 * hystrix熔断机制:
 * feign接口的实现类
 * @Date 2023/7/26 10:06
 * @Version 1.0
 */

@Component // 配置类:组件,必须需要加入ioc容器
public class CustomerRentFeignHystrix implements CustomerRentFeign {

    @Override
    public String rent() {
        return "我是备用方案";
    }
}

3.1.2.2 修改 CustomerRentFeign接口 增加一个 fallback=指定类名.class

// 有实现类备选方案时:fallback=类名.class
// 需要指定熔断的类
@FeignClient(value = "rent-car-service",fallback = CustomerRentFeignHystrix.class)

3.1.2.3 修改 yml 配置文件:Openfeign中配置hystrix熔断器时,circuitbreaker熔断器enabled:true开启

 
# 应用服务 WEB 访问端口
server:
  port: 8081

spring:
  application:
    name: customer-service
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka
  instance:
    hostname: localhost
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}


feign: # Openfeign中开启hystrix
  circuitbreaker:  # 注意
    enabled: true   # 在cloud的F版以前默认开启,但因有了其他的熔断组件,现默认关闭

 

3.1.3 启动 Customer-Service 访问测试;远程调用rent-car-Service

 

3.1.4 关掉 rent-car-service 访问测试; 8080宕机时

 说明 Hystrix 生效了

问题:@Autowired 报红原因

 原因一个类型2个对象造成问题

1. 使用@Resourece, Spring bean名字区分 

2. 使用@Autowired+@Qualifier("指定bean名字")

3. 使用下面方法,选择Syntax解决

 

4.手写断路器

4.1 断路器的设计 

 

 

 4.2 断路器的状态说明以及状态转变

关:服务正常调用 A---》B
开:在一段时间内,调用失败次数达到阀值(5s 内失败 3 次)(5s 失败 30 次的)则断路器打开,直接 return 返回值
半开:断路器打开后,过一段时间,让少许流量尝试调用 B 服务,如果成功则断路器关闭使服务正常调用,如果失败,则继续半开 

4.3 开始设计断路器模型

 

4.3.1 创建项目选择依赖 

4.3.2 创建断路器状态模型 HystrixStatus 

package com.tongda.model;

/**
 * 断路器状态开关: Open\Close\Half_Open半开
 * 引用spring拦截器AOP,先导入依赖
 * @Date 2023/7/28 11:27
 * @Version 1.0
 */

public enum FishStatus {

    CLOSE, 
    OPEN,
    HALF_OPEN

}
4.3.3 创建断路器 Hystrix 
package com.tongda.model;

import lombok.Data;

import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 这个是断路器的模型
 *
 * @Date 2023/7/29 11:43
 * @Version 1.0
 */
@Data
public class Fish {

    // 断路器状态:默认是关闭
    // 成员变量private
    private FishStatus status = FishStatus.CLOSE;

    // 断路器的窗口时间,多少时间内出现问题
    // 静态变量static final
    public static final Integer WINDOWS_TIME = 20;
    // private static final long WINDOWS_SLEEP_TIME = 5L;

    // 最大失败次数,阈值
    public static final Integer MAX_FAIL_COUNT = 3;

    /*
     * 当前这个断路器失败几次
     * i++线程是不安全的
     * AtomicInteger:原子类可以保证线程安全
     * @date 2023/7/31 15:18
     **/
    // 当前失败的次数
    private AtomicInteger currentFailCount = new AtomicInteger(0);

    // 使用多线程:必须要有线程池,分局部和全局,用于技术和清除失败次数
    // 线程池7个参数: 核心数,最大核心数,线程等待时间,时间单位,组织队列,默认的线程工厂,线程策略
    private ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
            4,
            8,
            30,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(2000),
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy()
    );
    
    // 锁对象
    private Object lock = new Object();

    // 如何实现每个5s内,统计到失败次数达到阈值呢?
    // 反向思考,没5s就清空断路器的统计次数,就可以了
    {
        poolExecutor.execute(()->{
            // 定期删除,死循环
            while (true) {
                try {
                    // 进来先睡几秒
                    TimeUnit.SECONDS.sleep(WINDOWS_TIME);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 优化:如果断路器是开的,那么不会去调用,就不会有失败,就不会记录次数,没有必要清零,这个线程可以不执行
                // 清零
                if (this.status.equals(FishStatus.CLOSE)){
                    // 清零
                    this.currentFailCount.set(0);
                }else {
                    // 半开或者开,不需要去记录次数,这个线程可以不工作。释放掉锁
                    synchronized (lock) {
                        try {
                            lock.wait();
                            // 当半开调用成功以后,线程被唤醒了,往下执行,又开始了循环统计了
                            System.out.println("我被唤醒了,开始工作");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });
    }

    // 描述:失败后增加次数,以及修改断路器状态和重置失败次数
    // 成员方案:记录失败次数
    public void addFailCount() {
        // ++i 使用方法,获取失败次数
        int i = currentFailCount.incrementAndGet(); // ++i
        // currentFailCount.getAndIncrement(); // i++
        if (i >= MAX_FAIL_COUNT) {
            // 说失败次数已经到了阈值了,则断路器打开
            // 修改当前状态为open
            this.setStatus(FishStatus.OPEN);
            // 当断路器打开以后,就不能去访问了,需要将他变成半开状态。
            // 等待一个时间窗口,开启一个线程,让断路器变成半开
            // 多线程+睡眠控制
            /*new Thread(() -> {
                try {
                    // 等待
                    TimeUnit.SECONDS.sleep(WINDOW_TIME);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.setStatus(FishStatus.HALF_OPEN); // 断路器成半开
                // 重置失败次数:重置成0,不然下次进来直接就会失败
                this.currentFailCount.set(0);
            }).start(); // 必须。start()启动线程*/
            // 有局部线程池后,通过线程池来执行任务
            poolExecutor.execute(()->{
                try {
                    // 等待
                    TimeUnit.SECONDS.sleep(WINDOWS_TIME);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.setStatus(FishStatus.HALF_OPEN); // 断路器成半开
                // 重置失败次数:重置成0,不然下次进来直接就会失败
                this.currentFailCount.set(0);
            });
        }
    }
}
4.3.4 引入切面类比拦截器 
 <!--spring切面AOP-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
4.3.5 创建 HystrixAspect
package com.tongda.aspect;

import com.tongda.model.Fish;
import com.tongda.model.FishStatus;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import static com.tongda.model.FishStatus.CLOSE;

/**
 * AOP切面技术:@Component配置组件+@Aspect切面注解
 * 这个就类比拦截器
 * 就是要判断单签断路器的状态 从而决定是否发起调用(是否执行目标方法
 * @Date 2023/7/28 11:34
 * @Version 1.0
 */

@Component
@Aspect
public class FishAspect {

    // AOP切点 POINT_CUT=表达式,不够灵活
    public static final String POINTCUT = "execution (* com.tongda.controller.FishController.doRpc(..))";

    // key=哪个服务,value=该服务器提供者对应的断路器,因为一个消费者可以去调佣多个提供者,每个提供者都有自己的断路器
    // 1.先在消费者里创建一个大的容器fishMap, Map<提供者,容器>
    public static Map<String, Fish> fishMap = new HashMap<>();
    static {
        // 假设 是需要去调佣order-service的服务
        fishMap.put("order-service",new Fish());
    }

    // 半开时:随机数用于产生少许流量
    Random random = new Random();

    // 环绕通知@Around:这个就是类比拦截器,使用注解,切面
    // 就是要判断,当前断路器的状态,从而决定是否发起调用(执行目标方法)
    @Around(value = "@annotation(com.tongda.anno.MyFish)")
    // @Around(value = POINTCUT) // 切点属性
    public Object fishAround(ProceedingJoinPoint joinPoint){
        Object result = null;
        // 获取到当前提供者的断路器,服务的熔断器
        Fish fish = fishMap.get("order-service");
        FishStatus status = fish.getStatus();
        
        // 执行调用前先判断断路器的状态
        switch (status) {
            // 正常: 断路器关闭状态,则远程去调用,执行目标方法
            case CLOSE:
                try {
                    result = joinPoint.proceed();
                    return result;
                } catch (Throwable e) {
                    // 说明调用失败,记录次数
                    fish.addFailCount();
                    return "我是备用方案";
                }
            // 断路器打开,不能调用,直接返回
            case OPEN:
                return "我是备用方案";
            // 断路器半开,可以用少许流量(20%)去远程调用
            case HALF_OPEN:
                int i = random.nextInt(5);
                System.out.println(i);
                if (i == 1) {
                    try {
                        // 去调用
                        result = joinPoint.proceed();
                        // 说明成功了,断路器关闭
                        fish.setStatus(CLOSE);
                        synchronized (fish.getLock()){ 
                            fish.getLock().notifyAll(); // 锁住,关闭后需要唤醒计数器线程开始计数。
                        }
                        // 返回
                        return result;
                    } catch (Throwable e) {
                        return "我是备用方案";
                    }
                }
            default:
                return "我是备用方案";
        }

    }

}
4.3.6 创建 TestController 去测试
package com.tongda.controller;


import com.tongda.anno.MyFish;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.client.RestTemplate;

public class FishController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("doRpc")
    @MyFish  // 
    public String doRpc() {
        // 当没有8989端口服务,要不要发起请求,配置model下FishStatus状态开关。
        // 使用AOP切面技术。
        String result = restTemplate.getForObject("http://localhost:8989/abc", String.class);
        return result;
    }
}

熔断器切面注解

package com.tongda.anno;

import java.lang.annotation.*;

/**
 * 熔断器切面注解
 * @Date 2023/7/29 10:17
 * @Version 1.0
 */
// 看注解使用类上还是使用方法上
@Target(ElementType.METHOD) // 注解使用在方法上
@Retention(RetentionPolicy.RUNTIME) // 作用域:运行时有效
@Documented // 文档
@Inherited // 可集成的
public @interface MyFish {

}

5.Hystrix 的常用配置 

 

hystrix:
  command: #用于控制HystrixCommand的行为
    default:
      execution:
        isolation:
          strategy: THREAD #控制HystrixCommand的隔离策略,THREAD->线程池隔离策略(默认),SEMAPHORE->信号量隔离策略
          thread:
            timeoutInMilliseconds: 1000 #配置HystrixCommand执行的超时时间,执行超过该时间会进行服务降级处理
            interruptOnTimeout: true #配置HystrixCommand执行超时的时候是否要中断
            interruptOnCancel: true #配置HystrixCommand执行被取消的时候是否要中断
          timeout:
            enabled: true #配置HystrixCommand的执行是否启用超时时间
          semaphore:
            maxConcurrentRequests: 10 #当使用信号量隔离策略时,用来控制并发量的大小,超过该并发量的请求会被拒绝
      fallback:
        enabled: true #用于控制是否启用服务降级
      circuitBreaker: #用于控制HystrixCircuitBreaker的行为
        enabled: true #用于控制断路器是否跟踪健康状况以及熔断请求
        requestVolumeThreshold: 20 #超过该请求数的请求会被拒绝
        forceOpen: false #强制打开断路器,拒绝所有请求
        forceClosed: false #强制关闭断路器,接收所有请求
      requestCache:
        enabled: true #用于控制是否开启请求缓存
  collapser: #用于控制HystrixCollapser的执行行为
    default:
      maxRequestsInBatch: 100 #控制一次合并请求合并的最大请求数
      timerDelayinMilliseconds: 10 #控制多少毫秒内的请求会被合并成一个
      requestCache:
        enabled: true #控制合并请求是否开启缓存
  threadpool: #用于控制HystrixCommand执行所在线程池的行为
    default:
      coreSize: 10 #线程池的核心线程数
      maximumSize: 10 #线程池的最大线程数,超过该线程数的请求会被拒绝
      maxQueueSize: -1 #用于设置线程池的最大队列大小,-1采用SynchronousQueue,其他正数采用LinkedBlockingQueue
      queueSizeRejectionThreshold: 5 #用于设置线程池队列的拒绝阀值,由于LinkedBlockingQueue不能动态改版大小,使用时需要用该参数来控制线程数

实例配置
实例配置只需要将全局配置中的default换成与之对应的key即可。

hystrix:
  command:
    HystrixComandKey: #将default换成HystrixComrnandKey
      execution:
        isolation:
          strategy: THREAD
  collapser:
    HystrixCollapserKey: #将default换成HystrixCollapserKey
      maxRequestsInBatch: 100
  threadpool:
    HystrixThreadPoolKey: #将default换成HystrixThreadPoolKey
      coreSize: 10
复制代码配置文件中相关key的说明
HystrixComandKey对应@HystrixCommand中的commandKey属性;
HystrixCollapserKey对应@HystrixCollapser注解中的collapserKey属性;
HystrixThreadPoolKey对应@HystrixCommand中的threadPoolKey属性。 
hystrix: # hystrix的全局控制
  command:
    default: # default是全局控制,也可以换成单个方法控制,把default换成方法名即可
      fallback:
      circuitBreaker: #熔断器
        enabled: true  #开启断路器
        requestVolumeThreshold: 3 # 失败次数(阈值)
        sleepWindowInMilliseconds: 20000 # 窗口时间
        errorThresholdPercentage: 60 #失败率
      execution:
        isolation:
          Strategy: thread #隔离方式 thread 线程隔离集合和 SEMAPHORE 信号量隔离级别
          thread:
            timeoutInMilliseconds: 3000 #调用超时时长

ribbon:
  ReadTimeout: 5000 #要结合 feign 的底层 ribbon 调用的时长
  ConnectTimeout: 5000

 

#隔离方式 两种隔离方式 thread 线程池 按照 group(10 个线程)划分服务提供者,用户请求的线程和做远程的线程不一样
# 好处 当 B 服务调用失败了 或者请求 B 服务的量太大了 不会对 C 服务造成影响 用户访问比较大的情况下使用比较好 异步的方式
# 缺点 线程间切换开销大,对机器性能影响
# 应用场景 调用第三方服务 并发量大的情况下
# SEMAPHORE 信号量隔离 每次请进来 有一个原子计数器 做请求次数的++ 当请求完成以后 --
# 好处 对 cpu 开销小
# 缺点 并发请求不易太多 当请求过多 就会拒绝请求 做一个保护机制
# 场景 使用内部调用 ,并发小的情况下
# 源码入门 HystrixCommand AbstractCommand HystrixThreadPool

6.Feign 的工程化实例

 

 

 关系简单,无需建立其他模块。 

 

eureka
ribbon
openfeign
hystrix 

 

 

6.1 创建父项目 feign 

 

6.2 创建子 module

Consumer-user-service 消费者
Provider-order-service 提供者
Model 公共实体类
Provider-api 消费者接口 

 

6.3 父项目 feign 的 pom.xml 

<?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">
    <modelVersion>4.0.0</modelVersion>
    <packaging>pom</packaging>
    <modules>
        <module>project-domain</module>
        <module>common-api</module>
        <module>user-center</module>
        <module>order-center</module>
    </modules>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.8</version>
        <relativePath/>
    </parent>

    <!--打包方式: jar还是war-->
    <!--管理方式:pom-->
    <!--<packaging>pom</packaging>-->


    <groupId>com.tongda</groupId>
    <artifactId>04-feign-project</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!--全局版本号控制-->
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <java.version>1.8</java.version>
        <spring-cloud.version>2022.0.3</spring-cloud.version>
    </properties>

    <!--全局依赖: 子模块都有-->
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

    <!--加载依赖管理: 这里的依赖不会真的引入项目,只是版本控制-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <!--<version>2022.0.3</version>-->
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>

    </dependencyManagement>

    <!--打包、仓库等配置-->
    <!--<build>-->

    <!--</build>-->



</project>

common-api公共接口需要依赖domain实体类,依赖关系pom.xml中

<!--依赖domain:引入-->
    <dependencies>
        <dependency>
            <groupId>com.tongda</groupId>
            <artifactId>project-domain</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

oreder、user模块需要依赖api接口,依赖关系pom.xml中

<!--引入api依赖-->
    <dependencies>
        <dependency>
            <groupId>com.tongda</groupId>
            <artifactId>common-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

 project-domain下创建order实体类 

 需要运行的order-center和user-center完善pom.xml

 <!--web依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.7.8</version>
        </dependency>
        <!--eureka依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-netflix-eureka-client</artifactId>
            <version>3.1.4</version>
        </dependency>

 

  <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.7.8</version>
            </plugin>
        </plugins>
    </build>

application.yml

server:
  port: 8080

spring:
  application:
    name: order-center

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka
  instance:
    hostname: localhost
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}

编写order-center的main方法 

package com.tongda;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.core.annotation.Order;

@SpringBootApplication
@EnableEurekaClient
public class OrderServiceApp {

    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApp.class,args);
    }
}

 common-api公共接口类:引入依赖

<!--依赖domain:引入-->
    <dependencies>
        <dependency>
            <groupId>com.tongda</groupId>
            <artifactId>project-domain</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
    </dependencies>

UserOrderFeign接口中:

package com.tongda.feign;

import lombok.extern.apachecommons.CommonsLog;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.core.annotation.Order;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient(value = "order-service")
public interface UserOrderFeign {

    // 查询订单
    @GetMapping("/order/getOrderByUserId")
    Order getOrderByUserId(@RequestParam Integer userId);
}

order-center下controller中OrderController实现UserOrderFeign接口 

package com.tongda.controller;

import com.tongda.domain.Order;
import com.tongda.feign.UserOrderFeign;
import org.springframework.web.bind.annotation.RestController;

@RestController
// 远程调用提取,放入api公共接口,实现接口
public class OrderController implements UserOrderFeign {

    @Override
    public Order getOrderByUserId(Integer userId) {
        System.out.println(userId);
        Order order = Order.builder()
                .name("鱼香肉丝")
                .price(15D)
                .orderId(1)
                .build();

        return null;
    }
}

common-app和user-center中其中一个添加熔断器

<!--熔断器依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
            <version>2.2.10.RELEASE</version>
        </dependency>

user-center模块

application.yml

server:
  port: 8081

spring:
  application:
    name: user-center

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka
  instance:
    hostname: localhost
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}

pom.xml依赖

<?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>04-feign-project</artifactId>
        <groupId>com.tongda</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>user-center</artifactId>

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

    <!--引入api依赖-->
    <dependencies>
        <dependency>
            <groupId>com.tongda</groupId>
            <artifactId>common-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--web依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.7.8</version>
        </dependency>
        <!--eureka依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-netflix-eureka-client</artifactId>
            <version>3.1.4</version>
        </dependency>
    </dependencies>

</project>

编写启动类

package com.tongda;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

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

yml开启熔断器

server:
  port: 8081

spring:
  application:
    name: user-center
  cloud:
    openfeign: # openfeign下开启
      circuitbreaker:
        enabled: true # 开启熔断器

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka
  instance:
    hostname: localhost
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}
hystrix:
  metrics:
    enabled: true # 开启熔断器

common-api或者user-center中编写熔断器hystrix

package com.tongda.feign.hystrix;

import com.tongda.domain.Order;
import com.tongda.feign.UserOrderFeign;

public class UserOrderFeignHystrix implements UserOrderFeign {
    /*
     * 一般远程调用的熔断可以直接返回null
     * @param userId
     * @return {@link com.tongda.domain.Order}
     *
     * @date 2023/8/7 18:39
     **/
    @Override
    public Order getOrderByUserId(Integer userId) {
        return null;
    }
}

在UserOrderFeign接口中添加指向类 fallback = 类名

 启动测试