《自学SpringCloud微服务架构》之第 8 篇 —— 服务容错保护:Hystrix

问:为什么需要服务容错保护?

答:在微服务架构中,系统往往被拆分成很多个服务单元,每个服务单元之间通过服务注册与订阅的方式相互依赖。而每个服务单元部署在不同的机器或者不同的进程中,依赖一般通过远程调用的方式进行。这样一来,就有可能因为网络故障等原因导致服务调用异常或延迟。如果此时调用方的请求不断增加(如网购的秒杀、抢票等场景),最后因为服务的请求形成积压、阻塞,结果导致服务瘫痪,宕机,俗称“服务雪崩”。为解决这一问题,断路器等技术应运而生,用于服务保护机制。

问:什么是断路器?

答:当某个服务单元发生故障时,通过断路器的故障监控,向调用方返回一个错误响应,而不是长时间等待。这就不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延。生活中,最常见的就是“电路保险丝”,当线路中出现短路时,保险丝立即断掉以切断电路,防止发生火灾等严重后果。

问:为什么用SpringCloud Hystrix?

答:SpringCloud Hystrix 实现了断路器、线程隔离等一系列服务保护功能,它基于 Netflix 的开源框架 Hystrix 实现的,能对延迟和故障提供更强大的容错能力。Hystrix 具备服务降级服务熔断、线程和信号隔离、请求缓存、请求合并和服务监控等强大功能。

 

这篇博客接着上一篇。

我们在 ribbon-consumer 模块的 pom.xml 配置文件中,增加 Hystrix 的依赖。

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>

