前言

       经过微服务精通之Ribbon原理解析的学习,我们了解到了服务消费者获取服务提供者实例的过程,在这之后,服务消费者会调用服务提供者的接口。但是在调用接口的过程中,我们经常会遇见服务之间的延迟和通信失败的问题,极端情况下,可能会出现因为某个服务性能的下降或者故障宕机,导致服务超时,层层传递,引发雪崩,最终导致整个系统崩溃,而这种情况,我们可以通过熔断器解决这个问题,提高系统的稳定性。


一、熔断器是什么?

       顾名思义,熔断器的作用就是将异常的通道关闭,从而保护整个系统。

       例如,有ABCD四个服务,BCD提供服务给A消费,当D因为负载过高或者网络异常导致响应超时时,会导致A对接很多的等待链接,从而导致A也变得异常,以至于其它依赖A的服务也跟着异常,以此类推,从而导致系统大面积的服务瘫痪,如下图。

sentinel微服务熔断不生效_spring boot


       这个时候,我们可以在所有服务中加上熔断器,正常情况下,熔断器保持关闭状态,当异常时,则开启熔断器。比如D异常的时候,我们可以把A跟D之间的熔断器打开,就可以避免服务的大面积瘫痪了。

二、Hystrix是什么?

       Hystrix是由Netflix开发的,集成到微服务体系里面的一个组件,是微服务体系里面的熔断器。

三、Hystrix原理解析

1.总体流程

sentinel微服务熔断不生效_spring cloud_02


流程说明:

       (1)创建Hystrix请求命令;

       (2)执行Hystrix请求命令;

       (3)请求结果是否被缓存,若有缓存,则直接返回;

       (4)熔断器开关是否打开,若打开,则直接执行步骤8;

       (5)信号量或者线程池资源请求,若信号量或者线程池资源已占满,则请求失败,执行步骤8;

       (6)执行请求,若执行失败或者请求超时(默认超时时间为1秒),则直接执行步骤8;

       (7)汇总请求信息,判断是否要打开熔断器开关;

       (8)执行fallback方法,并返回fallback处理结果;

       (9)返回成功响应。

2.请求命令和执行

       Hystrix请求采用命令模式,其中,HystrixCommand表示单个操作请求命令,HystrixObservableCommand表示多个操作请求命令。
       HystrixCommand执行:
              execute:同步执行,返回一个单结果对象;
              queue:异步执行,返回一个Future对象,其中包含了一个单结果对象。
       HystrixObservableCommand执行:
              observe:返回observable对象,它代表了多个操作结果,是一个Hot observable;
              toObservable:同样返回observable对象,同样代表多个操作结果,但是它是一个Cold observable。
              注:Hot observable和Cold observable的区别,可通过《RxJava 驯服数据流之 hot & cold Observable》这篇文章了解一下。

3.请求合并

       Hystrix支持请求合并,即将一定时间段内对相同依赖服务的请求合并成一个请求。

3.1前提条件

       依赖服务必须有一个多参数接口。比如说,有一个根据用户编号查询用户信息的接口,如下图:

sentinel微服务熔断不生效_spring boot_03


       如果要进行请求合并,则依赖服务USER-SERVICE必须提供一个批量查询用户信息的接口,如下图:

sentinel微服务熔断不生效_sentinel微服务熔断不生效_04

3.2 实现方法

       在单参数调用方法上面添加注解@HystrixCollapser,多参数调用方法上面添加注解@HystrixCommand,如下图:

sentinel微服务熔断不生效_java_05


       @HystrixCollapser参数说明:

              batchMethod:多参数调用方法名。

              collapserProperties:请求合并参数,timerDelayInMilliseconds表示请求合并时间长度,单位是毫秒;上图配置表示Hystrix会将最近100毫秒的请求合并。

3.3 实现原理

       (1)单入参接口配置请求合并以后,Hystrix会对该请求进行拦截,然后将一定时间段内的请求入参合并起来;
       (2)用合并后的请求参数去调用批量处理方法(3.2配置中的batchMethod);
       (3)批量处理方法按照正常的Hystrix请求流程进行处理,得到批量处理结果;
       (4)Hystrix将批量处理结果拆分,并返回给各个单入参接口;
       (5)单入参接口返回结果。

3.4 优缺点

优点:

       (1)降低依赖服务请求量,降低其服务压力;
       (2)减少调用依赖服务时的网络消耗;
       (3)减少调用线程池的线程占用。

缺点:

       增加了接口的响应时间。

3.5 适用场景

       (1)适用于响应时间原本就较长的接口,那合并请求导致的延迟对其几乎无影响;
       (2)适用于并发性能较高,且网络消耗占比较高的接口,那合并请求后,可以减少网络消耗。

4.请求缓存

       从Hystrix处理流程中,我们可以知道,Hystrix可以使用缓存来提高接口性能,相关注解如下图:

sentinel微服务熔断不生效_spring_06


       注:Hystrix缓存的是同一次请求中相同cachekey的查询结果,当新请求进来时,之前的缓存已失效。

5.熔断器状态

5.1 状态说明

       (1)打开状态:该状态下,该接口请求会被拦截,直接进行失败处理;
       (2)关闭状态:该状态下,该接口请求不会被拦截,可以继续后续处理;
       (3)半打开状态:当熔断器打开一段时间后(默认是5秒),Hystrix会允许进行一次接口请求,当请求成功后,关闭熔断器,若请求失败,则再等一段时间后,再进行打开尝试。

5.2 熔断器打开条件

       (1)请求总量达到阈值(默认是10秒内20个请求);
       (2)错误率达到阈值(默认是10秒内错误率50%)。

6.信号量与线程隔离

       信号量和线程池处理请求的流程如下图:

sentinel微服务熔断不生效_spring cloud_07


       线程池流程:当用户请求进来时,熔断器会向线程池请求资源,若请求到资源,则将请求放入线程池中执行,执行完成请求后,再将资源放入线程池;若请求不到资源,则进行降级处理,调用fallback方法。

       信号量流程:当用户请求进来时,熔断器会请求信号量,若请求到信号量,则直接执行请求,执行完成请求后,再释放信号量;若请求不到信号量,则进行降级处理,调用fallback方法。

       两者对比:

sentinel微服务熔断不生效_spring boot_08


四、Hystrix实战

1.Ribbon服务提供者

       注:若已看过《微服务精通之Eureka原理解析》,则此章节可跳过。

(1)创建名为service-hi的maven工程

(2)引入Eureka-client依赖

<?xml version="1.0"?>
<project
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
	xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>com.hxq</groupId>
		<artifactId>spring-cloud-hxq</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>

	<artifactId>service-hi</artifactId>
	<name>service-hi</name>

	<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>
		</dependency>
	</dependencies>

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

       (1)Web依赖:spring-boot-starter-web;
       (2)Eureka-client依赖:spring-cloud-starter-netflix-eureka-client。

(3)创建启动类

package com.hxq;

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.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * 服务提供者
 * 
 * @author Administrator
 *
 */
@SpringBootApplication
@EnableEurekaClient
@RestController
public class ServiceHiApplication {

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

    /**
     * 服务端口
     */
    @Value("${server.port}")
    private String port;

    @RequestMapping("/hi")
    public String home(@RequestParam(value = "name", defaultValue = "hxq") String name) {
        return "hi " + name + " ,i am from port:" + port;
    }
}

       启动类要加上@EnableEurekaClient注解。

(4)创建application.yml配置文件

server:
    port: 9100
    
spring:
    application:
        name: service-hi

eureka:
    serviceUrl:
        defaultZone: http://localhost:8761/eureka/

       配置说明:
              server.port:Eureka客户端服务端口
              spring.application.name:应用名称,将会显示在Eureka界面的应用名称列。
              eureka.serviceUrl.defaultZone:Eureka服务器的地址,类型为HashMap,缺省的Key为 defaultZone;缺省的Value为 http://localhost:8761/eureka。如果服务注册中心为高可用集群时,多个注册中心地址以逗号分隔。

2.修改Ribbon消费者

       注在微服务精通之Ribbon原理解析中的service-ribbon服务的基础上进行修改。

(1)增加Hystrix依赖

<?xml version="1.0"?>
<project
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
	xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>com.hxq</groupId>
		<artifactId>spring-cloud-hxq</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>

	<artifactId>service-ribbon</artifactId>
	<name>service-ribbon</name>

	<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>
		</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>

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

(2)启动类增加Hystrix注解

package com.hxq;

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

/**
 * 服务消费者
 * 
 * @author Administrator
 *
 */
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
@EnableHystrix
public class ServiceRibbonApplication {

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

    /**
     * 负载均衡器
     * 
     * @return
     */
    @LoadBalanced
    @Bean
    RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

       启动类新增@EnableHystrix注解,以启动Hystrix服务。

(3)修改测试接口

package com.hxq.service.impl;

import javax.annotation.Resource;

import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import com.hxq.service.HelloService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult;

@Service("helloService")
public class HelloServiceImpl implements HelloService {

    @Resource
    private RestTemplate restTemplate;

    @Override
    @CacheResult(cacheKeyMethod = "getCacheKey")
    @HystrixCommand(fallbackMethod = "hiError")
    public String hiService(String name) {
        return restTemplate.getForObject("http://SERVICE-HI/hi?name={1}", String.class, name);
    }

    private String getCacheKey(String name) {
        return "cache::" + name;
    }

    public String hiError(String name) {
        return "hi," + name + ",sorry,error!";
    }
}

       (1)@CacheResult注解:参考上述“请求缓存”章节,标注此接口使用接口缓存,缓存键值通过getCacheKey方法生成;
       (2)@HystrixCommand注解:参考上述“请求命令和执行”章节,标注此接口是一个单操作的Hystrix接口,接口调用失败后的回调函数为hiError方法。

(4)增加过滤器,初始化Hystrix上下文

       注:若不使用请求缓存,可不添加此过滤器。

package com.hxq.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;

import org.springframework.stereotype.Component;

import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;

/**
 * 设置Hystrix上下文
 * 
 * @author Administrator
 *
 */
@Component
@WebFilter(urlPatterns = "/*", asyncSupported = true)
public class HystrixRequestContextFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        // 上下文初始化
        HystrixRequestContext context = HystrixRequestContext.initializeContext();
        try {
            chain.doFilter(request, response);
        } finally {
            context.close();
        }
    }

}

3.服务验证

(1)启动service-hi服务

sentinel微服务熔断不生效_spring cloud_09

(2)启动service-ribbon服务

sentinel微服务熔断不生效_sentinel微服务熔断不生效_10

(3)调用测试接口

       在浏览器中输入http://localhost:9200/hi

请求成功

       如下图,可以看到接口返回service-hi服务的数据。

sentinel微服务熔断不生效_java_11

请求失败

       如下图,可以看到接口返回回调方法的数据。

sentinel微服务熔断不生效_spring_12