Spring Cloud认知学习(四):熔断器Hystrix的使用


💡上一篇介绍一个用于声明式调用服务的组件Fegin,主要用于解决前面的服务调用与restTemplate耦合紧密的问题。 Spring Cloud认知学习(三):声明式调用Feign的使用
💡这一篇介绍一个新的组件Hystrix,Hystrix是一个熔断器,可以用于解决微服务调用中发送的服务熔断和服务降级问题。


Hystrix


* Hystrix是服务熔断器,用于解决服务熔断和服务降级的情况。

服务熔断和服务降级

  • 主要是解决服务熔断和服务降级的问题。
    服务熔断主要用了解决服务雪崩,我们先来介绍一下服务雪崩的概念。
  • 服务雪崩:假如有很多个服务都需要调用A服务,但A突然卡住了,响应很慢,在高并发的情况下,此时A服务就会持有了过量的资源,而导致其他服务的资源不足,从而影响其他服务的使用,甚至可能传播性地导致整个系统崩溃。
  • 服务熔断:熔断的概念是什么呢?就是假如说你家里用了超大的功率的电器,你家的电闸就会为了避免造成危险而帮你跳闸断电。对于服务雪崩也是这样的,他会防止服务占用过量的资源。
    原理:用户的请求将不再直接调用服务,而是通过一个线程池来调用服务,当线程池中没有空闲的线程时,就会返回一个简单的执行结果(需要设定,可能是一个提示信息或者一个假数据)。
  • 服务降级:当某一个主要的服务端的资源不够的时候,可能此时其他的不太重要服务需要进行关闭来为他腾出空间(就好像与支付功能相比,换头像这个功能就可以暂时为支付功能献身了),这就是服务的降级。虽然降级了,但理论上还应该给消费者一个保底的回应,比如说返回提示说服务已经关闭。由于此时服务的提供者已经关闭了,所以这个判断只能发生在服务的消费者中。


💡:对于上面的这两种情况,如果是服务熔断,既可以部署在消费者也可以部署在生产者,因为只是一个“等太久就不等待”的处理,这个等太久既可以事消费者判断也可以事生产者判断;如果是服务降级,由于此时服务提供者大多都关闭了,所以这时候Hystrix只能部署在消费者端。

💡:由于Hystrix其实就是对于错误情况的处理,服务的过度消耗资源(雪崩)和降级其实都是服务不可用,他们的处理在Hystrix其实都是一样的。所以下面就根据Hystrix的部署位置来演示。


简单使用示例:

下面的代码可以参考:Hystrix简单使用实验

部署在服务提供者

这次我们修改模块spring-cloud-user-service-8003
1.导入依赖:

        <!--增加hystrix依赖 start-->
        <!--旧版本的依赖:
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix</artifactId>
        </dependency>-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <!--增加hystrix依赖 end-->

2.修改UserController,给listUser增加一个Hystrix处理方法listUserByHystirx。
@HystrixCommand(fallbackMethod = "listUserByHystirx")代表发生不可用的时候,就会调用listUserByHystirx来返回结果。

package com.progor.study.controller;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.progor.study.entity.User;
import com.progor.study.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;

// 由于返回json数据,懒得加注解@ResponseBody了,加个RestController
@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/user/{id}")
    public User getUser(@PathVariable Integer id) {
        User user = userService.getUser(id);
        if (user == null) {
            throw new RuntimeException("该ID:" + id + "没有对应的用户信息");
        }
        return user;
    }

    private static int count = 0;
    @GetMapping("/user/list")
    // fallbackMethod时发生错误时调用的方法,
    // commandProperties用于配置熔断器,requestVolumeThreshold代表请求多少次就不再尝试调用原方法,直接调用错误处理方法。
    @HystrixCommand(fallbackMethod = "listUserByHystirx",commandProperties = {
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "5"), //请求次数
    }) //
    public List<User> listUser() throws InterruptedException {
        count = count+1;  // 假装偶尔发生了bug
        System.out.println(count);
        // 偶尔发生时
//        if (count%2 == 0){
//            Thread.sleep(5000);
//        }
        // 一直发生时:
        Thread.sleep(5000);


        List<User> users = userService.listUser();
        return users;
    }

    public List<User> listUserByHystirx() {
        User user = new User(0,"null","null");
        List<User> users = new ArrayList<>();
        users.add(user);
        return users;
    }

}

<br>

3.在主程序类中开启hystrix:

@SpringBootApplication
@EnableEurekaClient
@EnableHystrix // 开启hystrix
public class UserService8003Application {
    public static void main(String[] args) {
        SpringApplication.run(UserService8003Application.class, args);
    }
}

