1.前言
1.1.技术要求
Java8 + maven + git、github + nginx + RabbitMQ + Spring Boot 2.0
1.2.微服务架构概述
什么是微服务
In short, the microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies.——James Lewis and Martin Fowler (2014)
- 微服务是一种架构风格
- 一个应用拆分为一组小型服务
- 每个服务运行在自己的进程内,也就是可独立部署和升级
- 服务之间使用轻量级HTTP交互
- 服务围绕业务功能拆分
- 可以由全自动部署机制独立部署
- 去中心化,服务自治。服务可以使用不同的语言、不同的存储技术
微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间互相协调、互相配合,为用户提供最终价值。每个微务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相协作(通常是基于HTTP协议的RESTful AP))。每个服务都围绕着具体业务进行构建,并且能够被独立的部署到生产环境、类生产环境等。另外,应当尽量避免统一的、集中式的服务管理机制,对具体的一个服务面善应根据业务上下文,选择合适的语言、工具对其进行构建。
Spring Cloud包含的功能
- 服务注册与发现
- 服务调用
- 服务熔断
- 负载均衡
- 服务降级
- 服务消息队列
- 配置中心管理
- 服务网关
- 服务监控
- 全链路追踪
- 自动化构建部署
- 服务定时任务
1.3.Spring Cloud简介
是什么?符合微服务技术维度
SpringCloud:分布式微服务架构的站式解决方案,是多种微服务架构落地技术的集合体,俗称微服务全家桶
京东:
阿里:
京东:
2.Boot和Cloud版本选型
2.1.SpringCloud的版本关系
Spring Cloud 采用了英国伦敦地铁站的名称来命名,并由地铁站名称字母A-Z依次类推的形式来发布迭代版本
SpringCloud是一个由许多子项目组成的综合项目,各子项目有不同的发布节奏。为了管理SpringCloud与各子项目的版本依赖关系,发布了一个清单,其中包括了某个SpringCloud版本对应的子项目版本。为了避免SpringCloud版本号与子项目版本号混淆,SpringCloud版本采用了名称而非版本号的命名,这些版本的名字采用了伦敦地铁站的名字,根据字母表的顺序来对应版本时间顺序。例如Angel是第一个版本, Brixton是第二个版本。
当SpringCloud的发布内容积累到临界点或者一个重大BUG被解决后,会发布一个"service releases"版本,简称SRX版本,比如Greenwich.SR2就是SpringCloud发布的Greenwich版本的第2个SRX版本。
2.2.Spring Boot 2.X 版
- 源码地址:https://github.com/spring-projects/spring-boot/releases/
- Spring Boot 2 的新特性:https://github.com/spring-projects/spring-boot/wiki/spring-Boot-2.0-Release-Notes
- 通过上面官网发现,Boot官方强烈建议你升级到2.X以上版本
2.3.Spring Cloud H版
- 源码地址:https://github.com/spring-projects/spring-cloud
- 官网:https://spring.io/projects/spring-cloud
2.4.Spring Boot 与 Spring Cloud 兼容性查看
- 文档:https://spring.io/projects/spring-cloud#adding-spring-cloud-to-an-existing-spring-boot-application
- JSON接口:https://start.spring.io/actuator/info
2.5.开发用到的组件版本
- Cloud - Hoxton.SR1:https://cloud.spring.io/spring-cloud-static/Hoxton.SR1/reference/htmlsingle/
中文翻译:https://www.bookstack.cn/read/spring-cloud-docs/docs-index.md - Boot - 2.2.2.RELEASE:https://docs.spring.io/spring-boot/docs/2.2.2.RELEASE/reference/htmlsingle/
- Cloud Alibaba - 2.1.0.RELEASE
- Java - Java 8
- Maven - 3.5及以上
- MySQL - 5.7及以上
3.Cloud组件停更说明
4.父工程Project空间新建
现在的编程风格:约定(Java编程规范、SQL编写规范、类和方法命名规范) > 配置(微服务架构的组件) > 编码
使用IDEA编译器时,需要先创建聚合project父工程,后续创建每一个module模块,即各种微服务模块。
4.1.创建父工程
4.1.1.New Project
4.1.2.聚合总父工程名字
4.1.3.Maven选版本
4.1.4.工程名字
4.1.5.字符编码
4.1.6.注解生效激活
4.1.7.java编译版本选8
4.1.8.File Type过滤
4.2.父工程pom文件
4.2.1.完善父工程
- 删除父工程的SRC文件夹
- 父工程pom.xml文件
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.stonebridge.springcloud</groupId>
<artifactId>cloud2021</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging><!-- 这里添加,注意不是jar或war -->
<!-- 统一管理jar包版本 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>4.12</junit.version>
<log4j.version>1.2.17</log4j.version>
<lombok.version>1.16.18</lombok.version>
<mysql.version>5.1.47</mysql.version>
<druid.version>1.1.16</druid.version>
<mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version>
</properties>
<!-- dependencyManagement用在父工程,子模块继承之后,提供作用:锁定版本+子modlue不用写groupId和version -->
<dependencyManagement>
<dependencies>
<!-- 要搭建一个基于Spring Cloud的分布式服务,spring boot、spring cloud、spring cloud alibaba是标配,不同的在于版本-->
<!--spring boot 2.2.2-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud Hoxton.SR1-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud alibaba 2.1.0.RELEASE-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.spring.boot.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<optional>true</optional>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
<addResources>true</addResources>
</configuration>
</plugin>
</plugins>
</build>
</project>
父工程创建完成执行mvn : install
将父工程发布到仓库方便子工程继承。
4.2.2.补充说明dependencyManagement
Maven使用dependencyManagement元素来提供了一种管理依赖版本号的方式。
通常会在一个组织或者项目的最顶层的父POM中看到dependencyManagement 元素。
使用pom.xml中的dependencyManagement 元素能让所有在子项目中引用一个依赖而不用显式的列出版本号。Maven会沿着父子层次向上走,直到找到一个拥有dependencyManagement元素的项目,然后它就会使用这个dependencyManagement 元素中指定的版本号。
这样做的好处就是:如果有多个子项目都引用同一样依赖,则可以避免在每个使用的子项目里都声明一个版本号,这样当想升级或切换到另一个版本时,只需要在顶层父容器里更新,而不需要一个一个子项目的修改 ;另外如果某个子项目需要另外的一个版本,只需要声明version就可。
<font color=red><strong>dependencyManagement里只是声明依赖,并不实现引入,因此子项目需要显示的声明需要用的依赖。</strong></font>
- 如果不在子项目中声明依赖,是不会从父项目中继承下来的;只有在子项目中写了该依赖项,并且没有指定具体版本,才会从父项目中继承该项,并且version和scope都读取自父pom;
<font color=red><strong>如果子项目中指定了版本号,那么会使用子项目中指定的jar版本。</strong></font>
5.微服务消费者支付模块构建
5.1.模块结构
创建客户端消费者订单模块(端口:80),去调用支付模块的微服务(端口:8001)
5.2.构建微服务模块
5.2.1.新建Module
5.2.2.pom.xml文件增加依赖
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2021</artifactId>
<groupId>com.stonebridge.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-provider-payment8001</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!--mysql-connector-java-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
5.2.3.application.yaml配置文件
# 1.设置端口号
server:
port: 8001
# 2.设置应用名字
spring:
application:
name: cloud-payment-service
datasource: # 3.配置数据源
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: com.mysql.jdbc.Driver # mysql驱动包 com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/db2019?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
# 4.配置mybatis
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.stonebridge.springcloud.entities # 所有Entity别名类所在包
5.2.4.主启动类
@SpringBootApplication
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class, args);
}
}
5.2.5.业务类
5.2.5.1.建表SQL
CREATE TABLE `payment` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`serial` varchar(200) DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8
5.2.5.2.entities
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Payment implements Serializable {
private long id;
private String serial;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T> {
private Integer code;
private String message;
private T data;
public CommonResult(Integer code, String message) {
this(code, message, null);
}
}
5.2.5.3.dao
@Mapper
public interface PaymentDao {
public int create(Payment payment);
public Payment getPaymentById(@Param("id") long id);
}
5.2.5.4.mapper映射文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.stonebridge.springcloud.dao.PaymentDao">
<resultMap id="BaseResulthMap" type="com.stonebridge.springcloud.entities.Payment">
<id column="id" property="id" jdbcType="BIGINT"></id>
<id column="serial" property="serial" jdbcType="VARCHAR"></id>
</resultMap>
<!-- useGeneratedKeys:配置自动生成主键,keyProperty="id"keyProperty对应的是Java对象的属性名,将数据库的主键反向赋值给id字段-->
<insert id="create" parameterType="Payment" useGeneratedKeys="true" keyProperty="id">
insert into payment(serial)
values (#{serial});
</insert>
<select id="getPaymentById" parameterType="Long" resultMap="BaseResulthMap">
select *
from payment
where id = #{id}
</select>
</mapper>
5.2.5.5.service
public interface PaymentService {
public int create(Payment payment);
public Payment getPaymentById(@Param("id") long id);
}
@Service
public class PaymentServiceImpl implements PaymentService {
@Autowired
private PaymentDao paymentDao;
@Override
public int create(Payment payment) {
return paymentDao.create(payment);
}
@Override
public Payment getPaymentById(long id) {
return paymentDao.getPaymentById(id);
}
}
6.2.5.6.controller
@Controller
@Slf4j
public class PaymentController {
@Autowired
private PaymentService paymentService;
@RequestMapping(value = "/payment/create", method = RequestMethod.POST)
@ResponseBody
public CommonResult create(Payment payment) {
int result = paymentService.create(payment);
log.info("result:" + result);
if (result > 0) {
return new CommonResult(200, "插入数据库成功", result);
} else {
return new CommonResult(500, "插入数据库失败", null);
}
}
@RequestMapping(value = "/payment/get/{id}", method = RequestMethod.GET)
@ResponseBody
public CommonResult getPaymentById(@PathVariable("id") Long id) {
Payment payment = paymentService.getPaymentById(id);
if (payment != null) {
return new CommonResult(200, "查询成功", payment);
} else {
return new CommonResult(200, "查询失败,没有对应记录", null);
}
}
}
5.2.5.7.项目结构
6.微服务消费者订单模块构建
6.1.创建模块
省略maven依赖的引入、主启动类、application配置文件设置端口即可
此时的消费者微服务模块不需要访问数据库,因此不需要引入mybatis、Druid、jdbc、数据库驱动的依赖
6.2.RestTemplate
6.2.1.概念
RestTemplate提供了多种便捷访问远程Http服务的方法,
是一种简单便捷的访问restful服务模板类,是Spring提供的用于访问Rest服务的客户端模板工具集
6.2.2.使用
使用restTemplate访问restful接口非常的简单粗暴无脑。
(url, requestMap, ResponseBean.class)这三个参数分别代表
REST请求地址、请求参数、HTTP响应转换被转换成的对象类型。
6.3.实现.RestTemplate调用支付模块
- ApplicationContextConfig
@Configuration
public class ApplicationContextConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
- Payment
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Payment implements Serializable {
private long id;
private String serial;
}
- CommonResult
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T> {
private Integer code;
private String message;
private T data;
public CommonResult(Integer code, String message) {
this(code, message, null);
}
}
- Controller
@Slf4j
@Controller
public class OrderController {
private static final String PAYMENT_URL = "http://localhost:8081";
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value = "/consumer/payment/create", method = RequestMethod.POST)
@ResponseBody
public CommonResult<Payment> creat(Payment payment) {
return restTemplate.postForObject(PAYMENT_URL + "/payment/create", payment, CommonResult.class);
}
@RequestMapping(value = "/consumer/get/{id}", method = RequestMethod.GET)
@ResponseBody
public CommonResult getPaymentById(@PathVariable("id") Long id) {
return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
}
}
支付模块create方法。
通过RestTemplate远程调用支付模块,支付模块接受Payment对象时需要使用@RequestBody接受
@RequestMapping(value = "/payment/create", method = RequestMethod.POST)
@ResponseBody
public CommonResult create(@RequestBody Payment payment) {
int result = paymentService.create(payment);
log.info("result:" + result);
if (result > 0) {
return new CommonResult(200, "插入数据库成功", result);
} else {
return new CommonResult(500, "插入数据库失败", null);
}
}
- 测试
6.4.开启dashBoard
View – tool windows – service
7.工程重构
观察问题:支付模块和订单模块系统中有重复部分,需要重构
- 创建公共项目
- 将公共类写在该项目下
- maven命令clean install
- 订单80和支付8001分别改造
删除cloud-provider-payment8001和cloud-consumer-order80的公共类,在其引入cloud-api-commons依赖
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.stonebridge.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
- 测试
- 目前工程样图
8.Eureka服务注册与发现
从consumer访问provider单个的请求很容易实现,但是随着跨微服务的请求增多,无法进行统一管理流量以及资源调度分配。
8.1.Eureka基础知识
8.1.1.什么是服务治理
Spring Cloud 封装了 Netflix 公司开发的 Eureka 模块来实现服务治理
在传统的rpc远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,管理服务于服务之间依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册。
8.1.2.什么是服务注册与发现
Eureka采用了CS的设计架构,Eureka Server作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用 Eureka的客户端连接到 Eureka Server并维持心跳连接。这样系统的维护人员就可以通过 Eureka Server 来监控系统中各个微服务是否正常运行。
在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息。比如 服务地址通讯地址等以别名方式注册到注册中心上。另一方(消费者|服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用RPC远程调用框架核心设计思想:在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何rpc远程框架中,都会有一个注册中心(存放服务地址相关信息(接口地址))
下左图是Eureka系统架构,右图是Dubbo的架构,请对比
Euraka Server和Service Provider都是采用集群部署,避免全部宕机
8.1.3.Eureka两组件
8.1.3.1.Eureka Server
Eureka Server提供服务注册服务
各个微服务节点通过配置启动后,会在Eureka Server中进行注册,这样Eureka Server中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。
8.1.3.2.Eureka Client
EurekaClient通过注册中心进行访问
是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒)
8.2.单机Eureka构建
8.2.1.生成Eureka Server服务端
- 建Module
- pom.xml引入依赖
1.X和2.X的对比说明:1.X版本不分Server端和Client端。
以前的老版本(当前使用2018)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
现在新版本(当前使用2020.2)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2021</artifactId>
<groupId>com.stonebridge.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-eureka-server7001</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!--eureka-server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.stonebridge.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--boot web actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--一般通用配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
</project>
- application.yaml配置文件
server:
port: 7001
eureka:
instance:
hostname: localhost #eureka服务端的实例名称
client:
#false表示不向注册中心注册自己。
register-with-eureka: false
#false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
#设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
- 主启动类
@EnableEurekaServer标注该微服务是Eureka的服务注册中心
@SpringBootApplication
@EnableEurekaServer
public class EurekaMain7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaMain7001.class, args);
}
}
- 启动EurekaServer服务进行测试
http://localhost:7001/
No application available 没有服务被发现
因为没有注册服务进来当然不可能有服务被发现
8.2.2.将服务提供者provider注册进EurekaServer端
EurekaClient端cloud-provider-payment8001将注册进EurekaServer成为服务提供者provider,类似尚硅谷学校对外提供授课服务。
- 修改pom,增加Eureka Client依赖
1.X和2.X的对比说明:1.X版本不分Server端和Client端。
以前老版本,别再使用
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
现在新版本,当前使用
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- yaml中增加属性Eureka Client配置
# 1.设置端口号
server:
port: 8081
# 2.设置应用名字
spring:
application:
name: cloud-payment-service
datasource: # 3.配置数据源
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: com.mysql.jdbc.Driver # mysql驱动包 com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/db2019?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
# 4.配置mybatis
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.stonebridge.springcloud.entities # 所有Entity别名类所在包
# 5.eureka注册进服务端
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
#eureka-server的地址
service-url:
defaultZone: http://localhost:7001/eureka
- 主启动类增加@EnableEurekaClient注解
主启动类增加@EnableEurekaClient注解,表明是Eureka客户端
@SpringBootApplication
@EnableEurekaClient
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class, args);
}
}
- 测试
先要启动Eureka Server,再启动cloud-payment-service - 微服务注册名配置说明
8.2.3.将服务消费者consumer注册进Eureka Server端
将注册进EurekaServer成为服务消费者consumer,类似来尚硅谷上课消费的各位同学
- 修改pom.xml,引入Eureka Client依赖
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- application.yaml配置文件中配置
在配置文件中增加spring.application.name以及将其注册进eureka服务端
server:
port: 80
spring:
application:
name: cloud-order-service
# 5.eureka注册进服务端
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
#eureka-server的地址
service-url:
defaultZone: http://localhost:7001/eureka
- 主启动增加@EnableEurekaClient注解,表明是Eureka客户端
@SpringBootApplication
@EnableEurekaClient
public class OrderMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class, args);
}
}
- 测试
先要启动Eureka Server7001服务。再要启动服务提供者provider8001服务
8.2.4.补充
- bug
Failed to bind properties under ‘eureka.client.service-url’ to java.util.Map<java.lang.String, java.lang.String> - 如果要从Eureka Server服务端移除
8.3.集群Eureka构建
8.3.1.Eureka集群原理说明
问题:微服务RPC远程服务调用最核心的是什么
高可用,试想你的注册中心只有一个only one, 它出故障了那就呵呵( ̄▽ ̄)"了,会导致整个为服务环境不可用,所以
解决办法:搭建Eureka注册中心集群 ,实现负载均衡+故障容错
集群的注册原理:互相注册,相互守望
8.3.2.EurekaServer集群环境
- 新建Eureka Server7002
参考cloud-eureka-server7001新建cloud-eureka-server7002 - 通过pom文件引入依赖,同7001一致
<dependencies>
<!--eureka-server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.stonebridge.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--boot web actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--一般通用配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
- 修改电脑的hosts文件
找到C:\Windows\System32\drivers\etc路径下的hosts文件添加配置
################ Spring Cloud2020 #################
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com
- 在Eureka项目中交叉配置Eureka服务器
集群的注册原理:互相注册,相互守望
- 7001
在7002中配置注册服务器地址
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com #eureka服务端的实例名称
client:
#false表示不向注册中心注册自己。
register-with-eureka: false
#false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
#设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
defaultZone: http://eureka7002.com:7002/eureka/
- 7002
在7001中配置注册服务器地址
server:
port: 7002
eureka:
instance:
hostname: eureka7002.com #eureka服务端的实例名称
client:
#false表示不向注册中心注册自己。
register-with-eureka: false
#false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
#设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
defaultZone: http://eureka7001.com:7001/eureka/
- 测试
访问地址:
8.3.3.将支付服务和消费者发布到Eureka集群
只需要在支付微服务和消费者微服务的配置文件中将Eureka集群中的每个eureka的地址配置到eureka.client.service-url即可,支付微服务和消费者微服务相同
# 5.eureka注册进服务端
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
#eureka-server的地址
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 集群版
# defaultZone: http://localhost:7001/eureka
测试:http://localhost/consumer/get/45
8.4.支付服务提供者集群环境构建
- 参考cloud-provider-payment8001创建cloud-provider-payment8002
- 修改pom.xml引入依赖
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2021</artifactId>
<groupId>com.stonebridge.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-provider-payment8002</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.stonebridge.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!--mysql-connector-java-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
- 主启动类
@SpringBootApplication
@EnableEurekaClient
public class PaymentMain8002 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8002.class, args);
}
}
- application.yaml
此时端口改为8002,其余项和8001一致
# 1.设置端口号
server:
port: 8002
# 2.设置应用名字
spring:
application:
name: cloud-payment-service
datasource: # 3.配置数据源
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: com.mysql.jdbc.Driver # mysql驱动包 com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/db2019?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
# 4.配置mybatis
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.stonebridge.springcloud.entities # 所有Entity别名类所在包
# 5.eureka注册进服务端
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
#eureka-server的地址
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 集群版
# defaultZone: http://localhost:7001/eureka
- 在consumer服务消费者中设置支付模块的地址
此时在eureka注册的支付模块暴露的唯一接口就是:http://CLOUD-PAYMENT-SERVICE
@Slf4j
@Controller
public class OrderController {
public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value = "/consumer/payment/create", method = RequestMethod.POST)
@ResponseBody
public CommonResult<Payment> creat(Payment payment) {
return restTemplate.postForObject(PAYMENT_URL + "/payment/create", payment, CommonResult.class);
}
@RequestMapping(value = "/consumer/get/{id}", method = RequestMethod.GET)
@ResponseBody
public CommonResult getPaymentById(@PathVariable("id") Long id) {
return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
}
}
- 在consumer服务消费者中RestTemplate组件设置负载均衡
使用@LoadBalanced注解赋予RestTemplate负载均衡的能力
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced //使用@LoadBalanced注解赋予RestTemplate负载均衡的能力
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
- 复制其他8001的Controller、Dao等
- 启动所有项目
- 检查Eureka
- 测试
8.5.actuator微服务信息完善
8.5.1.主机名称:服务名称修改
- 当前问题
- 修改cloud-provider-payment8001–>yaml
通过eureka.instance.instance-id服务名称修改
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 集群版
#defaultZone: http://localhost:7001/eureka # 单机版
instance:
instance-id: payment8001
- 修改后效果
8.5.2.访问信息有IP信息提示
- 当前问题
没有IP提示 - 修改cloud-provider-payment8001–>yaml
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 集群版
#defaultZone: http://localhost:7001/eureka # 单机版
instance:
instance-id: payment8001
prefer-ip-address: true #访问路径可以显示IP地址
- 修改后效果
8.6.服务发现Discovery
8.6.1.功能
对于注册进eureka里面的微服务,可以通过服务发现来获得该服务的信息
例如支付服务的微服务的希望暴露给外界微服务信息,例如主机名称,微服务端口号
8.6.2.在具体的微服务中书写Discovery接口
注意此处引入的ServiceInstance和DiscoveryClient的来源
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
@Controller
@Slf4j
public class PaymentController {
@Autowired
private DiscoveryClient discoveryClient;
@RequestMapping(value = "/payment/discovery", method = RequestMethod.GET)
@ResponseBody
public Object discovery() {
//Eureka服务中心注册的所有微服务
List<String> services = discoveryClient.getServices();
for (String element : services) {
System.out.println(element);
}
//获取Eureka服务中心注册的所有微服务中名称为CLOUD-PAYMENT-SERVICE所有实例,因为是集群部署,因此有多个
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
for (ServiceInstance element : instances) {
System.out.println(element.getServiceId() + "\t" + element.getHost() + "\t" + element.getPort() + "\t"
+ element.getUri());
}
return this.discoveryClient;
}
}
8.6.3.主启动类增加@EnableDiscoveryClient注解
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class, args);
}
}
8.6.4.测试
先要启动EurekaServer在启动8001
访问:http://localhost:8001/payment/discovery
8.7.Eureka自我保护机制
8.7.1.故障现象
概述
保护模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护。一旦进入保护模式,
Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据,也就是不会注销任何微服务。
一句话:某时刻某一个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存
如果在Eureka Server的首页看到以下这段提示,则说明Eureka进入了保护模式:
EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT.
RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE
8.7.2.导致原因
属于CAP里面的AP分支
- 为什么会产生Eureka自我保护机制?
为了防止EurekaClient可以正常运行,但是与EurekaServer网络不通情况下,EurekaServer不会立刻将EurekaClient服务剔除 - 什么是自我保护模式?
默认情况下,如果EurekaServer在一定时间内没有接收到某个微服务实例的心跳,EurekaServer将会注销该实例(默认90秒)。但是当网络分区故障发生(延时、卡顿、拥挤)时,微服务与EurekaServer之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个微服务。Eureka通过“自我保护模式”来解决这个问题——当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。
在自我保护模式中,Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。
它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。一句话讲解:避免误伤
综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留)也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定。
8.7.3.禁止自我保护
8.7.3.1.注册中心eureakeServer端7001
- 出厂默认,自我保护机制是开启的
eureka.server.enable-self-preservation=true
- 禁用自我保护模式
使用eureka.server.enable-self-preservation = false 可以禁用自我保护模式
eureka:
instance:
hostname: eureka7001.com
client:
register-with-eureka: false
fetch-registry: false
service-url:
#defaultZone: http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
defaultZone: http://eureka7001.com:7001/eureka
server:
#关闭自我保护机制,保证不可用服务被及时踢除
enable-self-preservation: false
eviction-interval-timer-in-ms: 2000
- 关闭效果
在eurekaServer端7001处设置关闭自我保护机制
8.7.3.2.生产者客户端eureakeClient端8001
- 默认
eureka.instance.lease-renewal-interval-in-seconds=30
eureka.instance.lease-expiration-duration-in-seconds=90 //单位为秒(默认是90秒)
- 配置心跳检测与续约时间
eureka:
client: #服务提供者provider注册进eureka服务列表内
service-url:
register-with-eureka: true
fetch-registry: true
# cluster version
#defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
# singleton version
defaultZone: http://eureka7001.com:7001/eureka
#心跳检测与续约时间
#开发时设置小些,保证服务关闭后注册中心能即使剔除服务
instance:
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-renewal-interval-in-seconds: 1
#Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
lease-expiration-duration-in-seconds: 2
8.7.3.3.测试
7001和8001都配置完成
先启动7001再启动8001
关闭8001服务
9.Zookeeper服务注册与发现
9.1.Eureka停止更新了你怎么办
https://github.com/Netflix/eureka/wiki
9.2.Linux安装jdk和zookeeper
9.2.1.Linux安装jdk
下载地址:https://www.oracle.com/java/technologies/downloads/#java8
- 下载JDK的安装包到Liunx系统
- 解压压缩包
tar -zxvf jdk-8u301-linux-x64.tar.gz
- 将解压后的jdk1.8.0_301移到/usr/local/jdk目录下
mkdir -p /usr/local/jdk && mv jdk1.8.0_301 /usr/local/jdk
- 编辑环境变量
最好是root权限
vim /etc/profile
找到文件最下面,i进入编辑状态粘贴
export JAVA_HOME=/usr/local/jdk/jdk1.8.0_301
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib:$CLASSPATH
export PATH=$JAVA_HOME/bin:$PATH
ESC + : + wq保存并退出
- 刷新环境变量
source /etc/profile
- 验证jdk是否安装成功
Java -version
9.2.2.Liunx安装zookeeper
https://archive.apache.org/dist/zookeeper/zookeeper-3.4.10/zookeeper-3.4.10.tar.gz
- 上传zookeeper到linux系统
- 在解压opt下创建module目录,将zookeeper解压至此
tar -zxvf apache-zookeeper-3.5.7-bin.tar.gz -C /opt/module/
- 进入conf,将zoo_sample.cfg重命名为zoo.cfg
mv zoo_sample.cfg zoo.cfg
- 在/opt/module/创建zkData
- 修改zoo.cfg中的dataDir,指向zkData
- 启动服务端
bin/zkServer.sh start
- 启动客户端
bin/zkCli.sh
- 退出
quit
- 查看zookeeper状态
bin/zkServer.sh status
9.2.Zookeeper
SpringCloud整合Zookeeper代替Eureka
9.2.1.注册中心Zookeeper
zookeeper是一个分布式协调工具,可以实现注册中心功能
关闭Linux服务器防火墙后启动zookeeper服务器
zookeeper服务器取代Eureka服务器,zk作为服务注册中心
9.2.2.服务提供者
- 关闭防火墙
systemctl stop firewalld
- 检查是否能连接主机(windows开发机器)
ping 192.168.174.1
- 新建cloud-provider-payment8004
- pom.xml
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2021</artifactId>
<groupId>com.stonebridge.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-provider-payment8004</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<groupId>com.stonebridge.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- SpringBoot整合zookeeper客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
- application.yaml
#8004表示注册到zookeeper服务器的支付服务提供者端口号
server:
port: 8004
#服务别名----注册zookeeper到注册中心名称
spring:
application:
name: cloud-provider-payment
cloud:
zookeeper:
connect-string: 192.168.174.141:2181
- Controller
@Controller
@Slf4j
public class PaymentController {
@Value("${server.port}")
private String serverPort;
@RequestMapping(value = "/payment/zk")
@ResponseBody
public String paymentzk() {
return "springcloud with zookeeper: " + serverPort + "\t" + UUID.randomUUID().toString();
}
}
- 启动可能的问题
启动后问题
why
- 解决zookeeper版本jar包冲突问题
- 排出zk冲突后的新POM
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>mscloud03</artifactId>
<groupId>com.atguigu.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-provider-payment8004</artifactId>
<dependencies>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!-- SpringBoot整合zookeeper客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<!--先排除自带的zookeeper3.5.3-->
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--添加zookeeper3.4.9版本-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.9</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
- 启动8004注册进zookeeper
- 启动后查看节点
{
"name": "cloud-provider-payment",
"id": "1228ebd1-d003-4294-abc9-6ef37ff5f5a0",
"address": "192.168.174.1",
"port": 8004,
"sslPort": null,
"payload": {
"@class": "org.springframework.cloud.zookeeper.discovery.ZookeeperInstance",
"id": "application-1",
"name": "cloud-provider-payment",
"metadata": {}
},
"registrationTimeUTC": 1632191305771,
"serviceType": "DYNAMIC",
"uriSpec": {
"parts": [
{
"value": "scheme",
"variable": true
},
{
"value": "://",
"variable": false
},
{
"value": "address",
"variable": true
},
{
"value": ":",
"variable": false
},
{
"value": "port",
"variable": true
}
]
}
}
- 测试
访问:http://localhost:8004/payment/zk - 思考:服务节点是临时节点还是持久节点
9.2.3.服务消费者
- 新建cloud-consumerzk-order80
- pom.xml引入依赖
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2021</artifactId>
<groupId>com.stonebridge.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-consumerzk-order80</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- SpringBoot整合zookeeper客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<!--先排除自带的zookeeper-->
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--添加zookeeper3.4.9版本-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.9</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
- aplication.yaml配置文件
#8004表示注册到zookeeper服务器的支付服务提供者端口号
server:
port: 80
#服务别名----注册zookeeper到注册中心名称
spring:
application:
name: cloud-consumerzk-order
cloud:
zookeeper:
connect-string: 192.168.174.142:2181
- 主启动类
@SpringBootApplication
@EnableDiscoveryClient //该注解用于向使用consul或者zookeeper作为注册中心时注册服务
public class OrderZKMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderZKMain80.class, args);
}
}
- 业务类
- 配置Bean
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
- Controller
@Controller
@Slf4j
public class OrderZkController {
public static final String INVOKE_URL = "http://cloud-provider-payment";
@Autowired
private RestTemplate template;
@RequestMapping("/consumer/payment/zk")
@ResponseBody
public String paymentInfo() {
return template.getForObject(INVOKE_URL + "/payment/zk", String.class);
}
}
- 验证测试
- 启动
- 测试:http://localhost/consumer/payment/zk
10.Consul服务注册与发现
10.1.Consul简介
10.1.1.是什么
https://www.consul.io/intro/index.html
Consul 是一套开源的分布式服务发现和配置管理系统,由 HashiCorp 公司用 Go 语言开发。
提供了微服务系统中的服务治理、配置中心、控制总线等功能。这些功能中的每一个都可以根据需要单独使用,也可以一起使用以构建全方位的服务网格,总之Consul提供了一种完整的服务网格解决方案。
它具有很多优点。包括: 基于 raft 协议,比较简洁; 支持健康检查, 同时支持 HTTP 和 DNS 协议 支持跨数据中心的 WAN 集群 提供图形界面 跨平台,支持 Linux、Mac、Windows。
10.1.2.功能介绍
10.1.2.1. 服务发现
提供HTTP和DNS两种发现方式。
10.1.2.2.健康监测
支持多种方式,HTTP、TCP、Docker、Shell脚本定制化监控
10.1.2.3.KV存储
Key、Value的存储方式
10.1.2.4.多数据中心
Consul支持多数据中心
10.1.2.5.可视化Web界面
10.1.3.下载
https://www.consul.io/downloads.html
10.1.4.操作手册
https://www.springcloud.cc/spring-cloud-consul.html
10.2.安装并运行Consul
10.2.1.官网安装说明
https://learn.hashicorp.com/consul/getting-started/install.html
10.2.2.下载后解压
下载完成后只有一个consul.exe文件,硬盘路径下双击运行,查看版本号信息
consul --version
10.2.3.使用开发模式启动
- 启动
consul agent -dev
- 访问Consul的首页
http://localhost:8500
10.3.服务提供者
10.3.1.新建Module支付服务provider8006
10.3.2.pom.xml
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2021</artifactId>
<groupId>com.stonebridge.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-providerconsul-payment8006</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!--SpringCloud consul-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--日常通用jar包配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
10.3.3.application.yaml
###consul服务端口号
server:
port: 8006
spring:
application:
name: consul-provider-payment
####consul注册中心地址
cloud:
consul:
host: localhost
port: 8500
discovery:
#hostname: 127.0.0.1
service-name: ${spring.application.name}
10.3.4.主启动类
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain8006 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8006.class, args);
}
}
10.3.5.业务类Controller
@Controller
@Slf4j
public class PaymentController {
@Value("${server.port}")
private String serverPort;
@RequestMapping(value = "/payment/consul")
@ResponseBody
public String paymentConsul() {
return "springcloud with Consul: " + serverPort + "\t" + UUID.randomUUID().toString();
}
}
10.3.6.测试
- 测试接口
http://localhost:8006/payment/consul - 访问consul注册中心
10.4.服务消费者
10.4.1.新建Module消费服务order80
10.4.2.pom.xml
<dependencies>
<!--SpringCloud consul-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--日常通用jar包配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
10.4.3.application.yaml
###consul服务端口号
server:
port: 80
spring:
application:
name: cloud-consumer-order
####consul注册中心地址
cloud:
consul:
host: localhost
port: 8500
discovery:
#hostname: 127.0.0.1
service-name: ${spring.application.name}
11.4.4.主启动类
@RestController
public class OrderConsulController {
public static final String INVOKE_URL = "http://consul-provider-payment"; //consul-provider-payment
@Autowired
private RestTemplate restTemplate;
@GetMapping(value = "/consumer/payment/consul")
public String paymentInfo() {
String result = restTemplate.getForObject(INVOKE_URL + "/payment/consul", String.class);
System.out.println("消费者调用支付服务(consule)--->result:" + result);
return result;
}
}
10.4.5.配置Bean
@Configuration
public class ApplicationContextBean {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
10.4.6.Controller
@SpringBootApplication
@EnableDiscoveryClient //该注解用于向使用consul或者zookeeper作为注册中心时注册服务
public class OrderConsulMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderConsulMain80.class, args);
}
}
10.4.7.验证测试
10.4.8.访问测试地址
10.5.三个注册中心异同点
CAP
- C:Consistency(强一致性)
- A:Availability(高可用性)
- P:Partition tolerance(分区容错性)
- CAP理论关注粒度是数据,而不是整体系统设计的策略
最多只能同时较好的满足两个。
CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,
因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三 大类:
CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。
CP - 满足一致性,分区容忍必的系统,通常性能不是特别高。
AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。
AP架构(Eureka)
当网络分区出现后,为了保证可用性,系统B可以返回旧值,保证系统的可用性。
结论:违背了一致性C的要求,只满足可用性和分区容错,即AP
CP(Zookeeper/Consul)
当网络分区出现后,为了保证一致性,就必须拒接请求,否则无法保证一致性
结论:违背了可用性A的要求,只满足一致性和分区容错,即CP
11.Ribbon负载均衡服务调用
11.1.概述
11.1.1.是什么
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端
负载均衡的工具。
简单的说,Ribbon是Netflix发布的开源项目,<strong主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。
11.1.2.官网资料
https://github.com/Netflix/ribbon/wiki/Getting-Started
Ribbon目前也进入维护模式
未来替换方案
11.1.3.功能
负载均衡+RestTemplate调用
11.1.3.1.LB(负载均衡)
- LB负载均衡(Load Balance)是什么?
简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA(高可用)。
常见的负载均衡有软件Nginx,LVS,硬件 F5等。 - Ribbon本地负载均衡客户端(进程内LB) VS Nginx服务端负载均衡区别(集中式LB)?
Nginx是服务器负载均衡(集中式LB),客户端所有请求都会交给nginx(挡在服务端最前面的),然后由nginx实现转发请求。即负载均衡是由服务端实现的。
Ribbon本地负载均衡(进程内LB),在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术。
可以理解为去医院看病,Nginx(集中式LB)是大门,Ribbon(进程内LB)是各个科室
11.1.3.2.集中式LB和进程内LB
- 集中式LB
即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5, 也可以是软件,如nginx), 由该设施负责把访问请求通过某种策略转发至服务的提供方; - 进程内LB
将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。
Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。
11.1.3.3.轮询负载访问
前面我们讲解过了80通过轮询负载访问8001/8002
11.2.Ribbon负载均衡演示
11.2.1.架构说明
总结:Ribbon其实就是一个软负载均衡的客户端组件,
他可以和其他所需请求的客户端结合使用,和eureka结合只是其中的一个实例。
Ribbon在工作时分成两步
第一步先选择 EurekaServer ,它优先选择在同一个区域内负载较少的server。
第二步再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址。
其中Ribbon提供了多种策略:比如轮询、随机和根据响应时间加权。
11.2.2.POM说明
之前写样例时候没有引入spring-cloud-starter-ribbon也可以使用ribbon,
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
猜测spring-cloud-starter-netflix-eureka-client自带了spring-cloud-starter-ribbon引用,
证明如下: 可以看到spring-cloud-starter-netflix-eureka-client 确实引入了Ribbon
11.2.3.RestTemplate的使用
- 官网
https://docs.spring.io/spring-framework/docs/5.2.2.RELEASE/javadoc-api/org/springframework/web/client/RestTemplate.html - getForObject方法
返回对象为响应体中数据转化成的对象,基本上可以理解为Json
@GetMapping("/consumer/payment/get/{id}")
public CommonResult<Payment> getPayment(@PathVariable("id") Long id) {
return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
}
- getForEntity方法
返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等
@GetMapping("/consumer/payment/getForEntity/{id}")
public CommonResult<Payment> getPayment2(@PathVariable("id") Long id) {
ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
if (entity.getStatusCode().is2xxSuccessful()) {
return entity.getBody();
} else {
return new CommonResult<>(444, "操作失败");
}
}
- postForObject方法
@GetMapping("/consumer/payment/create")
public CommonResult<Payment> create(Payment payment) {
return restTemplate.postForObject(PAYMENT_URL + "/payment/create", payment, CommonResult.class);
}
- postForEntity方法
@GetMapping("/consumer/payment/create")
public CommonResult<Payment> create1(Payment payment) {
ResponseEntity<CommonResult> entity = restTemplate.postForEntity(PAYMENT_URL + "/payment/create", payment, CommonResult.class);
if (entity.getStatusCode().is2xxSuccessful()) {
return entity.getBody();
} else {
return new CommonResult<>(444, "操作失败");
}
}
11.3.Ribbon核心组件IRule
11.3.1.IRule
根据特定算法中从服务列表中选取一个要访问的服务
IRule的主要实现类
出厂的默认负载均衡策略是轮询
- com.netflix.loadbalancer.RoundRobinRule
轮询 - com.netflix.loadbalancer.RandomRule
随机 - com.netflix.loadbalancer.RetryRule
先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务 - WeightedResponseTimeRule
对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择 - BestAvailableRule
会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务 - AvailabilityFilteringRule
先过滤掉故障实例,再选择并发较小的实例 - ZoneAvoidanceRule
默认规则,复合判断server所在区域的性能和server的可用性选择服务器
11.3.2.如何替换负载均衡策略
出厂的默认负载均衡策略是轮询
例如修改cloud-consumer-order80的负载均衡策略
- 注意项
官方文档明确给出了警告:这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,
否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了。
即在Spring Boot项目中不能在主启动类所在的包及其子包中,会被@SpringBootApplication中的@ComponentScan扫描到,导致对所有服务提供端都采用该负载均衡策略。 - spring-cloud-starter-netflix-eureka-client 引入了Ribbon
此时不加赘述,如果是其他注册中心,自行检查添加 - 新建package
不能在主启动类所在的包及其子包中 - 新建MySelfRule规则类
@Configuration
public class MySelfRule {
@Bean
public IRule myRule() {
return new RandomRule();//定义为随机
}
}
- 主启动类添加@RibbonClient
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration= MySelfRule.class)
public class OrderMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class, args);
}
}
- Controller
@Slf4j
@Controller
public class OrderController {
public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value = "/consumer/payment/get/{id}", method = RequestMethod.GET)
@ResponseBody
public CommonResult getPaymentById(@PathVariable("id") Long id) {
return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
}
}
- 测试
访问:http://localhost/consumer/payment/get/45
11.4.Ribbon负载均衡算法
11.4.1.原理
负载均衡算法:rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标 ,每次服务重启动后rest接口计数从1开始。
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
如: List [0] instances = 127.0.0.1:8002
List [1] instances = 127.0.0.1:8001
8001+ 8002 组合成为集群,它们共计2台机器,集群总数为2, 按照轮询算法原理:
当总请求数为1时: 1 % 2 =1 对应下标位置为1 ,则获得服务地址为127.0.0.1:8001
当总请求数位2时: 2 % 2 =0 对应下标位置为0 ,则获得服务地址为127.0.0.1:8002
当总请求数位3时: 3 % 2 =1 对应下标位置为1 ,则获得服务地址为127.0.0.1:8001
当总请求数位4时: 4 % 2 =0 对应下标位置为0 ,则获得服务地址为127.0.0.1:8002
如此类推…
11.4.2.RoundRobinRule源码
com.netflix.loadbalancer.RoundRobinRule
11.4.3.自定义负载均衡
- 8001/8002微服务改造
在其Controller中增加一个方法
@Controller
@Slf4j
public class PaymentController {
@Value("${server.port}")
private String serverPort;
@RequestMapping(value = "/payment/lb")
@ResponseBody
public String getPaymentLB(){
return serverPort;
}
}
- 80订单微服务改造
- ApplicationContextBean去掉注解@LoadBalanced
@Configuration
public class ApplicationContextConfig {
@Bean
// @LoadBalanced //使用@LoadBalanced注解赋予RestTemplate负载均衡的能力
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
- LoadBalancer接口
public interface LoadBalancer{
ServiceInstance instances(List<ServiceInstance> serviceInstances);
}
- 自定义负载均衡
@Component
public class MyLB implements LoadBalancer {
private AtomicInteger atomicInteger = new AtomicInteger(0);
public final int getAndIncrement() {
int current;
int next;
do {
current = this.atomicInteger.get();
next = current >= 2147483647 ? 0 : current + 1;
} while (!this.atomicInteger.compareAndSet(current, next));
System.out.println("*****第几次访问,次数next: " + next);
return next;
}
//负载均衡算法:rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标 ,每次服务重启动后rest接口计数从1开始。
@Override
public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
int index = getAndIncrement() % serviceInstances.size();
return serviceInstances.get(index);
}
}
组件使用@Component注解必须写在主启动类所在包及其子包下
- OrderController
@Slf4j
@Controller
public class OrderController {
@Resource
private DiscoveryClient discoveryClient;
@Resource
private LoadBalancer loadBalancer;
@RequestMapping("/consumer/payment/lb")
@ResponseBody
public String getPaymentLB() {
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
if (instances == null || instances.size() <= 0) {
return null;
}
ServiceInstance serviceInstance = loadBalancer.instances(instances);
URI uri = serviceInstance.getUri();
return restTemplate.getForObject(uri + "/payment/lb", String.class);
}
}
- 测试
12.openFeign
12.1.概述
12.1.1.OpenFeign是什么
GitHub:https://github.com/spring-cloud/spring-cloud-openfeign
Feign是一个声明式的Web服务客户端,让编写Web服务客户端变得非常容易,只需创建一个接口并在接口上添加注解即可
Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单。
它的使用方法是定义一个服务接口然后在上面添加注解。Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡
https://cloud.spring.io/spring-cloud-static/Hoxton.SR1/reference/htmlsingle/#spring-cloud-openfeign
12.1.2.功能实现
12.1.2.1.Feign能干什么
Feign旨在使编写Java Http客户端变得更容易。
前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。
12.1.2.2.Feign集成了Ribbon
利用Ribbon维护了Payment的服务列表信息,并且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。
12.1.3.Feign和OpenFeign两者区别
- Feign
Feign是Spring Cloud组件中的一个轻量级RESTful的HTTP服务客户端
Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
- OpenFeign
OpenFeign是Spring Cloud 在Feign的基础上支持了SpringMVC的注解,如@RequesMapping等等。OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
12.2.OpenFeign使用步骤
12.2.1.接口+注解
微服务调用接口+@FeignClient
12.2.2.新建cloud-consumer-feign-order80
Feign在消费端使用
12.2.3.pom.xml
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2021</artifactId>
<groupId>com.stonebridge.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-consumer-feign-order80</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.stonebridge.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--一般基础通用配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
12.2.4.application.yaml
server:
port: 80
eureka:
client:
register-with-eureka: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
12.2.5.主启动类
@EnableFeignClients开启FeignClients
@SpringBootApplication
@EnableFeignClients
public class OrderFeignMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderFeignMain80.class, args);
}
}
12.2.6.业务类
业务逻辑接口+@FeignClient配置调用provider服务
12.2.6.1.service
@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
//@RequestMapping("/webapp")
public interface PaymentFeignService {
@RequestMapping(value = "/payment/get/{id}")
CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);
}
provider提供的接口
@Controller
@Slf4j
public class PaymentController {
@Value("${server.port}")
private String serverPort;
@Autowired
private PaymentService paymentService;
@RequestMapping(value = "/payment/get/{id}", method = RequestMethod.GET)
@ResponseBody
public CommonResult getPaymentById(@PathVariable("id") Long id) {
Payment payment = paymentService.getPaymentById(id);
if (payment != null) {
return new CommonResult(200, "查询成功,serverPort:" + serverPort, payment);
} else {
return new CommonResult(200, "查询失败,没有对应记录,serverPort:" + serverPort, null);
}
}
}
12.2.6.2.Controller
@Controller
public class OrderFeignController {
@Autowired
private PaymentFeignService paymentFeignService;
@RequestMapping(value = "/consumer/payment/get/{id}")
@ResponseBody
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
return paymentFeignService.getPaymentById(id);
}
}
12.2.6.3.测试
Feign自带负载均衡配置项
先启动2个eureka集群7001/7002
再启动2个微服务8001/8002
启动OpenFeign启动
http://localhost/consumer/payment/get/45
12.3.OpenFeign超时控制
消费者微服务调用服务者微服务调用肯定会出现超时的现象
12.3.1.在服务提供端设置超时
即8001
@RequestMapping(value = "/payment/feign/timeout")
@ResponseBody
public String paymentFeignTimeOut() {
System.out.println("*****paymentFeignTimeOut from port: " + serverPort);
//暂停几秒钟线程
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return serverPort;
}
- 直接访问8001
- 通过消费端访问服务提供
12.3.2.是什么
默认Feign客户端只等待一秒钟,但是服务端处理需要超过1秒钟,导致Feign客户端不想等待了,直接返回报错。
为了避免这样的情况,有时候我们需要设置Feign客户端的超时控制。
OpenFeign默认支持Ribbon
12.3.3.设置超时控制
application.yaml文件里需要开启OpenFeign客户端超时控制
#设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:
#指的是建立连接后从服务器读取到可用资源所用的时间
ReadTimeout: 5000
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ConnectTimeout: 5000
此时即使延时也不会报错
12.4.OpenFeign日志打印功能
12.4.1.日志打印功能介绍
Feign 提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解 Feign 中 Http 请求的细节。即就是对Feign接口的调用情况进行监控和输出
12.4.2.日志级别
NONE:默认的,不显示任何日志;
BASIC:仅记录请求方法、URL、响应状态码及执行时间;
HEADERS:除了 BASIC 中定义的信息之外,还有请求和响应的头信息;
FULL:除了 HEADERS 中定义的信息之外,还有请求和响应的正文及元数据。
12.4.3.配置日志bean
将配置类放在主启动类所在包或者及其子包下
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
12.4.4.开启日志的Feign客户端
在application.yaml文件里需要开启日志的Feign客户端
logging:
level:
# feign日志以什么级别监控哪个接口
com.stonebridge.springcloud.service.PaymentFeignService: debug
访问查看日志
13.Hystrix断路器
13.1.概述
13.1.1.分布式系统面临的问题
复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。
服务雪崩
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”.
对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。
所以,
通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。
13.1.2.是什么
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
13.1.3.功能
- 服务降级
- 服务熔断
- 接近实时的监控
13.1.4.官网资料
https://github.com/Netflix/Hystrix/wiki/How-To-Use
13.1.5.Hystrix官宣,停更进维
https://github.com/Netflix/Hystrix
被动修复bugs
不再接受合并请求
不再发布新版本
13.2.Hystrix重要概念
13.2.1.服务降级fallback
对方系统不可用了,必须给出一个解决方案。
服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好提示,fallback
哪些情况会出发降级
- 程序运行异常
- 超时
- 服务熔断触发服务降级
- 线程池/信号量打满也会导致服务降级
13.2.2.服务熔断break
类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示
就是保险丝
服务的降级->进而熔断->恢复调用链路
13.2.3.服务限流flowlimit
秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行
13.3.hystrix案例
13.3.1.构建项目
- 新建cloud-provider-hystrix-payment8001
- pom.xml引入依赖
<dependencies>
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<groupId>com.stonebridge.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
- application.yaml
server:
port: 8001
spring:
application:
name: cloud-provider-hystrix-payment
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
#defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
defaultZone: http://eureka7001.com:7001/eureka
- 主启动类
@SpringBootApplication
@EnableEurekaClient //本服务启动后会自动注册进eureka服务中
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class, args);
}
}
- 业务类
- service
@Service
public class PaymentService {
/**
* 正常访问,一切OK
*
* @param id
* @return
*/
public String paymentInfo_OK(Integer id) {
return "线程池:" + Thread.currentThread().getName() + "paymentInfo_OK,id: " + id + "\t" + "O(∩_∩)O";
}
/**
* 超时访问,演示降级
*
* @param id
* @return
*/
public String paymentInfo_TimeOut(Integer id) {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池:" + Thread.currentThread().getName() + "paymentInfo_TimeOut,id: " + id + "\t" + "O(∩_∩)O,耗费3秒";
}
}
- controller
@Controller
@Slf4j
public class PaymentController {
@Autowired
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;
@RequestMapping("/payment/hystrix/ok/{id}")
@ResponseBody
public String paymentInfo_OK(@PathVariable("id") Integer id) {
String result = paymentService.paymentInfo_OK(id);
log.info("****result: " + result);
return result;
}
@RequestMapping("/payment/hystrix/timeout/{id}")
@ResponseBody
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) throws InterruptedException {
String result = paymentService.paymentInfo_TimeOut(id);
log.info("****result: " + result);
return result;
}
}
- 测试
- 启动eureka7001
- 启动cloud-provider-hystrix-payment8001
- 访问
- success的方法:http://localhost:8001/payment/hystrix/ok/31
- 每次调用耗费3秒钟:http://localhost:8001/payment/hystrix/timeout/31
13.3.2.Jmeter安装
官网地址
- 下载
- 选择压缩文件,进行下载
安装Jmeter5之前需要先配置Java环境,最好jdk为1.8以上的版本。
- 配置环境变量:JMETER_HOME 变量值为你Jmeter解压的路径
- 配置classpath变量
变量名为固定值:%JMETER_HOME%\lib\ext\ApacheJMeter_core.jar;%JMETER_HOME%\lib\jorphan.jar;%JMETER_HOME%\lib/logkit-2.0.jar; - 基本配置完成,找到解压文件,点击bin目录,找到jmeter.bat, 双击打开
工作界面
- 页面字体放大
13.3.3.高并发测试
13.3.3.1.Jmeter压测测试
13.3.3.2.Jmeter压测结论
jmeter访问有延时的接口http://localhost:8001/payment/hystrix/timeout/31接口,导致无延时的http://localhost:8001/payment/hystrix/ok/31的接口也会出现延迟。
为什么会被卡死
tomcat的默认的工作线程数都去处理http://localhost:8001/payment/hystrix/timeout/31请求,没有多余的线程来分解压力和处理http://localhost:8001/payment/hystrix/ok/31。
上面还是服务提供者8001自己测试,假如此时外部的消费者80也来访问,那消费者只能干等,最终导致消费端80不满意,服务端8001直接被拖死
13.3.4.新建微服务消费者
- 新建cloud-consumer-feign-hystrix-order80
- pom.xml引入依赖
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2021</artifactId>
<groupId>com.stonebridge.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-consumer-feign-hystrix-order80</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.stonebridge.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--一般基础通用配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
- application.yaml
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
- 主启动类
@SpringBootApplication
@EnableFeignClients
public class OrderHystrixMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain80.class, args);
}
}
- 业务类
- service
@Service
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
public interface PaymentHystrixService {
@RequestMapping("/payment/hystrix/ok/{id}")
String paymentInfo_OK(@PathVariable("id") Integer id);
@RequestMapping("/payment/hystrix/timeout/{id}")
String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
- Controller
@Controller
@Slf4j
public class OrderHystirxController {
@Autowired
private PaymentHystrixService paymentHystrixService;
@RequestMapping("/consumer/payment/hystrix/ok/{id}")
@ResponseBody
public String paymentInfo_OK(@PathVariable("id") Integer id) {
return paymentHystrixService.paymentInfo_OK(id);
}
@RequestMapping("/consumer/payment/hystrix/timeout/{id}")
@ResponseBody
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
return paymentHystrixService.paymentInfo_TimeOut(id);
}
}
- 正常测试
http://localhost/consumer/payment/hystrix/ok/31 - 高并发测试
2W个线程压8001
消费端80微服务再去访问正常的Ok微服务8001地址
http://localhost/consumer/payment/hystrix/ok/32结果:
- 要么转圈圈等待
- 要么消费端报超时错误
13.3.5.故障现象和导致原因
故障现象和导致原因:8001同一层次的其它接口服务被困死,因为tomcat线程池里面的工作线程已经被挤占完毕;80此时调用8001,客户端访问响应缓慢,转圈圈。
上诉结论:正因为有上述故障或不佳表现才有我们的降级/容错/限流等技术诞生
13.3.6.如何解决?解决的要求
超时导致服务器变慢(转圈):超时不再等待
出错(宕机或程序运行出错):出错要有兜底
解决方案:
- 对方服务(8001)超时了,调用者(80)不能一直卡死等待,必须有服务降级
- 对方服务(8001)down机了,调用者(80)不能一直卡死等待,必须有服务降级
- 对方服务(8001)OK,调用者(80)自己出故障或有自我要求(自己的等待时间小于服务提供者),自己处理降级
13.4.服务降级
13.4.1.服务提供端微服务
13.4.1.1.业务类启用
@HystrixCommand报异常后如何处理
一旦调用服务方法失败并抛出了错误信息后,会自动调用@HystrixCommand标注好的fallbackMethod调用类中的指定方法
上图故意制造两个异常:
1 int age = 10/0; 计算异常
2 我们能接受3秒钟,它运行5秒钟,超时异常。
当前服务不可用了,做服务降级,兜底的方案都是paymentInfo_TimeOutHandler
@Service
public class PaymentService {
/**
* 超时访问,演示降级
*
* @param id
* @return
*/
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
})
public String paymentInfo_TimeOut(Integer id) {
// int age = 10 / 0;
try {
TimeUnit.MILLISECONDS.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池: " + Thread.currentThread().getName() + " id: " + id + "\t" + "O(∩_∩)O哈哈~" + " 耗时(秒): ";
}
public String paymentInfo_TimeOutHandler(Integer id) {
return "线程池: " + Thread.currentThread().getName() + " 8001系统繁忙或者运行报错,请稍后再试,id: " + id + "\t" + "o(╥﹏╥)o";
}
}
13.4.1.2.主启动类激活
添加新注解@EnableCircuitBreaker
@SpringBootApplication
@EnableEurekaClient //本服务启动后会自动注册进eureka服务中
@EnableCircuitBreaker
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class, args);
}
}
13.4.1.3.测试
在paymentInfo_TimeOut方法中无论是报错还是执行超时都会跳转到paymentInfo_TimeOutHandler方法中。
13.4.2.服务消费端微服务
我们自己配置过的热部署方式对java代码的改动明显,但对@HystrixCommand内属性的修改建议重启微服务>
13.4.2.1.application.yaml
@EnableHystrix注解底层其实自带@EnableCircuitBreaker
feign.hystrix.enabled=true是开启feign对Hystrix的支持,可以直接在@FeignClent注解中的fallback属性指定回调的类
feign:
hystrix:
enabled: true
13.4.2.2.主启动增加@EnableHystrix注解
@EnableHystrix注解底层其实自带@EnableCircuitBreaker
@SpringBootApplication
@EnableFeignClients
@EnableHystrix
public class OrderHystrixMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain80.class, args);
}
}
13.4.2.3.业务类
@Controller
@Slf4j
public class OrderHystirxController {
@Autowired
private PaymentHystrixService paymentHystrixService;
@RequestMapping("/consumer/payment/hystrix/timeout/{id}")
@ResponseBody
@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
})
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
int age = 10 / 0;
return paymentHystrixService.paymentInfo_TimeOut(id);
}
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) {
return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
}
}
13.4.2.4.测试
在paymentInfo_TimeOut方法中无论是报错还是执行超时都会由paymentTimeOutFallbackMethod方法中。
13.4.3.优化问题
每个业务方法对应一个异常处理的方法,且代码混在一起,代码膨胀
13.4.3.1.每个方法配置一个异常处理方法,导致代码膨胀
@DefaultProperties(defaultFallback = “”)
1:1 每个方法配置一个服务降级方法,技术上可以,实际上傻X
1:N 除了个别重要核心业务有专属,其它普通的可以通过@DefaultProperties(defaultFallback = “”) 统一跳转到统一处理结果页面
通用的和独享的各自分开,避免了代码膨胀,合理减少了代码量。
@Controller
@Slf4j
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
public class OrderHystirxController {
@Autowired
private PaymentHystrixService paymentHystrixService;
@RequestMapping("/consumer/payment/hystrix/timeout1/{id}")
@ResponseBody
@HystrixCommand
public String paymentInfo_TimeOut1(@PathVariable("id") Integer id) {
int age = 10 / 0;
return paymentHystrixService.paymentInfo_TimeOut(id);
}
public String payment_Global_FallbackMethod() {
return "Global异常处理信息,请稍后再试,/(ㄒoㄒ)/~~";
}
}
注意点:
- @DefaultProperties(defaultFallback = “payment_Global_FallbackMethod”)指点全局异常降级处理方法
- @HystrixCommand标注需要进行降级处理的方法
- 定义全局异常降级处理方法
13.4.3.2.避免异常处理方法和业务逻辑混一起
本次案例服务降级处理是在客户端80实现完成的,与服务端8001没有关系只需要为Feign客户端定义的接口添加一个服务降级处理的实现类即可实现解耦
服务降级,客户端去调用服务端,碰上服务端宕机或关闭
未来我们要面对的异常
- 运行
- 超时
- 宕机
- 原先的业务类PaymentController
混合在一块 ,每个业务方法都要提供一个。
- 在Service统一处理
修改cloud-consumer-feign-hystrix-order80
根据cloud-consumer-feign-hystrix-order80已经有的PaymentHystrixService接口,重新新建一个类(PaymentFallbackService)实现该接口,统一为接口里面的方法进行异常处理
PaymentFallbackService类实现PaymentFeignClientService接口 - 具体实现
- 开启配置
# 用于服务降级 在注解@FeignClient中添加fallbackFactory属性值
feign:
hystrix:
enabled: true #在Feign中开启Hystrix
- 创建PaymentFallbackService类实现PaymentFeignClientService接口
@Service
public class PaymentFallbackService implements PaymentHystrixService {
@Override
public String paymentInfo_OK(Integer id) {
return "-----PaymentFallbackService fall back-paymentInfo_OK ,o(╥﹏╥)o";
}
@Override
public String paymentInfo_TimeOut(Integer id) {
return "-----PaymentFallbackService fall back-paymentInfo_TimeOut ,o(╥﹏╥)o";
}
}
- PaymentFeignClientService接口
fallback指向PaymentFallbackService.class
@Service
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT", fallback = PaymentFallbackService.class)
public interface PaymentHystrixService {
@RequestMapping("/payment/hystrix/ok/{id}")
String paymentInfo_OK(@PathVariable("id") Integer id);
@RequestMapping("/payment/hystrix/timeout/{id}")
String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
- 测试
- 正常访问:
- 关闭服务提供者后
此时服务端provider已经down了,但是我们做了服务降级处理,让客户端在服务端不可用时也会获得提示信息而不会挂起耗死服务器。
13.5.服务熔断
13.5.1.熔断机制概述
断路器:类似于家里的保险丝
熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。
当检测到该节点微服务调用响应正常后,恢复调用链路。
在Spring Cloud框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制。熔断机制的注解是@HystrixCommand。
大神论文:https://martinfowler.com/bliki/CircuitBreaker.html
保险丝正常使用,就是关闭状态(Closed)。电器太多功率过大导致断电,此时状态是断开状态(Open)。假设现在跳闸限电了,熔断器是Open状态,即使是正确的请求也不会返回期待的值。尝试去恢复放过去少量请求(半开状态Half Open),当能够承受所有请求后就将断路器合闭。
13.5.2.服务熔断的实际操作
修改cloud-provider-hystrix-payment8001
13.5.2.1.PaymentService
主要参数:
circuitBreaker.enabled = true //是否开启断路器
circuitBreaker.requestVolumeThreshold = 10 //请求次数
circuitBreaker.sleepWindowInMilliseconds = 10000 //时间窗口期
circuitBreaker.errorThresholdPercentage = 60 //失败率达到多少后跳闸
配置的效果是:
开启断路器后,在10000ms的实际窗口内,请求10次,如果达到60%的失败率达后跳闸
@Service
public class PaymentService {
/**
* 主业务逻辑类
* @param id
* @return
*/
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback", commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),//是否开启断路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),//请求次数
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"),//时间窗口期
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"),//失败率达到多少后跳闸
})
public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
if (id < 0) {
throw new RuntimeException("******id 不能负数");
}
String serialNumber = IdUtil.simpleUUID();
return Thread.currentThread().getName() + "\t" + "调用成功,流水号: " + serialNumber;
}
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id) {
return "id 不能负数,请稍后再试,/(ㄒoㄒ)/~~ id: " + id;
}
}
13.5.2.2.PaymentController
@Controller
@Slf4j
public class PaymentController {
@Autowired
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;
@RequestMapping("/payment/circuit/{id}")
@ResponseBody
public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
String result = paymentService.paymentCircuitBreaker(id);
log.info("****result: " + result);
return result;
}
}
13.5.2.3.测试
自测cloud-provider-hystrix-payment8001
正确访问:http://localhost:8001/payment/circuit/31
错误访问:http://localhost:8001/payment/circuit/-31
不停的进行错误访问,当达到10000ms内访问10次,60%的错误率后。此时正确访问也会显示错误访问,一段时间后正确访问恢复正常。
13.5.3.原理(小总结)
13.5.3.1.熔断类型
- 熔断打开
请求不再进行调用当前服务,内部设置时钟一般为MTTR(平均故障处理时间),当打开时长达到所设时钟则进入半熔断状态 - 熔断关闭
熔断关闭不会对服务进行熔断 - 熔断半开
部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断
13.5.3.2.官网断路器流程图
- 官网步骤
- 断路器在什么情况下开始起作用
涉及到断路器的三个重要参数:快照时间窗、请求总数阀值、错误百分比阀值。
- 快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。
- 请求总数阀值:在快照时间窗内,必须满足请求总数阀值才有资格熔断。默认为20,意味着在10秒内,如果该hystrix命令的调用次数不足20次,即使所有的请求都超时或其他原因失败,断路器都不会打开。
- 错误百分比阀值:当请求总数在快照时间窗内超过了阀值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%阀值情况下,这时候就会将断路器打开。
- 断路器开启或者关闭的条件
- 当满足一定的阀值的时候(默认10秒内超过20个请求次数)
- 当失败率达到一定的时候(默认10秒内超过50%的请求失败)
- 到达以上阀值,断路器将会开启
- 当开启的时候,所有请求都不会进行转发
- 一段时间之后(默认是5秒),这个时候断路器是半开状态,会让其中一个请求进行转发。如果成功,断路器会关闭,若失败,继续开启。重复4和5。
- 断路器打开之后
- 再有请求调用的时候,将不会调用主逻辑,而是直接调用降级fallback。通过断路器,实现了自动地发现错误并将降级逻辑切换为主逻辑,减少响应延迟的效果。
- 原来的主逻辑要如何恢复呢?
对于这一问题,hystrix也为我们实现了自动恢复功能。
当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑,
当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合,
主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时。
- All配置
//========================All
@HystrixCommand(fallbackMethod = "str_fallbackMethod",
groupKey = "strGroupCommand",
commandKey = "strCommand",
threadPoolKey = "strThreadPool",
commandProperties = {
// 设置隔离策略,THREAD 表示线程池 SEMAPHORE:信号池隔离
@HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"),
// 当隔离策略选择信号池隔离的时候,用来设置信号池的大小(最大并发数)
@HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", value = "10"),
// 配置命令执行的超时时间
@HystrixProperty(name = "execution.isolation.thread.timeoutinMilliseconds", value = "10"),
// 是否启用超时时间
@HystrixProperty(name = "execution.timeout.enabled", value = "true"),
// 执行超时的时候是否中断
@HystrixProperty(name = "execution.isolation.thread.interruptOnTimeout", value = "true"),
// 执行被取消的时候是否中断
@HystrixProperty(name = "execution.isolation.thread.interruptOnCancel", value = "true"),
// 允许回调方法执行的最大并发数
@HystrixProperty(name = "fallback.isolation.semaphore.maxConcurrentRequests", value = "10"),
// 服务降级是否启用,是否执行回调函数
@HystrixProperty(name = "fallback.enabled", value = "true"),
// 是否启用断路器
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
// 该属性用来设置在滚动时间窗中,断路器熔断的最小请求数。例如,默认该值为 20 的时候,
// 如果滚动时间窗(默认10秒)内仅收到了19个请求, 即使这19个请求都失败了,断路器也不会打开。
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"),
// 该属性用来设置在滚动时间窗中,表示在滚动时间窗中,在请求数量超过
// circuitBreaker.requestVolumeThreshold 的情况下,如果错误请求数的百分比超过50,
// 就把断路器设置为 "打开" 状态,否则就设置为 "关闭" 状态。
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),
// 该属性用来设置当断路器打开之后的休眠时间窗。 休眠时间窗结束之后,
// 会将断路器置为 "半开" 状态,尝试熔断的请求命令,如果依然失败就将断路器继续设置为 "打开" 状态,
// 如果成功就设置为 "关闭" 状态。
@HystrixProperty(name = "circuitBreaker.sleepWindowinMilliseconds", value = "5000"),
// 断路器强制打开
@HystrixProperty(name = "circuitBreaker.forceOpen", value = "false"),
// 断路器强制关闭
@HystrixProperty(name = "circuitBreaker.forceClosed", value = "false"),
// 滚动时间窗设置,该时间用于断路器判断健康度时需要收集信息的持续时间
@HystrixProperty(name = "metrics.rollingStats.timeinMilliseconds", value = "10000"),
// 该属性用来设置滚动时间窗统计指标信息时划分"桶"的数量,断路器在收集指标信息的时候会根据
// 设置的时间窗长度拆分成多个 "桶" 来累计各度量值,每个"桶"记录了一段时间内的采集指标。
// 比如 10 秒内拆分成 10 个"桶"收集这样,所以 timeinMilliseconds 必须能被 numBuckets 整除。否则会抛异常
@HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "10"),
// 该属性用来设置对命令执行的延迟是否使用百分位数来跟踪和计算。如果设置为 false, 那么所有的概要统计都将返回 -1。
@HystrixProperty(name = "metrics.rollingPercentile.enabled", value = "false"),
// 该属性用来设置百分位统计的滚动窗口的持续时间,单位为毫秒。
@HystrixProperty(name = "metrics.rollingPercentile.timeInMilliseconds", value = "60000"),
// 该属性用来设置百分位统计滚动窗口中使用 “ 桶 ”的数量。
@HystrixProperty(name = "metrics.rollingPercentile.numBuckets", value = "60000"),
// 该属性用来设置在执行过程中每个 “桶” 中保留的最大执行次数。如果在滚动时间窗内发生超过该设定值的执行次数,
// 就从最初的位置开始重写。例如,将该值设置为100, 滚动窗口为10秒,若在10秒内一个 “桶 ”中发生了500次执行,
// 那么该 “桶” 中只保留 最后的100次执行的统计。另外,增加该值的大小将会增加内存量的消耗,并增加排序百分位数所需的计算时间。
@HystrixProperty(name = "metrics.rollingPercentile.bucketSize", value = "100"),
// 该属性用来设置采集影响断路器状态的健康快照(请求的成功、 错误百分比)的间隔等待时间。
@HystrixProperty(name = "metrics.healthSnapshot.intervalinMilliseconds", value = "500"),
// 是否开启请求缓存
@HystrixProperty(name = "requestCache.enabled", value = "true"),
// HystrixCommand的执行和事件是否打印日志到 HystrixRequestLog 中
@HystrixProperty(name = "requestLog.enabled", value = "true"),
},
threadPoolProperties = {
// 该参数用来设置执行命令线程池的核心线程数,该值也就是命令执行的最大并发量
@HystrixProperty(name = "coreSize", value = "10"),
// 该参数用来设置线程池的最大队列大小。当设置为 -1 时,线程池将使用 SynchronousQueue 实现的队列,
// 否则将使用 LinkedBlockingQueue 实现的队列。
@HystrixProperty(name = "maxQueueSize", value = "-1"),
// 该参数用来为队列设置拒绝阈值。 通过该参数, 即使队列没有达到最大值也能拒绝请求。
// 该参数主要是对 LinkedBlockingQueue 队列的补充,因为 LinkedBlockingQueue
// 队列不能动态修改它的对象大小,而通过该属性就可以调整拒绝请求的队列大小了。
@HystrixProperty(name = "queueSizeRejectionThreshold", value = "5"),
}
)
public String strConsumer() {
return "hello 2020";
}
public String str_fallbackMethod()
{
return "*****fall back str_fallbackMethod";
}
13.6.服务限流
后面高级篇讲解alibaba的Sentinel说明
13.7.hystrix工作流程
https://github.com/Netflix/Hystrix/wiki/How-it-Works
Hystrix工作流程
- 官网图例
- 步骤说明
- 创建 HystrixCommand(用在依赖的服务返回单个操作结果的时候) 或 HystrixObserableCommand(用在依赖的服务返回多个操作结果的时候) 对象。
- 命令执行。其中 HystrixComand 实现了下面前两种执行方式;而 HystrixObservableCommand 实现了后两种执行方式:execute():同步执行,从依赖的服务返回一个单一的结果对象, 或是在发生错误的时候抛出异常。queue():异步执行, 直接返回 一个Future对象, 其中包含了服务执行结束时要返回的单一结果对象。observe():返回 Observable 对象,它代表了操作的多个结果,它是一个 Hot Obserable(不论 “事件源” 是否有 “订阅者”,都会在创建后对事件进行发布,所以对于 Hot Observable 的每一个 “订阅者” 都有可能是从 “事件源” 的中途开始的,并可能只是看到了整个操作的局部过程)。toObservable(): 同样会返回 Observable 对象,也代表了操作的多个结果,但它返回的是一个Cold Observable(没有 “订阅者” 的时候并不会发布事件,而是进行等待,直到有 “订阅者” 之后才发布事件,所以对于 Cold Observable 的订阅者,它可以保证从一开始看到整个操作的全部过程)。
- 若当前命令的请求缓存功能是被启用的, 并且该命令缓存命中, 那么缓存的结果会立即以 Observable 对象的形式 返回。
- 检查断路器是否为打开状态。如果断路器是打开的,那么Hystrix不会执行命令,而是转接到 fallback 处理逻辑(第 8 步);如果断路器是关闭的,检查是否有可用资源来执行命令(第 5 步)。
- 线程池/请求队列/信号量是否占满。如果命令依赖服务的专有线程池和请求队列,或者信号量(不使用线程池的时候)已经被占满, 那么 Hystrix 也不会执行命令, 而是转接到 fallback 处理逻辑(第8步)。
- Hystrix 会根据我们编写的方法来决定采取什么样的方式去请求依赖服务。HystrixCommand.run() :返回一个单一的结果,或者抛出异常。HystrixObservableCommand.construct(): 返回一个Observable 对象来发射多个结果,或通过 onError 发送错误通知。
- Hystrix会将 “成功”、“失败”、“拒绝”、“超时” 等信息报告给断路器, 而断路器会维护一组计数器来统计这些数据。断路器会使用这些统计数据来决定是否要将断路器打开,来对某个依赖服务的请求进行 “熔断/短路”。
- 当命令执行失败的时候, Hystrix 会进入 fallback 尝试回退处理, 我们通常也称该操作为 “服务降级”。而能够引起服务降级处理的情况有下面几种:第4步: 当前命令处于"熔断/短路"状态,断路器是打开的时候。第5步: 当前命令的线程池、 请求队列或 者信号量被占满的时候。第6步:HystrixObservableCommand.construct() 或 HystrixCommand.run() 抛出异常的时候。
- 当Hystrix命令执行成功之后, 它会将处理结果直接返回或是以Observable 的形式返回。
tips:如果我们没有为命令实现降级逻辑或者在降级处理逻辑中抛出了异常, Hystrix 依然会返回一个 Observable 对象, 但是它不会发射任何结果数据, 而是通过 onError 方法通知命令立即中断请求,并通过onError()方法将引起命令失败的异常发送给调用者。
13.8.服务监控hystrixDashboard
13.8.1.概述
除了隔离依赖服务的调用以外,Hystrix还提供了准实时的调用监控(Hystrix Dashboard),Hystrix会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等。Netflix通过hystrix-metrics-event-stream项目实现了对以上指标的监控。Spring Cloud也提供了Hystrix Dashboard的整合,对监控内容转化成可视化界面。
13.8.2.新建仪表盘项目9001
- 新建项目
- pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
- application.yaml
server:
port: 9001
- 主启动类
新注解@EnableHystrixDashboard
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardMain9001 {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardMain9001.class, args);
}
}
- 访问监控页面
13.8.3.断路器演示(服务监控hystrixDashboard)
此时使用9001监控8001,监控者是9001
13.8.3.1.修改cloud-provider-hystrix-payment8001
- 被监控项目8001必须有的依赖
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- 被监控项目8001主启动类增加配置
@EnableCircuitBreaker也不可缺少
@SpringBootApplication
@EnableEurekaClient //本服务启动后会自动注册进eureka服务中
@EnableCircuitBreaker
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class, args);
}
/**
* 此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
* ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",
* 只要在自己的项目里配置上下面的servlet就可以了
*/
@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;
}
}
13.8.3.2.监控测试
启动1个eureka或者3个eureka集群均可
- 9001监控8001
- 测试地址
http://localhost:8001/payment/circuit/31http://localhost:8001/payment/circuit/-31
先访问正确地址,再访问错误地址,再正确地址,会发现图示断路器都是慢慢放开的。 - 如何看?
- 一圈
实心圆:共有两种含义。它通过颜色的变化代表了实例的健康程度,它的健康度从绿色<黄色<橙色<红色递减。
该实心圆除了颜色的变化之外,它的大小也会根据实例的请求流量发生变化,流量越大该实心圆就越大。所以通过该实心圆的展示,就可以在大量的实例中快速的发现故障实例和高压力实例。 - 1线
曲线:用来记录2分钟内流量的相对变化,可以通过它来观察到流量的上升和下降趋势。 - 整图说明
- 整图说明2
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
```
3. application.yaml
```yaml
server:
port: 9001
```
4. 主启动类
<font color=red><strong>新注解@EnableHystrixDashboard</strong></font>
```java
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardMain9001 {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardMain9001.class, args);
}
}
```
5. 访问监控页面
<img src="https://s2.51cto.com/images/blog/202406/26144623_667bb93f783bf87179.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=" style="zoom:80%;" />
### 13.8.3.断路器演示(服务监控hystrixDashboard)
**此时使用9001监控8001,监控者是9001**
#### 13.8.3.1.修改cloud-provider-hystrix-payment8001
1. 被监控项目8001必须有的依赖
```xml
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
```
2. 被监控项目8001主启动类增加配置
@EnableCircuitBreaker也不可缺少
```java
@SpringBootApplication
@EnableEurekaClient //本服务启动后会自动注册进eureka服务中
@EnableCircuitBreaker
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class, args);
}
/**
* 此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
* ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",
* 只要在自己的项目里配置上下面的servlet就可以了
*/
@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;
}
}
```
#### 13.8.3.2.监控测试
启动1个eureka或者3个eureka集群均可
1. 9001监控8001
<img src="https://s2.51cto.com/images/blog/202406/26144623_667bb93fa9f9d17831.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=" />
2. 测试地址
http://localhost:8001/payment/circuit/31
http://localhost:8001/payment/circuit/-31
<font color=red>先访问正确地址,再访问错误地址,再正确地址,会发现图示断路器都是慢慢放开的。</font>
<img src="https://s2.51cto.com/images/blog/202406/26144623_667bb93fd8b8350755.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=" style="zoom:80%;" />
<img src="https://s2.51cto.com/images/blog/202406/26144624_667bb9401b82027650.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=" style="zoom:80%;" />
3. 如何看?
1. 一圈
实心圆:共有两种含义。它通过颜色的变化代表了实例的健康程度,它的健康度从绿色<黄色<橙色<红色递减。
该实心圆除了颜色的变化之外,它的大小也会根据实例的请求流量发生变化,流量越大该实心圆就越大。所以通过该实心圆的展示,就可以在大量的实例中快速的发现<font color=red><strong>故障实例和高压力实例</strong></font>。
2. 1线
曲线:用来记录2分钟内流量的相对变化,可以通过它来观察到流量的上升和下降趋势。
3. 整图说明
<img src="https://s2.51cto.com/images/blog/202406/26144624_667bb9404c89923198.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=" style="zoom:80%;" />
4. 整图说明2
<img src="https://s2.51cto.com/images/blog/202406/26144624_667bb9407be5845616.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=" style="zoom:80%;" />