服务的断路器:Hystrix

配套资料,免费下载
链接:https://pan.baidu.com/s/1la_3-HW-UvliDRJzfBcP_w
提取码:lxfx
复制这段内容后打开百度网盘手机App,操作更方便哦

第一章 Hystrix介绍

1.1、什么是Hystrix

Hystrix是由Netflix开源的一个服务隔离组件,通过服务隔离来避免由于依赖延迟、异常,引起资源耗尽导致系统不可用的解决方案。

1.2、为啥用Hystrix

在分布式系统,我们一定会依赖各种服务,那么这些个服务一定会出现失败的情况,Hystrix就是这样的一个工具,它通过提供了逻辑上延时和错误容忍的解决力来协助我们完成分布式系统的交互。Hystrix通过分离服务的调用点,阻止错误在各个系统的传播,并且提供了错误回调机制,这一系列的措施提高了系统的整体服务弹性。

第二章 Hystrix三大概念

2.1、服务降级含义

当一个目标服务执行时间过长,为了不让客户端无意义盲目等待,此时会立刻返回一个友好提示,比如:服务器过忙,请稍后再试,这就是服务降级。程序运行异常、程序运行超时、服务熔断都会触发服务降级、线程池/信号量打满也会触发服务降级。

2.2、服务熔断含义

这种模式主要是参考电路熔断,如果一条线路电压过高,保险丝会熔断,防止火灾。放到我们的系统中,如果某个目标服务调用慢或者有大量超时,此时,熔断该服务的调用,对于后续调用请求,不在继续调用目标服务,直接返回,快速释放资源,如果目标服务情况好转则恢复调用。

2.3、服务限流含义

上述两种模式都属于出错后的容错处理机制,而限流模式则可以称为预防模式。限流模式主要是提前对各个类型的请求设置最高的QPS阈值,若高于设置的阈值则对该请求直接返回,不再调用后续资源。这种模式不能解决服务依赖的问题,只能解决系统整体资源分配问题,因为没有被限流的请求依然有可能造成雪崩效应。

第三章 Hystrix入门案例

3.1、基础准备工作

我们接下来的所有操作均是在OpenFegin最后完成的工程上进行操作,相关代码请到配套资料中寻找。

(1)我们接下来的所有操作均在service-consumer9002工程中进行,请在pom.xml中添加Hystrix的依赖

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

(2)修改application.yaml,将Ribbon的超时时间全部注释,方便接下来测试

#ribbon:
#  ReadTimeout:  5000
#  ConnectTimeout: 5000

(3)修改service-provider8001ProductControllerfindByPid方法,根据pid分别模拟了运行正常、运行错误、超时等情况

@RequestMapping("/provider/product/findByPid")
public String findByPid(@RequestParam("pid") Integer pid) throws InterruptedException {
    //模拟:pid如果小于0说明出错
    if (pid < 0) { throw new RuntimeException("***** pid 不能负数 *****"); }

    //模拟:pid如果等于0说明业务正常
    if (pid == 0) { System.out.println("***** pid 业务正常 *****"); }

    //模拟:pid如果大于0说明业务超时
    if (pid > 0) { Thread.sleep(3000); }

    //结果:返回服务端口+方法+线程名
    return "8001 findByPid " + Thread.currentThread().getName();
}

(4)修改service-provider8002ProductControllerfindByPid方法,根据pid分别模拟了运行正常、运行错误、超时等情况

@RequestMapping("/provider/product/findByPid")
public String findByPid(@RequestParam("pid") Integer pid) throws InterruptedException {
    //模拟:pid如果小于0说明出错
    if (pid < 0) { throw new RuntimeException("***** pid 不能负数 *****"); }

    //模拟:pid如果等于0说明业务正常
    if (pid == 0) { System.out.println("***** pid 业务正常 *****"); }

    //模拟:pid如果大于0说明业务超时
    if (pid > 0) { Thread.sleep(3000); }

    //结果:返回服务端口+方法+线程名
    return "8002 findByPid " + Thread.currentThread().getName();
}

(5)依次启动如下程序

  1. eureka-server7001
  2. eureka-server7002
  3. service-provider8001
  4. service-provider8002
  5. service-consumer9002

(6)依次输入网址来查看

调用服务出错:http://localhost:9002/consumer/product/findByPid?pid=-1

调用服务正常:http://localhost:9002/consumer/product/findByPid?pid=0

调用服务超时:http://localhost:9002/consumer/product/findByPid?pid=1

正因为有上述故障或不佳表现,才有我们的降级/熔断/限流等技术诞生。

3.2、服务降级学习

3.2.1、单个方法降级

注意:服务降级既可以放到服务提供端,也可以放到服务消费端,因为服务消费端的controller是调用的服务提供端的controller,都是controller,那就都可以用,没有什么区别,不过需要注意一点就是第(2)步的配置,服务提供端是不需要配置这一段内容的,服务消费端就必须要提供这一段配置了。

(1)在入口类中使用@EnableCircuitBreaker注解或@EnableHystrix开启断路器功能,也可以使用一个名为@SpringCloudApplication的注解代替主类上的三个注解(@SpringBootApplication@EnableDiscoveryClient(默认自动开启)、@EnableCircuitBreaker),推荐@EnableHystrix,修改后如下:

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

(2)在application.yaml中新增一段代码:

feign:
  hystrix:
    enabled: true #如果处理自身的容错就开启。如果服务降级放到服务提供端不需要配置这一段代码

(3)在对应的控制器类ProductController上你所要对哪个方法进行服务降级,就对哪个方法进行特殊处理,这里我对findByPid进行处理,处理代码如下:

@HystrixCommand(fallbackMethod = "findByPidFallback", commandProperties = {
        /*这里用来放常见的配置,比如:该方法的超时时间是多少*/
        @HystrixProperty(name = "execution.timeout.enabled", value = "true"),
        @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000")
})
@RequestMapping("/consumer/product/findByPid")
public String findByPid(@RequestParam("pid") Integer pid) {
    return productFeignService.findByPid(pid);
}

/**
 * 服务降级方法,相当于findByPid方法的默认返回值
 * 由上边的从数据库中查出来的动态数据 -》 服务出错调用这个降级方法返回默认值的过程就是服务降级
 *
 * @param pid
 * @return 返回值类型要和原方法保持一致
 */
public String findByPidFallback(@RequestParam("pid") Integer pid) {
    return "发生故障,服务降级 ...";
}

(4)重启service-consumer9002,因为有时候热部署并不能很好的帮我们自动重启服务,手动重启保险

(5)依次输入网址来查看

调用服务出错:http://localhost:9002/consumer/product/findByPid?pid=-1

调用服务正常:http://localhost:9002/consumer/product/findByPid?pid=0

调用服务超时:http://localhost:9002/consumer/product/findByPid?pid=1

注意:hystrix 默认超时时间是 1000 毫秒,如果你后端的响应超过此时间,就会触发服务降级方法fallback,而修改这个默认时间通常有两种解决办法:

第一种:注解属性修改

@RequestMapping("/XXX/XXX")
@HystrixCommand(fallbackMethod = "findByPidFallback",commandProperties = {
        /*这里用来放常见的配置,比如:该方法的超时时间是多少*/
        @HystrixProperty(name = "execution.timeout.enabled",value = "true"),//默认为true,可以不配置
        @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1000")
})

第二种:配置文件配置

ribbon.ReadTimeout=6000
ribbon.ConnectTimeout=3000
hystrix.command.default.execution.timeout.enabled=true
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000

这里有个坑需要注意一下:

如果hystrix.command.default.execution.timeout.enabled为true,则会有两个执行方法超时的配置,一个就是ribbon的ReadTimeout,一个就是断路器hystrix的timeoutInMilliseconds,此时谁的值小谁生效;

如果hystrix.command.default.execution.timeout.enabled为false,则断路器不进行hystrix的超时熔断,而是根据ribbon的ReadTimeout抛出的异常而熔断,而ribbon的ConnectTimeout,配置的是请求服务的超时时间,除非服务找不到,或者网络原因,这个时间才会生效;