4.启动spring-cloud-user-service-8003,访问http://localhost:8003/user/list,发现如果发生错误的时候,会调用listUserByHystirx来返回
🔵如果你通过服务消费者来调用8003的服务的话,这时候也会一样会在发生错误的时候,调用listUserByHystirx来返回。
🔵注意,我上面的代码是一次执行成功,一次执行失败这样的顺序。



部署在服务消费者

目前我们只有一个服务消费者,所以我们要修改spring-cloud-user-consumer-80

1.在spring-cloud-user-consumer-80模块导入依赖:

        <!--增加hystrix依赖 start-->
        <!--旧版本的依赖:
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix</artifactId>
        </dependency>-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <!--增加hystrix依赖 end-->

2.修改MessageController2,增加@HystrixCommand


// 这个控制器用来处理使用fegin的情况
@RestController
public class MessageController2 {

    @Autowired
    private MessageService messageService;

    @GetMapping("/msg2/list")
    // 使用HystrixCommand
    @HystrixCommand(fallbackMethod = "listByHystirx",commandProperties = {
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "5"), //请求次数
    }) //如果请求次数达到5次都是失败,那么直接调用listByHystirx
    public Map<String, String> list() {
        return messageService.list();
    }

    public Map<String, String> listByHystirx() {
        Map<String, String> map = new HashMap<>();
        map.put("msg","服务端已停止服务");
        return map;
    }
}

3.修改主程序类,开始Hystrix,@EnableHystrix

@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "USERSERIVE", configuration = MyConfig.class)
@EnableFeignClients // 使用feign
@EnableHystrix
public class UserConsumer80Application {
    public static void main(String[] args) {
        SpringApplication.run(UserConsumer80Application.class, args);
    }
}

4.测试

  • 💡启动spring-cloud-user-consumer-80spring-cloud-eureka-server-7001
  • 💡访问一次http://localhost/msg2/list。此时由于没有服务实例,那么应该会调用listByHystirx来返回结果。
    • 请注意我们之前创建的MessageService在8004和8005,如果我们没有启动8004或8005,那么此时eureka内部应该没有MessageService服务实例,所以你会发现会调用我们的错误处理方法中的结果来返回给我们,而如果我们调用http://localhost/msg/list由于我们没有做hystrix处理,那么就会报错;
  • 💡启动spring-cloud-message-service-8004,访问http://localhost/msg2/list,是访问成功的。【但由于我们设置了重试次数为5,如果你启动了8004还是访问失败,那么尝试重启一下80吧,不然要等重新拉取eureka的信息(30s大概)。】
  • 💡再停掉spring-cloud-message-service-8004,访问http://localhost/msg2/list,会调用listByHystirx来返回结果。


整合feign

🔵Hystrix还可以与Fegin整合,也是相当于部署在服务消费者。

下面的代码可以参考:Hystrix整合Feign使用实验

1.修改Feign代码

由于我们只在spring-cloud-common-data模块整合了fegin,所以我们要在spring-cloud-common-data做实验了。
1.在spring-cloud-common-data模块导入依赖。

        <!--增加hystrix依赖 start-->
            <!--旧版本的依赖:
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-hystrix</artifactId>
            </dependency>-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <!--增加hystrix依赖 end-->

2.修改MessageService,在FeignClient注解中增加fallback

// 由于这种服务的服务消费者可能比较多,放到共有依赖中。
// 使用fallback指定一个类,这个类实现了MessageService,发生服务不可用的时候就会调用这个类中方法
@FeignClient(value = "MESSAGESERIVE",fallback = MessageServiceHystrix.class)
public interface MessageService {
    // 这里使用RequestMapping将服务提供者的方法与本地Service方法建立映射
    @RequestMapping(value = "/msg/list", method = RequestMethod.GET)
    Map<String, String> list();
}

3.创建MessageServiceHystrix:

package com.progor.study.service;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;

// 注意,要使用@Component
@Component
public class MessageServiceHystrix implements MessageService {
    @Override
    public Map<String, String> list() {
        Map<String, String> map = new HashMap<>();
        map.put("msg","服务端已停止服务");
        return map;
    }
}

2.修改消费者

1.由于我们上面在MessageController2中使用了Hystrix,我们新建一个MessageController3:

@RestController
public class MessageController3 {

    @Autowired
    private MessageService messageService;

    @GetMapping("/msg3/list")
    public Map<String, String> list() {
        return messageService.list();
    }
}

2.修改spring-cloud-user-consumer-80中的application.yml增加如下内容:

feign:
  hystrix:
    enabled: true # 用来开启fegin中的hystrix

🔴注意,此时主程序类中的@EnableHystrix可以没有


3.测试:

💡启动spring-cloud-user-consumer-80spring-cloud-eureka-server-7001,访问http://localhost/msg2/list

  • 💡访问一次http://localhost/msg3/list。此时由于没有服务实例,那么应该会调用listByHystirx来返回结果。
    • 请注意我们之前创建的MessageService在8004和8005,如果我们没有启动8004或8005,那么此时eureka内部应该没有MessageService服务实例,所以你会发现会调用我们的错误处理方法中的结果来返回给我们,而如果我们调用http://localhost/msg/list由于我们没有做hystrix处理,那么就会报错;
  • 💡启动spring-cloud-message-service-8004,访问http://localhost/msg2/list,是访问成功的。【如果你启动了8004还是访问失败,那么尝试重启一下80吧,不然要等重新拉取eureka的信息(30s大概)。】
  • 💡再停掉spring-cloud-message-service-8004,访问http://localhost/msg2/list,会调用listByHystirx来返回结果。

Hystrix Dashboard


💡Hystrix Dashboard是一个监控Hystrix熔断器状况的组件,有图形化的数据统计界面,你可以通过查看熔断器的统计数据来判断服务的状况。
💡对于restTemplate和fegin整合Hystrix Dashboard的方式都是一样的。

下面来演示整合,代码可以参考:Hystrix Dashboard整合使用实验,PS,这里的commit的注释上一个版本提交错了一个代码,请以这个版本的为准有点小问题,我以为我提交了一个错误的版本,没想到我revert成功了,参考这个版本的代码即可。上一个版本Hystrix整合Feign使用实验不存在错误。


1.配置启动Hystrix Dashboard

0.创建模块spring-cloud-hystrix-dashboard-9001
1.导入依赖:

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

2.主程序类添加注解@EnableHystrixDashboard:

@SpringBootApplication
@EnableHystrixDashboard
public class SpringCloudHystrixDashboard9001Application {

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

}

3.访问http://localhost:9001/hystrix,如果能正常看到页面,那么就启动成功了。
💡上面启动了Hystrix Dashboard服务,但Hystrix Dashboard是一个接收服务的数据的组件,服务不开放数据,它也接收不了,下面会进行配置。



2.修改服务提供者

1.在服务提供者spring-cloud-user-service-8003中增加依赖:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

2.在spring-cloud-user-service-8003的主程序类中增加ServletRegistrationBean的Bean:【这个是在Finchley的时候增加的好像,以前可以不配这个Bean】

@SpringBootApplication
@EnableEurekaClient
@EnableHystrix // 开启hystrix
public class UserService8003Application {
    public static void main(String[] args) {
        SpringApplication.run(UserService8003Application.class, args);
    }
    @Bean
    public ServletRegistrationBean getServlet(){

        HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
        ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
        registrationBean.addUrlMappings("/hystrix.stream");//路径
        return registrationBean;
    }
}

3.修改8003的熔断器处理代码:
因为Hystrix dashboard只能检测发生被熔断器处理的方法。没有熔断处理的方法无法被监控。这里解开注释,用于测试时好时坏时,对于Hystrix dashboard的成图效果。
20200512231547



3.测试:

💡启动spring-cloud-user-service-8003spring-cloud-eureka-server-7001
💡在谷歌浏览器中查看localhost:8003/hystrix.stream【如果是在火狐,可能会进行下载。】,这是8003开放的请求流,Hystrix dashboard实际上就是对这个流来分析的。

当你访问http://localhost:8003/user/list的时候因为我们的代码问题,它偶尔会发生问题,此时上图的ping才会显示出不同的内容。
💡访问http://localhost:9001/hystrix,是一个如下的页面:
20200512234305
在中间输入http://localhost:8003/hystrix.stream,让Hystrix Dashboard监控这个流,然后点击下面的按钮,你就进入一个这样的图:
Delay是监控的刷新时间。



7色:中间的7种颜色的,要参考右上角的颜色问题。

  • Success:代表请求成功
  • Short-Circuited:代表熔断数
  • Bad Request:代表抛出HystrixBadRequestException的次数
  • Timeout:代表请求超时的次数
  • Rejected:线程池拒绝数
  • Failure:抛出异常的请求
  • Error:最近10秒的错误比例
    1圈:这个圈会随着七种数字的总和而变大,圈越大,访问越多;圈的颜色是其中颜色的混搭,比如成功为主的时候,偏于绿色,如果是偏于红色,那么说明请求失败很多。【绿、黄、橙、红】
    20200512234416
    1线:线是请求次数的高低,没有请求的时候就是一条直线,有请求的时候就是一张折线图。
    想了解图中更多的内容可以参考:图内数据官方参考文档


补充:

  • 更多内容(包括工作机制,多个Hystrix Dashboard基于Turbo的聚合情况。),将在单章说明,咕咕咕。

posted @ 2020-05-15 14:20  随风行云  阅读(4094)  评论(0编辑  收藏  举报