07.SpringCloud - Spring Cloud Netflix 之 Hystrix熔断器(七)
07.SpringCloud - Spring Cloud Netflix 之 Hystrix熔断器(七)
阅读本文前可先参考
SpringCloud - Spring Cloud根/父项目,开发准备(二)_MinggeQingchun的博客-CSDN博客
在微服务架构中,一个应用往往由多个服务组成,这些服务之间相互依赖,依赖关系错综复杂。
通常情况下,一个用户请求往往需要多个服务配合才能完成。
如下图所示,我们有3个微服务 订单服务,库存服务,用户服务形成一个微服务系统

请求1需要调用 订单服务,库存服务,用户服务 3个服务才能完成
如果 库存服务 响应延迟或者没有响应,则会造成 订单服务 的线程挂起等待,如果大量的用户请求下订单,或导致大量的请求堆积,引起 订单服务 也不可用,如果还有另外一个服务依赖于 订单服 务,如用户服务,它需要查询用户订单,那么 用户服务 查询订单也会引起大量的延迟和请求积,导致用户服务也不可用
从以上过程可以看出,当微服务系统的一个服务出现故障时,故障会沿着服务的调用链路在系统中疯狂蔓延,最终导致整个微服务系统的瘫痪,这就是"雪崩效应"。为了防止此类事件的发生,微服务架构引入了"熔断器"的一系列服务容错和保护机制。
一、熔断器
熔断器(Circuit Breaker)一词来源物理学中的电路知识,它的作用是当线路出现故障时,迅速切断电源以保护电路的安全。
在微服务领域,熔断器最早是由 Martin Fowler 在他发表的 《Circuit Breaker》一文中提出。与物理学中的熔断器作用相似,微服务架构中的熔断器能够在某个服务发生故障后,向服务调用方返回一个符合预期的、可处理的降级响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常。这样就保证了服务调用方的线程不会被长时间、不必要地占用,避免故障在微服务系统中的蔓延,防止系统雪崩效应的发生。
二、Hystrix
Hystrix被称为熔断器(断路器),Spring Cloud Hystrix 是基于 Netflix 公司的开源组件 Hystrix 实现的,它提供了熔断器功能,能够有效地阻止分布式微服务系统中出现联动故障,以提高微服务系统的弹性。Spring Cloud Hystrix 具有服务降级、服务熔断、线程隔离、请求缓存、请求合并以及实时故障监控等强大功能。
在微服务系统中,Hystrix 主要帮我们实现了
1、服务降级
2、请求限流
1、服务降级
Hystrix 提供了服务降级功能,能够保证当前服务不受其他服务故障的影响,提高服务的健壮性。
当某个微服务响应时间过长,发生异常,或者服务不可用了,我们不能把错误信息返回回来,或者让它一直卡在那里,所以要准备一个对应的策略(一个方法),当发生这种问题时,我们直接调用这个备用的方法来快速返回一个默认的结果,让请求得到快速响应
我们可以通过重写 HystrixCommand 的 getFallBack() 方法或 HystrixObservableCommand 的 resumeWithFallback() 方法,使服务支持服务降级
1、新建一个springboot Module(springcloud-6-service-eureka-hystrix-consumer),设置父项目 (服务消费者)
2、添加 hystrix 依赖,spring-cloud-starter-netflix-hystrix
<!-- spring-cloud-starter-netflix-hystrix -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency><!--继承统一的父项目-->
    <parent>
        <groupId>com.company</groupId>
        <artifactId>springcloud-demo</artifactId>
        <version>1.0.0</version>
<!--        <relativePath/> <!– lookup parent from repository –>-->
    </parent>
    <groupId>com.company</groupId>
    <artifactId>springcloud-6-service-eureka-hystrix-consumer</artifactId>
    <version>1.0.0</version>
    <name>springcloud-6-service-eureka-hystrix-consumer</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!--spring web 起步依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--依赖统一的springcloud-service-commons项目-->
        <dependency>
            <groupId>com.company</groupId>
            <artifactId>springcloud-2-service-common</artifactId>
            <version>1.0.0</version>
        </dependency>
        <!-- springboot 开发自动热部署 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>
        <!--spring-cloud-starter-netflix-eureka-client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- spring-cloud-starter-netflix-hystrix -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>3、application.prperties配置文件中配置访问端口8081,以及配置超时时间