ribbon.ReadTimeout=6000
ribbon.ConnectTimeout=3000

3.2.2、多个方法降级

虽然我们可以使用上边那种方式,一个一个的给所有的方法添加服务降级的处理方法,但是,这样一来,你的控制层会多很多跟业务逻辑没有关系的代码,增加了很多代码,我们也成为代码膨胀,有没有一种方式,既可以处理每一个方法给他添加服务降级方法,又能跟控制器类中的方法进行解耦合,我们可以使用Fegin跟Hystrix相结合的方式来进行处理。

(1)我们打开ProductFeignService,会发现这里边的代码不就是Feign用于调用远程服务提供者所定义的接口方法,我们第一步就是重新编写一个类来继承这个接口,因为这个接口中的方法不就是我们ProductController中调用的方法吗。

com.caochenlei.service.impl.ProductFeignServiceFallBackImpl

@Component
public class ProductFeignServiceFallBackImpl implements ProductFeignService {
    @Override
    public List<Product> findAll() {
        return Arrays.asList(
                new Product(1, "小米手机-服务降级 ...", 1000.0D, 100),
                new Product(2, "华为手机-服务降级 ...", 2000.0D, 200),
                new Product(3, "苹果手机-服务降级 ...", 3000.0D, 300)
        );
    }

    @Override
    public String findByPid(Integer pid) {
        return "findByPid 服务降级 ...";
    }
}

(2)你得告诉ProductFeignService当调用远程方法失败后,你应该去哪一个类来找相对应的服务降级fallback来进行服务降级处理。

com.caochenlei.service.ProductFeignService

@Component
@FeignClient(value = "SERVICE-PROVIDER", fallback = ProductFeignServiceFallBackImpl.class)
public interface ProductFeignService {
    ...
}

(3)重新启动service-consumer9002,启动完成以后,手动强制关闭service-provider8001service-provider8002来模拟服务提供者端宕机

(4)依次输入网址来查看

查看商品列表:http://localhost:9002/consumer/product/findAll

调用服务出错:http://localhost:9002/consumer/product/findByPid?pid=-1

调用服务正常:http://localhost:9002/consumer/product/findByPid?pid=0

调用服务超时:http://localhost:9002/consumer/product/findByPid?pid=1

从上边的四个测试中不难看出,如果当前方法上已经增加了一个@HystrixCommand,则会有优先执行自己定义的,如果自己没有定义服务降级的方法,则会走ProductFeignServiceFallBackImpl中定义的方法。这样一来就实现了业务逻辑和fallback解耦操作了。

3.2.3、全局统一处理

我们不确定我们自己的ProductController中所写的方法一定是服务提供者中的方法,我们也有可能有自己的方法,那样的话,使用上边的处理方式,好像似乎会有点问题,我们总不能把我们自己特有的方法也写到ProductFeignService这个接口中吧,你不写到这个接口中,ProductFeignServiceFallBackImpl就没办法实现对应方法的fallback服务降级方法,你当然可以使用第一种的一个一个的使用注解@HystrixCommand来配,但是万一有100多个呢,那你不傻眼了吗,有没有一种可以统一处理全局的服务降级方法,这个全局就是,如果,你自己有指定的我就用你指定的(多个方法降级的也算指定的),你没有指定,都交给我来处理,具体的做法请参考如下步骤:

(1)首先我们需要在service-consumer9002ProductController中添加2个独有的控制器方法,并标注@HystrixCommand注解,代码如下:

@HystrixCommand
@RequestMapping("/consumer/product/findOne")
public String findOne() {
    int a = 1 / 0;//模拟异常
    return "findOne 正常方法 ...";
}

@HystrixCommand
@RequestMapping("/consumer/product/findTwo")
public String findTwo() {
    int a = 1 / 0;//模拟异常
    return "findTwo 正常方法 ...";
}

