1.1. 第一章 服务-知识点

1.1.1. 1、Restful、SOAP、RPC、SOA

【1】什么是Restful?

​ Restful是一种架构设计风格,提供了设计原则和约束条件,而不是架构,而满足这些约束条件和原则的应用程序或设计就是 Restful架构或服务。==【面向资源】【请求动作】==

​ 主要的设计原则:

(1)资源与URI:get+/user/1---->/findUserById (2)统一资源接口(HTTP方法如GET,PUT和POST) (3)资源的表述:get+/user/1==》我们以get方式去查询user的信息系 (4)状态的转移 get+/user/1,delete+/user/1

​ 总之,RESTful的核心就是后端将资源发布为URI,前端通过URI访问资源,并通过HTTP动词表示要对不同资源进行的操作。

【2】什么是SOAP?

​ SOAP(简单对象访问协议)是一种数据交换协议规范,是一种轻量的、简单的、基于XML的协议的规范。SOAP协议和HTTP协议一样,都是底层的通信协议,只是请求包的格式不同而已,SOAP包是XML格式的。

​ SOAP的消息是基于xml并封装成了符合http协议,因此,它符合任何路由器、 防火墙或代理服务器的要求。

​ SOAP可以使用任何语言来完成,只要发送正确的soap请求即可,基于soap的服务可以在任何平台无需修改即可正常使用。

【3】什么是RPC?

​ RPC:从一台机器(客户端)上通过参数传递的方式调用另一台机器(服务器)上的一个函数或方法(可以统称为服务)并得到返回的结果。

​ RPC 会隐藏底层的通讯细节(不需要直接处理Socket通讯或Http通讯)

​ RPC 是一个请求响应模型:==客户端发起请求,服务器返回响应(类似于Http的工作方式)

​ RPC 在使用形式上像调用本地函数(或方法)一样去调用远程的函数(或方法)。

5种典型RPC远程调用框架:

(1)RMI实现:利用java.rmi包实现,基于Java远程方法协议(Java Remote Method Protocol)和java的原生序列化。 (2)Hessian:是一个轻量级的remoting onhttp工具,使用简单的方法提供了RMI的功能。 基于HTTP协议,采用二进制编解码。【文件上传】 (3)thrift:是一种可伸缩的跨语言服务的软件框架。thrift允许你定义一个描述文件,描述数据类型和服务接口。依据该文件,编译器方便地生成RPC客户端和服务器通信代码。 (4)dubbo:阿里的RPC框架特性

连接个数:单连接
连接方式:长连接
传输协议:TCP
传输方式:NIO异步传输
适用范围:传入传出参数数据包较小(建议小于100K),消费者比提供者个数多,单一消费者无法压满提供者,尽量不要用dubbo协议传输大文件或超大字符串。
适用场景:常规远程服务方法调用

(5)SpringCloud:框架微服务全家桶。为开发人员提供了快速构建分布式系统的一些工具,包括配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等。

​ 微服务在本质上,就是rpc。rpc有基于tcp的,http的,mq的等等。spring cloud是基于spring boot的,spring boot 实现的是http协议的rpc,算是rpc的一个子集。

【4】什么是SOA?

​ SOA(Service-Oriented Architecture),中文全称:面向服务的架构。 通俗点来讲,SOA提倡将不同应用程序的业务功能封装成“服务”并独立部署起来,通常以接口和契约的形式暴露并提供给外界应用访问,达到不同系统可重用的目的,SOA是一个组件模型,它能将不同的服务通过定义良好的接口和契约联系起来。服务是SOA的基石。

微服务是SOA架构演进的结果。两者说到底都是对外提供接口的一种架构设计方式,随着互联网的发展,复杂的平台、业务的出现,导致SOA架构向更细粒度、更加精准通用化,就成了所谓的微服务了。

​ SOA与微服务的区别在于如下几个方面:

(1)微服务相比于SOA更加精细,微服务更多的以独立的进程的方式存在,互相之间并无影响; (2)微服务提供的接口方式更加通用化,例如HTTP RESTful方式,各种终端都可以调用,无关语言、平台限制; (3)微服务更倾向于分布式去中心化的部署方式,在互联网业务场景下更适合。

​ 技术为业务而生,架构也为业务而出现,当然SOA和微服务也是因为业务的发展而出现。出现SOA和微服务框架与业务的发展、平台的壮大密不可分,下面借用dubbo的网站架构发展图和说明

img

单一应用架构

​ 当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。 ​ 此时,用于简化增删改查工作量的 数据访问框架(ORM) 是关键。

垂直应用架构

​ 当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。 ​ 此时,用于加速前端页面开发的 Web框架(MVC) 是关键。

分布式服务架构

​ 当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。 ​ 此时,用于提高业务复用及整合的 分布式服务框架(RPC) 是关键。

流动计算架构

​ 当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。 ​ 此时,用于提高机器利用率的资源调度和治理中心(SOA) 是关键。

​ 平台随着业务的发展从 All in One 环境就可以满足业务需求(以Java来说,可能只是一两个war包就解决了),发展到需要拆分多个应用,并且采用MVC的方式分离前后端,加快开发效率;在发展到服务越来越多,不得不将一些核心或共用的服务拆分出来,其实发展到此阶段,如果服务拆分的足够精细,并且独立运行,我觉得就可以将之理解为一个微服务了。

1.1.2. 2、什么是微服务

​ 在介绍微服务时,首先得先理解什么是微服务,顾名思义,微服务得从两个方面去理解,什么是"微"、什么是"服务", 微 狭义来讲就是体积小、著名的"2 pizza 团队"很好的诠释了这一解释(2 pizza 团队最早是亚马逊 CEO Bezos提出来的,意思是说单个服务的设计,所有参与人从设计、开发、测试、运维所有人加起来 只需要2个披萨就够了 )。 而所谓服务,一定要区别于系统,服务一个或者一组相对较小且独立的功能单元,是用户可以感知最小功能集

​ 微服务可以按照业务功能本身的独立性来划分,如果系统提供的业务是非常底层的,如:操作系统内核、存储系统、网络系统、数据库系统等等,这类系统都偏底层,功能和功能之间有着紧密的配合关系,如果强制拆分为较小的服务单元,会让集成工作量急剧上升,并且这种人为的切割无法带来业务上的真正的隔离,所以无法做到独立部署和运行,也就不适合做成微服务了。

能不能做成微服务,取决于四个要素:

  • 小:微服务体积小,2 pizza 团队。
  • 独:能够独立的部署和运行。
  • 轻:使用轻量级的通信机制和架构。
  • 松:为服务之间是松耦合的。

微服务的特点:

  • 单一职责:微服务中每一个服务都对应唯一的业务能力,做到单一职责
  • 微:微服务的服务拆分粒度很小,例如一个用户管理就可以作为一个服务。每个服务虽小,但“五脏俱全”。
  • 面向服务:面向服务是说每个服务都要对外暴露Rest风格服务接口API。并不关心服务的技术实现,做到与平台和语言无关,也不限定用什么技术实现,只要提供Rest的接口即可。
  • 自治:自治是说服务间互相独立,互不干扰
    • 团队独立:每个服务都是一个独立的开发团队,人数不能过多。
    • 技术独立:因为是面向服务,提供Rest接口,使用什么技术没有别人干涉
    • 前后端分离:采用前后端分离开发,提供统一Rest接口,后端不用再为PC、移动段开发不同接口
    • 数据库分离:每个服务都使用自己的数据源
    • 部署独立,服务间虽然有调用,但要做到服务重启不影响其它服务。有利于持续集成和持续交付。每个服务都是独立的组件,可复用,可替换,降低耦合,易维护

1.1.3. 3、SpringCloud架构体系

image-20201203173118004

体系中的核心组件:

  • 服务发现——Netflix Eureka
  • 客服端负载均衡——Netflix Ribbon、OpenFeign
  • 断路器——Netflix Hystrix
  • 服务网关——Gateway
  • 分布式配置——Spring Cloud Config

1.1.4. 4、SpringCloud和Dubbo对比

或许很多人会说Spring Cloud和Dubbo的对比有点不公平,Dubbo只是实现了服务治理,而Spring Cloud下面有33个子项目(可能还会新增)分别覆盖了微服务架构下的方方面面,服务治理只是其中的一个方面,一定程度来说,Dubbo只是Spring Cloud Netflix中的一个子集。
Dubbo【dubbo协议】 Spring Cloud【http协议】
服务注册中心 Zookeeper/nacos Spring Cloud Netflix Eureka/nacos
服务调用方式 遵循dubbo协议的RPC 遵循http协议的REST API
服务网关 Spring Cloud gateway
熔断器 不完善【在alibaba-Alibaba Sentinel Spring Cloud Netflix Hystrix
分布式配置 nacos Spring Cloud Config
服务跟踪 Spring Cloud Sleuth
消息总线 nacos Spring Cloud Bus
数据流 Spring Cloud Stream
批量任务 Spring Cloud Task
…… …… ……
  • 一致性(Consistency) (所有节点在同一时间具有相同的数据)
  • 可用性(Availability) (保证每个请求不管成功或者失败都有响应)
  • 分隔容忍(Partition tolerance) (系统中任意信息的丢失或失败不会影响系统的继续运作)

image-20201215100854298

1.2. 第二章 SpringCloud核心组件

1.2.1. 1、Netflix Eureka

【1】工作图解

  • 作用:实现服务治理(服务注册与发现)
  • 简介:Spring Cloud Eureka是Spring Cloud Netflix项目下的服务治理模块。
  • 由两个组件组成:Eureka 服务端和 Eureka 客户端。
    • Eureka 服务端,用作服务注册中心,支持集群部署。
    • Eureka 客户端,是一个 Java 客户端,用来处理服务注册与发现。

img

删除二级缓存:

​ Eureka Client 发送 register和 renew、cancel 请求并更新 registry 注册表之后,删除二级缓存;

​ Eureka Server 自身的 Evict Task 剔除服务后,删除二级缓存;

​ 二级缓存本身设置了 guava 的失效机制,隔一段时间后自己自动失效;

加载二级缓存:

​ Eureka Client 发送 getRegistry 请求后,如果二级缓存中没有,就触发 guava的 load,即从 registry 中获取原始服务信息后进 行处理加工,再加载到二级缓存中。

​ Eureka Server 更新一级缓存的时候,如果二级缓存没有数据,也会触发 guava 的 load。

更新一级缓存:

​ Eureka Server 内置了一个 TimerTask,定时将二级缓存中的数据同步到一级缓存(这个动作包括了删除和加载)

image-20201203173118004

服务提供者

  • 启动后,向注册中心发起 register 请求,注册服务
  • 在运行过程中,定时向注册中心发送 renew 心跳,证明“我还活着”。
  • 停止服务提供者,向注册中心发起 cancel 请求,清空当前服务注册信息。

服务消费者

​ 启动后,从注册中心拉取服务注册信息

​ 在运行过程中,定时更新服务注册信息。

注册中心

​ 启动后,从其他节点同步服务注册信息。

​ 运行过程中,定时运行 evict 任务,剔除没有按时 renew 的服务(包括非正常停止和网络故障的服务)。

​ 运行过程中,接收到的 register、renew、cancel ,getRegister请求,都会同步至其他注册中心节点。

【2】POM依赖

eureka-server:

<!--eureka服务端依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

eureka-client:

<!--eureka客户端端依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

【3】配置方式

【3.1】eureka-servie单节点

image-20201204144459852

详见案例:eureka-service-singleton

pom依赖

<!--eureka服务端依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

application.yml文件:

server:
  port: 8080
  tomcat:
    max-threads: 400
    accept-count: 500
  connection-timeout: 60000
spring:
  application:
    #服务名称
    name: eureka-service-singleton
eureka:
  instance:
    #IP地址
    ip-address: 127.0.0.1
    #启用IP地址方式实例化
    prefer-ip-address: true
    #默认:90s
    #标识eureka server至上一次收到client的心跳之后,等待下一次心跳的超时时间,
    #太大:可能将流量转发过去的时候,该instance已经不存活了
    #太小:instance则很可能因为临时的网络抖动而被摘除掉
    lease-expiration-duration-in-seconds: 45
    #默认:30s
    #表示eureka client发送心跳给server端的频率
    lease-renewal-interval-in-seconds: 15
  client:
    #要不要去注册中心获取其他服务的地址
    fetch-registry: false
    #自己就是注册中心,不用注册自己
    register-with-eureka: false
    #服务地址
    service-url:
      defaultZone:  http://${eureka.instance.ip-address}:${server.port}/eureka/
  server:
    # 指定每分钟需要收到的续约次数的阈值,默认值就是:0.85
    renewal-percent-threshold: 0.8
    #enable-self-preservation: false
    #续期时间,即扫描失效服务的间隔时间(缺省为60*1000ms)
    eviction-interval-timer-in-ms: 30000

启动配置:

package com.itheima.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

/**
 * @ClassName EurekaServerStart.java
 * @Description eureka服务端启动类
 */
@SpringBootApplication
//启动服务端
@EnableEurekaServer
public class EurekaClusterServerStart {

    public static void main(String[] args) {
        SpringApplication.run(EurekaClusterServerStart.class);
    }
}
【3.2】eureka-servie-高可用

image-20201204113639247

详见案例:eureka-service-cluster

pom依赖

<!--eureka服务端依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

本地hostnam

打开C:\Windows\System32\drivers\etc,编辑hosts

127.0.0.1 eureka1
127.0.0.1 eureka2

application.yml文件:

spring:
  profiles:
    active: 8082 #启动不同项目的时候手动切换

application-8081.yml

这里把eureka1注册eureka2

server:
  port: 8081
eureka:
  instance:
    hostname: eureka1
    #默认:90s
    #标识eureka server至上一次收到client的心跳之后,等待下一次心跳的超时时间,
    #太大:可能将流量转发过去的时候,该instance已经不存活了
    #太小:instance则很可能因为临时的网络抖动而被摘除掉
    lease-expiration-duration-in-seconds: 45
    #默认:30s
    #表示eureka client发送心跳给server端的频率
    lease-renewal-interval-in-seconds: 15
  client:
    #要不要去注册中心获取其他服务的地址
    fetch-registry: false
    #自己就是注册中心,不用注册自己
    register-with-eureka: false
    #服务地址
    service-url:
      # 多个地址使用","分割,
      defaultZone:  http://eureka2:8082/eureka/
  server:
    # 指定每分钟需要收到的续约次数的阈值,默认值就是:0.85
    renewal-percent-threshold: 0.8
    #enable-self-preservation: false
    #续期时间,即扫描失效服务的间隔时间(缺省为60*1000ms)
    eviction-interval-timer-in-ms: 30000

application-8082.yml

这里把eureka2注册eureka1

server:
  port: 8082
eureka:
  instance:
    hostname: eureka2
    #默认:90s
    #标识eureka server至上一次收到client的心跳之后,等待下一次心跳的超时时间,
    #太大:可能将流量转发过去的时候,该instance已经不存活了
    #太小:instance则很可能因为临时的网络抖动而被摘除掉
    lease-expiration-duration-in-seconds: 45
    #默认:30s
    #表示eureka client发送心跳给server端的频率
    lease-renewal-interval-in-seconds: 15
  client:
    #要不要去注册中心获取其他服务的地址
    fetch-registry: false
    #自己就是注册中心,不用注册自己
    register-with-eureka: false
    #服务地址
    service-url:
      # 多个地址使用","分割
      defaultZone:  http://eureka1:8081/eureka/
  server:
    # 指定每分钟需要收到的续约次数的阈值,默认值就是:0.85
    renewal-percent-threshold: 0.8
    #enable-self-preservation: false
    #续期时间,即扫描失效服务的间隔时间(缺省为60*1000ms)
    eviction-interval-timer-in-ms: 30000

启动配置:

package com.itheima.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

/**
 * @ClassName EurekaServerStart.java
 * @Description eureka服务端启动类
 */
@SpringBootApplication
//启动服务端
@EnableEurekaServer
public class EurekaClusterServerStart {

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

访问http://127.0.0.1:8081/,发下DS中有个eureka2

image-20201205222151937

【3.3】eureka-client注册

​ 以module-user-service模块为例,把它注册到高可用eureka集群中,让他提供user的服务

pom依赖

module-user-service的pom中添加

<!--eureka-client支持-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

application.yml

module-user-service的application.yml中添加:

#注册中心
eureka:
  client:
    service-url:
      #这里会把模块注入到eureka1,eureka2
      defaultZone: http://eureka2:8082/eureka/,http://eureka1:8081/eureka/
    #表示eureka client间隔多久去拉取服务注册信息,默认为30秒,
    #对于api-gateway,如果要迅速获取服务注册状态,可以缩小该值,比如5秒
    registry-fetch-interval-seconds: 15

启动配置

package com.itheima.springcloud;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@Slf4j
//开启eureka的服务端
@EnableEurekaClient
public class UserServiceStart {

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

}

image-20201205224942651

【4】核心知识点

【4.1】服务注册

​ 服务提供者、服务消费者、以及服务注册中心自己,启动后都会向注册中心注册服务(如果配置了注册)。下图是介绍如何完成服务注册的:

img

注册中心【eureka-service】服务接收到 register 请求后:

保存服务信息,将服务信息保存到 registry 中;

更新队列,将此事件添加到更新队列中,供 Eureka Client 增量同步服务信息使用。

清空二级缓存,即 readWriteCacheMap,用于保证数据的一致性,将此事件同步至其他的 Eureka Server 节点。

相关配置

#注册中心
eureka:
  client:
    service-url:
      defaultZone: http://eureka2:8082/eureka/,http://eureka1:8081/eureka/
    #表示eureka client间隔多久去拉取服务注册信息,默认为30秒,
    #对于api-gateway,如果要迅速获取服务注册状态,可以缩小该值,比如5秒
    registry-fetch-interval-seconds: 15
【4.2】服务续约

img

服务注册后,要定时(默认 30S,可自己配置)向注册中心发送续约请求,告诉注册中心“我还活着”。

注册中心收到续约请求后:

更新服务对象的最近续约时间,即 Lease 对象的 lastUpdateTimestamp【0->45】

同步服务信息,将此事件同步至其他的 Eureka Server 节点。

剔除服务之前会先判断服务是否已经过期,判断服务是否过期的条件之一是续约时间和当前时间的差值是不是大于阈值。

相关配置

eureka:
  instance:
    hostname: eureka1
    #默认:90s
    #标识eureka server至上一次收到client的心跳之后,等待下一次心跳的超时时间,
    #太大:可能将流量转发过去的时候,该instance已经不存活了
    #太小:instance则很可能因为临时的网络抖动而被摘除掉
    lease-expiration-duration-in-seconds: 45
    #默认:30s
    #表示eureka client发送心跳给server端的频率
    lease-renewal-interval-in-seconds: 15
【4.3】服务剔除

img

Eureka Server 提供了服务剔除的机制,用于剔除没有正常下线的服务。

服务正常停止之前会向注册中心发送注销请求,告诉注册中心“我要下线了”。

注册中心服务接收到 cancel 请求后:

删除服务信息,将服务信息从 registry 注册表中删除;

更新队列,将此事件添加到更新队列中,供 Eureka Client 增量同步服务信息使用。

清空二级缓存,即 readWriteCacheMap,用于保证数据的一致性。

更新阈值,供剔除服务使用。

同步服务信息,将此事件同步至其他的 Eureka Server 节点。

服务正常停止才会发送 Cancel,如果是非正常停止,则不会发送,此服务由 Eureka Server 主动剔除。

eureka:
  server:
    # 指定每分钟需要收到的续约次数的阈值,默认值就是:0.85
    renewal-percent-threshold: 0.8
    #enable-self-preservation: false
    #续期时间,即扫描失效服务的间隔时间(缺省为60*1000ms)
    eviction-interval-timer-in-ms: 30000
【4.4】保护机制

img

1.2.2. 2、Netflix Ribbon

【1】工作图解

作用:主要提供客服端负载均衡

简介:Spring Cloud Ribbon 是一个基于 HTTP 和 TCP 的客户端负载均衡工具,它基于 Netflix Ribbon 实现。通过 Spring Cloud 的封装,可以让我们轻松地将面向服务的 REST 模版请求自动转换成客户端负载均衡的服务调用。

Ribbon 原理

【2】POM依赖

<!--ribbon依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

【3】配置方式

全局配置:

ribbon:
  #    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #配置规则 随机
  #    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule #配置规则 轮询
  #    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RetryRule #配置规则 重试
  #    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule #配置规则 响应时间权重
  NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
  #以下的配置需要配合fegin使用
  #ConnectTimeout: 1000 #请求连接超时时间
  #ReadTimeout: 1000 #请求处理的超时时间
  #OkToRetryOnAllOperations: true #对所有请求都进行重试
  #MaxAutoRetriesNextServer: 0 #切换实例的重试次数
  #MaxAutoRetries: 1 #对当前实例的重试次数

指定服务配置:

# 指定设置module-user-service 表示作用到哪个微服务
module-user-service:
  ribbon:
#    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #配置规则 随机
#    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule #配置规则 轮询
#    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RetryRule #配置规则 重试
#    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule #配置规则 响应时间权重
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
    #以下的配置需要配合fegin使用
#    ConnectTimeout: 500 #请求连接超时时间
#    ReadTimeout: 500 #请求处理的超时时间
#    OkToRetryOnAllOperations: true #对所有请求都进行重试
#    MaxAutoRetriesNextServer: 2 #切换实例的重试次数
#    MaxAutoRetries: 3 #对当前实例的重试次数

有的博客说需要开启loadbalancer,这里验证默认是开启的

spring:
  application:
    name: module-user-ribbon
  cloud:
    loadbalancer:
      retry:
        enabled: true #开启重试

image-20201206154519911

hystrix与ribbon组合使用的时候,需要注意execution.isolation.thread.timeoutInMilliseconds的时间要大于ribbon的处理时间

hystrix:
  command:
    default:
      execution.isolation.thread.timeoutInMilliseconds: 5000 # 请求5S超时后,执行熔断方法

启动配置

package com.itheima.springcloud;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RoundRobinRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

/***
 * @description UserRibbonStart
 * @return
 */
@SpringBootApplication
@EnableEurekaClient
public class UserRibbonStart {

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

    @Bean
    //提供负载均衡的功能
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

    @Bean
    public IRule ribbonRule() {
        return new RoundRobinRule();//这里配置策略,和配置文件对应
    }
}

使用方式:

package com.itheima.springcloud.service;

import com.itheima.springcloud.basic.ResponseWrap;
import com.itheima.springcloud.exception.ProjectException;
import com.itheima.springcloud.req.UserVo;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.client.RestTemplate;

import java.util.Date;

/**
 * @ClassName UserController.java
 * @Description 用户controller
 */
@Service
public class UserRibbonService {

    @Autowired
    RestTemplate restTemplate;

    private static String BASIC_URL ="http://module-user-service";


    /***
     * @description 注册用户
     * @param userVo 注册信息
     * @return: java.lang.Boolean
     */
    public ResponseWrap<Boolean> registerUser(UserVo userVo) throws ProjectException {
        return restTemplate.postForObject(BASIC_URL+"/user/register",
                                                userVo,
                                                ResponseWrap.class);
    }

   .....
}

【4】核心知识点

【4.1】负载均衡策略

Ribbon作为后端负载均衡器,比Nginx更注重的是承担并发而不是请求分发,可以直接感知后台动态变化来指定分发策略。它一共提供了7种负载均衡策略:

策略名 策略声明 策略描述 实现说明
BestAvailableRule public class BestAvailableRule extends ClientConfigEnabledRoundRobinRule 选择一个最小的并发请求的server 逐个考察Server,如果Server被tripped了,则忽略,在选择其中ActiveRequestsCount最小的server
AvailabilityFilteringRule public class AvailabilityFilteringRule extends PredicateBasedRule 过滤掉那些因为一直连接失败的被标记为circuit tripped的后端server,并过滤掉那些高并发的的后端server(active connections 超过配置的阈值) 使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就就是检查status里记录的各个server的运行状态
WeightedResponseTimeRule public class WeightedResponseTimeRule extends RoundRobinRule 根据响应时间分配一个weight,响应时间越长,weight越小,被选中的可能性越低。 一个后台线程定期的从status里面读取评价响应时间,为每个server计算一个weight。Weight的计算也比较简单responsetime 减去每个server自己平均的responsetime是server的权重。当刚开始运行,没有形成status时,使用roubine策略选择server。
RetryRule public class RetryRule extends AbstractLoadBalancerRule 对选定的负载均衡策略机上重试机制。 在一个配置时间段内当选择server不成功,则一直尝试使用subRule的方式选择一个可用的server
RoundRobinRule public class RoundRobinRule extends AbstractLoadBalancerRule roundRobin方式轮询选择server 轮询index,选择index对应位置的server
RandomRule public class RandomRule extends AbstractLoadBalancerRule 随机选择一个server 在index上随机,选择index对应位置的server
ZoneAvoidanceRule public class ZoneAvoidanceRule extends PredicateBasedRule 复合判断server所在区域的性能和server的可用性选择server 使用ZoneAvoidancePredicate和AvailabilityPredicate来判断是否选择某个server,前一个判断判定一个zone的运行性能是否可用,剔除不可用的zone(的所有server),AvailabilityPredicate用于过滤掉连接数过多的Server。

yml文件配置:

# 指定设置名称为module-user-service 服务
module-user-service:
  ribbon:
#    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #配置规则 随机
#    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule #配置规则 轮询
#    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RetryRule #配置规则 重试
#    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule #配置规则 响应时间权重
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

启动类:

    @Bean
    @LoadBalanced
    RestTemplate restTemplate() {
        return new RestTemplate();
    }

    @Bean
    public IRule ribbonRule() {
        return new RandomRule();//这里配置策略,和配置文件对应
    }

一定记得为IRule指定yml文件中选择的配置,很多文章没有。里面配具体的策略。

【4.2】请求流程源码

Ribbon是一个客户端负载均衡软件,通过注册到Eureka上的服务名,获取服务列表,缓存到本地,选择负载均衡算法,发送http请求。 在spring cloud可以通过简单配置,即可完成客户端负载均衡,用法如下:配置,注入,请求

LoadBalancerInterceptor

作用:对负载服务的拦截处理,用户发起请求时候他会处理3件事情:

  • 获得请求的路径:request.getURI()
  • 获得目标服务:originalUri.getHost()
  • 执行调用:this.loadBalancer.execute

image-20201206232026607

LoadBalancerClient

作用:执行目标请求

点击this.loadBalancer.execute进入LoadBalancerClient

public interface LoadBalancerClient extends ServiceInstanceChooser {
    <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
    /**
     * 使用来自LoadBalancer的ServiceInstance,对起执行请求
     * @param serviceId
     * @param 服务实例
     * @param Request。允许在执行前后添加metric
     * @return 回调结果
     */
    <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;
    /**
     * 创建一个真正的URL包含主机和端口:
     * http://myservice/path/to/service --> http://host:port/path/to/service
     * @param 服务实例
     * @param 源url,是一个包含serviceId或者dns的URL
     * @return 重新构造的URL
     */
    URI reconstructURI(ServiceInstance instance, URI original);
}

RibbonLoadBalancerClient

作用:是LoadBalancerClient的实现,进行追踪源码:

image-20201206233005133

继续追踪,ILoadBalancer是一个方法重要的接口

image-20201207093121410

ILoadBalancer

public interface ILoadBalancer {
    void addServers(List<Server> var1); // 初始化服务列表
    Server chooseServer(Object var1);    // 从列表中选择一个服务
    void markServerDown(Server var1);    // 标记服务为down
    List<Server> getReachableServers();    // 获取up和reachable的服务
    List<Server> getAllServers();        //获取所有服务(reachable and unreachable)
}
【4.3】负载均衡源码

在刚才的代码中,可以看到获取服务使通过一个getServer方法:

image-20201207093121410

进入到loadBalancer.chooseServer方法image-20201207093301113

我们继续跟入:

继续跟踪源码chooseServer方法,发现这么一段代码:

image-20201207093510873

我们看看这个rule是谁:

image-20201207093628658

这里的rule默认值是一个RoundRobinRule,看类的介绍:

image-20201207093825304

我们注意到,这个类其实是实现了接口IRule的,查看一下:

image-20201207093859812

image-20201207094015864

定义负载均衡的规则接口。

它有以下实现:

image-20201207094200330

1.2.3. 3、OpenFeign

【1】工作图解

作用:声明式的Web服务客户端,使得编写web服务客户端变得非常容易

简介:SpringCloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡

image-20201207104224991

image-20201224160521703

【2】POM依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

【3】配置方式

​ fegin内部集成了ribbon的很多机制,比如:负载均衡、重试机制等

全局配置

ribbon:
#    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #配置规则 随机
#    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule #配置规则 轮询
#    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RetryRule #配置规则 重试
#    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule #配置规则 响应时间权重
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
    ConnectTimeout: 500 #请求连接超时时间
    ReadTimeout: 500 #请求处理的超时时间
    OkToRetryOnAllOperations: false #对所有请求都进行重试
    MaxAutoRetriesNextServer: 2 #切换实例的重试次数
    MaxAutoRetries: 1 #对当前实例的重试次数

指定服务配置

# 指定设置module-user-service 表示作用到哪个微服务
module-user-service:
  ribbon:
#    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #配置规则 随机
#    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule #配置规则 轮询
#    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RetryRule #配置规则 重试
#    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule #配置规则 响应时间权重
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
    ConnectTimeout: 500 #请求连接超时时间
    ReadTimeout: 500 #请求处理的超时时间
    OkToRetryOnAllOperations: false #对所有请求都进行重试
    MaxAutoRetriesNextServer: 2 #切换实例的重试次数
    MaxAutoRetries: 1 #对当前实例的重试次数

启动方式

package com.itheima.springcloud.service;

import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Configuration;

/**
 * @ClassName UserFeginBasicService.java
 * @Description 最优化配置,自动开启feign的配置
 */
@EnableFeignClients
@EnableCircuitBreaker
@Configuration
public class UserFeginBasicService {
}

使用方式

package com.itheima.springcloud.service;

import com.itheima.springcloud.basic.ResponseWrap;
import com.itheima.springcloud.exception.ProjectException;
import com.itheima.springcloud.req.UserVo;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.client.RestTemplate;

import java.util.Date;

/**
 * @ClassName UserController.java
 * @Description 用户controller
 */
@FeignClient(value = "module-user-service")
public interface UserFeginService {

    /***
     * @description 注册用户
     * @param userVo 注册信息
     * @return: java.lang.Boolean
     */
    @PostMapping("user/register")
    public ResponseWrap<Boolean> registerUser(UserVo userVo);

    .......
}

【4】核心知识点

【4.1】重试机制
module-user-service:
  ribbon:
#    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #配置规则 随机
#    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule #配置规则 轮询
#    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RetryRule #配置规则 重试
#    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule #配置规则 响应时间权重
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
    ConnectTimeout: 500 #请求连接超时时间
    ReadTimeout: 500 #请求处理的超时时间
    OkToRetryOnAllOperations: false #对所有请求都进行重试
    MaxAutoRetriesNextServer: 2 #切换实例的重试次数
    MaxAutoRetries: 1 #对当前实例的重试次数

重试次数:MaxAutoRetries+MaxAutoRetriesNextServer+(MaxAutoRetries *MaxAutoRetriesNextServer)+1

计算结果:1+2+(2X1)+1=6

==当ribbon超时后且hystrix没有超时,便会采取重试机制。当OkToRetryOnAllOperations设置为false时,只会对get请求进行重试。如果设置为true,便会对所有的请求进行重试,如果是put或post等写操作,如果服务器接口没做==幂等性==,会产生不好的结果,所以OkToRetryOnAllOperations慎用。==

如果不配置ribbon的重试次数,默认会重试一次 默认情况下,GET方式请求无论是连接异常还是读取异常,都会进行重试 非GET方式请求,只有连接异常时,才会进行重试

【4.2】超时设置

使用Feign调用接口分两层,ribbon的调用和hystrix的调用,所以ribbon的超时时间和Hystrix的超时时间的结合就是Feign的超时时间

module-user-service:
  ribbon:
    ConnectTimeout: 500 #请求连接超时时间
    ReadTimeout: 500 #请求处理的超时时间
hystrix:
  command:
    default:
      execution.isolation.thread.timeoutInMilliseconds: 5000 # 熔断超时时间

因为涉及到ribbon的重试机制,一般情况下 都是 ==ribbon 的总超时时间(<)hystrix的超时时间==

hystrix超时时间计算ReadTimeout(MaxAutoRetries+MaxAutoRetriesNextServer+(MaxAutoRetries MaxAutoRetriesNextServer)+1)

计算结果:(1+2+(2X1)+1)X500=3000

【4.3】Feign和Ribbon的区别
  • 启动类用的注解不同。
    • Ribbon 使用的是 @RibbonClient
    • Feign 使用的是 @EnableFeignClients
  • 服务的指定位置不同。
    • Ribbon 是在 @RibbonClient 注解上设置。
    • Feign 则是在定义声明方法的接口中用 @FeignClient 注解上设置。
  • 调使用方式不同。
    • Ribbon 需要自己构建 Http 请求,模拟 Http 请求而后用 RestTemplate 发送给其余服务,步骤相当繁琐。
    • Feign 采使用接口的方式,将需要调使用的方法声明即可,不需要自己构建 Http 请求。不过要注意的是声明方法的注解、方法签名要和提供服务的方法完全一致
【4.4】fegin的文件上传问题
package com.itheima.springcloud.config;

import feign.form.spring.SpringFormEncoder;
import feign.codec.Encoder;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

/**
 * @ClassName UserWebMvcConfig.java
 * @Description webMvc高级配置
 */
@Configuration
public class BusinessFeignWebMvcConfig extends WebMvcConfigurationSupport {

    @Autowired
    private ObjectFactory<HttpMessageConverters> messageConverters;

    //解决上传文件编码解析
    @Bean
    public Encoder feignFormEncoder() {
        return new SpringFormEncoder(new SpringEncoder(messageConverters));
    }

....
}

其本质是添加springmvc对上传文件的编码解析

1.2.4. 4、Netflix Hystrix

【1】工作图解

作用:断路器,保护系统,控制故障范围。

简介:Hystrix 是一个延迟和容错库,旨在隔离远程系统,服务和第三方库的访问点,当出现故障是不可避免的故障时,停止级联故障并在复杂的分布式系统中实现弹性

Hystrix 原理

【2】POM依赖

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

【3】配置方式

#开启feign的熔断机制,在版本中feigin是自带hystrix的
feign:
  hystrix:
    enabled: true
hystrix:
  command:
    default:
      execution.isolation.thread.timeoutInMilliseconds: 2000 # 超时时间
      circuitBreaker:
        errorThresholdPercentage: 50 # 触发熔断错误比例阈值,默认值50%
        sleepWindowInMilliseconds: 10000 # 熔断后休眠时长,默认值5秒
        requestVolumeThreshold: 10 # 触发熔断的最小请求次数,默认20

启动方式

package com.itheima.springcloud;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RoundRobinRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

/***
 * @description UserFeginStart
 * @return
 */
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
//启动熔断
@EnableCircuitBreaker
public class UserFeginStart {

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

使用方式

package com.itheima.springcloud.service;

import com.itheima.springcloud.basic.ResponseWrap;
import com.itheima.springcloud.exception.ProjectException;
import com.itheima.springcloud.hystrix.UserFeginHystrix;
import com.itheima.springcloud.req.UserVo;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.client.RestTemplate;

import java.util.Date;

/**
 * @ClassName UserController.java
 * @Description 用户controller
 * fallback的熔断对象
 */
@FeignClient(value = "module-user-service",fallback = UserFeginHystrix.class)
public interface UserFeginService {
    ...
}

这里UserFeginHystrix实现UserFeginService,为每个方法编写降级方法

package com.itheima.springcloud.hystrix;

import com.itheima.springcloud.basic.ResponseWrap;
import com.itheima.springcloud.enums.StatusEnum;
import com.itheima.springcloud.req.UserVo;
import com.itheima.springcloud.service.UserFeginService;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * @ClassName UserFeginHystrix.java
 * @Description 用户熔断实现
 */
@Component
public class UserFeginHystrix implements UserFeginService {

    @Override
    public ResponseWrap<Boolean> registerUser(UserVo userVo) {
        return ResponseWrap.<Boolean>builder()
                .code(StatusEnum.REGISTER_USER_FAIL.getCode())
                .msg(StatusEnum.REGISTER_USER_FAIL.getMsg())
                .operationTime(new Date())
                .build();
    }
    .....
}

【4】核心知识点

【4.1】雪崩问题

微服务架构中服务间调用关系错综复杂,一个服务的业务,有可能需要调用多个其它微服务,才能完成,

1533829099748

如下图,Dependency-I发生了故障,此时,我们应用中,调用Dependency-I的服务,也会故障,造成阻塞

1533829198240

微服务I发生异常,请求阻塞,用户不会得到响应,则tomcat的这个线程不会释放,于是越来越多的用户请求到来,越来越多的线程会阻塞

1533829307389

服务器支持的线程和并发数有限,请求一直阻塞,会导致服务器资源耗尽,从而导致所有其它服务都不可用,形成雪崩效应,这里hystrix为我们提供对应的解决:

  • 线程隔离
  • 服务熔断降级
【4.2】线程池隔离

​ Hystrix 有两种隔离策略:线程池隔离信号量隔离,这里只做线程池隔离的理论分析,如下图:

1533829598310

​ Hystrix为每个服务调用的功能分配一个小的线程池,如果线程池已满调用将被立即拒绝,默认不采用排队.加速失败判定时间,用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,或者请求超时,则会进行降级处理:返回给用户一个错误提示或备选结果

【4.3】服务熔断

​ 断路器很好理解, 当Hystrix Command请求后端服务失败数量超过一定比例(默认50%), 断路器会切换到开路状态(Open). 这时所有请求会直接失败而不会发送到后端服务. 断路器保持在开路状态一段时间后(默认5秒), 自动切换到半开路状态(HALF-OPEN). 这时会判断下一次请求的返回情况, 如果请求成功, 断路器切回闭路状态(CLOSED), 否则重新切换到开路状态(OPEN). Hystrix的断路器就像我们家庭电路中的保险丝, 一旦后端服务不可用, 断路器会直接切断请求链, 避免发送大量无效请求影响系统吞吐量, 并且断路器有自我检测并恢复的能力.

image-20201207165927131

状态机有3个状态:

  • Closed:关闭状态(断路器关闭),所有请求都正常访问。
  • Open:打开状态(断路器打开),所有请求都会被降级。Hystix会对请求情况计数,当一定时间内失败请求百分比达到阈值,则触发熔断,断路器打开。默认失败比例的阈值是50%,请求次数最少不低于20次。
  • Half Open:半开状态,open状态不是永久的,打开后会进入休眠时间(默认是5S)。随后断路器会自动进入半开状态。此时会释放1次请求通过,若这个请求是健康的,则会关闭断路器,否则继续保持打开,再次进行5秒休眠计时。
hystrix:
  command:
    default:
      execution.isolation.thread.timeoutInMilliseconds: 2000 # 超时时间
      circuitBreaker:
          requestVolumeThreshold: 10 #触发熔断的最小请求次数,默认20
        errorThresholdPercentage: 50 #触发熔断错误比例阈值,默认值50%
        sleepWindowInMilliseconds: 10000 #熔断后休眠时长,默认值5000毫秒
  • requestVolumeThreshold:触发熔断的最小请求次数,默认20,这里我们设置为10,便于触发
  • errorThresholdPercentage:触发熔断的失败请求最小占比,默认50%
  • sleepWindowInMilliseconds:休眠时长,默认是5000毫秒,这里设置为2,便于观察熔断现象

多次访问/user/islogin,当发生熔断时候,我们在10秒内再次请求,则发现直接返回,而不再请求后端方法

image-20201207180258082

【4.4】服务降级

​ Fallback相当于是降级操作. 对于查询操作, 我们可以实现一个fallback方法, 当请求后端服务出现异常的时候, 可以使用fallback方法返回的值. fallback方法的返回值一般是设置的默认值或者来自缓存,当然,降级方法需要配置和编码,服务降级的目标是为了更友好的提示,如果你有备选服务组的话,可以走备选服务组,以最大限度的保存程序的高可用

image-20201207203656572

1.2.5. 5、Gateway

【1】工作图解

作用:

  • 协议转换,路由转发
  • 流量聚合,对流量进行监控,日志输出
  • 作为整个系统的,对流量进行控制,有限流的作用
  • 作为系统的前端边界,外部流量只能通过网关才能访问系统
  • 可以在网关层做权限的判断
  • 可以在网关层做缓存==》全局过滤器

简介:Spring Cloud Gateway是Spring Cloud官方推出的第二代网关框架,取代Zuul网关。网关作为流量的,在微服务系统中有着非常作用,网关常见的功能有路由转发、权限校验、限流控制等作用

image-20201207214617079

官网给出的执行流程:

  • 客户端向Spring Cloud Gateway发出请求。
  • 如果Gateway Handler Mapping确定请求与路由匹配,则将其发送到Gateway Web Handler。
  • Handler通过指定的过滤器链将请求发送到我们实际的服务执行业务逻辑,然后返回。
  • 过滤器由虚线分隔的原因是,过滤器可以在发送代理请求之前或之后执行逻辑

Spring Cloud Gateway根据作用范围划分为GatewayFilter和GlobalFilter,二者区别如下:

  • GatewayFilter : 需要通过spring.cloud.routes.filters 配置在具体路由下,只作用在当前路由上或通过spring.cloud.default-filters配置在全局,作用在所有路由上
  • GlobalFilter : 全局过滤器,不需要在配置文件中配置,作用在所有的路由上,最终通过GatewayFilterAdapter包装成GatewayFilterChain可识别的过滤器,它为请求业务以及路由的URI转换为真实业务服务的请求地址的核心过滤器,不需要配置,系统初始化时加载,并作用在每个路由上。

【2】POM依赖

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

【3】predicates配置

Spring Cloud Gateway内置了许多Predict,这些Predict的源码在org.springframework.cloud.gateway.handler.predicate包中,如果读者有兴趣可以阅读一下。现在列举各种Predicate如下图

20190601010916598

下列配置中添加的关于请求动作的断言,可以更细致的去路由请求

spring:
  cloud:
    gateway:
      routes:
        - id: module-user-feign #从eureka中找到服务
          uri: lb://module-user-feign
          predicates:
            - Path=/user/** #路径断言
            - Method=POST #如果添加这个断言,则表示、/user/**中所有的POST请求走此路由
        - id: module-user-service #从eureka中找到服务
          uri: lb://module-user-service
          predicates:
            - Path=/user/** #路径断言
            - Method=GET #如果添加这个断言,则表示、/user/**中所有的GET请求走此路由

【4】GatewayFilter配置

​ GatewayFilter工厂同Predicate工厂类似,都是在配置文件application.yml中配置,遵循了约定大于配置的思想,只需要在配置文件配置GatewayFilter Factory的名称,而不需要写全部的类名,比如AddRequestHeaderGatewayFilterFactory只需要在配置文件中写AddRequestHeader,而不是全部类名。在配置文件中配置的GatewayFilter Factory最终都会相应的过滤器工厂类处理。

Spring Cloud Gateway 内置的GatewayFilter如下:

在这里插入图片描述

【4.1】前缀截取-StripPrefix

image-20201210221538580

配置itheima-cloud-gateway的application.yml

    routes:
        - id: module-user-feign-web #从eureka中找到服务
          uri: lb://module-user-feign-web
          predicates:
            - Path=/platform/user/**,/platform/seller/** #路径断言
            #- Method=POST #如果添加这个断言,则表示、/user/**中所有的POST请求走此路由
          filters:
            - StripPrefix=1
        - id: module-business-feign-web #从eureka中找到服务
          uri: lb://module-business-feign-web
          predicates:
            - Path=/platform/affix/**,/platform/category/**,/platform/favorite/**,/platform/route/** #路径断言
          filters:
            - StripPrefix=1

上面这个配置的例子表示,当请求路径匹配到/platform/ 会将包含/platform和后边的字符串接去掉转发, StripPrefix=1就代表截取路径的个数,这样配置后当请求/platform/user/ 后端匹配到的请求路径就会变成/user/** ,修改之后还是能正常访问:

image-20201210223719436

【4.2】前缀添加-PrefixPath

PrefixPath Filter 的作用和 StripPrefix 正相反,是在 URL 路径前面添加一部分的前缀

routes:
  - id: module-user-feign-web #从eureka中找到服务
    uri: lb://module-user-feign-web
    predicates:
      - Path=/platform/user/**,/platform/seller/** #路径断言
      #- Method=POST #如果添加这个断言,则表示、/user/**中所有的POST请求走此路由
    filters:
      - PrefixPath=/mypath

大家可以下来去测试,这里不在演示

【4.3】限速-RequestRateLimiter

限速在高并发场景中比较常用的手段之一,可以有效的保障服务的整体稳定性,Spring Cloud Gateway 提供了基于 Redis 的限流方案。所以我们首先需要添加对应的依赖包spring-boot-starter-data-redis-reactive,因为Spring Cloud Gateway是基于webflux,不能应用普通的redis客户端:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

在需要限流的routes中filter中添加RequestRateLimiter

spring:
  application:
    name: cloud-gateway
  redis: #redis配置
    host: 127.0.0.1
    port: 7379
    database: 0
  cloud:
    gateway:
      global-filter:
        exclude-uri-map:
          ignore-uri:
            - /affix
            - /user
            - /seller
            - /category
            - /route
      routes:
        - id: module-user-feign-web #从eureka中找到服务
          uri: lb://module-user-feign-web
          predicates:
            - Path=/platform/user/**,/platform/seller/** #路径断言
            #- Method=POST #如果添加这个断言,则表示、/user/**中所有的POST请求走此路由
          filters:
            - StripPrefix=1
        - id: module-business-feign-web #从eureka中找到服务
          uri: lb://module-business-feign-web
          predicates:
            - Path=/platform/affix/**,/platform/category/**,/platform/favorite/**,/platform/route/** #路径断言
          filters:
            - StripPrefix=1
            - name: RequestRateLimiter
              args:
                #用于限流的键的解析器的 Bean 对象的名字。
                #它使用 SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象
                key-resolver: '#{@addressKeyResolver}'
                redis-rate-limiter.replenishRate: 1 #令牌桶每秒填充平均速率
                redis-rate-limiter.burstCapacity: 3 #令牌桶的容量,允许在一秒钟内完成的最大请求数
  • filter 名称必须是 RequestRateLimiter
  • redis-rate-limiter.replenishRate:令牌桶每秒填充平均速率
  • redis-rate-limiter.burstCapacity:令牌桶的容量,允许在一秒钟内完成的最大请求数
  • key-resolver:使用 SpEL 按名称引用 bean

image-20201210231349453

429 Too Many Requests 表示在一定的时间内用户发送了太多的请求,即超出了“频次限制”

【4.4】熔断-Hystrix

他也需要spring-cloud-starter-netflix-hystrix模块的支持

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

设置default-filters

#默认filters
default-filters:
  - name: Hystrix
    args:
      name: fallbackcmd
      fallbackUri: forward:/fallback

设置hystrix

hystrix:
  command:
    default:
      execution.isolation.thread.timeoutInMilliseconds: 1000 # 超时时间
      circuitBreaker:
        errorThresholdPercentage: 50 # 触发熔断错误比例阈值,默认值50%
        sleepWindowInMilliseconds: 10000 # 熔断后休眠时长,默认值5秒
        requestVolumeThreshold: 1 # 触发熔断的最小请求次数,默认20
package com.itheima.springcloud.web;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

/**
 * @ClassName FallBackController.java
 * @Description 统一熔断处理
 */
@RestController
@RequestMapping("fallback")
public class FallBackController {

    @RequestMapping
    public Map<String,Object> fall(){
        Map<String,Object> map = new HashMap<>();
        map.put("code","101010");
        map.put("msg","服务超时");
        return map;
    }
}

image-20201210235450326

【5】GlobalFilter配置

过滤器允许以某种方式修改传入的HTTP请求或传出的HTTP响应。过滤器可以限定作用在某些特定请求路径上。 Spring Cloud Gateway包含许多内置的GatewayFilter工厂

Spring Cloud Gateway框架内置的GlobalFilter如下:

在这里插入图片描述

上图中每一个GlobalFilter都作用在每一个router上,能够满足大多数的需求。但是如果遇到业务上的定制,可能需要编写满足自己需求的GlobalFilter。在下面的案例中将讲述如何编写自己GlobalFilter

【5.1】CheckLoginFilter
package com.itheima.springcloud.globalFilter;

import com.alibaba.fastjson.JSONObject;
import com.itheima.springcloud.config.JwtTokenAutoConfig;
import com.itheima.springcloud.enums.UserStatusEnum;
import com.itheima.springcloud.properties.GlobaFIlterProperties;
import com.itheima.springcloud.utils.EmptyUtil;
import com.itheima.springcloud.utils.ResponseWrapBuild;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import sun.rmi.runtime.Log;

import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;

/**
 * @ClassName JwtFilter.java
 * @Description 全局检查登录
 */
@Component
@Order(0)
@EnableConfigurationProperties(GlobaFIlterProperties.class)
@Slf4j
public class CheckLoginFilter implements GlobalFilter {

    @Autowired
    GlobaFIlterProperties globaFIlterProperties;

    //排除对应的key
    private static String IGNORE_URI = "ignore-uri";

    @Autowired
    JwtTokenAutoConfig jwtTokenAutoConfig;

    //处理响应
    private Mono<Void> handleUnauthorized(ServerWebExchange exchange) {
        ServerHttpResponse response=exchange.getResponse();
        String body= JSONObject.toJSONString(ResponseWrapBuild.build(UserStatusEnum.NO_LOGIN,null));
        DataBuffer buffer =  response.bufferFactory().wrap(body.getBytes(Charset.forName("UTF-8")));
        return response.writeWith(Mono.just(buffer));
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        Map<String, List<String>> excludeUriMap = globaFIlterProperties.getExcludeUriMap();
        //放过所有忽略的路径
        if (excludeUriMap.containsKey(IGNORE_URI)){
            List<String> excludeUriList = excludeUriMap.get(IGNORE_URI);
            String requestUri = exchange.getRequest().getPath().toString();
            final Boolean[] isPass = {true};
            for (String uri : excludeUriList) {
                Boolean isIgnore = requestUri.startsWith(uri.replace("*",""));
                if (isIgnore){
                    return chain.filter(exchange);
                }
            }
        }
        //校验令牌
        String jwtToken = exchange.getRequest().getHeaders().getFirst("jwtToken");
        //未获得令牌
        if (EmptyUtil.isNullOrEmpty(jwtToken)){
//            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
//            return exchange.getResponse().setComplete();
            return handleUnauthorized(exchange);
        }
        boolean verifyToken = true;
        try {
            jwtTokenAutoConfig.isVerifyToken(jwtToken);
        }catch (Exception e){
            log.warn("验证jwt签名过期");
            verifyToken =false;
        }
        //校验未通过
        if (!verifyToken){
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        //放过
        return chain.filter(exchange);
    }
}

在上面的CheckLoginFilter需要实现GlobalFilter接口,然后根据ServerWebExchange获取ServerHttpRequest,然后根据ServerHttpRequest中是否含有参数jwtToken,如果没有则阻断,终止转发,否则执行jwt的签名校验,校验通过则执行正常的逻辑。

【5.1】AddJwtTokenFilter
package com.itheima.springcloud.globalFilter;

import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.itheima.springcloud.utils.EmptyUtil;
import com.itheima.springcloud.utils.ExceptionsUtil;
import io.netty.buffer.ByteBufAllocator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;

@Component
@Slf4j
@Order(1)
public class AddJwtTokenFilter implements GlobalFilter {

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();

        String method = request.getMethodValue();
        String contentType = request.getHeaders().getFirst("Content-Type");
        log.info("请求链接:{}",request.getPath().toString());
        //文件上传直接放过
        if (!EmptyUtil.isNullOrEmpty(contentType)&&contentType.startsWith("multipart/form-data")){
            return chain.filter(exchange);
        }
        if ("POST".equals(method)||"PUT".equals(method)||"PATCH".equals(method)) {
            return DataBufferUtils.join(exchange.getRequest().getBody())
                    .flatMap(dataBuffer -> {
                        byte[] bytes = new byte[dataBuffer.readableByteCount()];
                        dataBuffer.read(bytes);
                        byte[] bytesNew =null;
                        try {
                            String bodyString = new String(bytes, "utf-8");
                            JsonNode jsonNode = objectMapper.readTree(bodyString);
                            ObjectNode objectNode = (ObjectNode) jsonNode;                            objectNode.put("jwtToken",exchange.getRequest().getHeaders().getFirst("jwtToken"));
                            String jsonString = objectMapper.writeValueAsString(objectNode);
                            log.info(jsonString);//打印请求参数
                            bytesNew = jsonString.getBytes("utf-8");
                        } catch (UnsupportedEncodingException e) {
                            log.error("数据参数转换错误:{}", ExceptionsUtil.getErrorMessageWithNestedException(e));
                        } catch (IOException e) {
                            log.error("数据读取错误:{}", ExceptionsUtil.getErrorMessageWithNestedException(e));
                        }
                        DataBufferUtils.release(dataBuffer);
                        byte[] bytesHandler = bytesNew;
                        Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
                            DataBuffer buffer = exchange.getResponse().bufferFactory()
                                    .wrap(bytesHandler);
                            return Mono.just(buffer);
                        });

                        ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(
                                exchange.getRequest()) {
                            //重要这里重写参数的时候,需要重写长度
                            @Override
                            public HttpHeaders getHeaders() {
                                HttpHeaders httpHeaders = new HttpHeaders();
                                httpHeaders.putAll(super.getHeaders());
                                httpHeaders.setContentLength(bytesHandler.length);
                                return httpHeaders;
                            }

                            @Override
                            public Flux<DataBuffer> getBody() {
                                return cachedFlux;
                            }
                        };
                        return chain.filter(exchange.mutate().request(mutatedRequest)
                                .build());
                    });
        }
        return chain.filter(exchange);
    }

}

上面的AddJwtTokenFilter,在最外面的gateway会把jwtToken存放在headers中,然后经过AddJwtTokenFilte时,把headers的jwtTken取出,然后再boby中传输

【6】跨域问题

globalcors:
        cors-configurations:
          '[/**]':
            # 允许携带认证信息
            # 允许跨域的源(网站域名/ip),设置*为全部
            # 允许跨域请求里的head字段,设置*为全部
            # 允许跨域的method, 默认为GET和OPTIONS,设置*为全部
            # 跨域允许的有效期
            allow-credentials: true
            allowedOrigins:
              - "http://127.0.0.1:8888"
              - "http://127.0.0.1:8091"
              - "http://127.0.0.1:8090"
            allowedHeaders: "*"
            allowedMethods: "*"

allowedOrigins属性在配置时,需要注意尽量使用域名

1.3. 第三章 SpringCloud晋级组件

1.3.1. 1、Config配置中心

【1】工作图解

作用:配置中心做配置的统一管理

简介:ConfigServer(配置中心服务端)从远端git拉取配置文件并在本地git一份,ConfigClient(微服务)从ConfigServer端获取自己对应 配置文件

image-20201211222745243

外部配置文件集中放置在一个git仓库里,用来管理所有的配置文件,维护的时候需要更改配置时,只需要在本地更改后,推送到远程仓库,所有服务实例都可以通过config server来获取配置文件

【2】POM依赖

config-service的配置


<!--配置服务端-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-server</artifactId>
</dependency>
<!--配置注册中心-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

其他config-client服务依赖

<!--监控检查-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--配置客户端端-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>

【3】配置方式

【3.1】上传配置到Git

image-20201211112945309

【3.2】config-service配置

项目默认优先加载:bootstrap.yml

server:
  port: 8092
spring:
  application:
    name: config-service
  cloud:
    config:
      label: master #git上的分支
      server:
        git:
          uri: https://gitee.com/shuwq/config-centre.git
          search-paths: config
          # 如果为公用仓库账号和密码这边可以不输入
          username:
          password:
#注册中心
eureka:
  client:
    service-url:
      defaultZone: http://eureka2:8082/eureka/,http://eureka1:8081/eureka/
    #表示eureka client间隔多久去拉取服务注册信息,默认为30秒,
    #对于api-gateway,如果要迅速获取服务注册状态,可以缩小该值,比如5秒
    registry-fetch-interval-seconds: 15
management:
  endpoints:
    web:
      exposure:
        include: "*" #暴露刷新

启动

package com.itheima.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

/**
 * 启动类
 */
@SpringBootApplication
@EnableEurekaClient
@EnableConfigServer
public class ConfigServiceStart {
    public static void main(String[] args) {
        SpringApplication.run(ConfigServiceStart.class);
    }
}

访问http://localhost:8092/module-user-service-dev.yml

image-20201211115207591

http请求地址和资源文件映射如下:

  • /{application}/{profile}[/{label}]
  • /{application}-{profile}.yml
  • /{label}/{application}-{profile}.yml
  • /{application}-{profile}.properties
  • /{label}/{application}-{profile}.properties
【3.3】配置其余系统配置

module-user-service目录添加bootstrap.yml

server:
  port: 8083 #以当前系统为准
spring:
  application:
    name: module-user-service #以当前系统为准与git中的名称保持一致
  cloud:
    config:
      label: master
      profile: dev #git配置文件名称版本后缀
      discovery:
        enabled: true  #Flag to indicate that config server discovery is enabled (config server URL will be looked up via discovery).
        service-id: config-service

#注册中心
eureka:
  client:
    service-url:
      defaultZone: http://eureka2:8082/eureka/,http://eureka1:8081/eureka/
    #表示eureka client间隔多久去拉取服务注册信息,默认为30秒,
    #对于api-gateway,如果要迅速获取服务注册状态,可以缩小该值,比如5秒
#    registry-fetch-interval-seconds: 15
management:
  endpoints:
    web:
      exposure:
        include: "*" #refresh所有接口都可以被刷新

module-user-feign-web目录添加bootstrap.yml

server:
  port: 8091
spring:
  application:
    name: module-user-feign-web
  main:
    allow-bean-definition-overriding: true
  cloud:
    config:
      label: master
      profile: dev #git配置文件名称版本后缀
      discovery:
        enabled: true  #Flag to indicate that config server discovery is enabled (config server URL will be looked up via discovery).
        service-id: config-service

#注册中心
eureka:
  client:
    service-url:
      defaultZone: http://eureka2:8082/eureka/,http://eureka1:8081/eureka/
    #表示eureka client间隔多久去拉取服务注册信息,默认为30秒,
    #对于api-gateway,如果要迅速获取服务注册状态,可以缩小该值,比如5秒
#    registry-fetch-interval-seconds: 15
management:
  endpoints:
    web:
      exposure:
        include: "*" #refresh所有接口都可以被刷新

module-business-service

server:
  port: 8089
spring:
  application:
    name: module-business-service
  main:
    # 多接口声明处理
    allow-bean-definition-overriding: true
  cloud:
    config:
      label: master
      profile: dev #git配置文件名称版本后缀
      discovery:
        enabled: true  #Flag to indicate that config server discovery is enabled (config server URL will be looked up via discovery).
        service-id: config-service

#注册中心
eureka:
  client:
    service-url:
      defaultZone: http://eureka2:8082/eureka/,http://eureka1:8081/eureka/
    #表示eureka client间隔多久去拉取服务注册信息,默认为30秒,
    #对于api-gateway,如果要迅速获取服务注册状态,可以缩小该值,比如5秒
#    registry-fetch-interval-seconds: 15
management:
  endpoints:
    web:
      exposure:
        include: "*" #refresh所有接口都可以被刷新

module-business-feign-web

server:
  port: 8090
spring:
  application:
    name: module-business-feign-web
  main:
    # 多接口声明处理
    allow-bean-definition-overriding: true
  cloud:
    config:
      label: master
      profile: dev #git配置文件名称版本后缀
      discovery:
        enabled: true  #Flag to indicate that config server discovery is enabled (config server URL will be looked up via discovery).
        service-id: config-service

#注册中心
eureka:
  client:
    service-url:
      defaultZone: http://eureka2:8082/eureka/,http://eureka1:8081/eureka/
    #表示eureka client间隔多久去拉取服务注册信息,默认为30秒,
    #对于api-gateway,如果要迅速获取服务注册状态,可以缩小该值,比如5秒
#    registry-fetch-interval-seconds: 15
management:
  endpoints:
    web:
      exposure:
        include: "*" #refresh所有接口都可以被刷新

itheima-cloud-gateway

server:
  port: 8088
spring:
  application:
    name: cloud-gateway
  main:
    # 多接口声明处理
    allow-bean-definition-overriding: true
  cloud:
    config:
      label: master
      profile: dev #git配置文件名称版本后缀
      discovery:
        enabled: true  #Flag to indicate that config server discovery is enabled (config server URL will be looked up via discovery).
        service-id: config-service
#注册中心
eureka:
  client:
    service-url:
      defaultZone: http://eureka2:8082/eureka/,http://eureka1:8081/eureka/
    #表示eureka client间隔多久去拉取服务注册信息,默认为30秒,
    #对于api-gateway,如果要迅速获取服务注册状态,可以缩小该值,比如5秒
#    registry-fetch-interval-seconds: 15
management:
  endpoints:
    web:
      exposure:
        include: "*" #refresh所有接口都可以被刷新

【4】核心知识点

springcloudConfig是如何注入配置的?

  • ConfigServer本身也可以注入自己的读取的配置,使得其他服务可以和ConfigServer配置在一起,比如Eureka
  • Config的自身注入在BootStrap阶段
  • ConfigServerBootstrapConfiguration#LocalPropertySourceLocatorConfiguration

image-20201211195805830

  • EnvironmentRepositoryPropertySourceLocator会调用EnvironmentRepository获取配置

image-20201211195954402

PropertySourceBootstrapConfiguration#init负责所有BootStrap配置的加载,所有实现PropertySourceLocator接口的服务都会被调用

image-20201211200210880

1.3.2. 2、Bus消息总线

【1】工作图解

作用:动态更新配置

简介:Spring Cloud Bus会向外提供一个http接口,即图中的/bus/refresh。我们将这个接口配置到远程的git的webhook上,当git上的文件内容发生变动时,就会自动调用/bus-refresh接口。Bus就会通知config-server,config-server会发布更新消息到消息总线的消息队列中,其他服务订阅到该消息就会信息刷新,从而实现整个微服务进行自动刷新

image-20201211222808503

  • 提交配置触发post调用config-service的bus/refresh接口
  • config-service更新配置并且发送给Spring Cloud Bus总线
  • Spring Cloud bus接到消息并通知给其它连接在总线上的客户端,所有总线上的客户端均能收到消息
  • 其它客户端接收到通知,请求Server端拉取最新配置
  • 全部客户端均获取到最新的配置

【2】POM依赖

<!--监控检查-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--配置服务端-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-server</artifactId>
</dependency>
<!--配置注册中心-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--配置消息总线-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

【3】配置方式

config-service-bus、module-user-feign-web添加rabbitmq的支持

rabbitmq:
  host: 192.168.112.129
  port: 5672
  username: admin
  password: pass

启动config-service-bus、module-user-feign-web

image-20201211211143629

全局刷新:POST访问http://127.0.0.1:8092/actuator/bus-refresh,自动加载配置

指定刷新:http://127.0.0.1:8092/actuator/bus-refresh/module-user-service,其中module-user-service为eureka中服务名称,这时候只是刷新module-user-service服务而不刷新其他服务

1.3.3. 3、Sleuth-Zipkin链路追踪

【1】工作图解

​ 微服务架构上通过业务来划分服务的,通过REST调用,对外暴露的一个接口,可能需要很多个服务协同才能完成这个接口功能,如果链路上任何一个服务出现问题或者网络超时,都会形成导致接口调用失败。随着业务的不断扩张,服务之间互相调用会越来越复杂

image-20201211214826987

作用:链路追踪

简介:Spring Cloud Sleuth 主要功能就是在分布式系统中提供追踪解决方案,并且兼容支持了 zipkin,你只需要在pom文件中引入相应的依赖即可

如何才能实现跟踪呢?需要明白下面几个概念:

  • 探针:负责在客户端程序运行时搜索服务调用链路信息,发送给收集器
  • 收集器:负责将数据格式化,保存到存储器
  • 存储器:保存数据
  • UI界面:统计并展示

探针会在链路追踪时记录每次调用的信息,Span是基本单元,一次链路调用(可以是RPC,DB等没有特定的限制)创建一个span,通过一个64位ID标识它;同时附加(Annotation)作为payload负载信息,用于记录性能等数据。

一个Span的基本数据结构:

type Span struct {
    TraceID    int64 // 用于标示一次完整的请求id
    Name       string
    ID         int64 // 当前这次调用span_id
    ParentID   int64 // 上层服务的调用span_id  最上层服务parent_id为null,代表根服务root
    Annotation []Annotation // 记录性能等数据
    Debug      bool
}

【2】POM依赖

<!--链路追踪-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>

【3】配置方式

下载zipkin-server-2.10.4-exec.jar,再课程资料中有,执行下列忙累

java -jar zipkin-server-2.10.4-exec.jar

访问http://127.0.0.1:9411/zipkin/

image-20201211215639861

再需要项目中添加如下配置:


  zipkin:
    base-url: http://127.0.0.1:9411 #配置zipkin地址
    sender:
      type: web
  sleuth:
    sampler:
      probability: 1 #采样比例

image-20201211220946194

1.3.4. 4、Nacos平台

【1】什么是Nacos?

​ Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。 是Spring Cloud Alibbaba 中的服务注册发现组件,类似于Consul、Eureka,同时它又提供了分布式配置中心的功能,这点和Consul的config类似,支持热加载。

Nacos 的关键特性包括:

  • 服务发现和服务健康监测
  • 动态配置服务,带管理界面,支持丰富的配置维度。
  • 动态 DNS 服务
  • 服务及其元数据管理

【2】nacos的安装

从官网下载Nacos的解压包,安装稳定版的,下载地址:https://github.com/alibaba/nacos/releases

本次案例下载的版本为1.4.0 ,下载完成后,上传contos系统,解压,在解压后的文件的/conf目录下找到nacos-mysql.sql,然后创建数据库

在解压后的文件的/conf目录下找到application.properties,做如下修改

image-20201212163235194

位置/bin在窗口中执行

./startup.sh -m standalone

启动成功,在浏览器上访问:http://192.168.112.128:8848/nacos,会跳转到登陆界面,默认的登陆用户名为nacos,密码也为nacos。

登陆成功后,展示的界面如下

image-20201212145539865

默认账号密码都是:nacos

image-20201212150521249

【3】nacos注册中心

在itheima-cloud-parent中导入依赖

<!--spring-cloud-alibaba版本-->
<spring-cloud-alibaba.version>2.1.0.RELEASE</spring-cloud-alibaba.version>

<!---spring-cloud-alibaba主配置-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
    <version>${spring-cloud-alibaba.version}</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

module-user-service和module-user-feign-web子系统需要使用nacos注册中心,则引入

<!--alibaba的 Nacos依赖-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--Spring的健康检测依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

module-user-service和module-user-feign-web子系统Spring Boot的启动文件加上@EnableDiscoveryClient注解,【这里不再使用:@EnableEurekaClient】

@SpringBootApplication
@Slf4j
//开启nacos的服务端
@EnableDiscoveryClient
public class UserServiceStart {

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

}

module-user-service和module-user-feign-web子系统application.yml添加

cloud:
  nacos:
    discovery:
      server-addr: 192.168.112.128:8848 # nacos注册中心

已经可以看见发布的项目

image-20201212172919146

访问http://127.0.0.1:8091/doc.html执行登录模块

image-20201212173016438

【4】nacos配置中心

module-user-service子系统需要使用nacos配置中心,则引入

<!--alibaba的 Nacos配置中心客户端依赖-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

module-user-service子系统bootstrap.yml添加

spring:
  profiles:
    active: dev
  #应用配置
  application:
    #应用名称
    name: module-user-service
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.112.128:8848 # nacos注册中心
      config:
        server-addr: 192.168.112.128:8848 # nacos配置中心地址
        file-extension: yml

nacos管理平台配置

image-20201212211312395

image-20201212211441593

  • dataId:consumer-dev.properties,其一般格式是:

    ${spring.application.name}-${spring.profiles.active}.${file.extension}
    # {服务名称}-{springboot激活的profile}.{后缀名}
    # consumer:是服务名
    # dev:是SpringBoot激活的profile
    # properties:后缀名
    
  • group:DEFAULT_GROUP

  • content:配置内容,包括

如果配置发生变化

image-20201212211610846

点击确认发布则程序会自动刷新配置:

image-20201212211657492

results matching ""

    No results matching ""