完整的 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>MyProject</artifactId>
        <groupId>com.study</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>ribbon-consumer</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>${netflix.eureka.client.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
    </dependencies>

</project>

ribbon-consumer 模块的启动类 RibbonConsumerApplication 中加入注解 @EnableCircuitBreaker 开启断路器功能,完整的代码如下:

package com.study;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

/**
 * @author biandan
 * @signature 让天下没有难写的代码
 * @create 2019-10-19 上午 11:50
 */
@EnableEurekaClient
@SpringBootApplication
@EnableDiscoveryClient //让该应用注册为 Eureka 客户端应用,以获得服务发现的能力
@EnableCircuitBreaker //开启断路器功能
//也可以使用 @SpringCloudApplication 注解,这样就不需要
//@SpringBootApplication、@EnableDiscoveryClient、@EnableCircuitBreaker 这3个注解了
//@SpringCloudApplication
public class RibbonConsumerApplication {

    @Bean
    @LoadBalanced //开启客户端负载均衡功能
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

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

说明:

我们跟进 @SpringCloudApplication 这个注解,发现它已经增加了如下注解,这说明一个标准的 SpringCloud 应用已经包含服务发现以及断路器功能。

 

接下来,我们在 study 上鼠标右键,创建一个 package 包:service,与 controller 同一级。

在 service 包下创建 HelloHystrixService 类,代码如下:

package com.study.service;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

/**
 * @author biandan
 * @signature 让天下没有难写的代码
 * @create 2019-10-20 下午 9:22
 */
@Service
public class HelloHystrixService {

    @Autowired
    private RestTemplate restTemplate;

    @HystrixCommand(fallbackMethod = "errorFallBack")
    public String helloHystrix(){
        return restTemplate.getForEntity("http://EUREKA-CLIENT-BIANDAN/say",String.class).getBody();
    }

    //定义 Hystrix 容错之后的返回方法
    public String errorFallBack(){
        return "做开发有 error 不可耻!";
    }
}

说明:

1、在方法 helloHystrix() 上增加 @HystrixCommand(fallbackMethod = "errorFallBack") 注解,并且制定了回调方法 errorFallBack(),然后我们新建 errorFallBack() 回调方法。

2、在类中加入 @Service 注解,主要是让这个类让 Spring 容器来管理,同时 @Service 用于标注业务层组件。当组件不好归类的时候,我们可以使用 @Component 这个注解进行标注。 

接着,我们在 ribbon-consumer 这个模块的 RibbonController 类里加入调用 HelloHystrixService 类的 helloHystrix() 方法。增加如下代码:

//注入 helloHystrixService 对象
    @Autowired
    private HelloHystrixService helloHystrixService;

    @RequestMapping(value = "/hystrix",method = RequestMethod.GET)
    public String testHystrix(){
        return helloHystrixService.helloHystrix();
    }

完整代码如下:

package com.study.controller;

import com.study.service.HelloHystrixService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/**
 * @author biandan
 * @signature 让天下没有难写的代码
 * @create 2019-10-19 下午 11:04
 */
@RestController //@RestController 注解相当于 @ResponseBody + @Controller 合在一起的作用。
//@Controller //如果使用 @Controller 注解,则需要在方法上增加 @ResponseBody 注解
public class RibbonController {

    @Autowired
    private RestTemplate restTemplate;

    //@ResponseBody
    @RequestMapping(value = "/ribbon",method = RequestMethod.GET)
    public String ribbonConsumer(){
        return restTemplate.getForEntity("http://EUREKA-CLIENT-BIANDAN/say",String.class).getBody();
    }

    //注入 helloHystrixService 对象
    @Autowired
    private HelloHystrixService helloHystrixService;

    @RequestMapping(value = "/hystrix",method = RequestMethod.GET)
    public String testHystrix(){
        return helloHystrixService.helloHystrix();
    }
}

 

接下来,我们依次启动服务:注册中心(EurekaServerApplication)、注册中心集群(EurekaServerClusterApplication 可不启动)、服务提供者(EurekaClientApplication 9000端口、然后修改端口号=9001,再启动,一共2个服务。具体方法见上一篇博客)、服务消费者(RibbonConsumerApplication)。

在浏览器中输入:http://main.study.com:10000/hystrix   多次刷新访问,浏览器显示如下内容:

eureka-client-biandan 说:让天下没有难写的代码!from port =9000
eureka-client-biandan 说:让天下没有难写的代码!from port =9001

服务正常访问。

1、测试服务故障或服务宕机情况

我们把 服务提供者(EurekaClientApplication  9000端口)这个服务关闭,只留9001端口服务,再次访问上述地址。

这时候,浏览器的显示结果如下(多次刷新,一般会交替出现):

做开发有 error 不可耻!
eureka-client-biandan 说:让天下没有难写的代码!from port =9001

 说明 Hystrix 断路器功能生效,并没有让客户端长时间等待服务返回结果。

2、测试网络延迟或者服务处理延迟等故障

接下来,我们模拟服务阻塞的情况,也就是服务长时间未响应的情况。在这之前,我们先了解 Hystrix 默认的超时时间

打开依赖包,找到 com.netflix.hystrix:hystrix-core:1.5.18(根据实际依赖的 jar 版本来,本系列教程是这个版本)

在 core 包下找到 com.netflix.hystrix.HystrixCommandProperties 类,如图:

在类 HystrixCommandProperties 中搜索:default_executionTimeoutInMilliseconds

所以得知,Hystrix 默认超时时间是1秒(1000毫秒=1秒)。

我们可以修改 Hystrix 的超时时间,在 ribbon-consumer 模块的 application.yml 配置文件加入如下代码:

# 修改 Hystrix 超时时间
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 1500

接下来,我们改造一下服务提供者 eureka-client 模块的 EurekaClientApplication 类。改造 say() 方法:

    @RequestMapping("/say")
    public String sayHello() throws InterruptedException {
        //生成 0—200 之间的随机数,模拟服务正常和超时的情况
        int sleepTime = new Random().nextInt(2000);
        System.out.println("线程休眠了:"+sleepTime+" 毫秒");
        Thread.sleep(sleepTime);
        return clientName + " 说:让天下没有难写的代码!from port =" + port;
    }

完整代码如下:

package com.study;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Random;

/**
 * @author biandan
 * @signature 让天下没有难写的代码
 * @create 2019-10-15 下午 11:30
 */
@SpringBootApplication
@EnableEurekaClient //表明此类是一个客户端
@RestController
public class EurekaClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaClientApplication.class, args);
    }

    //从 yml 配置文件里获取客户端的名字
    @Value("${spring.application.name}")
    private String clientName;

    @Value("${server.port}")
    private String port;//获取配置文件的端口号

    //测试方法:向调用者返回一句签名
    @RequestMapping("/say")
    public String sayHello() throws InterruptedException {
        //生成 0—200 之间的随机数,模拟服务正常和超时的情况
        int sleepTime = new Random().nextInt(2000);
        System.out.println("线程休眠了:"+sleepTime+" 毫秒");
        Thread.sleep(sleepTime);
        return clientName + " 说:让天下没有难写的代码!from port =" + port;
    }
}

 接下来,重启 服务提供者(EurekaClientApplication 9000端口、然后修改端口号=9001,再启动,一共2个服务。具体方法见上一篇博客),浏览器地址输入:http://main.study.com:10000/hystrix  多次刷新访问,情况如下:

当浏览器输出以下错误信息,我们去 eureka-client 服务(需要对应端口号的服务)的控制台查看信息:

做开发有 error 不可耻!
线程休眠了:1997 毫秒

当浏览器输出信息正常时,我们去 eureka-client 服务(需要对应端口号的服务)的控制台查看信息:

eureka-client-biandan 说:让天下没有难写的代码!from port =9001
线程休眠了:966 毫秒

OK,测试通过。断路器的好处就体现出来了:不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延

 

fallback处理:

当命令执行失败的时候,Hystrix 会进入 fallback 尝试回退处理,我们称该操作为“服务降级”。能够引起服务降级的情况有几种:

1、当前命令处于“熔断/短路”状态,断路器是打开的时候;

2、当前命令的线程池、请求队列或者信号量被占满的时候;

3、HystrixObservableCommand.construct() 或 HystrixCommand.run() 抛出异常的时候。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值