(2)在service-consumer9002ProductController中编写一个全局服务降级的fallback方法,代码如下:

public String GlobalFallbackMethod(){
    return "全局服务降级fallback ...";
}

(3)在service-consumer9002ProductController上标注一个默认配置的注解,代码如下:

@RestController
@DefaultProperties(defaultFallback = "GlobalFallbackMethod")  //全局的
public class ProductController {
    ...
}

(4)重新启动service-consumer9002

(5)依次输入网址来查看

查看商品列表:http://localhost:9002/consumer/product/findAll

调用服务出错:http://localhost:9002/consumer/product/findByPid?pid=-1

调用服务正常:http://localhost:9002/consumer/product/findByPid?pid=0

调用服务超时:http://localhost:9002/consumer/product/findByPid?pid=1

findOne:http://localhost:9002/consumer/product/findOne

findTwo:http://localhost:9002/consumer/product/findTwo

注意:这个全局服务降级的fallback方法也不是随便写的,至少你的返回值类型都应该和原方法保持一致,有人可能会问,我有的查询一件商品,有的查询商品列表,这怎么可能返回一致,那这种方法是不是没有效果了,我们可以写一个返回结果包装类,来统一处理返回结果,我这里给大家提供一种常见的包装类形式,仅供参考:

/**
 * 统一处理返回结果
 *
 * @author CaoChenLei
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result implements Serializable {
    private Integer code;//状态码,比如:200
    private String msg;//消息标题,比如:查询商品列表成功
    private Object data;//这个data里边就是具体返回的数据,什么类型都支持,如果没有数据就返回null
}

3.3、服务熔断学习

3.3.1、服务熔断演示

(1)重新启动service-provider8001service-provider8002,服务提供者

(2)首先访问:http://localhost:9002/consumer/product/findByPid?pid=0,我们发现可以正常访问

(3)再次访问:http://localhost:9002/consumer/product/findByPid?pid=-1,快速访问10次错误的,然后在访问一次正确的,你会发现正确的不能访问了(能访问再多刷新几次,反复尝试),然后你就不停的访问正确的,发现一会正确的又可以访问了,这个就是服务的熔断

3.3.2、服务熔断类型

  • 熔断打开:请求不再进行调用当前服务,内部设置时钟一般为MTTR(平均故障处理时间),当打开时长达到所设时钟则进入熔断状态
  • 熔断关闭:熔断关闭不会对服务进行熔断
  • 熔断半开:部分请求根据规则调用当前服务,如果请求成功且符合规则,则认为当前服务恢复正常,关闭熔断

3.3.3、服务熔断条件

  1. 当满足一定阀值的时候(默认10秒内超过20个请求次数)
  2. 当失败率达到一定的时候(默认10秒内超过50%请求失败)
  3. 到达以上阀值,断路器将会开启
  4. 当开启的时候,所有请求都不会进行转发
  5. 一段时间之后(默认是5秒),这个时候断路器是半开状态,会让其中一个请求进行转发。如果成功,断路器会关闭,若失败,继续开启。重复4和5

3.3.4、服务熔断设置

3.4、服务限流学习

hystrix限流就是限制你某个微服务的使用量(可用线程数、信号量),hystrix通过线程池的方式来管理微服务的调用,它默认是一个线程池(大小10个) 管理你的所有微服务,你可以给某个微服务开辟新的线程池:

(1)在service-consumer9002ProductController添加如下代码:

/**
 * threadPoolKey:是线程池唯一标识,hystrix会使用该标识来计数,看线程占用是否超过了,超过了就会直接降级该次调用
 * 这里coreSize给他值为2,那么假设你这个方法调用时间是1s执行完,那么在1s内如果有超过2个请求进来的话,剩下的请求则全部降级
 * 其中maxQueueSize是一个线程队列,里面只能放1个请求线程,本来线程数有2个,队列里面允许放一个,那么总共只能有3个请求线程执行,如果超过了就会限流
 */
