一、系统架构演变
随着互联网的发展,网站应用的规模不断扩大。需求的激增,带来的是技术上的压力。系统架构也因此也不断的演进、升级、迭代。
从单一应用,到垂直拆分,到分布式服务,到SOA,以及现在火热的微服务架构。让我们来看一下它们各自的特点:
1.1 集中式架构
当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是影响项目开发的关键。
存在的问题:
· 代码耦合,开发维护困难
· 无法针对不同模块进行针对性优化
· 无法水平扩展
· 单点容错率低,并发能力差
1.2 垂直拆分
当访问量逐渐增大,单一应用无法满足需求,此时为了应对更高的并发和业务需求,我们根据业务功能对系统进行拆分:
优点:
· 系统拆分实现了流量分担,解决了并发问题
· 可以针对不同模块进行优化
· 方便水平扩展,负载均衡,容错率提高
缺点:
· 系统间相互独立,会有很多重复开发工作,影响开发效率
1.3 分布式服务
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心, 使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式调用是关键。
优点:
· 将基础服务进行了抽取,系统间相互调用,提高了代码复用和开发效率
缺点:
· 系统间耦合度变高,调用关系错综复杂,难以维护
1.4 服务治理(SOA)
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键
以前出现了什么问题?
· 服务越来越多,需要管理每个服务的地址
· 调用关系错综复杂,难以理清依赖关系
· 服务过多,服务状态难以管理,无法根据服务情况动态管理
服务治理要做什么?
· 服务注册中心,实现服务自动注册和发现,无需人为记录服务地址
· 服务自动订阅,服务列表自动推送,服务调用透明化,无需关心依赖关系
· 动态监控服务状态监控报告,人为控制服务状态
缺点:
· 服务间会有依赖关系,一旦某个环节出错会影响较大
· 服务关系复杂,运维、测试部署困难,不符合DevOps(开发部署等一体化)思想
1.5 微服务概述
1.5.1 什么是微服务
目前的微服务并没有一个统一的标准,一般是以业务来划分将传统的一站式应用,拆分成一个个的服务,彻底去耦合,一个微服务就是单功能业务,只做一件事。
1.5.2 微服务与微服务架构
微服务是一种架构模式或者一种架构风格,提倡将单一应用程序划分成一组小的服务独立部署,服务之间相互配合、相互协调,每个服务运行于自己的进程中。
服务与服务间采用轻量级通讯,如HTTP的RESTful API等
避免统一的、集中式的服务管理机制
1.5.3 微服务的优缺点
优点
- 每个服务足够内聚,足够小,比较容易聚焦
- 开发简单且效率高,一个服务只做一件事情
- 开发团队小,一般2-5人足以(当然按实际为准)
- 微服务是松耦合的,无论开发还是部署都可以独立完成
- 微服务能用不同的语言开发
- 易于和第三方集成,微服务允许容易且灵活的自动集成部署(持续集成工具有Jenkins,Hudson,bamboo等)
- 微服务易于被开发人员理解,修改和维护,这样可以使小团队更加关注自己的工作成果,而无需一定要通过合作才能体现价值
- 微服务允许你融合最新的技术
- 微服务只是业务逻辑的代码,不会和HTML,CSS或其他界面组件融合。
- 每个微服务都可以有自己的存储能力,数据库可自有也可以统一,十分灵活。
缺点
- 开发人员要处理分布式系统的复杂性
- 多服务运维难度,随着服务的增加,运维的压力也会增大
- 依赖系统部署
- 服务间通讯的成本
- 数据的一致性
- 系统集成测试
- 性能监控的难度
二、远程调用方式
2.1调用方式介绍
无论是微服务还是SOA,都面临着服务间的远程调用。那么服务间的远程调用方式有哪些呢?
常见的远程调用方式有以下几种:
-
RPC:Remote Produce Call远程过程调用,类似的还有RMI(Remote Method Invocation,远程方法调用)。自定义数据格式,基于原生TCP通信,速度快,效率高。早期的webservice,现在热门的dubbo,都是RPC的典型
-
Http:http其实是一种网络传输协议,基于TCP,规定了数据传输的格式。现在客户端浏览器与服务端通信基本都是采用Http协议。也可以用来进行远程服务调用。缺点是消息封装臃肿。现在热门的Rest风格,就可以通过http协议来实现。
2.2 认识RPC
概念解释:
RPC,即 Remote Procedure Call(远程过程调用),是一个计算机通信协议。 该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。
说得通俗一点就是:A计算机提供一个服务,B计算机可以像调用本地服务那样调用A计算机的服务。
作用体现:
通过上面的概念,我们可以知道,实现RPC主要是做到两点:
-
实现远程调用其他计算机的服务
-
像调用本地服务一样调用远程服务
问题思考:
要实现远程调用,肯定是通过网络传输数据。A程序提供服务,B程序通过网络将请求参数传递给A,A程序接收参数调用本地服务执行后得到结果,再将结果返回给B程序。这里需要关注的有两点:
1)采用何种网络通讯协议?
现在比较流行的RPC框架,都会采用TCP作为底层传输协议
2)数据传输的格式怎样?
两个程序进行通讯,必须约定好数据传输格式。就好比两个人聊天,要用同一种语言,否则无法沟通。所以,我们必须定义好请求和响应的格式。另外,数据在网路中传输需要进行序列化,所以还需要约定统一的序列化的方式。
如果仅仅是远程调用,还不算是RPC,因为RPC强调的是过程调用,调用的过程对用户而言是应该是透明的,用户不应该关心调用的细节,可以像调用本地服务一样调用远程服务。
所以RPC一定要对调用的过程进行封装
2.3 认识http
概念解释:
Http协议:超文本传输协议,是一种应用层协议。规定了网络传输的请求格式、响应格式、资源定位和操作的方式等。但是底层采用什么网络传输协议,并没有规定,不过现在都是采用TCP协议作为底层传输协议。说到这里,大家可能觉得,Http与RPC的远程调用非常像,都是按照某种规定好的数据格式进行网络通信,有请求,有响应。没错,在这点来看,两者非常相似,但是还是有一些细微差别。
http和rpc差别:
-
· RPC并没有规定数据传输格式,这个格式可以任意指定,不同的RPC协议,数据格式不一定相同。
-
· Http中定义了资源定位的路径,RPC中并不需要
-
· 最重要的一点:RPC需要满足像调用本地服务一样调用远程服务,也就是对调用过程在API层面进行封装。Http协议没有这样的要求,因此请求、响应等细节需要我们自己去实现。
-
优点:RPC方式更加透明,对用户更方便。Http方式更灵活,没有规定API和语言,跨语言、跨平台
缺点:RPC方式需要在API层面进行封装,限制了开发的语言环境
例如我们通过浏览器访问网站,就是通过Http协议。只不过浏览器把请求封装,发起请求以及接收响应,解析响应的事情都帮我们做了。如果是不通过浏览器,那么这些事情都需要自己去完成。
2.4 如何选择
既然两种方式都可以实现远程调用,我们该如何选择呢?
· 速度来看,RPC要比http更快,虽然底层都是TCP,但是http协议的信息往往比较臃肿,不过可以采用gzip压缩。
· 难度来看,RPC实现较为复杂,http相对比较简单
· 灵活性来看,http更胜一筹,因为它不关心实现细节,跨平台、跨语言。
因此,两者都有不同的使用场景:
· 如果对效率要求更高,并且开发过程使用统一的技术栈,那么用RPC还是不错的。
· 如果需要更加灵活,跨语言、跨平台,显然http更合适
那么我们该怎么选择呢?
微服务,更加强调的是独立、自治、灵活。而RPC方式的限制较多,因此微服务框架中,一般都会采用基于Http的Rest风格服务。
三、SpringCloud入门概述
Spring的三大模块:SpringBoot(构建),Spring Cloud(协调),Spring Cloud Data Flow(连接)
3.1、SpringCloud是什么?
Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、熔断器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。
Spring Cloud项目的官方网址:https://spring.io/projects/spring-cloud
3.2、 SpringCloud和SpringBoot的关系
Spring Boot 是 Spring 的一套快速配置脚手架,可以基于Spring Boot 快速开发单个微服务,Spring Cloud是一个基于Spring Boot实现的云应用开发工具;
Spring Boot专注于快速方便开发单个微服务个体,Spring Cloud关注全局的服务治理框架,它将SpringBoot开发的一个个微服务整合并管理起来,为各个服务之间提供 服务发现、负载均衡、断路器、路由、配置管理、微代理,消息总线、全局锁、分布式会话等集成服务;
Spring Boot使用了默认大于配置的理念,很多集成方案已经帮你选择好了,能不配置就不配置,Spring Cloud很大的一部分是基于Spring Boot来实现,可以不基于Spring Boot吗?不可以。
Spring Boot可以离开Spring Cloud独立使用开发项目,但是Spring Cloud离不开Spring Boot,属于依赖的关系。
3.3、 SpringCloud主要框架
服务发现——Netflix Eureka
服务调用——Netflix Feign
熔断器——Netflix Hystrix
服务网关——Netflix Zuul
分布式配置——Spring Cloud Config
消息总线 —— Spring Cloud Bus
四、服务发现组件Eureka
4.1、Eureka简介
Eureka 是Spring Cloud Netflix 微服务套件中的一部分, 它基于Netflix Eureka 做了二次封装, 主要负责完成微服务架构中的服务治理功能。我们只需通过简单引入依赖和注解配置就能让Spring Boot 构建的微服务应用轻松地与Eureka 服务治理体系进行整合。
Eureka包含两个组件:Eureka Server和Eureka Client。
Eureka Server提供服务注册服务。
Eureka Client是一个java客户端,用来简化与Eureka Server的交互、客户端同时也就是一个内置的、使用轮询(round-robin)负载算法的负载均衡器。
4.2、SpringCloud父工程创建
1.创建父工程 springboot项目hello-parent
2.修改父工程配置文件
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.offcn</groupId>
<artifactId>hello-parent</artifactId>
<version>1.0</version>
<name>hello-parent</name>
<packaging>pom</packaging>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3.删除其余文件
只留下 iml文件 和 父工程的pom文件即可 其余的没有用 包括src
4.3、Eureka服务端开发
1.创建EurekaServer01模块(Maven工程即可)
2.导入pom文件
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
3.编写配置文件
#内置的tomcat服务启动监听端口号
server:
port: 8888
#应用名称
spring:
application:
name: EurekaServer01
#EurekaServer配置
eureka:
instance:
hostname: localhost
server:
#关闭自我保护模式(缺省为打开)
enable-self-preservation: false
#扫描失效服务的间隔时间(缺省为60*1000ms)
eviction-interval-timer-in-ms: 1000
client:
register-with-eureka: false #此EurekaServer不在注册到其他的注册中心
fetch-registry: false #不在从其他中心中心拉取服务器信息
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka #注册中心访问地址
4.编写启动类
@SpringBootApplication
@EnableEurekaServer
public class EurekaServer01Start {
public static void main(String[] args) {
SpringApplication.run(EurekaServer01Start.class, args);
}
}
5.启动测试结果
然后在浏览器地址栏输入 http://localhost:8888/ 运行效果如下:
主界面中system status为系统信息 General Info为一般信息 Instances currently
registered with Eureka为注册到注册中心的所有微服务列表
4.4基于Eureka入门接口模块
1.创建HelloInterface01模块
2.编写接口
package com.offcn.service;
public interface HelloService {
public String sayHello();
}
3.安装到本地仓库
注意:
hello-parent父工程的pom.xml不需要声明springboot的maven编译插件,这样就可以避免把所有继承父工程的项目打包成springboot的jar包。
<!--
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
-->
4.5基于Eureka入门生产者服务
1.创建HelloProvider01模块
2.编写pom文件
<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>com.offcn</groupId>
<artifactId>HelloInterface01</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
3.编写yml文件
spring:
application:
name: HelloProvider01
server:
port: 9001
eureka:
client:
service-url:
defaultZone: http://localhost:8888/eureka
4.编写接口的实现类
package com.offcn.service.impl;
import com.offcn.service.HelloService;
import org.springframework.stereotype.Service;
@Service
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello() {
return "hello Eureka!";
}
}
5.编写HelloController实现类
package com.offcn.controller;
import com.offcn.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@Autowired
private HelloService helloService;
@GetMapping("/hello")
public String sayHello(){
return helloService.sayHello();
}
}
6.编写启动类
package com.offcn;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class HelloProvider01Start {
public static void main(String[] args) {
SpringApplication.run(HelloProvider01Start.class,args);
}
}
7.启动服务
查看Eureka管理控制台,可以看到服务已经注册上去!
4.6、基于Eureka入门消费者服务‘
1.创建HelloConsumer01模块
2.导入pom依赖
<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>com.offcn</groupId>
<artifactId>HelloInterface01</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
3.yml配置文件
spring:
application:
name: helloConsumer01
server:
port: 9002
eureka:
client:
service-url:
defaultZone: http://localhost:8888/eureka
4.编写启动类
@SpringBootApplication
@EnableDiscoveryClient
public class HelloConsumer01Start {
public static void main(String[] args) {
SpringApplication.run(HelloConsumer01Start.class,args);
}
}
5.编写配置类(也可以在启动类中进行配置)实例化远程调用模板类
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
6.编写实现类
package com.offcn.service.impl;
import com.offcn.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@Service
public class HelloServiceImpl implements HelloService {
@Autowired
private DiscoveryClient discoveryClient; //是一个服务查询工具,可以连接到EurekaServer,根据服务名称去查询服务信息,注意别导错包了
@Autowired
private RestTemplate restTemplate; //调用rest风格接口工具类
//从EurekaServer获取对应服务的地址和端口
public String getServerInfo(){
List<ServiceInstance> instanceList = discoveryClient.getInstances("HELLOPROVIDER01");
if(instanceList!=null&&instanceList.size()>0){
ServiceInstance serviceInstance = instanceList.get(0);
//获取对应服务的主机地址
String host = serviceInstance.getHost();
//获取对应服务端口号
int port = serviceInstance.getPort();
return "http://"+host+":"+port;
}
return null;
}
@Override
public String sayHello(){
ResponseEntity<String> responseEntity = restTemplate.getForEntity(getServerInfo() + "/hello", String.class);
String body = responseEntity.getBody();
System.out.println("调用远程服务返回值:"+body);
return body;
}
}
7.controller调用代码
package com.offcn.controller;
import com.offcn.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloControllerConsumer {
@Autowired
private HelloService helloService;
@RequestMapping("/testHello")
public String sayHello(){
return helloService.sayHello();
}
}
8.启动服务
9.测试访问
http://localhost:9002/testHello
4.7、Eureka的服务剔除与保护机制
1.服务剔除
注册到eureka的服务可能由于内存溢出或网络故障等原因使得服务不能正常的工作,而服务注册中心并未收到“服务下线”的请求。服务注册中心在启动时会创建一个定时任务,默认每隔一段时间(默认为60秒)将当前清单中超时(默认为90秒)没有续约的服务剔除,这个操作被称为失效剔除。
2.服务保护
我们关停一个服务,很可能会在Eureka面板看到一条警告:
这是触发了Eureka的自我保护机制。当服务未按时进行心跳续约时,Eureka会统计服务实例最近15分钟心跳续约的比例是否低于了85%。
在生产环境下,因为网络延迟等原因,心跳失败实例的比例很有可能超标,但是此时就把服务剔除列表并不妥当,因为服务可能没有宕机。
Eureka在这段时间内不会剔除任何服务实例,直到网络恢复正常。生产环境下这很有效,保证了大多数服务依然可用,不过也有可能获取到失败的服务实例,因此服务调用者必须做好服 务的失败容错。
可以通过下面的配置来关停自我保护:
五、Eureka高可用
5.1高可用介绍
EurekaServer可以是一个集群,形成高可用的Eureka注册中心
多个Eureka Server之间也会互相注册为服务,当服务提供者注册到Eureka Server集群中的某个节点时,该节点会把服务的信息同步给集群中的每个节点,从而实现数据同步。
因此,无论客户端访问到Eureka Server集群中的哪一个节点,都可以获取到完整的服务列表信息。
5.2高可用实现
1.创建第二个eureka2
2.修改eureka1的yml文件配置
#内置的tomcat服务启动监听端口号
server:
port: 10086
#应用名称
spring:
application:
name: EurekaServer
#EurekaServer配置
eureka:
instance:
hostname: localhost
server:
#关闭自我保护模式(缺省为打开)
enable-self-preservation: false
#扫描失效服务的间隔时间(缺省为60*1000ms)
eviction-interval-timer-in-ms: 1000
client:
register-with-eureka: true #需要设置为true允许去其他eurekaserver注册
fetch-registry: true #允许从其他中心中心拉取服务器信息
service-url:
defaultZone: http://${eureka.instance.hostname}:10087/eureka #注册中心访问地址,指向另外一台eurekaserver
3.编写eureka2的yml文件
#内置的tomcat服务启动监听端口号
server:
port: 10087
#应用名称
spring:
application:
name: EurekaServer
#EurekaServer配置
eureka:
instance:
hostname: localhost
server:
#关闭自我保护模式(缺省为打开)
enable-self-preservation: false
#扫描失效服务的间隔时间(缺省为60*1000ms)
eviction-interval-timer-in-ms: 1000
client:
register-with-eureka: true #此EurekaServer不在注册到其他的注册中心
fetch-registry: true #不在从其他中心中心拉取服务器信息
service-url:
defaultZone: http://${eureka.instance.hostname}:10086/eureka #注册中心访问地址,指向另外一台eurekaserver
4.启动两个eureka
启动项目EurekaServer1、EurekaServer2
5.修改提供者和消费者的注册中心地址
#配置EurekaServer注册中心服务器地址
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka,http://localhost:10087/eureka
此时配置文件同时指向两个配置中心
6.启动测试
六、服务调用组件
在上节课,我们启动了一个服务作为生产者,通过DiscoveryClient来获取服务实例信息,然后获取ip和端口来访问。但是实际环境中,我们往往会开启很多个服务的集群。此时我们获取的服务列表中就会有多个,到底该访问哪一个呢?
一般这种情况下我们就需要编写负载均衡算法,在多个实例列表中进行选择。
不过SpringColud中已经帮我们集成了一系列负载均衡组件:LoadBalancerClient、Ribbon、Feign,简单修改代码即可使用。
6.1基于LoadBalance服务调用
6.1.1原理介绍
6.1.2代码实现
6.1.2.1创建接口模块UserInterface
1.创建实体类com.offcn.pojo.User 提供属性 id、name、age等属性,提供getset 全参和空参构造器、toString方法等
package com.offcn.pojo;
import java.io.Serializable;
public class User implements Serializable {
private Long id;
private String username;
private Integer age;
//省略getset方法 空参数 全参数构造器 以及toString方法
3.创建接口com.offcn.service.UserService,提供方法List<User>findAll()
;
package com.offcn.service;
import com.offcn.pojo.User;
import java.util.List;
public interface UserService {
Map<String,Object> findAll();
}
4.安装模块到本地仓库
6.1.2.2创建提供者
1.创建两个提供者模块测试负载均衡
创建新模块UserProvider01、 UserProvider02 参考模块HelloProvider01,引入依赖
<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>com.offcn</groupId>
<artifactId>UserInterface</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
2.修改两个模块的yml文件
spring:
application:
name: UserProvider
server:
port: 8001
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka,http://localhost:10087/eureka
spring:
application:
name: UserProvider
server:
port: 8002
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka,http://localhost:10087/eureka
3.创建接口的实现类实现接口方法(两个模块一样)
package com.offcn.service.impl;
import com.offcn.pojo.User;
import com.offcn.service.UserService;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class UserServiceImpl implements UserService {
@Override
public Map<String,Object> findAll() {
List<User> list=new ArrayList<User>();
list.add(new User(1L,"zhangsan1",21));
list.add(new User(2L,"zhangsan2",22));
list.add(new User(3L,"zhangsan3",23));
Map<String,Object> map=new HashMap<String,Object>();
map.put("list",list);
return map;
}
}
4.修改两个controller方法
package com.offcn.controller;
import com.offcn.pojo.User;
import com.offcn.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
//获取全部用户数据
@GetMapping("/")
public Map<String,Object> findAll(){
Map<String,Object> map=userService.findAll();
map.put("version","UserProvider01");
return map;
}
}
package com.offcn.controller;
import com.offcn.pojo.User;
import com.offcn.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
//获取全部用户数据
@GetMapping("/")
public Map<String,Object> findAll(){
Map<String,Object> map=userService.findAll();
map.put("version","UserProvider02");
return map;
}
}
为了区分访问的具体微服务,存储version字段作为区别
5.编写启动类
package com.offcn;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class UserProvider01Start {
public static void main(String[] args) {
SpringApplication.run(UserProvider01Start.class,args);
}
}
package com.offcn;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class UserProvider02Start {
public static void main(String[] args) {
SpringApplication.run(UserProvider02Start.class,args);
}
}
5.启动两个工程查看注册中心
1.新建模块UserWeb(参考HelloConsumer01),引入依赖如下
<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>com.offcn</groupId>
<artifactId>UserInterface</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
2.编写yml文件
server:
port: 9001
spring:
thymeleaf:
prefix: classpath:/templates/ #在构建URL时预先查看名称的前缀
suffix: .html #构建URL时附加到查看名称的后缀
cache: false
application:
name: UserWeb
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka,http://localhost:10087/eureka
3.远程调用业务类代码
@Service
public class UserServiceImpl implements UserService {
//远程服务调用客户端
@Autowired
RestTemplate restTemplate;
//支持负载均衡的调用客户端
@Autowired
LoadBalancerClient loadBalancerClient;
/***
* 通过客户端负载均衡器获取生产者服务器基础地址
* @return
*/
public String getServerUrl() {
//通过客户端调用服务均衡器查找服务
ServiceInstance inst = loadBalancerClient.choose("USERPROVIDER");
//获取服务提供者服务器ip、端口号
String ip = inst.getHost();
int port = inst.getPort();
//拼接调用地址
String url="http://"+ip+":"+port;
return url;
}
@Override
public Map<String,Object> findAll() {
String url=getServerUrl();
Map<String,Object> map = restTemplate.getForObject(url + "/user/", Map.class);
return map;
}
}
注意:LoadBalancerClient 具备负载均衡功能,可以根据服务名从多个实例中选择一个实例
4.controller代码
package com.offcn.controller;
import com.offcn.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.Map;
@Controller
public class UserController {
@Autowired
private UserService userService;
//查询全部用户数据,显示列表
@GetMapping("/")
public String findAll(Model model){
Map<String, Object> map = userService.findAll();
model.addAttribute("page",map.get("list"));
//获取服务提供者信息
model.addAttribute("version",map.get("version"));
System.out.println("version:"+map.get("version"));
return "user/list";
}
}
4.创建list.html页面代码
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1 th:text="${version}"></h1>
<table width="200" style="text-align: center;">
<tr>
<th>编号</th>
<th>姓名</th>
<th>年龄</th>
<th>index</th>
</tr>
<tr th:each=" user,iterStat : ${page}">
<td th:text="${user.id}"></td>
<td th:text="${user.username}"></td>
<td th:text="${user.age}"></td>
<td th:text="${iterStat.index}">index</td>
</tr>
</table>
</body>
</html>
5.编写主启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableDiscoveryClient
public class UserWebApplication {
public static void main(String[] args) {
SpringApplication.run(UserWebApplication.class,args);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
6.测试效果
顺序启动项目EurekaServer01、EurekaServer02、UserProvdier01、UserProvdier02、UserWeb
访问Eureka管理控制台界面,可以看到各个服务以及注册成功
访问消费者工程UserWeb02地址:http://localhost:9001
多次刷新后可以看见 控制台在 provider1和provider2之间切换
6.2基于Ribbon的远程调用
6.2.1Ribbon介绍
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。它是一个基于HTTP和TCP的客户端负载均衡器。
下面我们带领大家使用Ribbon来实现客户端服务调用。
6.2.2 消费者项目创建
1、创建新模块UserWeb02
复制工程UserWeb的内容到UserWeb02
2、修改pom.xml引入Ribbon依赖包
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
3.修改UserWeb02工程中配置文件application.yml
#服务器端口号
server:
port: 9002
spring:
thymeleaf:
prefix: classpath:/templates/ #在构建URL时预先查看名称的前缀
suffix: .html #构建URL时附加到查看名称的后缀
cache: false
application:
name: UserWeb02
4.修改项目启动类AppStartApplication
在RestTemplate定义上增加注解@LoadBalanced,即可开启Ribbon
5.修改类UserServiceImpl
@Service
public class UserServiceImpl implements UserService {
@Autowired
private RestTemplate restTemplate; //rest接口调用工具
//编写查询服务方法
public String getServerUrl(){
return "http://USERPROVIDER";
}
@Override
public Map<String,Object> findAll() {
String url=getServerUrl();
Map<String,Object> map = restTemplate.getForObject(url + "/user/", Map.class);
return map;
}
}
6.测试效果
访问消费者工程UserWeb02地址:http://localhost:9002
可以看到不停刷新页面,显示的用户服务信息发生变化,在2个生产者服务之间来回切换。 后台打印:
6.2.3负载均衡规则配置[了解]
1.规则分类
默认情况下Ribbon的负载均衡策略是轮询,Ribbon常用的负载均衡规则:
1、简单轮询策略(RoundRobinRule)
以轮询的方式依次将请求调度不同的服务器
2、随机策略 (RandomRule)
随机选择状态为UP的Server
3、加权响应时间策略 (WeightedResponseTimeRule)
根据响应时间分配一个weight,响应时间越长,weight越小,被选中的可能性越低。
4、区域权重策略(ZoneAwareRule)
综合判断server所在区域的性能和server的可用性选择server
5、最低并发策略(BestAvailableRule)
逐个查看server,选择一个并发连接最低的server
6、重试策略(RetryRule)
对选定的负载均衡策略机上重试机制
7、可用过滤策略(AvailabilityFilteringRule)
过滤掉一直失败并被标记为circuit tripped的server
8、ResponseTimeWeightedRule
已废弃,作用同WeightedResponseTimeRule
2.规则配置
yml文件配置指定服务的负载均衡策略
# com.netflix.loadbalancer.RandomRule #配置规则 随机
# com.netflix.loadbalancer.RoundRobinRule #配置规则 轮询
# com.netflix.loadbalancer.RetryRule #配置规则 重试
# com.netflix.loadbalancer.WeightedResponseTimeRule #配置规则 响应时间权重
# com.netflix.loadbalancer.BestAvailableRule #配置规则 最空闲连接策略
针对每个服务来在消费端如UserWeb02的application.yml中配置负载均衡规则
USERPROVIDER: #服务名称
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
6.2.4重试机制[了解]
1.重试机制理解
当我们选择轮询负载均衡策略的时候,如果一个服务停掉,因为服务剔除的延迟,消费者并不会立即得到最新的服务列表,此时再次访问你会得到错误提示:
Spring Cloud 整合了Spring Retry 来增强RestTemplate的重试能力,当一次服务调用失败后,不会立即抛出一次,而是再次重试另一个服务。
2.重试机制实现
1.修改配置文件application.yml
spring:
#开启Spring Cloud的重试功能
cloud:
loadbalancer:
retry:
enabled: true
USERPROVIDER:
ribbon:
#配置指定服务的负载均衡策略
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
# 是否对所有操作都进行重试,默认支持读造作,不支持写操作
#(写操作要保证接口 幂等性:防止多次操作造成数异常)
OkToRetryOnAllOperations: true
ConnectTimeout: 250 # Ribbon的连接超时时间
ReadTimeout: 1000 # Ribbon的数据读取超时时间
# 切换实例的重试次数
MaxAutoRetriesNextServer: 1
# 对当前实例的重试次数
MaxAutoRetries: 1
注意:使用重试,要设置负载均衡策略为轮询com.netflix.loadbalancer.RoundRobinRule
2.修改pom文件引入依赖
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
4.测试效果
重启项目UserWeb02,
不断刷新页面,可以看到用户服务显示内容在变化,负载均衡效果已经生效,到此ribbon消费者成功
如图:
关闭生产者项目ProviderService002
重试机制生效不会再产生刚才的调用错误。
注意:重试过于频繁后台也会出现异常,但是页面不会再次出现错误。
一、基于Feign的远程调用
1.Feign介绍
Feign是一个声明式的web服务客户端,它使编写web服务客户端变得更加容易。创建一个接口并添加一个Fegin的注解@FeignClient,就可以通过该接口调用生产者提供的服务。Spring Cloud对Feign进行了增强,使得Feign支持了Spring MVC注解
两点:1、Feign采用的是接口加注解的声明式服务调用;
2、Fegin整合Ribbon及Eureka,支持负载均衡
接下带领大家创建一个消费者项目,体验Feign的使用方式。
2.消费者项目创建
2.1 创建新模块UserWeb03
复制工程UserWeb02的内容到UserWeb03
2.2 修改pom.xml引入Feign依赖包
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.3 修改应用启动类
增加注解@EnableFeignClients
2.4 配置文件application.yml 进行如下修改
server:
port: 9003
spring:
thymeleaf:
cache: false
application:
name: UserWeb03
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka,http://localhost:10087/eureka
2.5 在com.offcn.service中添加接口UserService
@FeignClient(value = "USERPROVIDER")
public interface UserService {
//查询全部
@GetMapping("/user/")
public Map<String,Object> findAll();
}
2.6 删除实现类UserServiceImpl,以及依赖pom.xml移除对接口UserInterface的依赖
2.7 UserController直接调用接口UserService
@Controller
public class UserController{
@Autowired
UserService userService;
}
2.7 访问UserWeb03可以看到一样得到访问结果
访问http://localhost:9003/
3. 开启调用日志
@FeignClient注解修改的客户端在被代理时,都会创建一个新的Fegin.Logger实例。我们需要额外指定这个日志的级别才可以。
3.1修改application.yml设置对应包的日志级别
#设置消费者指定包日志级别
logging:
level:
com.offcn : debug
3.2编写配置类,定义日志级别
这里指定的Level级别是FULL,Feign支持4种级别:
NONE:不记录任何日志信息,这是默认值。
BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
创建类FeignConfig ,配置Feign日志级别定义
package com.offcn.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import feign.Logger;
@Configuration
public class FeignConfig {
@Bean
public Logger.Level getFeignlogger(){
return Logger.Level.FULL;
}
}
3.3 修改接口UserService
修改注解:@FeignClient,指定Feign日志配置文件
3.4 重启工程UserWeb03
访问地址http://localhost:9003/
查看控制台即可看到Feign调用日志:
3.5、设置feign默认超时时间
feign:
client:
config:
default:
connectTimeout: 10000 #连接超时配置
readTimeout: 60000 #执行超时配置
二、熔断器组件Netflix Hystrix
1 熔断器Hystix介绍
Hystix是Netflix的针对微服务分布式系统的熔断保护中间件,一个有关延迟和失败容错的开源库包,用于隔离访问远程服务、第三方库,防止出现级联失败。
Hystix是Netflix的针对微服务分布式系统的熔断保护中间件,是一个有关延迟和失败容错的开源库包,用来设计隔离访问远程服务、第三方库等,防止出现级联式失败。
主页:https://github.com/Netflix/Hystrix/
2 熔断器Hystix的原理和作用
熔断器机制的原理很简单,像家里的电路保险丝,如果电路发生短路能立刻熔断电路,避免发生灾难。
在分布式系统中应用这一模式之后,服务调用方可以自己进行判断某些服务反应慢或者存在大量超时的情况时,能够主动熔断,防止整体系统被拖垮。
不同于电路熔断只能断不能自动重连,Hystrix可以实现弹性容错,当情况好转之后,可以自动重连。
正常工作的情况下,客户端请求调用服务API接口:
当有服务出现异常时,直接进行失败回滚,服务降级处理:
3 Ribbon使用Hystix
3.1 修改项目UserWeb02,引入Hystix熔断器
pom.xml增加Hystix依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
3.2修改应用启动类。增加注解@EnableCircuitBreaker允许Hystix熔断器生效
3.3 修改业务实现类UserServiceImpl
@Override
@HystrixCommand(fallbackMethod ="findAllCallBack" )
public Map<String,Object> findAll() {
String url=getServerUrl();
Map<String,Object> map = restTemplate.getForObject(url + "/user/", Map.class);
return map;
}
//熔断触发后,回调方法
public Map<String,Object> findAllCallBack() {
Map<String,Object> map=new HashMap<>();
map.put("list",new ArrayList<>());
map.put("version","熔断被触发,远程调用失败");
return map;
}
@HystrixCommand(fallbackMethod=“findAllCallBack”):
声明一个失败回滚处理函数findAllCallBack,当findAll执行超时(默认是1000毫秒),就会执行findAllCallBack函数,返回错误提示。
为了方便查看熔断的触发时机,我们记录请求访问时间。
3.4 修改服务提供者UserProvdier01、UserProvdier02
让服务随机休眠一段时间,以触发熔断
//获取全部用户数据
@GetMapping("/")
public Map<String,Object> findAll(){
Map<String,Object> map=userService.findAll();
map.put("version","UserProvider01");
//随机睡 0-1500毫秒
try {
Thread.sleep(new Random().nextInt(1500));
} catch (InterruptedException e) {
e.printStackTrace();
}
return map;
}
3.5 测试
启动应用,访问地址:http://localhost:9002/
可以看到当请求时间超过1000毫秒是发生熔断
4 优化Ribbon使用Hystix
虽然熔断实现了,但是我们的重试机制似乎没有生效,是这样吗?
其实这里是因为我们的Ribbon超时时间设置的是1000ms
USERPROVIDER: #服务名称
ribbon:
#配置指定服务的负载均衡策略
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
# Ribbon的连接超时时间
ConnectTimeout: 250
# Ribbon的数据读取超时时间
ReadTimeout: 1000
# 是否对所有操作都进行重试
OkToRetryOnAllOperations: true
# 切换实例的重试次数
MaxAutoRetriesNextServer: 1
# 对当前实例的重试次数
MaxAutoRetries: 1
而Hystix的超时时间默认也是1000ms,因此重试机制没有被触发,而是先触发了熔断。
注意,针对RestTemplate发起调用设置超时时间
所以,RestTemplate、Ribbon的超时时间一定要小于Hystix的超时时间。
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setReadTimeout(250);
factory.setConnectTimeout(250);
return new RestTemplate(factory);
}
4.1 设定消费者UserWeb02熔断超时时间
修改application.yml
#设定Hystrix熔断超时时间
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 6000
4.2 修改服务提供者UserProvdier01、UserProvdier02
让服务随机休眠一段时间(1-6000毫秒)
4.3 重启相关项目,发现重试机制已经生效,不会再发生错误
重试已经生效
5 Feign使用Hystix
Feign默认也有对Hystix的集成,只不过,默认情况下是关闭的。我们需要通过下面的参数来开启:
5.1修改项目UserWeb03的application.yml
# 开启Feign的熔断功能
feign:
hystrix:
enabled: true
#总连接超时时间=(切换服务实例次数+1)*(每个实例重试次数+1)*连接超时时间
USERPROVIDER: #服务名称
ribbon:
#配置指定服务的负载均衡策略
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
# Ribbon的连接超时时间
ConnectTimeout: 250
# Ribbon的数据读取超时时间
ReadTimeout: 250
# 是否对所有操作都进行重试
OkToRetryOnAllOperations: true
# 切换实例的重试次数
MaxAutoRetriesNextServer: 1
# 对当前实例的重试次数
MaxAutoRetries: 1
#设定Hystrix熔断超时时间 ,理论上熔断时间应该大于总连接超时时间
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 6000
5.2 导入熔断所需依赖包
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
5.3 编写Feign声明接口UserService的实现类UserServiceImpl(注意要声明为@Service)
@Service
public class UserServiceImpl implements UserService {
@Override
public Map<String, Object> findAll() {
Map<String,Object> map=new HashMap<>();
map.put("list",new ArrayList<>());
map.put("version","调用远程服务失败,熔断被触发!");
return map;
}
}
5.4 在Feign调用接口UserService中,使用注解@FeignClient声明熔断调用实现类
@FeignClient(value="USERPROVIDER",
configuration=FeignConfig.class,
fallback=UserServiceImpl.class)
5.5 重启动项目UserWeb03
访问地址http://localhost:9003/
超时后熔断被触发
6 Hystix监控服务器搭建【了解】
断路器是根据一段时间窗内的请求情况来判断并操作断路器的打开和关闭状态的。而这些请求情况的指标信息都是HystrixCommand和HystrixObservableCommand实例在执行过程中记录的重要度量信息,它们除了Hystrix断路器实现中使用之外,对于系统运维也有非常大的帮助。这些指标信息会以“滚动时间窗”与“桶”结合的方式进行汇总,并在内存中驻留一段时间,以供内部或外部进行查询使用,Hystrix Dashboard就是这些指标内容的消费者之一。
接下来搭建使用Hystix监控
6.1 新建一个模块hystrix-dashboard,编辑pom.xml导入所需依赖包
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
</dependencies>
6.2 为应用主类加上@EnableHystrixDashboard,启用Hystrix Dashboard功能
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardStart {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardStart.class, args);
}
}
6.3 修改yml文件
spring:
application:
name: hystrix-dashboard
server:
port: 1301
hystrix:
dashboard:
proxy-stream-allow-list: "*"
6.4 启动该应用,并访问
http://localhost:1301/hystrix,我们可以看到如下页面:
7 启动客户端监控
项目:UserWeb03的UserService接口实现使用了@FeignClient修饰,feign是自带断路器功能的,并且已经开启了断路器功能,所以这个接口的调用情况会被Hystrix记录下来,以用来给断路器和Hystrix Dashboard使用。
7.1 修改项目UserWeb04的pom.xml增加Hystrix监控所需依赖包
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
7.2 在服务实例的主类中已经使用@EnableCircuitBreaker或@EnableHystrix注解,开启断路器功能。
同时增加监控路径访问地址定义/hystrix.stream可以访问
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableHystrix //开启断路器
@EnableHystrixDashboard //启用HystrixDashboard
public class AppStartApplication {
public static void main(String[] args) {
SpringApplication.run(AppStartApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
@Bean
public ServletRegistrationBean getServlet(){
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1); //系统启动时加载顺序
registrationBean.addUrlMappings("/hystrix.stream");//路径
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
7.3 重启应用,访问地址:http://localhost:9004/hystrix.stream
注意:如果没有访问消费者使用Hystrix熔断的接口,监控数据看不到任何数据。
需要首先访问消费者接口:http://localhost:9003/
才能看到Hystrix监控数据。
4.使用Hystrix Dashboard对Hystrix监控数据进行图形化监控
访问http://localhost:9004/hystrix,在出现的Hystrix Dashboard的首页中输入http://localhost:9004/hystrix.stream,
点击“Monitor Stream”按钮,此时我们可以看到如下页面: