# SpringCloud2020 **Repository Path**: yuejuncheng/springcloud2020 ## Basic Information - **Project Name**: SpringCloud2020 - **Description**: 笔记来自:尚硅谷SpringCloud(H版&alibaba)框架开发教程(大牛讲授spring cloud)。视频网址:https://www.bilibili.com/video/BV18E411x7eT - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 3 - **Created**: 2022-06-21 - **Last Updated**: 2022-06-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # SpringCloud2020 > * 笔记来自:[尚硅谷SpringCloud(H版&alibaba)框架开发教程(大牛讲授spring cloud)](https://www.bilibili.com/video/BV18E411x7eT). > * 软件架构:SpringCloud(H版&alibaba) > * 技术要求:Java8+maven+git、github+Nginx+RabbitMQ+SpringBoot2.0 项目说明 * 简单的RestTemplate远程调用 >* cloud-api-comment 公共模块 >* cloud-consumer-order80 消费模块 >* cloud-provider-payment8001 支付模块 * 单机版Eureka >* cloud-api-comment 公共模块 >* cloud-eureka-server7001 注册中心 >* cloud-consumer-order9080 消费模块 >* cloud-provider-payment9001 支付模块 * 集群版Eureka >* cloud-api-comment 公共模块 >* cloud-eureka-server7002 注册中心 >* cloud-eureka-server7003 注册中心 >* cloud-consumer-order9080 消费模块 >* cloud-provider-payment9001 支付模块 * Ribbon负载均衡 >* cloud-api-comment 公共模块 >* cloud-eureka-server7002 注册中心 >* cloud-eureka-server7003 注册中心 >* cloud-consumer-order9081 消费模块 >* cloud-provider-payment9001 支付模块 >* cloud-provider-payment9002 支付模块 * zookeeper做注册中心 >* cloud-api-comment 公共模块 >* cloud-provider-payment8003 支付模块 >* cloud-consumerzk-order80 消费模块 * consul做注册中心 >* cloud-api-comment 公共模块 >* cloud-providerconsul-payment8006 支付模块 >* cloud-consumerconsul-order80 消费模块 * OpenFeign >* cloud-api-comment 公共模块 >* cloud-eureka-server7002 注册中心 >* cloud-eureka-server7003 注册中心 >* cloud-consumer-feign-order80 消费模块 >* cloud-provider-payment9001 支付模块 >* cloud-provider-payment9002 支付模块 * Hystrix >* cloud-api-comment 公共模块 >* cloud-eureka-server7001 注册中心 >* cloud-consumer-feign-hystrix-order80 消费模块 >* cloud-provider-hystrix-payment8001 支付模块 * Hystrix-Dashboard >* cloud-api-comment 公共模块 >* cloud-eureka-server7001 注册中心 >* cloud-comsumer-hystrix-dashboard9001 监控中心 >* cloud-provider-hystrix-payment8001 支付模块(被监控项目) * Gateway >* cloud-api-comment 公共模块 >* cloud-eureka-server7002 注册中心 >* cloud-eureka-server7003 注册中心 >* cloud-gateway-gateway9527 网关 >* cloud-provider-payment9001 支付模块 >* cloud-provider-payment9002 支付模块 * Config >* cloud-api-comment 公共模块 >* cloud-eureka-server7002 注册中心 >* cloud-eureka-server7003 注册中心 >* cloud-config-center-3344 config服务端 >* cloud-config-client-3355 config客户端 * Bus >* cloud-api-comment 公共模块 >* cloud-eureka-server7002 注册中心 >* cloud-eureka-server7003 注册中心 >* cloud-config-center-3345 config服务端 >* cloud-config-client-3356 config客户端 >* cloud-config-client-3357 config客户端 * Stream >* cloud-api-comment 公共模块 >* cloud-eureka-server7002 注册中心 >* cloud-eureka-server7003 注册中心 >* cloud-stream-rabbitmq-provider8801 消息提供者 >* cloud-stream-rabbitmq-consumer8802 消息消费者 >* cloud-stream-rabbitmq-consumer8802 消息消费者 * Nacos作为服务注册中心演示 >* cloud-api-comment 公共模块 >* cloudalibaba-provider-payment9001 支付模块 >* cloudalibaba-provider-payment9002 支付模块 >* cloudalibaba-consumer-nacos-order83 消费模块 ## 1.微服务架构零基础理论入门(小白必看) * 课程内容(SpringCloud+SpringCloud alibaba) * 技术要求 java8+maven+git、github+Nginx+RabbotMQ+SpringBoot2.0 * SpringCloud技术维度
![SpringCloud技术维度](img/springcloud技术维度.png)
* SpringCloud集成相关优质项目
![SpringCloud集成相关优质项目](img/springcloud集成相关优质项目.png)
* 大厂案例(略) * SpringCloud技术栈
![springcloud技术栈](img/springcloud技术栈.png)
## 2.从2.2.x和H版开始说起 * SpringBoot版本选择 * SpringCloud版本选择 * SpringCloud第二季定稿版(截止2020.2.15) > * cloud: Hoxton.SR1 > * boot: 2.2.RELEASE > * cloud: alibaba 2.1.0.RELEASE > * java: 1.8+ > * maven: 3.5+ > * mysql: 5.7+ ## 3.关于Cloud各种组件的停更/升级/替换 ### 3.1.由停更引发的"升级惨案"(略) ### 3.2.参考资料见官网 #### 3.2.1.Spring Cloud > * https://cloud.spring.io/spring-cloud-static/Hoxton.SR1/reference/htmlsingle/ > * [Spring Cloud中文文档](https://www.bookstack.cn/read/spring-cloud-docs/docs-index.md) #### 3.2.2.Spring Boot > * https://docs.spring.io/spring-boot/docs/2.2.2.RELEASE/reference/htmlsingle/ ## 4.微服务架构编码构建 - **SpringCloud停更组件和备选方案**
![SpringCloud停更组件和备选方案](img/Springcloud升级.png)
### 4.1.约定>配置>编码 #### 4.1.1.slave会从master读取binlog来进行数据同步 #### 4.1.2.三步骤+原理图 - **mysql复制过程**
![mysql复制过程](img/mysql复制过程.png)
> 1. master将改变记录到二进制日志(binary log)。这些记录过程叫做二进制日志事件,binary log events; > 2. slave将master的binary log events拷贝到它的中继日志(relay log); > 3. slave重做中继日志中的事件,将改变应用到自己的数据库中。 MySQL复制是异步的且串行化的 ### 4.2.IDEA新建project工作空间 #### 4.2.1微服务cloud整体聚合工程 ##### 4.2.1.1父工程步骤 > * New Project > * 聚合总父工程名字 > * Maven版本选择 > * 工程名称 > * 字符编码 > * 注解生效激活 > * Java编译版本选择8 > * File Type过滤 ##### 4.2.1.2.父工程POM ```xml 4.0.0 com.wangjialu.learner springcloud2020 0.0.1 pom UTF-8 1.8 1.8 4.12 1.2.17 1.18.12 8.0.19 1.1.16 2.2.2.RELEASE Hoxton.SR1 2.1.0.RELEASE 1.3.0 org.springframework.boot spring-boot-dependencies ${spring.boot.version} pom import org.springframework.cloud spring-cloud-dependencies ${spring.cloud.version} pom import com.alibaba.cloud spring-cloud-alibaba-dependencies ${spring.cloud.alibaba.version} pom import mysql mysql-connector-java ${mysql.version} com.alibaba druid-spring-boot-starter ${druid.version} org.projectlombok lombok org.springframework.boot spring-boot-maven-plugin true true nexus-aliyun Nexus aliyun http://maven.aliyun.com/nexus/content/groups/public true false ``` ### 4.2.Rest微服务工程搭建 #### 4.2.1.构建步骤 ##### 4.2.1.1.cloud-provider-payment8001 微服务提供者Module模块 >* 1 建module >* 2 改POM(dependences) > ```xml > > > org.springframework.boot > spring-boot-starter-web > > > org.springframework.boot > spring-boot-starter-actuator > > > org.mybatis.spring.boot > mybatis-spring-boot-starter > > > com.alibaba > druid-spring-boot-starter > > > mysql > mysql-connector-java > > > org.springframework.boot > spring-boot-starter-jdbc > > > org.projectlombok > lombok > true > > > org.springframework.boot > spring-boot-starter-test > test > > > ``` >* 3 写YML >* 4 主启动 >* 5 业务类 >* 测试 >* 小结 ##### 4.2.1.2.热部署Devtools >* 1.Adding devtools to your project > ```xml > > org.springframework.boot > spring-boot-devtools > runtime > true > >``` >* 2.Adding plugin to your pom.xml > ```xml > > 你自己的工程名字 > > > org.springframework.boot > spring-boot-maven-plugin > > true > true > > > > > ``` >* 3.Enabling automatic build >* 4.Update the value of >* 5.重启idea ##### 4.2.1.3.cloud-consumer-order80 微服务消费者订单Module模块 >* 1 建module >* 2 改POM(dependences) > ```xml > > > org.springframework.boot > spring-boot-starter-web > > > org.springframework.boot > spring-boot-starter-actuator > > > org.projectlombok > lombok > true > > > org.springframework.boot > spring-boot-starter-test > test > > > org.springframework.boot > spring-boot-devtools > runtime > true > > > ``` >* 3 写YML >* 4 主启动 >* 5 业务类 >* 测试 ##### 4.2.1.4.工程重构 项目cloud-provider-payment8001和cloud-consumer-order80中重复代码提到cloud-api-commons中,这两个项目分别依赖cloud-api-commons,至此一个简单的通过RestTemplate远程调用的工程完成重构。 简单的RestTemplate远程调用项目如下: >* cloud-api-comment 公共模块 >* cloud-consumer-order80 消费模块 >* cloud-provider-payment8001 支付模块 ## 5.Eureka服务注册与发现 ### 5.1.Eureka基础知识 #### 5.1.1.什么是服务治理? Spring Cloud 封装了Netflix公司开发的Eureka来实现服务治理。 在传统的RPC远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要使用服务之间的依赖关系,来实现服务调用、负载均衡、容错 等,实现服务发现与注册。 #### 5.1.2.什么是服务注册? Eureka采用了CS的设计架构,Eureka Server作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用Eureka的客户端连接到Eureka Server并维持心跳连接。这样系统的维护人员就可以通过Eureka Server来监控系统中各个微服务是否正常运行。 在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息比如服务地址通讯地址等以别名方式注册到注册中心上。另-方(消费者 服务提供者) .以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用RPC远程调用框架核心设计思想:在于注册中心,因为使用注册中心 管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何rpc远程框架中,都会有一个注册中心(存放服务地址相关信息(接口地址)) - **eureka_vs_dubbo**
![eureka_vs_dubbo](img/eureka_vs_dubbo.png)
#### 5.1.3.Eureka包含两个组件: Eureka Server和Eureka Client * Eureka Server提供服务注册服务 >各个微服务节点通过配置启动后,会在EurekaServer中进行注册, 这样EurekaServer中的服务注册表中将会存储所有可用服务节点的 信息,服务节点的信息可以在界面中直观看到。 * EurekaClient通过注册中心进行访问 >是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备个内置的、 使用轮询(round-robin)负载算法的负载均衡器 。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。 如果Eureka Server在多个心跳周期内没有接收到某个节点的心 跳,EurekaServer将 会从服务注册表中把这个服务节点移除(默认90秒) ## 5.2.单机Eureka构建步骤 ### 5.2.1.构建EurekaServer #### 5.2.1.1.建module: cloud-eureka-server7001 #### 5.2.1.2.改POM 依赖如下: ```xml org.springframework.cloud spring-cloud-starter-netflix-eureka-server org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator com.wangjialu.learner cloud-api-commons 0.0.1 ``` #### 5.2.1.3.写YML ```yml 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/ ``` #### 5.2.1.4.启动类 #### 5.2.1.5.测试 ### 5.2.2.构建EurekaClient 将cloud-consumer-order80和cloud-provider-payment8001复制一份,pom里都分别增加eureka-client依赖: ```xml org.springframework.cloud spring-cloud-starter-netflix-eureka-client ``` yml文件里都增加向EurekaServer注册的信息: ```yml eureka: client: register-with-eureka: true fetch-registry: true service-url: # 设置与Eureka Server交互的导致查询服务和注册服务都需要依赖则个地址 defaultZone: http://localhost:7001/eureka/ ``` 在启动类上增加EurekaClient注解:@EnableEurekaClient 单机版Eureka构建完成,项目结构如下: >* cloud-api-comment 公共模块 >* cloud-eureka-server7001 注册中心 >* cloud-consumer-order9080 消费模块 >* cloud-provider-payment9001 支付模块 ### 5.2.3.集群Eureka构建步骤 #### 5.2.3.1.Eureka集群原理说明 - **eureka集群原理说明**
![eureka集群原理说明](img/eureka集群原理说明.png)
- 微服务RPC远程调用最核心的是什么? > 高可用!如果注册中心只有一个,那么存在单点故障问题,注册中心挂了,所有服务都挂了。(服务也存在单点故障问题,所以服务也是分布式/集群部署) #### 5.2.3.2.Eureka集群环境构建步骤 * 复制cloud-eureka-server7001两份,分别为cloud-eureka-server7002和cloud-eureka-server7003。 * 修改hosts, C:\Windows\System32\drivers\etc\hosts文件,增加如下内容 ```shell 127.0.0.1 eureka7002.com 127.0.0.1 eureka7003.com ``` * 修改yml > 注意修改cloud-eureka-server7002和cloud-eureka-server7003的端口号和服务实例名称,并且进行相互注册。 #### 5.2.3.3.将订单和支付服务注册到Eureka集群中 修改注册中心defaultZone值: ```yml # defaultZone: http://localhost:7001/eureka/ #单机版 defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ # 集群版 ``` 集群版Eureka构建完毕,项目结构如下: >* cloud-api-comment 公共模块 >* cloud-eureka-server7002 注册中心 >* cloud-eureka-server7003 注册中心 >* cloud-consumer-order9080 消费模块 >* cloud-provider-payment9001 支付模块 #### 5.2.3.4.负载均衡 复制消费模块cloud-consumer-order9080为cloud-consumer-order9081。 * 修改yml里端口号为9081 * 将Controller里写死ip和端口的请求路径改为Eureka里的服务名称 ```java public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE"; ``` * 将RestTemplate修改为支持负载均衡 ```java @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } ``` * 复制支付模块cloud-provider-payment9001为cloud-provider-payment9002,修改端口号 * 为了能观察到支付模块的请求的服务,Controller里的请求返回增加端口号信息: ```java @Value("${server.port}") private Integer port; ``` 至此Ribbon负载均衡构建完毕,项目结构如下: >* cloud-api-comment 公共模块 >* cloud-eureka-server7002 注册中心 >* cloud-eureka-server7003 注册中心 >* cloud-consumer-order9081 消费模块 >* cloud-provider-payment9001 支付模块 >* cloud-provider-payment9002 支付模块 ### 5.2.4.actuator微服务信息完善 * 主机名称:服务名称修改 > 服务端增加配置项 ```yml eureka: instance: instance-id: payment9001 #实例Id ``` * 访问信息有IP信息提示 > 服务端增加配置 ```yml eureka: instance: prefer-ip-address: true #显示访问路径的IP ``` ### 5.2.5.服务发现Discovery * 对于注册eureka里面的微服务,可以通过服务发现来获得该服务的信息 * 修改cloud-provider-payment8001的Controller,增加如下内容: ```java @Resource private DiscoveryClient discoveryClient; @GetMapping("/payment/discovery") public CommonResult discovery() { List services = discoveryClient.getServices(); for (String service : services) { log.debug("*********element:"+service); } // 一个微服务下的全部实例 List instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE"); for (ServiceInstance instance : instances) { log.debug(instance.getServiceId() + "\t" + instance.getHost() + "\t" + instance.getPort() + instance.getUri()); } return new CommonResult(200, "success:port:" + port, discoveryClient); } ``` * 修改启动类 增加注解@EnableDiscoveryClient ### 5.2.6.eureka自我保护 #### 5.2.6.1.故障现象 - **eureka自我保护**
![eureka自我保护](img/eureka自我保护.png)
保护模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护。一旦进入保护模式, Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据,也就是不会注销任何微服务。 #### 5.2.6.2.导致原因 * 为什么会产生Eureka自我保护机制? > 为了防止EurekaClient可以正常运行, 但是与EurekaServer网络不通情况下, EurekaServer不会立刻将EurekaClient服务剔除 * 什么是自我保护模式? > 默认情况下,如果EurekaServer在一 定时间内没有接收到某个微服务实例的心跳,EurekaServer将会注销该实例 (默认90秒)。但 是当网络分区故障发生(延时、卡顿、拥挤)时,微服务与EurekaServer之间无法正常通信,以上行为可能变得非常危险了一因为微 服务本身其实是健康的,此时本不应该注销这个微服务。Eureka通过 ”自我保护模式”来解决这个问题一-当EurekaServer节 点在 短时间内丢失过多客户端时(可能发生了网络分区故障), 那么这个节点就会进入自我保护模式。 自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服 务都会保留)也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定。 #### 5.2.6.3.怎么禁止自我保护 Eureka注册中心如下配置: ```yaml eureka: server: enable-self-preservation: true ``` ## 6.Zookeeper服务注册与发现 ### 6.1.Eureka停止更新了,你怎么办? * 停更不停用 * 替代方案:Zoomkeper、Consul、Nacos等 ### 6.1.SpringCloud整合Zookeeper替代Eureka #### 6.1.1.服务提供者 * 建立工程cloud-provider-payment8003 * POM如下: ```xml springcloud2020 com.wangjialu.learner 0.0.1 4.0.0 cloud-provider-payment8003 cloud-provider-payment8003 Zookeeper服务提供者 com.wangjialu.learner cloud-api-commons 0.0.1 org.springframework.boot spring-boot-starter-web org.springframework.cloud spring-cloud-starter-zookeeper-discovery org.apache.zookeeper zookeeper org.apache.zookeeper zookeeper 3.4.9 org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-devtools runtime true org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test ``` * yml ```yaml server: # 8004表示注册到zookeeper服务器的支付服务提供者端口号 port: 8004 spring: application: # 服务别名---注册zookeeper到注册中心的名称 name: cloud-provider-payment cloud: zookeeper: # 默认localhost:2181 connect-string: localhost:2181 ``` * 启动类 ```java @SpringBootApplication @EnableDiscoveryClient public class Payment8003Application { public static void main(String[] args) { SpringApplication.run(Payment8003Application.class, args); } } ``` * Controller ```java @RestController @Slf4j public class PaymentController { @Value("${server.port}") private String serverPort; @RequestMapping(value = "payment/zk") public String paymentZk() { return "SpringCloud with zookeeper:" + serverPort + "\t" + UUID.randomUUID().toString(); } } ``` * 启动zk,测试 * 思考:服务节点是临时节点还是持久节点? > 临时性的!心跳超时后会删除节点 #### 6.1.2.服务消费者 cloud-consumerzk-order80的创建过程略。 至此,简单的以zk为注册中心的微服务构建完毕,项目结构: >* cloud-api-comment 公共模块 >* cloud-provider-payment8003 支付模块 >* cloud-consumerzk-order80 消费模块 ## 7.Consul服务注册与发现 ### 7.1.Consul简介 * [是什么?](https://www.consul.io/intro/index.html) > Consul是一开源的分布式**服务发现**和**配置管理**系统,由HashiCorp公司用Go语言开发。 > > 提供了微服务系统中的服务治理、配置中心、控制总线等功能。这些功能中的每一个都可以根据需要 单独使用,也可以一起使用以构建全方位的服 务网格,总之Consul提供了-一种完整的服务网格解决方案。 > >它具有很多优点。包括:基于raft协议,比较简洁;支持健康检查,同时支持HTTP和DNS协议支持跨数据中心的WAN集群提供图形界面跨 平台,支持Linux、Mac、Windows * 能干嘛? >* 服务发现: 提供HTTP/DNS两种发现方式 >* 健康检测: 支持多种方式,HTTP、TCP、Docker、shell脚本定制化 >* KV存储: Key、Value的存储方式 >* 多数据中心: Consul支持多数据中心 >* 可视化界面 * [下载](https://www.consul.io/downloads.html) * [SpringCloudConsul官方说明](https://www.springcloud.cc/spring-cloud-consul.html) ### 7.2.安装并运行Consul * [下载](https://learn.hashicorp.com/consul/getting-started/install.html) * 解压、启动 ```shell consul agent -dev ``` * 访问首页 http://localhost:8500 ### 7.3.构建项目 创建项目过程略,yml内容为(消费端类似) ```yaml server: port: 8006 spring: application: name: cloud-provider-payment cloud: consul: # consul注册中心地址 host: localhost port: 8500 discovery: hostname: 127.0.0.1 service-name: ${spring.application.name} ``` 项目结构如下: >* cloud-api-comment 公共模块 >* cloud-providerconsul-payment8006 支付模块 >* cloud-consumerconsul-order80 消费模块 ### 7.3.三种注册中心的异同点 #### 7.3.1.三种注册中心比较 | 组件名 | 语言 | CAP | 服务健康检查 | 对外暴露接口 | SpringaCloud集成 | | :---- | :---- | :---: | :------ | :------- | :--------------- | | Eureka | Java | AP | 可配支持 | HTTP | 已集成 | | Consul | Go | CP | 支持 | HTTP/DNS | 已集成 | | Zookeeper | Java | CP | 支持 | 客户端 | 已集成 | #### 7.3.2.CAP理论 >* C: Consistency(强一致性) >* A: Availability(可用性) >* P: Parttition tolerance(分区容错性) CAP理论关注粒度是否是数据,而不是整体系统设计的策略 CAP最多同时满足两个 CAP理论的核心是: 一个分布式系统不可能同时很好的满足一致性、可用性和分区容错性这三个需求, 因此,根据CAP原理将NoSQL数据库分成了满足CA原则、满足CP原则和满足AP原则三大类: CA-单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。 CP-满足一致性,分区容错性的系统,通常性能不是特别高。 AP-满足可用性,分区容错性的系统,通常可能对一致性要求低一些。 ## 8.Ribbon负载均衡调用 ### 8.1.概述 #### 8.1.1.是什么? Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。 简单的说,Ribbon是Netflix发布的开源项目, 要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供- 系列 完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer (简称LB)后面所有的机器,Ribbon会自动的帮 助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。 #### 8.1.2.[官网资料](https://github.com/Netflix/ribbon/wiki/Getting-Started) #### 8.1.3.能干嘛? LB(负载均衡) * LB负载均衡(Load Balance)是什么? > 简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA (高可用)。 > > 常见的负载均衡有软件Nginx, LVS,硬件F5等。 * Ribbon本地负载均衡客户端VS Nginx服务端负载均衡区别 > > Nginx是服务器负载均衡,客户端所有请求都会交给nginx,然后由nginx实现转发请求。即负载均衡是由服务端实现的。 Ribbon本地负载均衡,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远 程服务调用技术。 * 集中式LB > 即在服务的消费方和提供方之间使用独立的L B设施(可以是硬件,如F5, 也可以是软件,如nginx), 由该设施负责把访问请求通过某种策 略转发至服务的提供方; * 进程内LB , > 将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。 Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。 Ribbon提供了多种负载均衡策略:比如**轮询**、**随机**和**根据响应时间加权**等。 ### 8.2.Ribbon负载均衡演示 参考*5.2.3.4.负载均衡*,Eureka内置了Ribbon,当然,Ribbon是独立组件,可以和其它注册中心组合使用。 * 二说RestTemplate的使用 >* getForObject方法/getForEntity方法 >* postForObject/postEntity ### 8.3.Ribbon核心组件IRule #### 8.3.1.轮询方式 **IRule及其子类**
![IRule及其子类](img/IRule子类.png)
>* **RoundRobinRule** 轮询 >* **RandomRule** 随机 >* **RetryRule** 先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内进行重试,获取可用的服务 >* **BestAvailableRule** 会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务 >* **AvailabilityFilteringRule** 先过滤掉故障实例,再选择并发较小的实例 >* **WeightedResponseTimeRule** 对RoundRobinRule的扩展,响应速度越快的实例选择权重越多大,越容易被选择 >* **ZoneAvoidanceRule** 默认规则,复合判断server所在区域的性能和server的可用性选择服务器 #### 8.3.2.如何替换 * 将IRule实例替换为指定实现方式 ```java @Configuration public class MySelfRule { @Bean public IRule iRule() { return new RoundRobinRule(); } } ``` * 指定某个或某几个服务使用指定IRule 上述方式将导致所有负载均衡使用同一负载均衡算法,如果要指定某个或某几个服务使用指定算法,那么该IRule实例只能在指定范围生效。 实现方式如下: >* IRule所在配置类(@Configuration)不能被@ComponentScan扫描到 > (_放在@ComponentScan扫描不到的目录或者excludeFilters进行排除_) >* 启动类上明确指定服务所使用的配置类 > ```java > @SpringBootApplication > @EnableEurekaClient > @RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MySelfRule.class) > public class OrderMain80 { > public static void main(String[] args) { > SpringApplication.run(OrderMain80.class, args); > } > } > ``` #### 8.3.3.Ribbon负载均衡算法 轮询算法的实现源码: 可用服务数组取模,即 原子类+CAS+自旋锁 ## 9.OpenFeign服务接口调用 ### 9.1.概述 #### 9.1.1.OpenFeign是什么? [官方解释](https://cloud.spring.io/spring-cloud-static/Hoxton.SR1/reference/htmlsingle/#spring-cloud-openfeign) Feign是一 个声明式WebService客户端。 使用Feign能让编写Web Service客户端更加简单。 它的使用方法是定义一个服务接口然后在上面添加注解。Feign也支持可拔插式的编码器和解码器。 Spring Cloud对Feign进行了封装 ,使其支持了Spring MVC标准注解和HttpMessageConverters。 Feign可以与Eureka和Ribbon组合使用以支持负载均衡 #### 9.1.2.Feign能干什么? Feign旨在使编写Java Http客户端变得更容易。 前面在使用Ribbon+ RestTemplate时,利用RestTemplate对http请求的封装处理,形成了-套模版化的调用方法。但是在实际开发 中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用, 所以通常都会针对每个微服务自行封装一些客户端类来包装 这些依赖服务的调用。所以, Feign在此基础上做了进一步封装, 由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下 ,我们只需创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口 上面标注一个 Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。 #### 9.1.3.Feign和OpenFeign两者区别
Feign OpenFeign
Feign是Spring Cloud组件中的一个轻量级RESTful的HTTP服务客户端。
Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。 Feign的使用方式是:使用Feign的注解定义接口,调用这个接口就可以调用服务注册中心的服务。
OpenFeign是Spring Cloud在Feign的基础上支持了SpringMVC的注解,如@RequesMapping等等。OpenFeign的@FeignClient可以解析, SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
### 9.2.OpenFeign使用步骤 **只在Rest Client使用** 新建项目cloud-consumer-feign-order80 * pom依赖 ```xml org.springframework.cloud spring-cloud-starter-openfeign org.springframework.cloud spring-cloud-starter-netflix-eureka-server com.wangjialu.learner cloud-api-commons 0.0.1 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-starter-test test org.projectlombok lombok ``` * yml ```yaml server: port: 80 eureka: client: register-with-eureka: false fetch-registry: true service-url: defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ ``` * 启动类 ```java @SpringBootApplication @EnableFeignClients public class FeignOrderApplication { public static void main(String[] args) { SpringApplication.run(FeignOrderApplication.class, args); } } ``` * 定义接口(注意保持和RestServer端Controller暴漏接口一致) ```java @Component @FeignClient(value="CLOUD-PAYMENT-SERVICE") public interface PaymentFeignService { @GetMapping("/payment/getById/{id}") CommonResult getById(@PathVariable("id")Long id); } ``` * controller ```java @RestController @Slf4j public class OrderFeignController { @Resource private PaymentFeignService paymentFeignService; @GetMapping("/consumer/getPaymentById/{id}") public CommonResult getPaymentById(@PathVariable("id")Long id) { return paymentFeignService.getById(id); } } ``` RestServer不做任何改动,至此完成OpenFeign的加入,项目结构: >* cloud-api-comment 公共模块 >* cloud-eureka-server7002 注册中心 >* cloud-eureka-server7003 注册中心 >* cloud-consumer-feign-order80 消费模块 >* cloud-provider-payment9001 支付模块 >* cloud-provider-payment9002 支付模块 * 测试 测试发现**OpenFeign自带负载均衡** ### 9.3.OpenFeign超时控制 OpenFeign默认超时时间为1秒钟,自定义如下: ```yaml ribbon: ReadTimeout: 5000 # 建立连接的超时时间 ConnectTimeout: 5000 # 建立连接后读取服务器资源的超时时间 ``` ### 9.4.OpenFeign日志打印功能 Feign增加了日志打印功能,以便于了解Http请求的细节。 Feign的日志级别有: >* **NONE**:默认的,不显示任何日志; >* **BASIC**:仅记录请求方法、URL、 响应状态码及执行时间; >* **HEADERS**:除了BASIC中定义的信息之外,还有请求和响应的头信息; >* **FULL**: 除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据。 * 支持的日志级别配置 ```java @Configuration public class FeignConfig { @Bean public Logger.Level feignLoggerLevel() { // 请求和响应的头信息,请求和响应的正文及元数据 return Logger.Level.FULL; } } ``` * yml定义日志级别 ```yaml logging: level: com.wangjialu.learner.consumer.service.PaymentFeignService: debug ``` ## 10.Hystrix熔断器 ### 10.1.概述 #### 10.1.1.分布式系统面临的问题 -复杂分布式体系结构中的应用程序,有数10个依赖关系,每个依赖关系在某些时候将不可避免地失败,由此引发**服务雪崩**
![服务雪崩](img/avalanche.png)
>多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。 >如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而弓|起系统崩溃,所谓的“雪崩效应” > >对于高流量的应用来说,单- 的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还 >可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延 >迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。 > >所以,通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就 >会发生级联故障,或者叫雪崩。 #### 10.1.2.是什么? Hystrix是一个用于处理分布式系统的**延迟**和**容错**的开源库, 在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等, Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。 ”断路器”本身是一种开关装置, 当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合 预期的、可处理的备选响应(FallBack) ,而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会 被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。 #### 10.1.2.能干嘛 >* 服务降级 >* 服务熔断 >* 接近实时的监控 #### 10.1.3.官方资料 https://github.com/Netflix/hystrix/wiki ### 10.2.HyStrix重要概念 * 服务降级 >* 服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好提示,fallback >* 哪些情况会发出降级 > * 程序运行异常 > * 超时 > * 服务熔断触发服务降级 > * 线程池/信号量也会导致服务降级 * 服务熔断 > 类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示 > > 服务的降级->进而熔断->恢复调用链路 * 服务限流 > 秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行 ### 10.3.HyStrix案例 #### 10.3.1.服务降级 项目创建过程略,需要注意的点: ##### 10.3.1.1.RestServer端加入HyStrix * RestServer加入依赖 ```xml org.springframework.cloud spring-cloud-starter-netflix-hystrix ``` * RestServer启动类加入注解**@EnableCircuitBreaker** * RestServer对外接口(Controller里)的方法加入如下注解内容: ```java @HystrixCommand(fallbackMethod = "paymentInfo_ERROR_Handler", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500") }) ``` >* @HystrixCommand里的fallbackMethod指定超时/异常时回调的方法 >* @HystrixProperty里制定了超时时间 Hystrix项目参考以下项目结构内容: >* cloud-api-comment 公共模块 >* cloud-eureka-server7001 注册中心 >* cloud-consumer-feign-hystrix-order80 消费模块 >* cloud-provider-hystrix-payment8001 支付模块 ##### 10.3.1。2.RestClient端加入HyStrix 以上已经完成了RestServer端的Hystrix,如果RestClient也要加入Hystrix,那么注意以下几点: * yml加入 ```yaml feign: hystrix: enabled: true ``` * 启动类加入注解**@EnableHystrix** * 在Controller方法上加入@HystrixCommand(略) ##### 10.3.1.3.默认fallback 为了解决Controller每个方法都要配置@HystrixCommand参数信息,可以在Controller使用@DefaultProperties(defaultFallback = "xxx")。 xxx为方法名,Controller里方法上依然需要@HystrixCommand注解,只是有了默认的fallback。 ##### 10.3.1.4.为每个Feign接口实现fallback 以上方法都存在的问题是和业务代码混在一起。@FeignClient里参数fallback可以指定所使用的RestServer出现故障时的回调类,回调方法与接口方法声明一致。 参考cloud-consumer-feign-hystrix-order80里PaymentHystrixService和PaymentFallbackService。 #### 10.3.2.服务熔断 ##### 10.3.2.1.熔断是什么? 熔断机制概述 >熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错不可用或者响应时间太长时, 会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。 **当检测到该节点微服务调用响应正常后,恢复调用链路**。 在Spring Cloud框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况, 当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制。熔断机制的注解是**@HystrixCommand**。 [大神论文](https://martinfowler.com/bliki/CircuitBreaker.html) ##### 10.3.2.2.实操 略 ##### 10.3.2.3.原理 - 服务熔断论文结论
![服务熔断论文结论](img/服务熔断论文结论.png)
熔断类型 >* **熔断打开** 请求不再调用当前服务,内部设置一般为MTTR(平均故障处理时间),当打开长达导所设时钟则进入半熔断状态 >* **熔断关闭** 熔断关闭后不会对服务进行熔断 >* **熔断半开** 部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断 ##### 10.3.2.4.断路器流程 - 断路器流程
![断路器流程](img/断路器流程.png)
###### 10.3.2.4.1.断路器在什么情况下开始起作用 ```java @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"),//失败率达到多少后跳闸 }) ``` 涉及到断路器的三个重要参数:**快照时间窗**、**请求总数阀值**、**错误百分比阀值**。 >* **快照时间窗**:断路器确定是否打开需要统计一一些请求和错误数据, 而统计的时间范围就是快照时间窗,默认为最近的10秒。 >* **请求总数阀值**:在快照时间窗内,必须满足请求总数阀值才有资格熔断。默认为20,意味着在10秒内,如果该hystrix命令的调用次数不足20次, 即使所有的请求都超时或其他原因失败,断路器都不会打开。 >* **错误百分比阀值**:当请求总数在快照时间窗内超过了阀值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过 50%的错误百分比,在默认设定50%阀值情况下,这时候就会将断路器打开。 ###### 10.3.2.4.2.断路器开启或者关闭的条件 >* 当满足一定的阈值的时候(默认10秒钟超过20个请求次数) >* 当失败率达到一定的时候(默认10秒内超过50%的请求次数) >* 到达以上阈值,断路器将会开启 >* 当开启的时候,所有请求都不会进行转发 >* 一段时间之后(默认5秒),这个时候断路器是半开状态,会让其他一个请求进行转发. 如果成功,断路器会关闭,若失败,继续开启.重复4和5 ###### 10.3.2.4.3.断路器打开之后 * 1:再有请求调用的时候,将不会调用主逻辑,而是直接调用降级fallback。通过断路器,实现了自动地发现错误并将降级逻辑切换为主逻辑,减少响 应延迟的效果。 * 2:原来的主逻辑要如何恢复呢? > 对于这一问题,hystrix也为我们实现了 自动恢复功能。 当断路器打开,对主逻辑进行熔断之后, hystrix会启动- 个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑, 当休眠时间窗到期,断路器将进入半开状态,释放- -次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合, 主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时。 ###### 10.3.2.4.3.ALl配置 略 #### 10.3.3.服务限流 后面高级篇讲解alibaba的Sentinel说明 ### 10.4.HyStrix工作流程 [官方文档](https://github.com/Netflix/Hystrix/wiki/How-it-Works) ![官网图例](img/hystrik官方图例.png) * 1、创建一个HystrixCommand或HystrixObservableCommand对象; * 2、通过以下四种方法执行命令(前两种方法仅适用于简单HystrixCommand对象且不可用HystrixObservableCommand): * execute():阻塞的,返回从依赖项收到的单个响应(或在出现错误时抛出异常) * queue(): 非阻塞的,返回一个可以从依赖项中获取单个响应的方法的Future对象。 * observe(): 订阅Observable表示来自依赖项的响应,并返回Observable复制该源的响应Observable * toObservable(): 返回一个Observable,如果订阅了,会执行Hystrix命令并发出其响应 * 3、如果为此请求设置了缓存,首先判断缓存是否是可用的(即是否可以从缓存得到本次请求的响应),如果可以得到则直接返回,得不到再走下一步。 * 4、判断断路器是否打开,如果打开了说明之前的请求失败了(或者说之前满足了断路器的打开条件),则直接调用重写的fallback()函数;如果断路器未打开再到下一步。 * 5、判断与该command关联的线程池和队列(或信号量)是否已满,如果已经满了,则Hystrix不会执行该Command,直接调用fallback()返回;否则到下一步。 * 6、此时通过HystrixObservableCommand.construct()或HystrixCommand.run()执行目的方法调用对依赖项的请求,如果执行失败或超时则直接执行fallback()。 * 7、健康状况统计Hystrix在执行过程中会记录断路器的成功,失败,拒绝和超时状态,Hystrix维护一个记录这些数据的计数器,计数器的数据决定断路器的开闭状态。 * 8、当命令失败时Hystrix都会尝试回退,一般情况下,如果实现了HystrixCommand.getFallback()会返回单个回退值,或者实现HystrixObservableCommand.resumeWithFallback()会发出一个或多个回退值的Observable。如果没有实现fallback方法或在执行fallback方法时抛出了异常,Hystrix仍然会返回一个Observable,但不会返回任何内容,并立即终止并发出onError通知。通过此onError通知,导致命令失败的异常被传回给调用者。 * 9、成功返回 ### 10.4.服务监控hystrixDashboard #### 10.4.1.概述 除了隔离依赖服务的调用以外,Hystrix还提供了**准实时的调用监控(Hystrix Dashboard)**,Hystrix会持续地记录所有通过Hystrix发 起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等。Netflix通过 hystrix-metrics- event-stream项目实现了对以上指标的监控。Spring Cloud也提供了Hystrix Dashboard的整合,对监控内容转化成 可视化界面。 #### 10.4.2.仪表盘 * 新建cloud-consumer-hystrix-dashboard9001 * pom依赖 ```xml org.springframework.cloud spring-cloud-starter-netflix-hystrix-dashboard org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-devtools runtime true org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test ``` * yml ```yaml server: port: 9001 ``` * 启动类 ```java @SpringBootApplication @EnableHystrixDashboard public class HystrixDashboardMain9001 { public static void main(String[] args) { SpringApplication.run(HystrixDashboardMain9001.class); } } ``` * 所有被监控项目实例都必须依赖actuator ```xml org.springframework.boot spring-boot-starter-actuator ``` * 由于升级BUG,被监控项目必须有ServletRegistrationBean实例 ```java /** * 此配置是为了服务监控而配置,与服务容错本身无观,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; } ``` 至此搭建完毕,项目结构: >* cloud-api-comment 公共模块 >* cloud-eureka-server7001 注册中心 >* cloud-comsumer-hystrix-dashboard9001 监控中心 >* cloud-provider-hystrix-payment8001 支付模块(被监控项目) 访问http://localhost:9001/hystrix 并填写监控地址 http://localhost:8001/hystrix.stream - 仪表盘图例
![仪表盘图例](img/Hystrik-Dasboard.png)
## 11.zuul路由网关 由于zuul1.x不再维护,zuul2.x迟迟未推出,不建议使用。 ## 12.Gateway新一代网关 ### 12.1.概述 #### 12.1.1.是什么 [官方网站](https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/) - Gateway在服务组件中的位置
![Gateway在服务组件中的位置](img/gateway.png)
- 微服务架构中网关的位置![SOA-Gateway](img/SOA-Gateway.png) SpringCloud Gateway是Spring Cloud的一个全新项目, 基纡Spring 5.0+ Spring Boot 2.0和Project Reactor等技术开发的网关,它旨在为 微服务架构提供一种简单有效的统一的API路由管理方式。 SpringCloud Gateway作为Spring Cloud生态系统中的网关,目标是替代Zuul,在Spring Cloud 2.0以上版本中,没有对新版本的Zuul 2.0以上 最新高性能版本进行集成,仍然还是使用的Zuul 1.x非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架 实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。 Spring Cloud Gateway的目标提供统-的路由方式且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。 #### 12.1.2.能干嘛 * 反向代理 * 鉴权 * 流量控制 * 熔断 * 日志监控 * ... ### 12.2.三大核心概念 * **Route(路由)**: 路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如断言为true则匹配该路由。 * **Predicate(断言)**: 参考的是Java8的java.util.function.Predicate,开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由 * **Filter(过滤)**:指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改处理。 web请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制, predicate就是我们的匹配条件;而filter. 就可以理解为-个无所不能的拦截器。有了这两个元素。再加上目标uri.就可以实现一个具体的路由了。 ### 12.3.Gateway工作流程 - gateway工作流程的官方描述
![gateway工作流程](img/gateway工作流程.png)
客户端向 Spring Cloud Gateway 发出请求。如果网关处理程序映射(Gateway Handler Mapping)确定请求与路由匹配,则将其发送到网关Web处理程序(Gateway Web Handler)。 此处理程序通过特定于请求的过滤器链运行请求。过滤器被虚线分隔的原因是过滤器可以在发送代理请求之前和之后运行逻辑。执行所有“pre”过滤器逻辑。然后进行代理请求。发出代理请求后,将运行“post”过滤器逻辑。 Filter在"pre"类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等, 在"post"类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。 核心逻辑:**路由转发+执行过滤器链** ### 12.4.入门配置 * 新建模块项目cloud-gateway-gateway9527 * pom依赖 ```xml org.springframework.cloud spring-cloud-starter-gateway org.springframework.cloud spring-cloud-starter-netflix-eureka-client com.wangjialu.learner cloud-api-commons 0.0.1 ``` * yml ```yaml server: port: 9527 spring: application: name: cloud-gateway-service cloud: gateway: routes: - id: payment_routh #uri: http://localhost:8001 uri: lb://cloud-payment-service predicates: - Path=/payment/getById/** - id: payment_routh2 #uri: http://localhost:8001 uri: lb://cloud-payment-service predicates: - Path=/payment/lb/** discovery: locator: enabled: true #开启从注册中心动态生成路由的功能,用微服务名进行路由 eureka: instance: hostname: cloud_gateway_service client: service-url: defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7002.com:7002/eureka/ register-with-eureka: true fetch-registry: true ``` * 启动类 ```java @SpringBootApplication @EnableEurekaClient public class Gateway9527Application { public static void main(String[] args) { SpringApplication.run(Gateway9527Application.class, args); } } ``` 有网关的项目落成,项目结构: >* cloud-api-comment 公共模块 >* cloud-eureka-server7002 注册中心 >* cloud-eureka-server7003 注册中心 >* cloud-gateway-gateway9527 网关 >* cloud-provider-payment9001 支付模块 >* cloud-provider-payment9002 支付模块 * 通过编码配置路由 以上是通过配置文件配置的路由,也可以通过RouteLocator实例编码实现,如: ```java @Configuration public class GatewayConfig { /** *

配置一个id为path_route_guonei的路由规则,当访问http://localhost:9527/guonei时会转发到http://news.baidu.com/guonei

* @param builder * @return */ @Bean public RouteLocator guoneiRouteLocator(RouteLocatorBuilder builder) { RouteLocatorBuilder.Builder routes = builder.routes(); routes.route("path_route_guonei", r -> r.path("/guonei").uri("http://news.baidu.com/guonei")).build(); return routes.build(); } @Bean public RouteLocator guojiRouteLocator(RouteLocatorBuilder builder) { RouteLocatorBuilder.Builder routes = builder.routes(); routes.route("path_route_guoji", r -> r.path("/guoji").uri("http://news.baidu.com/guoji")).build(); return routes.build(); } } ``` ### 12.5.通过服务名实现动态 配置文件yml里uri的协议lb,表示启用**Gateway的负载均衡**功能。 lb://serverName是spring cloud gatway在微服务中自动为我们创建的负载均衡uri。 ### 12.6.Predicate Gateway启动日志如下: ```shell 2021-06-24 10:24:55.535 INFO 4172 --- [ main] o.s.c.g.r.RouteDefinitionRouteLocator : Loaded RoutePredicateFactory [After] 2021-06-24 10:24:55.535 INFO 4172 --- [ main] o.s.c.g.r.RouteDefinitionRouteLocator : Loaded RoutePredicateFactory [Before] 2021-06-24 10:24:55.535 INFO 4172 --- [ main] o.s.c.g.r.RouteDefinitionRouteLocator : Loaded RoutePredicateFactory [Between] 2021-06-24 10:24:55.535 INFO 4172 --- [ main] o.s.c.g.r.RouteDefinitionRouteLocator : Loaded RoutePredicateFactory [Cookie] 2021-06-24 10:24:55.535 INFO 4172 --- [ main] o.s.c.g.r.RouteDefinitionRouteLocator : Loaded RoutePredicateFactory [Header] 2021-06-24 10:24:55.535 INFO 4172 --- [ main] o.s.c.g.r.RouteDefinitionRouteLocator : Loaded RoutePredicateFactory [Host] 2021-06-24 10:24:55.535 INFO 4172 --- [ main] o.s.c.g.r.RouteDefinitionRouteLocator : Loaded RoutePredicateFactory [Method] 2021-06-24 10:24:55.535 INFO 4172 --- [ main] o.s.c.g.r.RouteDefinitionRouteLocator : Loaded RoutePredicateFactory [Path] 2021-06-24 10:24:55.535 INFO 4172 --- [ main] o.s.c.g.r.RouteDefinitionRouteLocator : Loaded RoutePredicateFactory [Query] 2021-06-24 10:24:55.535 INFO 4172 --- [ main] o.s.c.g.r.RouteDefinitionRouteLocator : Loaded RoutePredicateFactory [ReadBodyPredicateFactory] 2021-06-24 10:24:55.535 INFO 4172 --- [ main] o.s.c.g.r.RouteDefinitionRouteLocator : Loaded RoutePredicateFactory [RemoteAddr] 2021-06-24 10:24:55.536 INFO 4172 --- [ main] o.s.c.g.r.RouteDefinitionRouteLocator : Loaded RoutePredicateFactory [Weight] 2021-06-24 10:24:55.536 INFO 4172 --- [ main] o.s.c.g.r.RouteDefinitionRouteLocator : Loaded RoutePredicateFactory [CloudFoundryRouteService] ``` #### 12.6.1.Predicate是什么 Spring Cloud Gateway将路由匹配作为Spring WebFlux HandlerMapping基础架构的一部分。 Spring Cloud Gateway包括许多内置的Route Predicate工厂。所有这些Predicate都与HTTP请求的不同属性匹配。多个Route Predicate厂可以进行组合。 Spring Cloud Gateway创建Route对象时,使用RoutePredicateFactory创建Predicate对象,Predicate对象可以赋值给 Route。Spring Cloud Gateway包含许多内置的Route Predicate Factories。 所有这些谓词都匹配HTTP请求的不同属性。多种谓词工厂可以组合,并通过逻辑and。 #### 12.6.2.常用RotePredicate ![RoutePredicateFactory](img/RoutePredicateFactory.png) * After Route Predicate * Before Route Predicate * Between Route Predicate * Cookie Route Predicate * Header Route Predicate * Host Route Predicate * Method Route Predicate * Path Route Predicate * Query Route Predicate * RemoteAddr Route Predicate * Weight Route Predicate 个别示例如下: ```yaml spring: application: name: cloud-gateway-service cloud: gateway: routes: - id: payment_routh #uri: http://localhost:8001 uri: lb://cloud-payment-service predicates: - Path=/payment/getById/** - id: payment_routh2 #uri: http://localhost:8001 uri: lb://cloud-payment-service predicates: - Path=/payment/lb/** - After=2021-06-24T13:26:00.908+08:00[Asia/Shanghai] #- Before=2021-06-25T13:26:00.908+08:00[Asia/Shanghai] #- Between=2021-06-24T13:26:00.908+08:00[Asia/Shanghai], 2021-06-25T13:26:00.908+08:00[Asia/Shanghai] #- Cookie=name, [0-9a-f]{32} # Cookie必须有name并且值为uuid(正则表达式) #- Header=token, [0-9a-f]{32} # Header必须有id并且值为uuid(正则表达式) - Host=local*,127.0.0.* # 多个用ant风格的逗号分割 - Method=GET # 必须为GET方法 - Query=id, \d+ # 要有参数id并且必须为数字 ``` ### 12.7.Filter的使用 #### 12.7.1.是什么 路由过滤器可以用于修改进入HTTP请求和返回HTTP响应,路由过滤器只能指定路由进行使用。 #### 12.7.2.Spring Cloud Gateway的Filter * 生命周期只有两个,pre和post * 种类只有两种(但是小分类很多) >* [GatewayFilter](https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#gatewayfilter-factories) >* [GlobalFilter](https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#global-filters) #### 12.7.3.实例 可以进行yml配置,也可以自定义实现。以下是自定义实现的简单的鉴权Filter: ```java @Component @Slf4j public class MyLogGatewayFilter implements GlobalFilter, Ordered { @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { log.info("****************come in MyLogGatewayFilter:" + LocalDateTime.now()); String token = exchange.getRequest().getQueryParams().getFirst("token"); if (StringUtils.isBlank(token)) { log.info("*************用户token非法"); exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); byte[] bytes = "{\"code\":\"401\",\"msg\":\"缺少参数token,没有权限\"}".getBytes(StandardCharsets.UTF_8); DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes); return exchange.getResponse().writeWith(Flux.just(buffer)); } return chain.filter(exchange); } @Override public int getOrder() { // Filter的顺序,值越小越优先,取值范围在Integer之内 return 0; } } ``` **【思考】** 有一个面试题,开发阶段可以通过可视化UI工具(例如swagger-ui)查看其它服务提供的接口,并能正常访问; 但为了保证接口的安全,在生产阶段向服务实际发送的整个请求体是加密的,怎么做? ## 13.SpringCloud Config分布式配置中心 ### 13.1.概述 * 是什么
![config-center](img/config-center.png)
Springcloud config为微服务架构中的微服务提供**集中化的外部配置支持**,配置服务器为各个不同微服务应用的所有环境提供一个中心化的外部配置。 * 能干嘛 > * 集中管理配置文件 > * 不同环境不同配置,动态化的配置更新,分环境部署比如dev/test/prod/beta/release > * 运行期间动态调整配置,不再需要字啊每个服务器的机器上编写配置文件,服务会向配置中心同意拉去配置自己的信息 > * 当配置发生变动时,服务不需要重启即可感知到配置的变化并应用新的配置 > * 将配置信息以REST接口的形式暴露 ### 13.2.Config服务端配置与测试 参考cloud-config-center-3344(略) ### 13.3.Config客户端配置与测试 bootstrap.yml是什么 > applicaion.yml是用户级的资源配置项,bootstrap.yml是系统级的,**优先级更高**。 > > Spring Cloud会创建一个"Bootstrap Context”,作为Spring应用的Application Context的父上下文。初始化的时候,"Bootstrap > Context'负责从**外部源**加载配置属性并解析配置。这两个士下文共享一个从外部获取的"Environment'。 > > "Bootstrap属性有高优先级,默认情况下,它们不会被本地配置覆盖。‘"Bootstrap context和Application Context有着不同的约定 > 所以新增了一个bootstrap.yml文件,保证Bootstrap Context和Application Context配置的分离。 > > **要将Client模块下的application.yml文件改为bootstrap.yml,这是很关键的**, > 因为bootstrap.yml是比application.yml先加载的。bootstrap.yml优先级高于application.yml config客户端项目cloud-config-client-3355创建过程略。整个Config项目结构为: >* cloud-api-comment 公共模块 >* cloud-eureka-server7002 注册中心 >* cloud-eureka-server7003 注册中心 >* cloud-config-center-3344 config服务端 >* cloud-config-client-3355 config客户端 ### 13.4.Config客户端动态刷新 上边存在的问题是git上更新了配置文件,config-server可以动态刷新到新配置数据,但是config-client无法获取最新配置数据。 解决办法: > * config-client需要依赖actuator监控 > * yml里增加监控端点 >```yaml ># 暴漏监控端点 >management: > endpoints: > web: > exposure: > include: "*" >``` > * Controller类都得增加注解@RefreshScope > * 需要对config-client地址"**/actuator/refresh**"发送POST请求 仍然存在的问题是需要手动刷新,能否**广播通知**?请看下节:消息总线。 ## 14.SpringCloud Bus消息总线 ### 14.1.概述 #### 14.1.1.是什么 [官方文档](https://www.springcloud.cc/spring-cloud-bus.html) Spring Cloud Bus将分布式系统的节点与轻量级消息代理链接。这可以用于广播状态更改(例如配置更改)或其他管理指令。一个关键的想法是,Bus就像一个扩展的Spring Boot应用程序的分布式执行器,但也可以用作应用程序之间的通信渠道。当前唯一的实现是使用AMQP代理作为传输,但是相同的基本功能集(还有一些取决于传输)在其他传输的路线图上。 一言以蔽之,分布式自动刷新配置功能。SpringCloud Bus配合Springcloud Config使用可以实现配置的动态刷新。 - 刷新配置文件
![刷新配置文件](img/刷新配置文件.png)
Bus支持两种消息代理:RabbitMQ和kafka #### 14.1.2.能干嘛 - SpringCloudBus的作用
![SpringCloudBus的作用](img/SpringCloudBus的作用.png)
Spring Cloud Bus能管理和传播分布式系统间的消息,就像一个分布式执行器, 可用于广播状态更改、事件推送等,也可以当作微服务间的通信通道 #### 14.1.3.为何被称为总线 什么是总线 >在微服务架构的系统中,通常会使用轻量级的消息代理来构建一个公用的消息主题,并让系统中的所有微服务实例都连接上来。由于该主题中产生的消息会被所有实例监听和消费,所以称为消息总线。在总线上的各个实例,都可以方便地广播一些需要让其他链接在该主题行的实例都知道的消息。 基本原理 >ConfigClient实例都监听MQ中的同一个topic(默认是SpringcloudBus)。当一个服务刷新数据的时候,它会把这个信息放入到topic中,这样其它监听同一个topic的服务就能得到通知,然后去更新自身的配置。 ### 14.2.搭建 * Config-Server端,复制项目cloud-config-center-3344为cloud-config-center-3345, 修改相应端口号,添加spring-cloud-starter-bus-amqp依赖 ```xml org.springframework.cloud spring-cloud-starter-bus-amqp ``` * cloud-config-center-3345的yml增加 ```yaml spring: rabbitmq: host: localhost port: 5672 username: guest password: guest management: endpoints: web: exposure: include: 'bus-refresh' ``` * Config-Client端,复制项目cloud-config-client-3355为cloud-config-client-3356和cloud-config-client-3357, 修改相应端口号,都添加spring-cloud-starter-bus-amqp依赖 ```xml org.springframework.cloud spring-cloud-starter-bus-amqp ``` * cloud-config-client-3356和cloud-config-client-3357都在yml添加 ```yaml spring: rabbitmq: host: localhost port: 5672 username: guest password: guest ``` * 项目搭建完成,项目结构: >* cloud-api-comment 公共模块 >* cloud-eureka-server7002 注册中心 >* cloud-eureka-server7003 注册中心 >* cloud-config-center-3345 config服务端 >* cloud-config-client-3356 config客户端 >* cloud-config-client-3357 config客户端 * 测试 远程修改配置文件,向Config-Server发送POST请求查看Config-Client读到的配置文件是否发生相应改变: ```shell curl -X POST "http://localhost:3345/actuator/bus-refresh" ``` * 指定具体某一实例生效而不是全部 添加具体实例地址(ip:port),如: ```shell curl -X POST "http://localhost:3345/actuator/bus-refresh/config-clent:3355" ``` ## 15.SpringCloud Stream消息驱动 ### 15.1.概述 * 是什么 > 官方定义Spring Cloud Stream是一个构建消息驱动微服务的框架。 > 一句话:屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型 > Stream中的消息通讯方式遵循了发布-订阅模式,Topic主题进行广播:在RabbitMQ就是Exchange,在Kafka中就是Topic. * SpringCloud Stream标准流程套路 ![SpringCloudStream1](img/SpringCloudStream1.png) ![SpringCloudStream2](img/SpringCloudStream2.png) >* **Binder**:很方便的连接中间件,屏蔽差异 >* **Channel**:通道,是队列Queue的一种抽象,在消息通讯系统中就是实现存储和转发的媒介,通过channel对队列进行配置。 >* **Source**和**Sink**:简单的可理解为参照对象是Springcloud Stream自身,从Stream发布消息就是输出,接受消息就是输入。 * 编码API和常用注解 | 组成 | 说明 | | :------- | :--------- | | Middleware | 中间件,目前只支持RabbitMQ和Kafka | | Binder | Binder是应用与消息中间件之间的封装,目前实行了Kafka和RabbitMQ的Binder,通过Binder可以很方便的连接中间件,可以动态的改变消息类型(对应于Kafka的topic,RabbitMQ的exchange),这些都可以通过配置文件来实现 | | @Input | 注解标识输入通道,通过该输入通道接收到的消息进入应用程序 | | @Output | 注解标识输出通道,发布的消息将通过该通道离开应用程序 | | @StreamListener | 监听队列,用于消费者的队列的消息接收 | | @EnableBinding | 指信道channeI和exchange绑定在一起 | ### 15.2.项目搭建 (略) 项目结构: >* cloud-api-comment 公共模块 >* cloud-eureka-server7002 注册中心 >* cloud-eureka-server7003 注册中心 >* cloud-stream-rabbitmq-provider8801 消息提供者 >* cloud-stream-rabbitmq-consumer8802 消息消费者 >* cloud-stream-rabbitmq-consumer8802 消息消费者 ### 15.3.重复消费 消费者存在对同一消息的重复消费,为了避免消息被重复消费,要对消息的消费者进行分组,分组后同组只有一个消费者能够消费消息。 ### 15.4.消费持久化 消息在默认情况下(未分组)它都属于一种临时消息,当消费端由于宕机、网络中断等因素导致无法接收消息时,消息中间件会保留未消费的消息。 当已分组的消费者重新提供接入时,会拿到未消费的消息进行消费;未分组的消费者拿不到消息。 消息中间件在服务期间没有注册过已分组消费者,那么不会持久化消息,即使有分组消费者接入也收不到未消费的消息。 ## 16.SpringCloud Sleuth分布式链路跟踪 ### 16.1.概述 在微服务框架中,-个由客户端发起的请求在后端系统中会经过多个不同的的服务节点调用来协同产生最后的请求结果,每一个前段 请求都会形成一 复杂的分布式服务调用链路,链路中的任何- 环出现高延时或错误都会引起整个请求最后的失败。 * 是什么 > SpringCloud Sleuth提供了一套完整的服务跟踪的解决方案,在分布式系统中提供追踪解决方案并且兼容支持了zipkin * zipkin [下载](https://dl.bintray.com/openzipkin/maven/io/zipkin/java/zipkin-server/zipkin-server-2.12.9-exec.jar) 运行 ```shell java -jar zipkin-server-2.12.9-exec.jar ``` 查看http://localhost:9411/zipkin 概念 >* Trace:类似于树结构的Span集合,表示一条调用链路,存在唯一标识 >* span:表示调用链路来源,通俗的理解span就是一次请求信息 - 表示请求链路,一条链路通过Trace ld唯一标识, Span标识发起的请求信息,各span通过parent id关联起来。
![zipkin](img/zipkin1.png)
- 简化图
![zipkin](img/zipkin2.png)
测试 > 在`简单的RestTemplate远程调用`项目中消费和支付模块POM里都加入zipkin依赖,yml里都添加配置 ```xml org.springframework.cloud spring-cloud-starter-zipkin ``` ```yaml spring: zipkin: base-url: http://localhost:9411 sleuth: sampler: probability: 1 # 采样率在0~1之间,1表示全部采样 ``` 请求发生后可在http://localhost:9411/zipkin查看调用的链路跟踪。 SpringCloud Alibaba 请参考SpringCloudAlibaba.md