@HystrixCommand(fallbackMethod = "miaoShaFallback",
        threadPoolKey = "miaoSha",
        threadPoolProperties = {
                @HystrixProperty(name = "coreSize", value = "2"),
                @HystrixProperty(name = "maxQueueSize", value = "1")
        })
@RequestMapping("/consumer/product/miaoSha")
public String miaoSha() throws InterruptedException {
    Thread.sleep(500);//模拟业务逻辑消耗的时间
    return "恭喜你,抢到了,^_^";
}

public String miaoShaFallback() {
    return "秒杀高峰期,线程池已满,服务降级...";
}

(2)如果浏览器快速访问4次,可能并不会看到效果,我这里采用Apache JMeter来进行并发测试,设置了4个线程同时进行访问,结果到了第4个就服务限流了

第四章 Hystrix服务监控

4.1、单个服务监控

4.1.1、监控概述

Hystrix 仪表盘(Hystrix Dashboard),就像汽车的仪表盘实时显示汽车的各项数据一样,Hystrix 仪表盘主要用来监控 Hystrix 的实时运行状态,通过它我们可以看到 Hystrix 的各项指标信息,从而快速发现系统中存在的问题进而解决它,要使用 Hystrix 仪表盘功能,我们首先需要有一个Hystrix Dashboard项目,这个功能我们可以在原来的消费者应用上添加,让原来的消费者应用具备 Hystrix 仪表盘功能,但一般地,微服务架构思想是推崇服务的拆分,Hystrix Dashboard 也是一个服务,所以通常会单独创建一个新的工程专门用做Hystrix Dashboard服务。

4.1.2、工程搭建

(1)创建一个新的子工程,名字叫hystrix-dashboard6001

(2)导入工程所需要的依赖

pom.xml

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

(3)创建一个启动类,并打开Hystrix的面板

com.caochenlei.HystrixDashboard6001Application

@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboard6001Application {
    public static void main(String[] args) {
        SpringApplication.run(HystrixDashboard6001Application.class);
    }
}

(4)编写配置文件application.yaml

server:
  port: 6001

spring:
  application:
    name: hystrix-dashboard6001

hystrix:
  dashboard:
    proxy-stream-allow-list: "*"

(5)我们找到你要监控的服务,这里我们需要配置service-consumer9002的启动类ServiceConsumer9002Application

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

    @Bean
    public ServletRegistrationBean getServlet(){
        HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
        ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
        registrationBean.setLoadOnStartup(1);
        registrationBean.addUrlMappings("/actuator/hystrix.stream");
        registrationBean.setName("HystrixMetricsStreamServlet");
        return registrationBean;
    }
}

(6)重新启动service-consumer9002hystrix-dashboard6001

(7)我们先访问以一下业务看看是不是正常(这一步必须做,否则下一步会出错):http://localhost:9002/consumer/product/miaoSha

(8)访问地址查看ping的信息:http://localhost:9002/actuator/hystrix.stream

(9)把需要监控的地址http://localhost:9002/actuator/hystrix.stream填入http://localhost:6001/hystrix

4.1.3、图表说明

4.2、多个服务监控

4.2.1、监控概述

Hystrix Dashboard 前面已经知道了,它的主要功能是可以对某一项微服务进行监控,但真实情况下,不可能只对一个微服务进行监控,我们有很多微服务,所以我们需要对很多微服务进行监控,这个时候就需要使用到turbine [ˈtɜːbaɪn] 来完成。

4.2.2、工程搭建

(1)打开子项目service-consumer9002,找到eureka的配置,我们需要修改,把服务消费者也注册到eureka的注册中心,因为turbine需要找到被监控的消费者服务的地址,因此需要注册

eureka:
  instance:
    #是否使用 ip 地址注册
    prefer-ip-address: true
    #该实例注册到服务中心的唯一ID
    instance-id: ${spring.cloud.client.ip-address}:${server.port}
  client:
    #是否将自己注册到注册中心,默认为 true
    register-with-eureka: true
    #表示 Eureka Client 间隔多久去服务器拉取注册信息,默认为 30 秒
    registry-fetch-interval-seconds: 10
    #设置服务注册中心地址
    service-url:
      defaultZone: http://root:123456@eureka-server7001.com:7001/eureka/,http://root:123456@eureka-server7002.com:7002/eureka/

