SpringCloud上
知识体系思维导图
注意 :没有特殊说明 ,所有SpringBoot项目的启动类的父项目版本为2.1.7
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
</parent>
SpringCloud高级阶段上 (当前所在位置)
第一章 负载均衡Ribbon
第二章 声明式调用Feign
第三章 服务容错保护Hystrix
SpringCloud高级阶段中 传送门
第四章 如何设计微服务
第五章 服务网关Zuul
第六章 分布式配置中心
SpringCloud高级阶段下 传送门
第七章 消息总线Bus
第八章 消息驱动Stream
第九章 分布式服务跟踪Sleuth
第一章 负载均衡Ribbon
一、 Ribbon 在微服务中的作用
1. Ribbon 介绍
- Ribbon 是一个基于 Http 和 TCP 的客服端负载均衡工具,它是基于 Netflix Ribbon 实现的。
- 它不像 spring cloud 服务注册中心、配置中心、API 网关那样独立部署,但是它几乎存在于每个 spring cloud 微服务中。 包括 feign 提供的声明式服务调用也是基于该 Ribbon 实现的。
- ribbon 默认提供很多种负载均衡算法, 例如 轮询、随机 等等。甚至包含自定义的负 载均衡算法。
- 服务之间的通讯方式有两种 :一种是Ribbon方式 ,一种是声明式调用Feign
2. Ribbon 解决了什么问题
解决并提供了微服务的负载均衡的问题。
二、 集中式与进程内负载均衡的区别
1. 负载均衡解决方案的分类
目前业界主流的负载均衡方案可分成两类:
第一类:集中式负载均衡, 即在 consumer 和 provider 之间使用独立的负载均衡设施(可以是硬件,如 F5, 也可以是软件,如 nginx), 由该设施负责把访问请求通过某种策略转发 至 provider;
第二类:进程内负载均衡,将负载均衡逻辑集成到 consumer,consumer 从服务注册中 心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的 provider。 Ribbon 就属于后者,它只是一个类库,集成于 consumer 进程,consumer 通过它来获取 到 provider 的地址。
2. 两种负载均衡方式结构图
三、Ribbon 的入门案例
Ribbon 中对于集群的服务采用的负载均衡的策略默认的是轮询
案例参照初级阶段的Eureka入门案例
将provider部署到虚拟机上,远程调用服务,根据ip的变化查看负载均衡策略
provider
server:
port: 8081
spring:
application:
name: Eureka-Provider
eureka:
client:
serviceUrl:
defaultZone: http://admin:admin@eureka1:8761/eureka/,http://admin:admin@eureka2:8761/eureka/
consumer
server:
port: 8091
spring:
application:
name: Eureka-Consumer
eureka:
client:
serviceUrl:
defaultZone: http://admin:admin@eureka1:8761/eureka/,http://admin:admin@eureka2:8761/eureka/
虚拟机两台虚拟机上需要有Eureka集群和打包好的provider项目,本地hosts文件需要配置对于虚拟机的映射
业务层
@Service
public class UserService {
@Autowired
// ribbon 负载均衡器
private LoadBalancerClient balancerClient;
public List<User> getUsers() {
// 选择调用的服务的名称
// ServiceInstance 封装了服务的基本信息,如 IP,端口
ServiceInstance si = this.balancerClient.choose("Eureka-Provider");
String url = "http://" + si.getHost() + ":" + si.getPort() + "/user";
//StringBuffer sb = new StringBuffer();
//sb.append("http://").append(si.getHost()).append(":").append(si.getPort()).append("/user");
System.out.println(url);
// springMVC RestTemplate
// 发送http请求
RestTemplate restTemplate = new RestTemplate();
// 参数化类型引用
ParameterizedTypeReference<List<User>> responseType = new ParameterizedTypeReference<List<User>>() {};
// ResponseEntity:封装了返回值信息 exchange发送请求 路径 方式 参数
ResponseEntity<List<User>> responseEntity = restTemplate.exchange(url, HttpMethod.GET, null, responseType);
// 获取响应体
List<User> list = responseEntity.getBody();
return list;
}
}
测试结果
四、 Ribbon 的常见负载均衡策略
五、 Ribbon 指定其他负载均衡策略
-
修改代码更换负载均衡策略 ( 添加配置类,或者在启动类进行添加这个方法)
//方式一 @Configuration public class Config { @Bean public RandomRule changeRole() { return new RandomRule(); } } //方式二 @SpringBootApplication @EnableEurekaClient public class SpringCloudEurekaApplication { @Bean public RandomRule changeRole() { return new RandomRule(); } public static void main(String[] args) { SpringApplication.run(SpringCloudEurekaApplication.class, args); } }
-
配置文件更换负载均衡策略
#设置负载均衡策略 eureka-provider 为调用的服务的名称 eureka-provider.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
六、 Ribbon 的点对点直连
不通过注册中心,直接连接服务提供者
-
去掉eureka依赖,添加Ribbon依赖
<!-- ribbon 坐标 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency>
-
修改配置文件
#禁用 eureka ribbon.eureka.enabled=false #指定具体的服务实例主机和端口号 ,而不是Eureka服务的注册中心的端口 Eureka-Provider.ribbon.listOfServers=192.168.179.137:8081
-
同样 默认使用轮询,可以修改
-
测试
访问服务端具体实例
访问消费者这一端
第二章 声明式调用Feign
一、 什么是 Feign
Feign 是一种声明式、模板化的 HTTP 客户端(仅在 consumer 中使用)。
二、 什么是声明式,有什么作用,解决什么问题
声明式调用就像调用本地方法一样调用远程方法;无感知远程 http 请求。
- Spring Cloud 的声明式调用, 可以做到使用 HTTP 请求远程服务时能就像调用本地方法一样的体验,开发者完全感知不到这是远程方法,更感知不到这是个 HTTP 请求。
- 它像 Dubbo 一样,consumer 直接调用接口方法调用 provider,而不需要通过常规的 Http Client 构造请求再解析返回数据。
- 它解决了让开发者调用远程接口就跟调用本地方法一样,无需关注与远程的交互细节,更无需关注分布式环境开发。
三、 编写 Feign 的入门案例
- 需求
实现 Ego 电商平台中的商品基本操作 - 项目设计
如何使用SpringBoot快速创建项目或找到相应版本jar
SpringBoot项目快速构建网址 : https://start.spring.io/
-
创建 Product-service项目
使用SpringBoot快速创建项目。导入相关依赖:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.7.RELEASE</version> </parent>
<!-- SpringBoot配套的运行环境,一定要加上,不加会报错!!! --> <properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.SR2</spring-cloud.version> <maven-jar-plugin.version>2.6</maven-jar-plugin.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
创建pojo(为了简便,将pojo放在server接口项目中)
//1. 实现序列化 //2. 注意取值赋值方法一定要是 public!!! public class Product implements Serializable{ private Integer id; private String name; private double price; }
创建服务接口
/** * 业务层接口 * 注意: @RequestMapping可以修饰接口类与接口方法 * @author chy * */ @RequestMapping("/product") public interface ProductService { /** * 查询所有 * @return */ @RequestMapping(value="findAll",method=RequestMethod.GET) public List<Product> findAll(); }
-
创建Product-Provider项目
使用SpringBoot快速创建jar项目。
修改pom.xml , 导入相关依赖( 导入Product-service项目 )<!-- SpringBoot配套的运行环境,一定要加上,不加会报错!!! --> <properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.SR2</spring-cloud.version> <maven-jar-plugin.version>2.6</maven-jar-plugin.version> </properties> <dependencies> <!-- springBoot 的启动器 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- thymeleaf --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- 添加Ego-Product-Service的坐标 --> <dependency> <groupId>ah.sxzx.springcloud</groupId> <artifactId>12-SpringCloud-Ego-Product-Service</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <!-- Maven的打包插件 --> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
创建全局配置文件application.yml
spring: application: name: Ego-Provider-Provider # jackson: # serialization: # FAIL_ON_EMPTY_BEANS: false server: port: 8001 eureka: client: serviceUrl: defaultZone: http://admin:admin@eureka1:8761/eureka/,http://admin:admin@eureka2:8761/eureka/
创建启动类
@SpringBootApplication @EnableEurekaClient public class StartClass { public static void main(String[] args) { SpringApplication.run(StartClass.class, args); } }
创建控制层(为了简便,直接在控制层处理业务逻辑)
@RestController public class ProductController implements ProductService{ @Override public List<Product> findAll() { List<Product>products=new ArrayList<Product>(); products.add(new Product(1,"保健品")); products.add(new Product(2,"家电产品")); products.add(new Product(3,"生活用品")); products.add(new Product(4,"数码产品")); System.out.println(products); return products; } }
测试项目
启动项目,通过浏览器访问测试是否可用。http://localhost:8001/product/findAll
-
创建Product-Consumer
使用SpringBoot快速创建项目。
修改pom坐标,添加依赖<properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.SR2</spring-cloud.version> <maven-jar-plugin.version>2.6</maven-jar-plugin.version> </properties> <dependencies> <!-- springBoot 的启动器 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!-- 添加Feign的坐标 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!-- 添加Ego-Product-Service的坐标 --> <dependency> <groupId>ah.sxzx.springcloud</groupId> <artifactId>12-SpringCloud-Ego-Product-Service</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</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>
配置文件
spring: application: name: Ego-Provider-Consumer # jackson: # serialization: # FAIL_ON_EMPTY_BEANS: false server: port: 8002 eureka: client: serviceUrl: defaultZone: http://admin:admin@eureka1:8761/eureka/,http://admin:admin@eureka2:8761/eureka/
创建Feign声明式服务调用接口,继承了service接口
@FeignClient(name="Ego-Provider-Provider")//使用@FeignClient注解 name属性指定服务提供者的应用名 public interface ConsumerService extends ProductService{ }
创建控制层
@Controller public class ConsumerController { @Autowired private ConsumerService consumerService; @RequestMapping(value="getAll",method=RequestMethod.GET)//必须使用get @ResponseBody public List<Product> getAll() { return this.consumerService.findAll(); } }
修改启动类
@SpringBootApplication @EnableFeignClients @EnableDiscoveryClient public class SpringCloueEgoProductConsumerApplication { public static void main(String[] args) { SpringApplication.run(SpringCloueEgoProductConsumerApplication.class, args); } }
测试项目
访问指定的路径可以通过远程调用获取数据。
四、 Feign 的请求参数处理
一. 单个参数处理
-
修改服务接口
使用@RequestParam注解修饰参数,确保参数接受过程中不出错。public List<Product> findAll(); /** * 根据id查询信息 ,这里的参数格式要与控制层保持一致 * 但是value值不必一致 * @param id * @return */ @RequestMapping(value="findById",method=RequestMethod.GET) public Product findById(@RequestParam("id")Integer id) ;
-
修改Provider端服务接口实现
@Override public Product findById(Integer id) { return new Product(id, "时间静止服务"); }
-
修改Consumer端服务接口调用
@RequestMapping(value="findId",method=RequestMethod.GET) @ResponseBody public Product findById(@RequestParam("id") Integer id) { return this.consumerService.findById(id); }
二. 多个参数处理
GET 提交方式不能直接传递一个pojo,只能将数据打散,分别传递。可以使用get方式传递对象
GET 提交方式指的是consumer与Provider传递信息的方式,而不是浏览器与consumer的请求方式,当然一致也是可以-
修改服务接口
使用@RequestParam注解修饰参数,确保参数接受过程中不出错。@RequestMapping(value="/add",method=RequestMethod.GET) public Product add(@RequestParam("id")Integer id,@RequestParam("name") String name,@RequestParam("price") Double price);
-
修改Provider端服务接口实现
@Override public Product add(Integer id, String name,Double price) { return new Product(id, name, price); }
-
修改Consumer端服务接口调用(@RequestMapping值可以相同,也可不同)
@RequestMapping(value="add",method=RequestMethod.GET) public Product add(Product product) { //使用get方式请求时,必须将要传递的对象数据平铺 ,即不能将整合对象直接发送 return server.add(product.getId(),product.getName(),product.getPrice()); }
POST 提交方式
Post方式可以传递对象需要使用@ResquestBody
POST 提交方式指的是consumer与Provider传递信息的方式,而不是浏览器与consumer的请求方式,当然一致也是可以 -
-
修改服务接口
使用@RequestBody注解修饰参数,表示从请求体中获取参数。
```java
@RequestMapping(value="/add2",method=RequestMethod.POST)
public Product add2(@RequestBody Product product);
```
-
修改Provider端服务接口实现
@Override public Product add2(Product product) { return product; }
-
修改Consumer端服务接口调用
@RequestMapping(value="/add2") @ResponseBody public Product add2(Product product) { return server.add2(product); }
五、 Feign 的性能优化
一、通过 Gzip 压缩算法,提升网络通信速度
-
gzip 介绍
gzip 介绍:gzip 是一种数据格式,采用用 deflate 算法压缩 data;gzip 是一种流行的文件 压缩算法,应用十分广泛,尤其是在 Linux 平台。 gzip 能力:当 Gzip 压缩到一个纯文本文件时,效果是非常明显的,大约可以减少 70%以上的文件大小。gzip 作用:网络数据经过压缩后实际上 降低了网络传输的字节数, 最明显的好处就是 可以加快网页加载的速度。 网页加载速度加快的好处不言而喻,除了节省流量,改善用户的浏览体验外,另一个潜在的好处是 Gzip 与搜索引擎的抓取工具有着更好的关系。例如 Google 就可以通过直接读取 gzip 文件来比普通手工抓取 更快地检索网页。
-
HTTP 协议中关于压缩传输的规定
第一:客户端向服务器请求中带有:Accept-Encoding:gzip, deflate 字段,向服务器表示, 客户端支持的压缩格式(gzip 或者 deflate),如果不发送该消息头,服务器是不会压缩的。
第二:服务端在收到请求之后,如果发现请求头中含有 Accept-Encoding 字段,并且支持该类型的压缩,就对响应报文压缩之后返回给客户端,并且携带 Content-Encoding:gzip 消息头,表示响应报文是根据该格式压缩过的。
第三:客户端接收到请求之后,先判断是否有 Content-Encoding 消息头,如果有,按该格式解压报文。否则按正常报文处理。
二、. 编写支持 Gzip 压缩案例 (对SpringBoot项目的优化)
这里配置的Gzip压缩是只对Consumer请求Provider时进行的压缩 ,而不是对浏览器请求Consumer进行的压缩,这个筒仓是浏览器负责的
全局配置文件 application.properties
#-----------------------------feign gzip
#配置请求 GZIP 压缩
feign.compression.request.enabled=true
#配置响应 GZIP 压缩
feign.compression.response.enabled=true
#配置压缩支持的 MIME TYPE
feign.compression.request.mime-types=text/xml,application/xml,application/json
#配置压缩数据大小的最小阀值,默认 2048
feign.compression.request.min-request-size=512
对所有请求过程都进行Gzip压缩处理 ,通过查看浏览器的开发者模式查看请求头与响应头验证
全局配置文件 application.properties
#-----------------------------spring boot gzip #是否启用压缩
server.compression.enabled=true
server.compression.mime-types=application/json,application/ xml,text/html,text/xml,text/plain
三、采用 Http 连接池,提升 Feign 的并发吞吐量
为什么 http 连接池能提升性能?
- http 的背景原理
a. 两台服务器建立 http 连接的过程是很复杂的一个过程,涉及到多个数据包的交换,并 且也很耗时间。 b. Http 连接需要的 3 次握手 4 次分手开销很大,这一开销对于大量的比较小的 http 消 息来说更大。 - 优化解决方案
a. 如果我们直接采用 http 连接池,节约了大量的 3 次握手 4 次分手;这样能大大提升吞 吐率。 b. feign 的 http 客户端支持 3 种框架;HttpURLConnection、httpclient、okhttp;默认是 HttpURLConnection。 c. 传统的 HttpURLConnection 是 JDK 自带的,并不支持连接池,如果要实现连接池的 机制,还需要自己来管理连接对象。对于网络请求这种底层相对复杂的操作,如果有可用的 其他方案,也没有必要自己去管理连接对象。 d. HttpClient 相比传统 JDK 自带的 HttpURLConnection,它封装了访问 http 的请求头, 参数,内容体,响应等等;它不仅使客户端发送 HTTP 请求变得容易,而且也方便了开发人 员测试接口(基于 Http 协议的),即提高了开发的效率,也方便提高代码的健壮性;另外 高并发大量的请求网络的时候,还是用“连接池”提升吞吐量。
四、将 Feign 的 Http 客户端工具修改为 HttpClient
添加HttpClient依赖
<!-- 使用Apache HttpClient替换Feign原生httpURLConnection -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/io.github.openfeign/feign-httpclient -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>10.3.0</version>
</dependency>
修改配置文件
#使用HttpClient替换默认的HttpURLConnection HttpClient自带连接池
feign:
httpclient:
enabled: true
修改服务接口
/**
* 使用HTTPClient提升连接性能,get方式
*
* @param product
* @return
*/
@RequestMapping(value="/add3",method=RequestMethod.GET,consumes=MediaType.APPLICATION_JSON_VALUE)
public Product add3(@RequestBody Product product);
修改Provider端服务接口实现
@Override
public Product add3(@RequestBody Product product) {
return product;
}
修改Consumer中的controller
@RequestMapping(value="/add3")
@ResponseBody
public Product add3(Product product) {
return this.consumerService.add3(product);
}
注意:
使用HttpClient是可以使用get方式传递对象参数,但需要请求的类型
@RequestMapping(value="/add3",method=RequestMethod.GET,consumes=MediaType.APPLICATION_JSON_VALUE)
Post方式也需要指明参数类型。
六、 查看微服务日志中记录每个接口 URL,状态码和耗时信息
架构介绍
-
添加日志配置文件logback.xml.
设置日志级别为debug<?xml version="1.0" encoding="UTF-8" ?> <configuration> <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径--> <property name="LOG_HOME" value="${catalina.base}/logs/" /> <!-- 控制台输出 --> <appender name="Stdout" class="ch.qos.logback.core.ConsoleAppender"> <!-- 日志输出编码 --> <layout class="ch.qos.logback.classic.PatternLayout"> <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n </pattern> </layout> </appender> <!-- 按照每天生成日志文件 --> <appender name="RollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender"> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!--日志文件输出的文件名--> <FileNamePattern>${LOG_HOME}/server.%d{yyyy-MM-dd}.log</FileNamePattern> <MaxHistory>30</MaxHistory> </rollingPolicy> <layout class="ch.qos.logback.classic.PatternLayout"> <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n </pattern> </layout> <!--日志文件最大的大小--> <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"> <MaxFileSize>10MB</MaxFileSize> </triggeringPolicy> </appender> <!-- 日志输出级别 --> <root level="DeBug"> <appender-ref ref="Stdout" /> <appender-ref ref="RollingFile" /> </root> <!--日志异步到数据库 --> <!-- <appender name="DB" class="ch.qos.logback.classic.db.DBAppender"> 日志异步到数据库 <connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource"> 连接池 <dataSource class="com.mchange.v2.c3p0.ComboPooledDataSource"> <driverClass>com.mysql.jdbc.Driver</driverClass> <url>jdbc:mysql://127.0.0.1:3306/databaseName</url> <user>root</user> <password>root</password> </dataSource> </connectionSource> </appender> --> </configuration>
-
创建配置类(将@Bean和其下的方法直接复制到启动类中 ):
@Configuration public class LogConfig { // NONE:不记录任何信息,默认值 // BASIC:记录请求方法、请求 URL、状态码和用时 // HEADERS:在 BASIC 基础上再记录一些常用信息 // FULL:记录请求和相应的所有信息 @Bean public Logger.Level getLogs(){ return Logger.Level.FULL; } }
七、 配置 Feign 负载均衡请求超时时间
Feign 的负载均衡底层用的就是 Ribbon
修改配置文件,设置超时时间
-
全局配置 (二选一)
#全局配置 # 请求连接的超时时间 默认的时间为 1 秒 ribbon.ConnectTimeout=5000 # 请求处理的超时时间 ribbon.ReadTimeout=5000
#全局配置 # 请求连接的超时时间 默认的时间为 1 秒 # 请求处理的超时时间 ribbon: ConnectTimeout: 5000 ReadTimeout: 5000
-
根据服务名称进行局部超时配置
前面的是服务提供者的name(Ego-Provider-Provider),可变,针对不同的服务局部设置超时时限。#局部配置 # 对所有操作请求都进行重试 Ego-Provider-Provider.ribbon.OkToRetryOnAllOperations=true # 对当前实例的重试次数 Ego-Provider-Provider.ribbon.MaxAutoRetries=2 # 切换实例的重试次数 Ego-Provider-Provider.ribbon.MaxAutoRetriesNextServer=0 # 请求连接的超时时间 Ego-Provider-Provider.ribbon.ConnectTimeout=3000 # 请求处理的超时时间 Ego-Provider-Provider.ribbon.ReadTimeout=3000
#局部配置 # 对所有操作请求都进行重试 # 对当前实例的重试次数 # 切换实例的重试次数 # 请求连接的超时时间 # 请求处理的超时时间 Ego-Provider-Provider: ribbon: OkToRetryOnAllOperations: true MaxAutoRetries: 2 MaxAutoRetriesNextServer: 0 ConnectTimeout: 3000 ReadTimeout: 3000
第三章 服务容错保护Hystrix
一、 什么是灾难性的雪崩效应
1)当发起少量请求时一切正常
2)当微服务中某个服务发送了大量请求后,可能会导致系统的某个服务瘫痪
3)由于得不到请求 ,会有更多的请求发送 ,会导致瘫痪掉的服务的上一个服务也会因为进程阻塞而瘫痪 ,以此类推 ,导致某系列功能完全瘫痪
4)因为某些节点服务瘫痪 ,可能会导致整个系统瘫痪 ,这就是雪崩效应的大致原理
二、 如何解决灾难性雪崩效应
处理方式 | 具体操作 |
---|---|
降级 | 超时降级、资源不足时(线程或信号量)降级,降级后可以配合降级接口返回托底数据。 实现一个 fallback 方法, 当请求后端服务出现异常的时候, 可以使用 fallback 方法返回的值. |
隔离(线程池隔离和信号量隔离) | 限制调用分布式服务的资源使用,某一个调用的服务出现问题不会影响其他服务调用。 |
熔断 | 当失败率(如因网络故障/超时造成的失败率高)达到阀值自动触发降级, 熔断器触发的快 速失败会进行快速恢复。 |
请求缓存 | 提供了请求缓存。 |
请求合并 | 提供请求合并。 |
降级
对服务做降级处理 , 当服务宕机时, 返回托底数据
1.创建jar项目 ,添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2.修改consumer的配置文件(端口号 ,应用名)
server:
port: 8091
spring:
application:
name: Eureka-Consumer-Ribbon-hystrix
eureka:
client:
serviceUrl:
defaultZone: http://admin:admin@eureka1:8761/eureka/,http://admin:admin@eureka2:8761/eureka/
3.修改消费者启动类,开启熔断器
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker //添加熔断器,开启熔断
public class SpringCloudEurekaApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudEurekaApplication.class, args);
}
}
4.修改服务提供者的业务
添加了@HystrixCommand(fallbackMethod=“fallback”)注解
添加了返回托底数据的方法
@Service
public class ProductService {
@Autowired
// ribbon 负载均衡器
private LoadBalancerClient balancerClient;
@HystrixCommand(fallbackMethod="fallback")
public List<Product> getUsers() {
// 选择调用的服务的名称
// ServiceInstance 封装了服务的基本信息,如 IP,端口
ServiceInstance si = this.balancerClient.choose("Ego-Provider-Provider");
// http://localhost:8001/product/findAll
String url = "http://" + si.getHost() + ":" + si.getPort() + "/product/findAll";
//StringBuffer sb = new StringBuffer();
//sb.append("http://").append(si.getHost()).append(":").append(si.getPort()).append("/user");
System.out.println(url);
// springMVC RestTemplate
// 发送http请求
RestTemplate restTemplate = new RestTemplate();
// 参数化类型引用
ParameterizedTypeReference<List<Product>> responseType = new ParameterizedTypeReference<List<Product>>() {};
// ResponseEntity:封装了返回值信息 exchange发送请求 路径 方式 参数
ResponseEntity<List<Product>> responseEntity = restTemplate.exchange(url, HttpMethod.GET, null, responseType);
// 获取响应体
List<Product> list = responseEntity.getBody();
return list;
}
//返回返回托底数据的方法
public List<Product> fallback() {
List<Product>list=new ArrayList<Product>();
list.add(new Product(-1,"返回托底数据"));
return list;
}
}
测试结果:
提供者正常工作时
提供者休眠或是宕机时
托底数据会在服务提供者无法正常提供服务时返回结果。
注意:
以下四种情况将触发 getFallback 调用
(1) 方法抛出非 HystrixBadRequestException 异常。
(2) 方法调用超时
(3) 熔断器开启拦截调用
(4) 线程池/队列/信号量是否跑满
请求缓存
Hystrix 为了降低访问服务的频率,支持将一个请求与返回结果做缓存处理。如果再次请求的 URL 没有变化,那么 Hystrix 不会请求服务,而是直接从缓存中将结果返回。这样可以大大降低访问服务的压力。
Hystrix 自带缓存。有两个缺点:
- 是一个本地缓存。在集群情况下缓存是不能同步的。
- 不支持第三方缓存容器。Redis,memcache 不支持的。 可以使用 spring 的 cache。
Hystrix整合redis缓存项目案例(可以独立于Hystrix)
1.创建项目 .添加坐标
<!-- 添加Redis的jar,开启redis本地缓存 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.修改全局配置文件
spring:
application:
name: Eureka-Consumer-Ribbon-Cache
redis:
#redis单机配置
host: 192.168.179.131
port: 6379
database: 0
#redis连接池配置
jedis:
pool:
max-idle: 10
min-idle: 5
max-active: 100
max-wait: 3000
timeout: 600
eureka:
client:
serviceUrl:
defaultZone: http://admin:admin@eureka1:8761/eureka/,http://admin:admin@eureka2:8761/eureka/
3.修改业务层功能实现类
@CacheConfig(cacheNames={ “ah.szxy.pojo.Product” }) //指定对哪个实体类开启缓存
@Service
@CacheConfig(cacheNames={ "ah.szxy.pojo.Product" }) //指定对哪个实体类开启缓存
public class ProductService {
//注意使用缓存时key的设置,使用字符串需要加单引号。#id表示应用参数中的id属性
@Cacheable(key="'product'+#id")
public Product findProduct(Integer id) {
System.out.println("++++++SAVE+++++++"+id);
return new Product(id, "新的商品");
}
//删除缓存
@CacheEvict(key="'product'+#id")
public void delProduct(Integer id) {
System.out.println("++++++DEL+++++++"+id);
}
}
4.修改启动类
@EnableCaching //在启动类中开启缓存机制
@SpringBootApplication
@EnableEurekaClient
//@EnableCircuitBreaker //添加熔断器,开启熔断
@EnableCaching
public class SpringCloudEurekaApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudEurekaApplication.class, args);
}
}
5.测试 ,只启动consumer,访问如下路径 ,查看redis数据库中是否缓存该数据,运行删除,查看redis数据库是否删除该数据库
请求合并
未合并请求之前
合并请求之后, 将所有请求放在一个list集合中 ,然后统一放到线程池中请求provider
什么情况下使用请求合并
在微服务架构中,我们将一个项目拆分成很多个独立的模块,这些独立的模块通过远程调用来互相配合工作,但是,在高并发情况下,通信次数的增加会导致总的通信时间增加, 同时,线程池的资源也是有限的,高并发环境会导致有大量的线程处于等待状态,进而导致响应延迟,为了解决这些问题,我们需要来了解 Hystrix 的请求合并。
请求合并的缺点
设置请求合并之后,本来一个请求可能 5ms 就搞定了,但是现在必须再等 10ms 看看还 有没有其他的请求一起的,这样一个请求的耗时就从 5ms 增加到 15ms 了,不过,如果我们要发起的命令本身就是一个高延迟的命令,那么这个时候就可以使用请求合并了,因为这个时候时间窗的时间消耗就显得微不足道了,另外高并发也是请求合并的一个非常重要的场景。
1.创建快速构建项目,修改pom.xml
<!-- 添加hystrix坐标 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2.修改全局文件
server:
port: 8888
spring:
application:
name: Eureka-Consumer-Ribbon-Batch
eureka:
client:
serviceUrl:
defaultZone: http://admin:admin@eureka1:8761/eureka/,http://admin:admin@eureka2:8761/eureka/
3.修改业务层的具体实现代码
注意
两个注解 @HystrixCollapser(合并参数的设置) ,@HystrixCommand (真正调用provider的方法 形参必须是list不能是数组)
/**
* 利用Hystrix进行请求合并
*
* @return
*/
@HystrixCollapser(batchMethod = "batchProduct", scope = Scope.GLOBAL, collapserProperties = {
// 请求时间间隔在 20ms 之内的请求会被合并为一个请求,默认 为 10ms
@HystrixProperty(name = "timerDelayInMilliseconds", value = "20"),
// 设置触发批处理执行之前,在批处理中允许的最大请求数。
@HystrixProperty(name = "maxRequestsInBatch", value = "200") })
public Future<Product> getProduct(Integer id) {//consumer的controller调用方法,该方法返回Future类型
System.out.println("ProductConsumerServer.getProduct()");
return null;
}
// 真正调用provider的方法 形参必须是list不能是数组
@HystrixCommand
public List<Product> batchProduct(List<Integer> ids) {
for (Integer id : ids) {
System.out.println(id);
}
List<Product> list = new ArrayList<Product>();
list.add(new Product(1, "电视"));
list.add(new Product(2, "电脑"));
list.add(new Product(3, "ssd"));
System.out.println("获取到数据");
return list;
}
4.Controller
@RequestMapping("/find")
@ResponseBody
//请求getProduct, 实现上执行的是batchProduct
public void allProducts() throws Exception{
Future<Product> product = userService.getProduct(1);
Future<Product> product2 = userService.getProduct(2);
Future<Product> product3 = userService.getProduct(3);
System.out.println("id:1"+product.get());
System.out.println("id:2"+product2.get());
System.out.println("id:3"+product3.get());
}
测试结果
浏览器访问 : http://localhost:8888/find
控制台显示
服务熔断
1.快速构建创建,修改pom文件
<!-- 添加hystrix坐标 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2.修改全局配置文件
server:
port: 8888
spring:
application:
name: Eureka-Consumer-Ribbon-Breaker
eureka:
client:
serviceUrl:
defaultZone: http://admin:admin@eureka1:8761/eureka/,http://admin:admin@eureka2:8761/eureka/
3.修改consumer业务层的具体实现
注意 :
rollback方法中的参数要与调用托底方法的方法一致,不然会出现关于 hystrix 的异常 fallback method wasn’t found
@Service
public class ProductService {
@Autowired
// ribbon 负载均衡器
private LoadBalancerClient balancerClient;
/**
* 利用Hystrix进行服务熔断
*
* @return
*/
// 设置熔断
@HystrixCommand(fallbackMethod = "fallback", commandProperties = {
// 默认 20 个;10s 内请求数大于 20 个时就启动熔断器,当请 求符合熔断条件时将触发 getFallback()。
@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD, value = "10"),
// 请求错误率大于 50%时就熔断,然后 for 循环发起请求, 当请求符合熔断条件时将触发 getFallback()。
@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE, value = "50"),
// 默认 5 秒;熔断多少秒后去尝试请求
@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS, value = "5000") })
public List<Product> getProduct(Integer id) {
System.out.println(id);
if (id==1) {
throw new RuntimeException();
}
// 选择调用的服务的名称
// ServiceInstance 封装了服务的基本信息,如 IP,端口
ServiceInstance si = this.balancerClient.choose("Ego-Provider-Provider");
// http://localhost:8001/product/findAll
String url = "http://" + si.getHost() + ":" + si.getPort() + "/product/findAll";
// StringBuffer sb = new StringBuffer();
// sb.append("http://").append(si.getHost()).append(":").append(si.getPort()).append("/user");
System.out.println(url);
// springMVC RestTemplate
// 发送http请求
RestTemplate restTemplate = new RestTemplate();
// 参数化类型引用
ParameterizedTypeReference<List<Product>> responseType = new ParameterizedTypeReference<List<Product>>() {
};
// ResponseEntity:封装了返回值信息 exchange发送请求 路径 方式 参数
ResponseEntity<List<Product>> responseEntity = restTemplate.exchange(url, HttpMethod.GET, null, responseType);
// 获取响应体
List<Product> list = responseEntity.getBody();
return list;
}
/**
* 返回托底数据(这里的参数要与调用托底方法的方法一致)
*
* @return
*/
public List<Product> fallback(Integer id) {
List<Product> list = new ArrayList<Product>();
list.add(new Product(-1, "返回托底数据"));
return list;
}
}
4.controller
@RestController
public class ProductController {
@Autowired
private ProductService userService;
@RequestMapping("consumer")
public List<Product> getUsers(@RequestParam("id") Integer id) {
return this.userService.getProduct(id);
}
}
5.启动类
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker //添加熔断器,开启熔断
public class SpringCloudEurekaApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudEurekaApplication.class, args);
}
}
测试结果 访问 http://localhost:8888/consumer?id=1
控制台
隔离
一. 线程隔离
将多个服务接口同时放到一个线程池中 ,当一个服务并发量急剧增加时 ,线程池会瘫痪,另一个线程也会完蛋
预测哪个服务接口线程并发量大 ,将并发量大的服务接口线程单独拉出来放到另一个线程池中 , 减少服务器的压力,同时这个服务出错时也不会影响其他服务
线程隔离____实现步骤
1.快速构建SpringCloud项目,修改pom文件
<!-- 添加hystrix坐标 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2.修改全局配置文件
server:
port: 8888
spring:
application:
name: Eureka-Consumer-Ribbon-ThreadPool
eureka:
client:
serviceUrl:
defaultZone: http://admin:admin@eureka1:8761/eureka/,http://admin:admin@eureka2:8761/eureka/
3.修改业务层的具体实现
注意:
@HystrixCommand注解以及相关属性的使用
@Service
public class ProductService {
@Autowired
// ribbon 负载均衡器
private LoadBalancerClient balancerClient;
/**
* 线程隔离的实现
* 主要修改 groupKey , commandKey ,threadPoolKey三个属性
* @param id
* @return
*/
@HystrixCommand(
//线程组名
groupKey="Eureka-Consumer-Ribbon-ThreadPool",
//consumer接口名称
commandKey="getProduct",
//线程池名
threadPoolKey="Eureka-Consumer-Ribbon-threadPool",
threadPoolProperties= {
//线程池大小
@HystrixProperty(name=HystrixPropertiesManager.CORE_SIZE,value="30"),
//最大队列长度
@HystrixProperty(name=HystrixPropertiesManager.MAX_QUEUE_SIZE,value="100"),
//线程存活时间
@HystrixProperty(name=HystrixPropertiesManager.KEEP_ALIVE_TIME_MINUTES,value="2"),
//拒绝请求
@HystrixProperty(name=HystrixPropertiesManager.QUEUE_SIZE_REJECTION_THRESHOLD,value="15"),
},
fallbackMethod="fallback")
public List<Product> getProduct() {
System.out.println(Thread.currentThread().getName());
// 选择调用的服务的名称
// ServiceInstance 封装了服务的基本信息,如 IP,端口
ServiceInstance si = this.balancerClient.choose("Ego-Provider-Provider");
// http://localhost:8001/product/findAll
String url = "http://" + si.getHost() + ":" + si.getPort() + "/product/findAll";
// StringBuffer sb = new StringBuffer();
// sb.append("http://").append(si.getHost()).append(":").append(si.getPort()).append("/user");
System.out.println(url);
// springMVC RestTemplate
// 发送http请求
RestTemplate restTemplate = new RestTemplate();
// 参数化类型引用
ParameterizedTypeReference<List<Product>> responseType = new ParameterizedTypeReference<List<Product>>() {
};
// ResponseEntity:封装了返回值信息 exchange发送请求 路径 方式 参数
ResponseEntity<List<Product>> responseEntity = restTemplate.exchange(url, HttpMethod.GET, null, responseType);
// 获取响应体
List<Product> list = responseEntity.getBody();
return list;
}
/**
* 返回托底数据(这里的参数要与调用托底方法的方法一致)
*
* @return
*/
public List<Product> fallback() {
System.out.println(Thread.currentThread().getName());
List<Product> list = new ArrayList<Product>();
list.add(new Product(-1, "返回托底数据"));
return list;
}
//线程隔离的对比方法
public void showThread() {
System.out.println("ProductService.showThread():"+Thread.currentThread().getName());
}
}
4.controller
@RestController
public class ProductController {
@Autowired
private ProductService userService;
@RequestMapping("consumer")
public List<Product> getUsers() {
return this.userService.getProduct();
}
@RequestMapping("show")
public void showThread() {
this.userService.showThread();
}
}
5.启动类同上
测试结果,分别访问
请求被隔离的线程时 ( http://localhost:8888/consumer )
请求没被隔离的线程时 ( http://localhost:8888/show )
信号量隔离
当Consumer发送大量请求到provider时 ,会访问信号量池
信号量隔离____实现步骤
1.快速构建消费者项目,添加相关坐标
<!-- 添加hystrix坐标 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2.修改全局配置文件
server:
port: 8888
spring:
application:
name: Eureka-Consumer-Ribbon-Semaphore
eureka:
client:
serviceUrl:
defaultZone: http://admin:admin@eureka1:8761/eureka/,http://admin:admin@eureka2:8761/eureka/
3.修改业务层的具体实现类
相比上一种方式,主要是修改了@HystrixCommand注解里面的属性
@Service
public class ProductConsumerServer {
@Autowired
//ribbon 负 载均衡器
private LoadBalancerClient balancerClient;
@HystrixCommand(
fallbackMethod="fallback",
commandProperties= {
// 信号量 隔离
@HystrixProperty(name=HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY,value="SEMAPHORE"),
//信号量最大并度
@HystrixProperty(name=HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS,value="100"),
})
public List<Product> allProducts(){
//选择调用的服务的名称
//ServiceInstance 封装了服务的基本信息,如 IP,端口
ServiceInstance si = this.balancerClient.choose("Ego-Provider-Provider");
String url="http://"+si.getHost()+":"+si.getPort()+"/product/findAll";
System.out.println(url);
//springMVC RestTemplate
//发送http请求
RestTemplate restTemplate=new RestTemplate();
//参数化类型引用
ParameterizedTypeReference<List<Product>> responseType=new ParameterizedTypeReference<List<Product>>() { };
//ResponseEntity:封装了返回值信息 exchange发送请求 路径 方式 参数
ResponseEntity<List<Product>> responseEntity = restTemplate.exchange(url, HttpMethod.GET, null, responseType);
//获取响应体
List<Product> list=responseEntity.getBody();
return list;
}
public List<Product> fallback(){
List<Product> list=new ArrayList<Product>();
list.add(new Product(-1, "拖地数据"));
return list;
}
}
4.controller,启动类,同上
三、线程池隔离和信号量隔离的区别
二者的使用前提
四、Feign的降级处理
Feign的雪崩效应的处理
1.快速构建项目,修改pom文件
<!-- 添加Feign的坐标 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
注意 : Feign的底层封装了Ribbon的所有功能 ,故hystrix坐标不添加也能实现该功能 ,因此实启动类可以不加@EnableCircuitBreaker 开启熔断
2.修改全局配置文件application.yml
spring:
application:
name: SpringCloud-Ego-Product-Feign-rollback
server:
port: 9999
#指定Eureka集群的注册中心 http://注册中心名称:注册中心密码@注册中心主机所在地址:端口号/eureka/
eureka:
client:
serviceUrl:
defaultZone: http://admin:admin@eureka1:8761/eureka/,http://admin:admin@eureka2:8761/eureka/
#Feign默认不开启Hystrix,为false
feign:
hystrix:
enabled: true
3.创建业务层的接口
注意@FeignClient注解以及相关属性的使用 ,指定返回托底数据的类
@FeignClient(name="Ego-Provider-Provider",fallback=ConsumerServiceImplRallback.class)//使用@FeignClient注解 name属性指定服务提供者的应用名
public interface ConsumerService{
/**
* 查询所有商品
* @return
*/
@RequestMapping(value="/product/findAll",method=RequestMethod.GET)
public List<Product> findAll();
}
4.创建托底数据类( 相当于业务层的实现类 )
注意加上 @Component
@Component
public class ConsumerServiceImplRallback implements ConsumerService{
@Override
public List<Product> findAll() {
List<Product>list=new ArrayList<Product>();
list.add(new Product(-1, "本次返回托底数据"));
return list;
}
}
5.controller类
@Controller
public class ConsumerController {
@Autowired
private ConsumerService consumerService;
@RequestMapping(value="consumer",method=RequestMethod.GET)//必须使用get
@ResponseBody
public List<Product> getAll() {
return this.consumerService.findAll();
}
}
6.启动类
不需要加熔断器的注解
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class StartClass {
public static void main(String[] args) {
SpringApplication.run(StartClass.class, args);
}
}
测试结果
提供者正常运行
提供者宕机时(可以在controller中令某个方法休眠即可)
Feign降级后的异常记录
在使用Feign的开发过程中 ,出现了托底数据 ,
可以通过这种方式获取异常的相关信息 ,帮助我们解决异常
1.快速构建SpringCloud项目,添加Feign声明式调用的坐标
2.修改全局配置文件,开启hystrix
spring:
application:
name: SpringCloud-Ego-Product-Feign-rollback
server:
port: 9999
#指定Eureka集群的注册中心 http://注册中心名称:注册中心密码@注册中心主机所在地址:端口号/eureka/
eureka:
client:
serviceUrl:
defaultZone: http://admin:admin@eureka1:8761/eureka/,http://admin:admin@eureka2:8761/eureka/
#Feign默认不开启Hystrix,为false
feign:
hystrix:
enabled: true
3.修改业务层接口
注意 : @FeignClient注解fallbackFactory属性的使用
//使用@FeignClient注解 name属性指定服务提供者的应用名
@FeignClient(name="Ego-Provider-Provider",fallbackFactory=ConsumerServiceImplRallbackFactory.class)
public interface ConsumerService{
/**
* 查询所有商品
* @return
*/
@RequestMapping(value="/product/findAll",method=RequestMethod.GET)
public List<Product> findAll();
}
4.接口的具体实现类
继承FallbackFactory接口
开启日志组件
继承了业务层的接口 , 实例化这个接口, 通过匿名内部类实现
/**
* FallbackFactory<接口类>
* @author 曹海洋
*
*/
@Component
public class ConsumerServiceImplRallbackFactory implements FallbackFactory<ConsumerService>{
//使用的是slf4j中的日志组件
Logger logger=LoggerFactory.getLogger(ConsumerServiceImplRallbackFactory.class);
/**
* 继承了业务层的接口 , 实例化这个接口, 通过匿名内部类实现
* @param arg0
* @return
*/
@Override
public ConsumerService create(Throwable arg0) {
return new ConsumerService() {
@Override
public List<Product> findAll() {
//显示日志信息
logger.warn("手动打印FallBack Exception:",arg0);
List<Product>list=new ArrayList<Product>();
list.add(new Product(-1, "本次返回托底数据"));
return list;
}
};
}
}
5.controller类
@Controller
public class ConsumerController {
@Autowired
private ConsumerService consumerService;
@RequestMapping(value="consumer",method=RequestMethod.GET)//必须使用get
@ResponseBody
public List<Product> getAll() {
return this.consumerService.findAll();
}
}
测试效果
服务提供者正常的情况下访问消费者
服务提供者宕机的情况下访问消费者
控制台输出业务层定义的日志打印信息
如果觉得这样麻烦可以考虑在Resource目录下直接放入日志定义文件logback.xml, 选择合适的日志级别(debug)信息进行输出
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
<property name="LOG_HOME" value="${catalina.base}/logs/" />
<!-- 控制台输出 -->
<appender name="Stdout" class="ch.qos.logback.core.ConsoleAppender">
<!-- 日志输出编码 -->
<layout class="ch.qos.logback.classic.PatternLayout">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
</pattern>
</layout>
</appender>
<!-- 按照每天生成日志文件 -->
<appender name="RollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<FileNamePattern>${LOG_HOME}/server.%d{yyyy-MM-dd}.log</FileNamePattern>
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<layout class="ch.qos.logback.classic.PatternLayout">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
</pattern>
</layout>
<!--日志文件最大的大小-->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>
</appender>
<!-- 日志输出级别 -->
<root level="info">
<appender-ref ref="Stdout" />
<appender-ref ref="RollingFile" />
</root>
<!--日志异步到数据库 -->
<!-- <appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
日志异步到数据库
<connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
连接池
<dataSource class="com.mchange.v2.c3p0.ComboPooledDataSource">
<driverClass>com.mysql.jdbc.Driver</driverClass>
<url>jdbc:mysql://127.0.0.1:3306/databaseName</url>
<user>root</user>
<password>root</password>
</dataSource>
</connectionSource>
</appender> -->
</configuration>
五、 可视化的数据监控 Hystrix-dashboard
监控单个服务
Hystrix-dashboard 是一款针对 Hystrix 进行实时监控的工具,通过 Hystrix Dashboard 我们可以在直观地看到各 Hystrix Command 的请求响应时间, 请求成功率等数据。
创建被监控的程序
1.创建项目
复制上面的threadpool项目
2.添加依赖
<!-- 添加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>
<!-- 可视化数据监控dashboard -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
3.修改全局配置文件application.yml
server:
port: 8888
spring:
application:
name: Eureka-Consumer-Ribbon-DashBoard
eureka:
client:
serviceUrl:
defaultZone: http://admin:admin@eureka1:8761/eureka/,http://admin:admin@eureka2:8761/eureka/
4.添加配置类(SpringBoot2.0版本以上) 或将该方法放到启动类中
@Configuration
public class Config {
// SpringBoot2.0以后,不提供 hystrix.stream节点,需要自己增加
@Bean
public ServletRegistrationBean<HystrixMetricsStreamServlet> getServlet(){
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean<HystrixMetricsStreamServlet> registrationBean = new ServletRegistrationBean<HystrixMetricsStreamServlet>(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/actuator/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
5.修改启动类
添加了 @EnableHystrix , @EnableHystrixDashboard 这两个注解
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker //开启熔断
@EnableHystrix
@EnableHystrixDashboard
public class SpringCloudEurekaApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudEurekaApplication.class, args);
}
}
6.实体类以及controller
public class Product implements Serializable{
private static final long serialVersionUID = 46756798510036334L;
private Integer id;
private String name;
//...
}
@RestController
public class ProductController {
@Autowired
private ProductService userService;
@RequestMapping("consumer")
public List<Product> getUsers() {
return this.userService.getProduct();
}
@RequestMapping("show")
public void showThread() {
this.userService.showThread();
}
}
运行测试
首先先让程序跑一下 http://localhost:8888/consumer,
然后访问 http://服务所在ip:端口号/actuator/hystrix.stream
创建Hystrix-dashboard 监控中心
用于检测服务的各种数据 , 预防雪崩效应
1.创建项目 , 修改pom文件
<!-- 添加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>
<!-- 可视化数据监控dashboard -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
2.修改全局配置文件
server:
port: 9999
spring:
application:
name: Eureka-Consumer-Ribbon-DashBoard-view
#可有可无,添加Eureka的目的是方便管理服务
eureka:
client:
serviceUrl:
defaultZone: http://admin:admin@eureka1:8761/eureka/,http://admin:admin@eureka2:8761/eureka/
3.修改启动类
添加了@EnableHystrix , @EnableHystrixDashboard注解
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker //开启熔断
@EnableHystrix
@EnableHystrixDashboard
public class SpringCloudEurekaApplication {
// SpringBoot2.0以后,不提供 hystrix.stream节点,需要自己增加
@Bean
public ServletRegistrationBean<HystrixMetricsStreamServlet> getServlet(){
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean<HystrixMetricsStreamServlet> registrationBean = new ServletRegistrationBean<HystrixMetricsStreamServlet>(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/actuator/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
public static void main(String[] args) {
SpringApplication.run(SpringCloudEurekaApplication.class, args);
}
}
测试效果
访问http://hystrix-app:port/hystrix,输入要被检测的服务的url。
在下方的第一个输入框输入: http://服务所在主机ip : 端口号/actuator/hystrix.stream。
可以看到仪表盘显示相关信息
监控多个服务
Turbine对监控服务做了聚合 ,但是仍需要依赖Dashboard
前提:被监控的所有服务需要
a,添加如下坐标
<!-- 被Turbine监控服务的三件套 -->
<!-- 添加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>
<!-- 可视化数据监控dashboard -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
b.修改启动类
添加 @EnableHystrix ,@EnableHystrixDashboard 两个注解
添加 @Bean以及getServlet方法,以便服务数据能够被监控
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker //开启熔断
@EnableHystrix
@EnableHystrixDashboard
public class SpringCloudEurekaApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudEurekaApplication.class, args);
}
// SpringBoot2.0以后,不提供 hystrix.stream节点,需要自己增加
@Bean
public ServletRegistrationBean<HystrixMetricsStreamServlet> getServlet(){
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean<HystrixMetricsStreamServlet> registrationBean = new ServletRegistrationBean<HystrixMetricsStreamServlet>(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/actuator/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
步骤
1.快速构建项目 ,添加jar
<!-- 添加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>
<!-- 可视化数据监控dashboard -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<!-- 添加Turbine坐标,同时监控多个服务 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-turbine</artifactId>
</dependency>
2.修改全局配置文件
通过app-config指定要监控的服务应用的名称
server:
port: 8899
spring:
application:
name: Eureka-Consumer-Ribbon-Turbine
eureka:
client:
serviceUrl:
defaultZone: http://admin:admin@eureka1:8761/eureka/,http://admin:admin@eureka2:8761/eureka/
#---------------------------------------turbine-------------------------
#配置 Eureka 中的 serviceId 列表,表明监控哪些服务
turbine:
app-config: SpringCloud-Ego-Product-Feign-rollback,Eureka-Consumer-Ribbon-DashBoard
#指定聚合哪些集群,多个使用","分割,默认为 default。可使用 http://.../turbine.stream?cluster={clusterConfig 之一}访问
aggregator:
cluster-config:
- default
# 1. clusterNameExpression 指定集群名称,默认表达式 appName;此 时:turbine.aggregator.clusterConfig 需要配置想要监控的应用名称;
# 2. 当 clusterNameExpression: default 时, turbine.aggregator.clusterConfig 可以不写,因为默认就是 default;
# 3. 当 clusterNameExpression: metadata['cluster']时,假设想要 监控的应用配置了 eureka.instance.metadata-map.cluster: ABC,
# 则需要配置,同时 turbine.aggregator.clusterConfig: ABC
clusterNameExpression: new String("default")
#开启监控端点 2.0后需要手动开启,这段配置的效果相当于创建了Dashboard的配置类
#management:
# endpoints:
# web:
# exposure:
# include: turbine.stream
3.创建配置类(三种方式任选其一)
可以单独创建
可以将@bean下的方法放到启动类中
也可以直接在全局配置文件中开启监控端点(最下方注释的部分)
@Configuration
public class Config {
// SpringBoot2.0以后,不提供 hystrix.stream节点,需要自己增加
@Bean
public ServletRegistrationBean<HystrixMetricsStreamServlet> getServlet(){
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean<HystrixMetricsStreamServlet> registrationBean = new ServletRegistrationBean<HystrixMetricsStreamServlet>(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/actuator/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
4.启动类
使用Turbine管理工具需要加@EnableTurbine 注解
@SpringBootApplication
@EnableTurbine
public class SpringCloudEurekaApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudEurekaApplication.class, args);
}
}
测试效果
访问第一个被监控的项目的一个Controller的方法,然后访问监控端点 http://localhost:9999/actuator/hystrix.stream
访问第二个被监控的项目的一个Controller的方法,然后访问监控端点 http://localhost:8888/actuator/hystrix.stream
访问Turbine监控地址 http://localhost:8899/turbine.stream
访问Dashboard的视图管理页面 http://localhost:10000/hystrix/ ,在视图界面的输入框添加Turbine监控地址
监控集群
即监控相同服务名的多个consumer。(根据上个案例稍作修改)
a.复制上方案例的其中一个consumer,放入到Linux系统中 ,编写启动脚本,发布运行点击查看如何在虚拟机中发布SpringCloud项目
b.运行本地的这个同名consumer
c.修改Turbine监控的配置文件 ,启动运行
#配置 Eureka 中的 serviceId 列表,表明监控哪些服务
turbine:
#app-config: SpringCloud-Ego-Product-Feign-rollback,Eureka-Consumer-Ribbon-DashBoard
app-config: EUREKA-CONSUMER-RIBBON-DASHBOARD-FORLINUX #被监控的同名Consumer
d,访问两个同名Consumer的Controller中的方法,产生数据
然后访问每个Consumer对应的服务监控的url (/actuator/hystrix.stream),
访问Turbine监控的url( /turbine.stream )
e. 打开Dashboard图形化管理界面 ,输入Turbine的url,进入该管理界面
host为2表示有连个同名服务,即服务集群 ,也可以访问Eureka的注册中心验证
六、 采用RabbitMQ,收集监控数据
解释:
采取第一种方式时, 使用的是Turbine来聚合多种服务 ,然后通过Dashboard图形化管理界面来进行管理
采取第二种方式(RabbitMQ),使用的是RabbitMQ来对服务进行聚合然后将数据传输给Turbine最后发送给Dashboard图形化管理界面来进行管理
配置步骤:
consumer端
- 创建项目,修改pom文件(其实这里添加了DashBoard坐标,也可以通过这里来访问图形化界面)
<!-- 添加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>
<!-- 可视化数据监控dashboard -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<!-- 使用RabbitMQ监控时需添加的-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-hystrix-stream</artifactId>
</dependency>
<!-- 使用RabbitMQ监控时需添加的-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
- 修改配置文件
server:
port: 8888
spring:
application:
name: Eureka-Consumer-Ribbon-DashBoard-ForLinux
rabbitmq: #rabbitmq相关信息的设置
host: 192.168.179.136
port: 5672
username: mquser
password: mquser
virtual-host: /
eureka:
client:
serviceUrl:
defaultZone: http://admin:admin@eureka1:8761/eureka/,http://admin:admin@eureka2:8761/eureka/
#开启监控端点
management:
endpoints:
web:
exposure:
include: hystrix.stream
- 启动类
@SpringBootApplication
@EnableFeignClients
@EnableCircuitBreaker//开启熔断
@EnableHystrix
@EnableHystrixDashboard
public class SpringCloueApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloueApplication.class, args);
}
}
TurbineMQ(负责聚合服务并传输信息给图形化界面)
1.创建项目, 修改pom文件(必须有actuator、turbine、turbine-stream、hystrix-stream、stream-rabbit)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-turbine-stream</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-turbine</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-hystrix-stream</artifactId>
</dependency>
2.修改全局配置文件
server:
port: 8899
spring:
application:
name: Eureka-Consumer-Ribbon-Turbine
rabbitmq: #rabbitmq相关信息的设置
host: 192.168.179.136
port: 5672
username: mquser
password: mquser
virtual-host: /
eureka:
client:
serviceUrl:
defaultZone: http://admin:admin@eureka1:8761/eureka/,http://admin:admin@eureka2:8761/eureka/
#开启监控端点 2.0后需要手动开启,这段配置的效果相当于创建了Dashboard的配置类
management:
endpoints:
web:
exposure:
include: turbine.stream,hystrix.stream
3.启动类
更换成@@EnableTurbineStream注解,用于启动TurbineMQ
@SpringBootApplication
@EnableTurbineStream
public class SpringCloudEurekaApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudEurekaApplication.class, args);
}
}
测试:
a.访问消费者程序的一个Controller的方法,产生数据,然后访问它的数据监控端
b.访问TurbineMQ的聚合数据的url,看看是否聚合了数据(http://RabbitMQ_Application-name:port/turbine.stream)
c.访问Dashboard数据图形化管理界面
同时RabbitMQ图形化界面显示了这两个注册的程序