主要配置
#hystrix 默认超时时间是 1000 毫秒,如果你后端的响应超过此时间,就会触发断路器
#链接超时时长(建立连接后从服务端读取到可用资源所用的时间)
ribbon.ReadTimeout=6000
#feign.client.config.default.read-timeout=6000
#读取超时时长(建立连接所用的时间,适用于网络状况正常的情况下,两端连接所需要的时间)
ribbon.ConnectTimeout=3000
#feign.client.config.default.connect-timeout=3000
#如果hystrix.command.default.execution.timeout.enabled为true,则会有两个执行方法超时的配置,一个就是ribbon的ReadTimeout,一个就是熔断器hystrix的timeoutInMilliseconds, 此时谁的值小谁生效;
#如果hystrix.command.default.execution.timeout.enabled为false,则熔断器不进行超时熔断,而是根据ribbon的ReadTimeout抛出的异常而熔断,也就是取决于ribbon的ConnectTimeout,配置的是请求服务的超时时间,除非服务找不到,或者网络原因,这个时间才会生效;
#设置HystrixCommand.run()的执行是否有超时限制
#hystrix.command.default.execution.timeout.enabled=false
#设置调用者等待命令执行的超时限制,超过此时间,HystrixCommand被标记为TIMEOUT,并执行回退逻辑
#hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000server.port=8081
#设置应用名称,对应Eureka控制台下 DS Replicas 的 Application
spring.application.name=springcloud-6-service-eureka-hystrix-consumer
#每间隔5s,向Eureka服务注册中心发送一次心跳,证明服务是否依然"存活"
eureka.instance.lease-renewal-interval-in-seconds=30
#告诉服务端,如果10s之内没有发送心跳,就代表故障,将本服务踢出
eureka.instance.lease-expiration-duration-in-seconds=60
#告诉服务端,服务实例以IP作为链接,不是取机器名
eureka.instance.prefer-ip-address=false
#注册服务实例ID,,服务ID必须唯一
eureka.instance.instance-id=springcloud-6-service-eureka-hystrix-consumer
#注册中心的链接地址  http://localhost:8761/eureka
eureka.client.service-url.defaultZone=http://eureka8761:8761/eureka,http://eureka8762:8762/eureka,http://eureka8763:8763/eureka
#hystrix 默认超时时间是 1000 毫秒,如果你后端的响应超过此时间,就会触发断路器
#链接超时时长(建立连接后从服务端读取到可用资源所用的时间)
ribbon.ReadTimeout=6000
#feign.client.config.default.read-timeout=6000
#读取超时时长(建立连接所用的时间,适用于网络状况正常的情况下,两端连接所需要的时间)
ribbon.ConnectTimeout=3000
#feign.client.config.default.connect-timeout=3000
#如果hystrix.command.default.execution.timeout.enabled为true,则会有两个执行方法超时的配置,一个就是ribbon的ReadTimeout,一个就是熔断器hystrix的timeoutInMilliseconds, 此时谁的值小谁生效;
#如果hystrix.command.default.execution.timeout.enabled为false,则熔断器不进行超时熔断,而是根据ribbon的ReadTimeout抛出的异常而熔断,也就是取决于ribbon的ConnectTimeout,配置的是请求服务的超时时间,除非服务找不到,或者网络原因,这个时间才会生效;
#设置HystrixCommand.run()的执行是否有超时限制
#hystrix.command.default.execution.timeout.enabled=false
#设置调用者等待命令执行的超时限制,超过此时间,HystrixCommand被标记为TIMEOUT,并执行回退逻辑
#hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=50004、 Springboot启动类上添加@EnableCircuitBreaker 注解或 @EnableHystrix开启断路器功能,也可以使用一个名为@SpringCloudApplication 的注解代替主类上的三个注解@SpringBootApplication @EnableDiscoveryClient @EnableCircuitBreaker
@EnableHystrix  //开启Hystrix的服务熔断降级支持
@EnableFeignClients  //表示开启 Spring Cloud OpenFeign的支持功能
@EnableEurekaClient  //开启 Eureka client服务
@SpringBootApplication
public class EurekaHystrix6ConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaHystrix6ConsumerApplication.class, args);
    }
}5、使用 springcloud-2-service-common 项目中的 ProviderGoodsRemoteClient 服务接口
@Component
@FeignClient("springcloud-6-service-eureka-hystrix-provider")
//@FeignClient("springcloud-5-service-eureka-openfeign-provider")
public interface ProviderGoodsRemoteClient {
    /**
     * 声明一个feign的接口,它的实现是服务提供者的controller实现
     */
    @GetMapping(value = "/eureka/hystrix/service/goodList")
//    @GetMapping(value = "/eureka/openfeign/service/goodList")
    public List<Goods> goods();
}6、创建服务提供者访问方法,基于RESTFUL风格
server.port=9001
#设置应用名称,对应Eureka控制台下 DS Replicas 的 Application(集群部署服务提供者,Application一致,对应多个eureka.instance.instance-id)
spring.application.name=springcloud-6-service-eureka-hystrix-provider
#设置mysql数据库连接信息
spring.datasource.url=jdbc:mysql://localhost:3306/springcloud?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=admin123456
#每间隔5s,向Eureka服务注册中心发送一次心跳,证明服务是否依然"存活"
eureka.instance.lease-renewal-interval-in-seconds=5
#告诉服务端,如果10s之内没有发送心跳,就代表故障,将本服务踢出
eureka.instance.lease-expiration-duration-in-seconds=10
#告诉服务端,服务实例以IP作为链接,不是取机器名
eureka.instance.prefer-ip-address=false
#注册服务实例ID,,服务ID必须唯一
eureka.instance.instance-id=springcloud-6-service-eureka-hystrix-provider
#注册中心的链接地址  http://localhost:8761/eureka
eureka.client.service-url.defaultZone=http://eureka8761:8761/eureka,http://eureka8762:8762/eureka,http://eureka8763:8763/eureka@RestController
public class GoodsController {
    @Autowired
    private GoodsService goodsService;
    @GetMapping(value = "/eureka/hystrix/service/goodList")
    public List<Goods> goodList(){
        List<Goods> goodsList = goodsService.getAllGoods();
        System.out.println("查询商品列表成功:");
        for (Goods good:goodsList) {
            System.out.println("查询商品:"+ good);
        }
        return goodsList;
    }
}7、创建服务消费者访问方法,基于RESTFUL风格
注:hystrix 默认超时时间是 1000 毫秒,如果你后端的响应超过此时间,就会触发断路器
我们设置 2个 异常
//1、服务超时,会降级
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //2、服务异常,会降级
        /*String str = null;
        if (str == null) {
            throw new RuntimeException("服务异常了.");
        }*/hystrix 默认超时时间是 1000 毫秒,如果你后端的响应超过此时间,就会触发断路器
1、使用application.properties配置文件中的配置
ribbon.ReadTimeout=6000
ribbon.ConnectTimeout=3000
hystrix.command.default.execution.timeout.enabled=true hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=50002、或者此处的
hystrix.command.default.execution.timeout.enabled=true;
application.properties配置文件可以不写 1 中配置
@HystrixProperty(name = "execution.timeout.enabled",value = "true"), @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "5000")如果hystrix.command.default.execution.timeout.enabled为false,则熔断器不进行超时熔断,而是根据ribbon的ReadTimeout抛出的异常而熔断,也就是取决于ribbon的ConnectTimeout,配置的是请求服务的超时时间,除非服务找不到,或者网络原因,这个时间才会生效;
HystrixCommand注解
fallbackMethod:配置回调方法,即服务降级方法
ignoreExceptions:配置不进入降级方法的异常
commandProperties:hystrix属性设置,包括超时设置
threadPoolkey:线程池唯一标识threadPoolProperties:线程池属性设置,包括核心线程树和队列大小,可以做限流
@RestController
public class GoodsController {
    //产品服务的接口地址(直连)
//    private final String GOODS_SERVICE_URL = "http://localhost:9001/service/goodList";
    //产品服务的接口地址 (注册中心服务名)
//    private final String GOODS_SERVICE_EUREKA_HYSTRIX_URL = "http://springcloud-6-service-eureka-hystrix-provider/eureka/hystrix/service/goodList";
    @Autowired
    private ProviderGoodsRemoteClient providerGoodsRemoteClient;
    @Autowired
    private RestTemplate restTemplate;
    @GetMapping(value = "/springcloud/eureka/hystrix/goodList")
    @HystrixCommand(fallbackMethod = "fallback",
                    commandProperties = {
                        /*
                        hystrix 默认超时时间是 1000 毫秒,如果你后端的响应超过此时间,就会触发断路器
                        1、使用application.properties配置文件中的
                            ribbon.ReadTimeout=6000
                            ribbon.ConnectTimeout=3000
                            hystrix.command.default.execution.timeout.enabled=true
                            hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000
                        2、或者此处的 hystrix.command.default.execution.timeout.enabled=true;application.properties配置文件可以不写 1 中配置
                            @HystrixProperty(name = "execution.timeout.enabled",value = "true"),
                            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "5000")
                        如果hystrix.command.default.execution.timeout.enabled为false,则熔断器不进行超时熔断,而是根据ribbon的ReadTimeout抛出的异常而熔断,也就是取决于ribbon的ConnectTimeout,配置的是请求服务的超时时间,除非服务找不到,或者网络原因,这个时间才会生效;
                        * */
//                        @HystrixProperty(name = "execution.timeout.enabled",value = "false"),
//                            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "5000")
                    },
                ignoreExceptions = Throwable.class
    )
    public @ResponseBody Object getGoodList(){
        //hystrix 默认超时时间是 1000 毫秒,如果你后端的响应超过此时间,就会触发断路器
        //1、服务超时,会降级
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //2、服务异常,会降级
        /*String str = null;
        if (str == null) {
            throw new RuntimeException("服务异常了.");
        }*/
        // 调用远程的一个controller, restful的调用,通过openfeign这种声明式的远程调用,providerGoodsRemoteClient就像dubbo里面的接口层一样
        return  providerGoodsRemoteClient.goods();
        //调用远程的一个controller(Restful风格调用)
//        ResponseEntity<Object> responseEntity = restTemplate.getForEntity(GOODS_SERVICE_EUREKA_HYSTRIX_URL, Object.class);
//        return responseEntity.getBody();
    }
    public @ResponseBody Object fallback(Throwable throwable){
        throwable.printStackTrace();
        System.out.println(throwable.getMessage());
        return "/springcloud/eureka/hystrix/goodList 服务降级了";
    }
}8、启动Eureka(springcloud-3-service-eureka) ,服务提供者(springcloud-6-service-eureka-hystrix-provider),服务消费者(springcloud-6-service-eureka-hystrix-consumer)


Hystrix异常处理
1、我们在调用服务提供者时,服务提供者可能抛出异常,我们自己也可能抛异常,默认情况下方法抛了异常会自动进行服务降级,交给服务降级中的方法去处理;
当我们自己发生异常后,只需要在服务降级方法中添加一个 Throwable 类型的参数就能够获取到抛出的异常的类型
public @ResponseBody Object fallback(Throwable throwable){
        throwable.printStackTrace();
        System.out.println(throwable.getMessage());
        return "/springcloud/eureka/hystrix/goodList 服务降级了";
    }2、 如果远程服务有一个异常抛出后我们不希望进入到服务降级方法中去处理,而是直接将异常抛给用户,那么我们可以在@HystrixCommand 注解中添加忽略异常
@HystrixCommand(fallbackMethod = "fallback", ignoreExceptions=Throwable.class)2、请求限流
Hystrix限流就是限制你某个微服务的使用量(可用线程数、信号量)
Hystrix通过线程池的方式来管理微服务的调用,它默认是一个线程池(大小10个) 管理你的所有微服务,可以给某个微服务开辟新的线程池
@HystrixCommand(fallbackMethod = "fallback",
                    //threadPoolKey 是线程池唯一标识, hystrix 会使用该标识来计数,看线程占用是否超过了, 超过了就会直接降级该次调用
                    threadPoolKey = "goodsService",
                    threadPoolProperties = {
                        //coreSize,线程池核心线程数,默认为10;值为2 那么假设你这个方法调用时间是1s执行完, 那么在1s内如果有超过2个请求进来的话,剩下的请求则全部降级
                        @HystrixProperty(name = "coreSize", value = "2"),
                            //maxQueueSize,线程队列,如果maxQueueSize=-1的话,则该选项不起作用;里面只能放一个请求线程,本来线程数有2个,队列里面允许放一个,那么总共只能有3个请求线程执行,如果超过了就会限流
                            @HystrixProperty(name = "maxQueueSize", value = "1")
                    }
                )
    @GetMapping(value = "/springcloud/eureka/hystrix/goodLimitList")
    public @ResponseBody Object getGoodLimitList(){
        ResponseEntity<Object> responseEntity = restTemplate.getForEntity(GOODS_SERVICE_EUREKA_HYSTRIX_URL, Object.class);
        return responseEntity.getBody();
    }来源:https://blog.csdn.net/MinggeQingchun/article/details/125308948