(2)由于我们要对多个带有Hystrix(可以放到服务提供端、也可以放到服务消费端)的微服务进行监控,但是目前为止,我们只有一个比较完整的项目,也就是service-consumer9002,因为service-consumer9001并没有Hystrix,为了保持项目整体思路的清晰度,我们就不修改service-consumer9001了,我们要求,重新创建一个子项目service-consumer9003,把service-consumer9002中的所有代码和配置文件都拷贝进来,需要修改的地方只有端口号(主启动类的9002也要改),把所有9002端口修改为9003即可,然后启动service-consumer9003。千万不要直接拷贝项目图省事,否则,你有可能会因为idea的bug导致复制后的项目不能很好的编译。

(3)在进行下一步之前,我们要求你的工程中至少已经启动了以下项目,如果没有启动,请重启:

  1. eureka-server7001
  2. eureka-server7002
  3. service-provider8001
  4. service-provider8002
  5. service-consumer9002
  6. service-consumer9003
  7. hystrix-dashboard6001

(4)在创建一个子项目hystrix-turbine6002,在该项目的pom.xml中添加以下依赖:

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

(5)创建一个配置文件application.yaml,配置文件内容如下:

server:
  port: 6002

spring:
  application:
    name: hystrix-turbine6002

eureka:
  client:
    #是否将自己注册到注册中心,默认为 true
    register-with-eureka: false
    #表示 Eureka Client 间隔多久去服务器拉取注册信息,默认为 30 秒
    registry-fetch-interval-seconds: 10
    #设置服务注册中心地址
    service-url:
      defaultZone: http://root:123456@eureka-server7001.com:7001/eureka/,http://root:123456@eureka-server7002.com:7002/eureka/

turbine:
  #这里写你要监控的微服务,多个服务之间使用逗号分隔
  app-config: SERVICE-CONSUMER9002,SERVICE-CONSUMER9003
  clusterNameExpression: new String("default")

(6)创建一个主启动类com.caochenlei.HystrixTurbine6002Application,主启动类代码如下,然后启动hystrix-turbine6002

@SpringBootApplication
@EnableTurbine
public class HystrixTurbine6002Application {
    public static void main(String[] args) {
        SpringApplication.run(HystrixTurbine6002Application.class);
    }
}

(7)打开浏览器,检查业务是否正常运行,这一步必做,在地址栏输入:http://localhost:9002/consumer/product/miaoSha

(8)打开浏览器,检查业务是否正常运行,这一步必做,在地址栏输入:http://localhost:9003/consumer/product/miaoSha

(9)打开浏览器,查看9002的Hystrix的流信息:http://localhost:9002/actuator/hystrix.stream

(9)打开浏览器,查看9003的Hystrix的流信息:http://localhost:9003/actuator/hystrix.stream

(10)打开Hystrix的聚合信息,在地址栏输入:http://localhost:6002/turbine.stream

(11)打开Hystrix的监控面板,我们现在不是监控某一个微服务,而是监控这个turbine之后的聚合信息流,在地址栏输入地址:http://localhost:6001/hystrix

(12)我们换一个服务地址进行调用,然后看看这个控制面板会不会帮我们监控到,比如:http://localhost:9002/consumer/product/findByPid?pid=0

第五章 Hystrix配置详解

相关文章

Nacos 中的参数有很多,如:命名空间、分组名、服务名、保护...
Nacos 支持两种 HTTP 服务请求,一个是 REST Template,另一...
Nacos 是 Spring Cloud Alibaba 中一个重要的组成部分,它提...
Spring Cloud Alibaba 是阿里巴巴提供的一站式微服务开发解决...
在 Nacos 的路由策略中有 3 个比较重要的内容:权重、保护阈...
前两天遇到了一个问题,Nacos 中的永久服务删除不了,折腾了...