Spring Cloud 微服务讲义

news2024/11/24 2:52:01

Spring Cloud 微服务讲义

  • 第一部分 微服务架构
    • 第 1 节 互联网应用架构演进
    • 第 2 节 微服务架构体现的思想及优缺点
    • 第 3 节 微服务架构中的核心概念
  • 第二部分 Spring Cloud 综述
    • 第 1 节 Spring Cloud 是什么
    • 第 2 节 Spring Cloud 解决什么问题
    • 第 3 节 Spring Cloud 架构
      • 3.1 Spring Cloud 核心组件
      • 3.2 Spring Cloud 体系结构(组件协同工作机制)
    • 第 4 节 Spring Cloud 与 Dubbo 对比
    • 第 5 节 Spring Cloud 与 Spring Boot 的关系
  • 第三部分 案例准备
    • 第 1 节 案例说明
    • 第 2 节 案例数据库环境准备
    • 第 3 节 案例工程
      • 3.1 父工程 hyq-parent
      • 3.2 公共组件微服务
      • 3.3 商品微服务
      • 3.4 页面静态化微服务
    • 第 4 节 案例代码问题分析
  • 第四部分 第一代 Spring Cloud 核心组件
    • 第 1 节 Eureka服务注册中心
      • 1.1 关于服务注册中心
        • 1.1.1注册中心实现原理
        • 1.1.2主流服务中心对比
      • 1.2 服务注册中心组件 Eureka
      • 1.3 搭建单例Eureka Server服务注册中心
        • 1.3.1、搭建Eureka Server服务 hyq-cloud-eureka
        • 1.3.2、hyq-cloud-eureka工程pom.xml中引入依赖
        • 1.3.3、在yml文件中配置Eureka server服务端口,服务名等信息
        • 1.3.4、编写启动类,声明当前服务为Eureka注册中心
        • 1.3.5、访问http://127.0.0.1:9200
        • 1.3.6、商品微服务和页面静态化微服务注册到Eureka
      • 1.4 搭建Eureka Server 高可用集群
      • 1.5 Eureka细节详解
        • 1.5.1 Eureka元数据详解
        • 1.5.2 Eureka客户端详解
        • 1.5.3 Eureka服务端详解
    • 第 2 节 Ribbon负载均衡
      • 2.1 关于负载均衡
      • 2.2 Ribbon高级应用
      • 2.3 Ribbon负载均衡策略
      • 2.4 Ribbon核心源码剖析
    • 第 3 节Hystrix熔断器
      • 3.1 微服务中的雪崩效应
      • 3.2 雪崩效应解决方案
      • 3.3 Hystrix简介
      • 3.4 Hystrix应用
        • 3.4.1.熔断处理
        • 3.4.2降级处理
      • 3.5 Hystrix舱壁模式
      • 3.6 Hystrix工作流程与高级应用
    • 第 4 节 Feign远程调用组件
      • 4.1 Feign简介
      • 4.2 Feign配置应用
      • 4.3 Feign对负载均衡的支持
      • 4.4 Feign对熔断器的支持
      • 4.5 Feign对请求压缩和响应压缩的支持

第一部分 微服务架构

第 1 节 互联网应用架构演进

随着互联⽹的发展,⽤户群体逐渐扩大,⽹站的流量成倍增⻓,常规的单体架构已⽆法满⾜请求压⼒和业务的快速迭代,架构的变化势在必⾏。下⾯我们就最开始的单体架构分析,⼀步步的到现在的微服务架构。
淘宝:LAMP,Linux、Apache、MySQL、PHP

  • 1)单体应用架构
    在诞⽣之初,一般项目的⽤户量、数据量规模都⽐较⼩,项目所有的功能模块都放在一个工程中编码、
    编译、打包并且部署在一个Tomcat容器中的架构模式就是单体应用架构,这样的架构既简单实 ⽤、便
    于维护,成本⼜低,成为了那个时代的主流架构⽅式。

    在这里插入图片描述
    在这里插入图片描述

    优点:

    高效开发:项⽬前期开发节奏快,团队成员少的时候能够快速迭代
    架构简单:MVC架构,只需要借助IDE开发、调试即可
    易于测试:只需要通过单元测试或者浏览器完成
    易于部署:打包成单⼀可执⾏的jar或者打成war包放到容器内启动单体架构的应用比较容易部署、测试, 在项目的初期,
    		单体应用可以很好地运行。然而,随着需求的不断增加, 越来越多的人加入开发团队,代码库也在飞速地膨胀。
    		慢慢地,单体应用变得越来越臃肿,可维护性、灵活性逐渐降低,维护成本越来越高。
    

    缺点:

    可靠性差: 某个应用Bug,例如死循环、内存溢出等, 可能会导致整个应用的崩溃
    复杂性高: 以一个百万行级别的单体应用为例,整个项目包含的模块多、模块的边界模糊、 依赖关系不清晰、 
    		代码质量参差不齐、 混乱地堆砌在一起。使得整个项目非常复杂。
    扩展能力受限: 单体应用只能作为一个整体进行扩展,无法根据业务模块的需要进行伸缩。例如,
    		应用中有的模块是计算密集型的,它需要强劲的CPU; 有的模块则是IO密集型的,需要更大的内存。 
    		由于这些模块部署在一起,不得不在硬件的选择上做出妥协。
    

    业务量上涨之后,单体应用架构进一步丰富变化,比如应用集群部署、使用Nginx进行负载均衡、增加
    缓存服务器、增加文件服务器、数据库集群并做读写分离等,通过以上措施增强应对高并发的能力、应
    对一定的复杂业务场景,但依然属于单体应用架构。

    在这里插入图片描述

  • 2)垂直应用架构
    为了避免上⾯提到的那些问题,开始做模块的垂直划分,做垂直划分的原则是基于项目现有的业务
    特性来做,核心目标标第⼀个是为了业务之间互不影响,第⼆个是在研发团队的壮⼤后为了提⾼效率,
    减少组件之间的依赖。

    在这里插入图片描述
    优点

    系统拆分实现了流量分担,解决了并发问题
    
    可以针对不同模块进⾏优化
    
    ⽅便⽔平扩展,负载均衡,容错率提⾼
    
    系统间相互独⽴,互不影响,新的业务迭代时更加⾼效
    

    缺点

    服务之间相互调⽤,如果某个服务的端⼝或者ip地址发⽣改变,调⽤的系统得⼿动改变
    
    搭建集群之后,实现负载均衡⽐较复杂,如:内⽹负载,在迁移机器时会影响调⽤⽅的路 由,导致
    线上故障
    
    服务之间调⽤⽅式不统⼀,基于 httpclient 、 webservice ,接⼝协议不统⼀
    
    服务监控不到位:除了依靠端⼝、进程的监控,调⽤的成功率、失败率、总耗时等等这些监 控指标
    是没有的
    
  • 3)SOA应用架构
    在做了垂直划分以后,模块随之增多,维护的成本在也变⾼,⼀些通⽤的业务和模块重复的越来越多,为了解决上⾯提到的接⼝协议不统⼀、服务⽆法监控、服务的负载均衡,引⼊了阿⾥巴巴开源的Dubbo ,⼀款⾼性能、轻量级的开源Java RPC框架,可以和Spring框架无缝集成。它提供了三⼤核⼼能⼒:⾯向接⼝的远程⽅法调⽤,智能容错和负载均衡,以及服务⾃动注册和发现。

    SOA (Service-Oriented Architecture),即面向服务的架构。根据实际业务,把系统拆分成合适的、独立部署的模块,模块之间相互独立(通过Webservice/Dubbo等技术进行通信)。

    优点:分布式、松耦合、扩展灵活、可重用。
    缺点:服务抽取粒度较大、服务调用方和提供方耦合度较高(接口耦合度)

    在这里插入图片描述

  • 4)微服务应用架构
    微服务架构可以说是SOA架构的一种拓展,这种架构模式下它拆分粒度更小、服务更独立。把应用
    拆分成为一个个微小的服务,不同的服务可以使用不同的开发语言和存储,服务之间往往通过Restful等
    轻量级通信。微服务架构关键在于微小、独立、轻量级通信

    微服务是在 SOA 上做的升华粒度更加细致,微服务架构强调的⼀个重点是业务需要彻底的组件化和
    服务化

    在这里插入图片描述
    微服务架构和SOA架构相似又不同

    微服务架构和SOA架构很明显的一个区别就是服务拆分粒度的不同,但是对于项目的架构发展来说,我们所看到的SOA阶段其实服务拆分粒度相对来说已经比较细了(超前哦!),所以上述项目SOA到项目微服务,从服务拆分上来说变化并不大,只是引入了相对完整的新一代Spring Cloud微服务技术。自然,上述我们看到的都是项目架构演变的阶段结果,每一个阶段其实都经历了很多变化,项目的服务拆分其实也是走过了从粗到细,并非绝对的一步到位。

    举个案例来说明SOA和微服务拆分粒度不同
    我们在SOA架构的初期,“简历投递模块”和“人才搜索模块”都有简历内容展示的需求,只不过说可能
    略有区别,一开始在两个模块中各维护了一套简历查询和展示的代码;后期我们将服务更细粒度拆分,
    拆分出简历基础服务,那么不同模块调用这个基础服务即可。

第 2 节 微服务架构体现的思想及优缺点

微服务架构设计的核心思想就是**“微”**,拆分的粒度相对比较小,这样的话单一职责、开发的耦合度
就会降低、微小的功能可以独立部署扩展、灵活性强,升级改造影响范围小。

微服务架构的优点:

  • 微服务很小,便于特定业务功能的聚焦
  • 微服务很小,每个微服务都可以被一个小团队单独实施(开发、测试、部署上线、运维),团队合
    作一定程度解耦,便于实施敏捷开发
  • 微服务很小,便于重用和模块之间的组装
  • 微服务很独立,那么不同的微服务可以使用不同的语言开发,松耦合
  • 微服务架构下,我们更容易引入新技术

微服务架构的缺点

  • 微服务架构下,分布式复杂难以管理,当服务数量增加,管理将越加复杂;
  • 微服务架构下,分布式链路跟踪难等;

第 3 节 微服务架构中的核心概念

  • 服务注册与服务发现

    例如:职位搜索 ->简历服务
    服务提供者:简历服务
    服务消费者:职位搜索
    服务注册:服务提供者将所提供服务的信息(服务器IP和端口、服务访问协议等)注册/登记到注册中心
    服务发现:服务消费者能够从注册中心获取到较为实时的服务列表,然后根据一定的策略选择一个服务访问
    

    在这里插入图片描述

  • 负载均衡

    负载均衡即将请求压力分配到多个服务器(应用服务器、数据库服务器等),以此来提高服务的性能、可靠性
    

    在这里插入图片描述

  • 熔断

    熔断即断路保护。微服务架构中,如果下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体可用性,
    可以暂时切断对下游服务的调用。这种牺牲局部,保全整体的措施就叫做熔断。
    

    在这里插入图片描述

  • 链路追踪

    微服务架构越发流行,一个项目往往拆分成很多个服务,那么一次请求就需要涉及到很多个服务。不同的微服务可能是由不同的团队开发、
    可能使用不同的编程语言实现、整个项目也有可能部署在了很多服务器上(甚至百台、千台)横跨多个不同的数据中心。所谓链路追踪,
    就是对一次请求涉及的很多个服务链路进行日志记录、性能监控
    

    在这里插入图片描述

  • API 网关

微服务架构下,不同的微服务往往会有不同的访问地址,客户端可能需要调用多个服务的接口才能完成一个业务需求,
如果让客户端直接与各个微服务通信可能出现:
	1)客户端需要调用不同的url地址,增加了维护调用难度
	2)在一定的场景下,也存在跨域请求的问题(前后端分离就会碰到跨域问题,原本我们在后端采用Cors就能解决,
		现在利用网关,那么就放在网关这层做好了)
	3)每个微服务都需要进行单独的身份认证
	
那么,API网关就可以较好的统一处理上述问题,API请求调用统一接入API网关层,由网关转发请
求。API网关更专注在安全、路由、流量等问题的处理上(微服务团队专注于处理业务逻辑即可),它的
功能比如
	1)统一接入(路由)
	2)安全防护(统一鉴权,负责网关访问身份认证验证,与“访问认证中心”通信,实际认证业务逻辑交移“访问认证中心”处理)
	3)黑白名单(实现通过IP地址控制禁止访问网关功能,控制访问)
	3)协议适配(实现通信协议校验、适配转换的功能)
	4)流量管控(限流)
	5)长短链接支持
	6)容错能力(负载均衡)

在这里插入图片描述

第二部分 Spring Cloud 综述

第 1 节 Spring Cloud 是什么

[百度百科]Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分
布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,
都可以用 Spring Boot的开发风格做到一键启动和部署。Spring Cloud并没有重复制造轮子,它只是将
目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封
装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统
开发工具包。

Spring Cloud是一系列框架的有序集合(Spring Cloud是一个规范) ,开发服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,利用Spring Boot的开发便利性简化了微服务架构的开发(自动装配)

在这里插入图片描述
这里,我们需要注意,Spring Cloud其实是一套规范,是一套用于构建微服务架构的规范,而不是一个可以拿来即用的框架(所谓规范就是应该有哪些功能组件,然后组件之间怎么配合,共同完成什么事情)。在这个规范之下第三方的Netflix公司开发了一些组件、Spring官方开发了一些框架/组件,包括第三方的阿里巴巴开发了一套框架/组件集合Spring Cloud Alibaba,这些才是Spring Cloud规范的实现。

Netflix搞了一套 ,简称SCN

Spring Cloud 吸收了Netflix公司的产品基础之上自己也搞了几个组件

阿里巴巴在之前的基础上搞出了一堆微服务组件,Spring Cloud Alibaba(SCA)

第 2 节 Spring Cloud 解决什么问题

Spring Cloud 规范及实现意图要解决的问题其实就是微服务架构实施过程中存在的一些问题,比如微服务架构中的服务注册发现问题、网络问题(比如熔断场景)、统一认证安全授权问题、负载均衡问题、链路追踪等问题。

  • Distributed/versioned configuration (分布式/版本化配置)
  • Service registration and discovery (服务注册和发现)
  • Routing (智能路由)
  • Service-to-service calls (服务调用)
  • Load balancing (负载均衡)
  • Circuit Breakers (熔断器)
  • Global locks (全局锁)
  • Leadership election and cluster state ( 选举与集群状态管理)
  • Distributed messaging (分布式消息传递平台)

第 3 节 Spring Cloud 架构

如之前所述,Spring Cloud是一个微服务相关规范,这个规范意图为搭建微服务架构提供一站式服务,采用组件(框架)化机制定义一系列组件,各类组件针对性的处理微服务中的特定问题,这些组件共同来构成Spring Cloud微服务技术栈

3.1 Spring Cloud 核心组件

Spring Cloud 生态圈中的组件,按照发展可以分为第一代 Spring Cloud组件和第二代 Spring Cloud组件。

在这里插入图片描述

3.2 Spring Cloud 体系结构(组件协同工作机制)

在这里插入图片描述

Spring Cloud中的各组件协同工作,才能够支持一个完整的微服务架构。比如

  • 注册中心负责服务的注册与发现,很好将各服务连接起来
  • API网关负责转发所有外来的请求
  • 断路器负责监控服务之间的调用情况,连续多次失败进行熔断保护。
  • 配置中心提供了统一的配置信息管理服务,可以实时的通知各个服务获取最新的配置信息

第 4 节 Spring Cloud 与 Dubbo 对比

Dubbo是阿里巴巴公司开源的一个高性能优秀的服务框架,基于RPC调用,对于目前使用率较高的Spring Cloud Netflix来说,它是基于HTTP的,所以效率上没有Dubbo高,但问题在于Dubbo体系的组件不全,不能够提供一站式解决方案,比如服务注册与发现需要借助于Zookeeper等实现,而Spring Cloud Netflix则是真正的提供了一站式服务化解决方案,且有Spring大家族背景。

前些年,Dubbo使用率高于SpringCloud,但目前Spring Cloud在服务化/微服务解决方案中已经有了非常好的发展趋势。
在这里插入图片描述

第 5 节 Spring Cloud 与 Spring Boot 的关系

Spring Cloud 只是利用了Spring Boot 的特点,让我们能够快速的实现微服务组件开发,否则不使用Spring Boot的话,我们在使用Spring Cloud时,每一个组件的相关Jar包都需要我们自己导入配置以及需要开发人员考虑兼容性等各种情况。所以Spring Boot是我们快速把Spring Cloud微服务技术应用起来的一种方式。

第三部分 案例准备

第 1 节 案例说明

本部分我们按照普通方式模拟一个微服务之间的调用,后续我们将一步步使用Spring Cloud的组件对案例进行改造。

需求:
在这里插入图片描述

完整业务流程图:
在这里插入图片描述

第 2 节 案例数据库环境准备

本教程数据库使用Mysql 5.7.x
商品信息表:

create table products(
	id INT PRIMARY KEY AUTO_INCREMENT,
	NAME VARCHAR(50), #商品名称
	price DOUBLE,
	flag VARCHAR(2), #上架状态
	goods_desc VARCHAR(100), #商品描述
	images VARCHAR(400), #商品图片
	goods_stock INT, #商品库存
	goods_type VARCHAR(20) #商品类型
);

第 3 节 案例工程

我们基于SpringBoot来构造工程环境,我们的工程模块关系如下所示:
在这里插入图片描述

3.1 父工程 hyq-parent

在Idea中新建project,命名为hyq-parent
pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!--spring boot 父启动器依赖-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
    </parent>

    <groupId>com.hyq</groupId>
    <artifactId>hyq-parent</artifactId>
    <version>1.0-SNAPSHOT</version>
    <!--父工程打包方式-->
    <packaging>pom</packaging>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>
        <!--web依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--日志依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>
        <!--测试依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--lombok工具-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.4</version>
            <scope>provided</scope>
        </dependency>
        <!-- Actuator可以帮助你监控和管理Spring Boot应用-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--热部署-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>


    <build>
        <plugins>
            <!--编译插件-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                    <encoding>utf-8</encoding>
                </configuration>
            </plugin>
            <!--打包插件-->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

3.2 公共组件微服务

  1. 在公共组件微服务中引入数据库驱动及mybatis-plus
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>hyq-parent</artifactId>
            <groupId>com.hyq</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>hyq-service-common</artifactId>
    
        <properties>
            <maven.compiler.source>11</maven.compiler.source>
            <maven.compiler.target>11</maven.compiler.target>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>3.3.2</version>
            </dependency>
            <!--pojo持久化使用-->
            <dependency>
                <groupId>javax.persistence</groupId>
                <artifactId>javax.persistence-api</artifactId>
                <version>2.2</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.37</version>
                <scope>runtime</scope>
            </dependency>
        </dependencies>
    </project>
    
  2. 生成数据库实体类:com.hyq.common.pojo.Products
    package com.hyq.common.pojo;
    
    import lombok.Data;
    
    import javax.persistence.Id;
    import javax.persistence.Table;
    
    @Data
    @Table(name = "products")
    public class Products {
        @Id
        private Integer id;
        private String name;
        private double price;
        private String flag;
        private String goodsDesc;
        private String images;
        private long goodsStock;
        private String goodsType;
    }
    

3.3 商品微服务

商品微服务是服务提供者,页面静态化微服务是服务的消费者
创建商品微服务hyq-service-product,继承hyq-parent

  1. 在商品微服务的pom文件中,引入公共组件坐标

    <dependency>
        <groupId>com.hyq</groupId>
        <artifactId>hyq-service-common</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    
  2. 在yml文件中配置端口、应用名、数据库连接等信息

    server:
      port: 9000 # 后期该微服务多实例,9000(10个以内)
    
    spring:
      application:
        name: hyq-service-product
      datasource:
        url: jdbc:mysql://localhost:3306/hyq?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useSSL=false
        driver-class-name: com.mysql.jdbc.Driver
        username: root
        password: root
    
  3. Mapper接口开发

    package com.hyq.product.mapper;
    
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.hyq.common.pojo.Products;
    
    /**
     * 现在使用的Mybatis-plus组件,该组件是Mybatis的加强版
     * 能够与SpringBoot进行非常友好的整合,对比Mybatis框架只有使用便捷的改变
     * 没有具体功能的改变
     * 具体使用:让具体的Mapper接口继承BaseMapper即可
     */
    public interface ProductMapper extends BaseMapper<Products> {
    }
    
    
  4. serive层开发

    ProductService 接口

    package com.hyq.product.service;
    
    import com.hyq.common.pojo.Products;
    
    public interface ProductService {
    
        public Products findById(Integer productId);
    }
    
    

    ProductServiceImpl 实现类

    package com.hyq.product.service.impl;
    
    import com.hyq.common.pojo.Products;
    import com.hyq.product.mapper.ProductMapper;
    import com.hyq.product.service.ProductService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    
    @Service
    public class ProductServiceImpl implements ProductService {
    
        @Autowired
        private ProductMapper productMapper;
    
        /**
         * 根据商品ID查询商品对象
         * @param productId
         * @return
         */
        @Override
        public Products findById(Integer productId) {
            return productMapper.selectById(productId);
        }
    }
    
    
  5. controller层开发

    package com.hyq.product.controller;
    
    import com.hyq.common.pojo.Products;
    import com.hyq.product.service.ProductService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RequestMapping("/product")
    public class ProductController {
    
        @Autowired
        private ProductService productService;
    
        @RequestMapping("/query/{id}")
        public Products query(@PathVariable Integer id){
            return productService.findById(id);
        }
    }
    
    
  6. 启动类

    package com.hyq.product;
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    @MapperScan("com.hyq.product.mapper")
    public class ProductApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ProductApplication.class);
        }
    }
    
    

3.4 页面静态化微服务

  1. 在pom文件中,引入公共组件依赖
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>hyq-parent</artifactId>
            <groupId>com.hyq</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>hyq-service-page</artifactId>
    
        <properties>
            <maven.compiler.source>11</maven.compiler.source>
            <maven.compiler.target>11</maven.compiler.target>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>com.hyq</groupId>
                <artifactId>hyq-service-common</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
        </dependencies>
    </project>
    
  2. 在yml文件中配置端口、应用名、数据库连接等信息
    server:
      port: 9100 # 后期该微服务多实例,端口从9100递增(10个以内)
    
    spring:
      application:
        name: hyq-service-page
      datasource:
        url: jdbc:mysql://localhost:3306/hyq?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useSSL=false
        driver-class-name: com.mysql.jdbc.Driver
        username: root
        password: root
    
  3. 编写PageController,在PageController中调用商品微服务对应的URL
    package com.hyq.page.controller;
    
    import com.hyq.common.pojo.Products;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.client.RestTemplate;
    
    @RestController
    @RequestMapping("/page")
    public class PageController {
        @Autowired
        private RestTemplate restTemplate;
        
        @GetMapping("/getData/{id}")
        public Products findDataById(@PathVariable Integer id){
            Products products =  restTemplate.getForObject("http://localhost:9000/product/query/"+id, Products.class);
            System.out.println("从hyq-service-product获得product对象:"+products);
            return products;
        }
    }
    
    
  4. 编写启动类,注入RestTemplate
    package com.hyq.page;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.annotation.Bean;
    import org.springframework.web.client.RestTemplate;
    
    @SpringBootApplication
    public class PageApplication {
        public static void main(String[] args) {
            SpringApplication.run(PageApplication.class);
        }
    
        @Bean
        public RestTemplate restTemplate(){
            return new RestTemplate();
        }
    }
    
    

第 4 节 案例代码问题分析

我们在页面静态化微服务中使用RestTemplate调用商品微服务的商品状态接口时(Restful API 接口)。在微服务分布式集群环境下会存在什么问题呢?怎么解决?

存在的问题:

  1. 在服务消费者中,我们把url地址硬编码到代码中,不方便后期维护。
  2. 服务提供者只有一个服务,即便服务提供者形成集群,服务消费者还需要自己实现负载均衡。
  3. 在服务消费者中,不清楚服务提供者的状态。
  4. 服务消费者调用服务提供者时候,如果出现故障能否及时发现不向用户抛出异常页面?
  5. RestTemplate这种请求调用方式是否还有优化空间?能不能类似于Dubbo那样玩?
  6. 这么多的微服务统一认证如何实现?
  7. 配置文件每次都修改好多个很麻烦!?

上述分析出的问题,其实就是微服务架构中必然面临的一些问题:

  1. 服务管理:自动注册与发现、状态监管
  2. 服务负载均衡
  3. 熔断
  4. 远程过程调用
  5. 网关拦截、路由转发
  6. 统一认证
  7. 集中式配置管理,配置信息实时自动更新

这些问题,Spring Cloud 体系都有解决方案,后续我们会逐个学习。

第四部分 第一代 Spring Cloud 核心组件

说明:上面提到网关组件Zuul性能一般,未来将退出Spring Cloud 生态圈,所以我们直接讲解GateWay,在课程章节规划时,我们就把GateWay划分到第一代Spring Cloud 核心组件这一部分了。
各组件整体结构如下:
在这里插入图片描述
从形式上来说,Feign一个顶三,Feign = RestTemplate + Ribbon + Hystrix
在这里插入图片描述

第 1 节 Eureka服务注册中心

常用的服务注册中心:Eureka、Nacos、Zookeeper、Consul

1.1 关于服务注册中心

注意:服务注册中心本质上是为了解耦服务提供者和服务消费者。

服务消费者 ==> 服务提供者
服务消费者 ==> 服务注册中心 ==> 服务提供者

对于任何一个微服务,原则上都应存在或者支持多个提供者(比如商品微服务部署多个实例),这是由微服务的分布式属性决定的。

更进一步,为了支持弹性扩、缩容特性,一个微服务的提供者的数量和分布往往是动态变化的,也是无法预先确定的。因此,原本在单体应用阶段常用的静态LB机制就不再适用了,需要引入额外的组件来管理微服务提供者的注册与发现,而这个组件就是服务注册中心。

1.1.1注册中心实现原理

在这里插入图片描述
分布式微服务架构中,服务注册中心用于存储服务提供者地址信息、服务发布相关的属性信息,消费者通过主动查询和被动通知的方式获取服务提供者的地址信息,而不再需要通过硬编码方式得到提供者的地址信息。消费者只需要知道当前系统发布了那些服务,而不需要知道服务具体存在于什么位置,这就是透明化路由。

  1. 服务提供者启动
  2. 服务提供者将相关服务信息主动注册到注册中心
  3. 服务消费者获取服务注册信息:
    pull模式:服务消费者可以主动拉取可用的服务提供者清单
    push模式:服务消费者订阅服务(当服务提供者有变化时,注册中心也会主动推送更新后的服务清单给消费者
  4. 服务消费者直接调用服务提供者
    另外,注册中心也需要完成服务提供者的健康监控,当发现服务提供者失效时需要及时剔除;

1.1.2主流服务中心对比

  • Zookeeper
    Dubbo + Zookeeper
    Zookeeper它是一个分布式服务框架,是Apache Hadoop 的一个子项目,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等。

    简单来说zookeeper本质 = 存储 + 监听通知。

    Zookeeper 用来做服务注册中心,主要是因为它具有节点变更通知功能,只要客户端监听相关服务节点,服务节点的所有变更,都能及时的通知到监听客户端,这样作为调用方只要使用Zookeeper 的客户端就能实现服务节点的订阅和变更通知功能了,非常方便。另外,Zookeeper可用性也可以,因为只要半数以上的选举节点存活,整个集群就是可用的,最少节点数为3。

  • Eureka
    由Netflix开源,并被Pivatal集成到SpringCloud体系中,它是基于 RestfulAPI 风格开发的服务注册与发现组件。

  • Consul
    Consul是由HashiCorp基于Go语言开发的支持多数据中心分布式高可用的服务发布和注册服务软件, 采用Raft算法保证服务的一致性,且支持健康检查。

  • Nacos
    Nacos是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。简单来说Nacos 就是 注册中心 + 配置中心的组合,帮助我们解决微服务开发必会涉及到的服务注册 与发现,服务配置,服务管理等问题。Nacos 是 Spring Cloud Alibaba 核心组件之一,负责服务注册与发现,还有配置。

    在这里插入图片描述
    CAP定理又称CAP原则,指的是在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),最多只能同时三个特性中的两个,三者不可兼得。

    P:分区容错性:分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性或可用
    性的服务(一定的要满足的)
    C:数据一致性:all nodes see the same data at the same time
    A:高可用:Reads and writes always succeed

CAP不可能同时满足三个,要么是AP,要么是CP

1.2 服务注册中心组件 Eureka

服务注册中心的一般原理、对比了主流的服务注册中心方案,目光聚焦Eureka。

  • Eureka 基础架构
    在这里插入图片描述

  • Eureka 交互流程及原理
    在这里插入图片描述
    Eureka 包含两个组件:Eureka Server 和 Eureka Client,Eureka Client是一个Java客户端,用于简化与Eureka Server的交互;Eureka Server提供服务发现的能力,各个微服务启动时,会通过EurekaClient向Eureka Server 进行注册自己的信息(例如网络信息),Eureka Server会存储该服务的信息;

    1. 图中us-east-1c、us-east-1d,us-east-1e代表不同的区也就是不同的机房
    2. 图中每一个Eureka Server都是一个集群。
    3. 图中Application Service作为服务提供者向Eureka Server中注册服务,Eureka Server接受到注册事件会在集群和分区中进行数据同步,Application Client作为消费端(服务消费者)可以从Eureka Server中获取到服务注册信息,进行服务调用。
    4. 微服务启动后,会周期性地向Eureka Server发送心跳(默认周期为30秒,默认Eureka Server 90S会将还没有续约的给剔除)以续约自己的信息
    5. Eureka Server在一定时间内没有接收到某个微服务节点的心跳,Eureka Server将会注销该微服务节点(默认90秒)
    6. 每个Eureka Server同时也是Eureka Client,多个Eureka Server之间通过复制的方式完成服务注册列表的同步
    7. Eureka Client会缓存Eureka Server中的信息。即使所有的Eureka Server节点都宕掉,服务消费者依然可以使用缓存中的信息找到服务提供者

    Eureka通过心跳检测、健康检查和客户端缓存等机制,提高系统的灵活性、可伸缩性和高可用性。

1.3 搭建单例Eureka Server服务注册中心

实现过程:

  1. 单实例Eureka Server—>访问管理界面
  2. 服务提供者(商品微服务注册到集群)
  3. 服务消费者(页面静态化微服务注册到Eureka/从Eureka Server获取服务信息)
  4. 完成调用

1.3.1、搭建Eureka Server服务 hyq-cloud-eureka

hyq-parent中引入Spring Cloud 依赖
Spring Cloud 是一个综合的项目,下面有很多子项目,比如eureka子项目

 <dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Greenwich.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

1.3.2、hyq-cloud-eureka工程pom.xml中引入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>hyq-parent</artifactId>
        <groupId>com.hyq</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>hyq-cloud-eureka</artifactId>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>
    
    <dependencies>
        <!--Eureka server依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>

</project>

注意:在父工程的pom文件中手动引入jaxb的jar,因为Jdk9之后默认没有加载该模块,Eureka
Server使用到,所以需要手动导入,否则EurekaServer服务无法启动

<!--引入Jaxb,开始-->
<dependency>
	<groupId>com.sun.xml.bind</groupId>
	<artifactId>jaxb-core</artifactId>
	<version>2.2.11</version>
</dependency>
<dependency>
	<groupId>javax.xml.bind</groupId>
	<artifactId>jaxb-api</artifactId>
</dependency>
<dependency>
	<groupId>com.sun.xml.bind</groupId>
	<artifactId>jaxb-impl</artifactId>
	<version>2.2.11</version>
</dependency>
<dependency>
	<groupId>org.glassfish.jaxb</groupId>
	<artifactId>jaxb-runtime</artifactId>
	<version>2.2.10-b140310.1920</version>
</dependency>
<dependency>
	<groupId>javax.activation</groupId>
	<artifactId>activation</artifactId>
	<version>1.1.1</version>
</dependency>
<!--引入Jaxb,结束-->

1.3.3、在yml文件中配置Eureka server服务端口,服务名等信息

server:
  port: 9200 #Eureka server服务端口

spring:
  application:
    name: hyq-cloud-eureka-server # 应用名称,会在Eureka中作为服务的id标识(serviceId)

eureka:
  instance:
    hostname: localhost
  client:
    service-url: # 客户端与EurekaServer交互的地址,如果是集群,也需要写其它Server的地址
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
    register-with-eureka: false # 自己就是服务不需要注册自己
    fetch-registry: false #自己就是服务不需要从Eureka Server获取服务信息,默认为true,置为false

1.3.4、编写启动类,声明当前服务为Eureka注册中心

package com.hyq.eureka;

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

@SpringBootApplication
// 声明本项目是一个Eureka服务
@EnableEurekaServer
public class EurekaApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaApplication.class);
    }
}

1.3.5、访问http://127.0.0.1:9200

如果看到如下页面(Eureka注册中心后台),则表明EurekaServer发布成功

在这里插入图片描述

1.3.6、商品微服务和页面静态化微服务注册到Eureka

pom文件中添加Eureka Client依赖

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

yml配置Eureka服务端信息

eureka:
  client:
    service-url:
      defaultZone: http://localhost:9200/eureka
    fetch-registry: true
    register-with-eureka: true
  instance:
    #使用ip注册,否则会使用主机名注册了(此处考虑到对老版本的兼容,新版本经过实验都是ip)
    prefer-ip-address: true
    #自定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@

修改启动类

package com.hyq.product;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@MapperScan("com.hyq.product.mapper")
@EnableDiscoveryClient //等价于 @EnableEurekaClient
public class ProductApplication {

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

1.4 搭建Eureka Server 高可用集群

在互联网应用中,服务实例很少有单个的。
如果EurekaServer只有一个实例,该实例挂掉,正好微服务消费者本地缓存列表中的服务实例也不可用,那么这个时候整个系统都受影响。
在生产环境中,我们会配置Eureka Server集群实现高可用。Eureka Server集群之中的节点通过点对点(P2P)通信的方式共享服务注册表。我们开启两台 Eureka Server 以搭建集群。
由于是在个人计算机中进行测试很难模拟多主机的情况,Eureka配置server集群时需要执行host地址。 所以需要修改个人电脑中host地址:
windows 操作系统下:C:\Windows\System32\drivers\etc\host

127.0.0.1 HyqCloudEurekaServerA
127.0.0.1 HyqCloudEurekaServerB

将hyq-cloud-eureka复制一份为hyq-cloud-eureka9201

  • 修改 hyq-cloud-eureka-server 工程中的yml配置文件
    9200

    server:
      port: 9200 #Eureka server服务端口
    
    spring:
      application:
        name: hyq-cloud-eureka-server # 应用名称,会在Eureka中作为服务的id标识(serviceId)
    
    eureka:
      instance:
        hostname: HyqCloudEurekaServerA
      client:
        service-url: # 客户端与EurekaServer交互的地址,如果是集群,也需要写其它Server的地址
          defaultZone: http://HyqCloudEurekaServerB:9201/eureka
        register-with-eureka: true
        fetch-registry: true
    

    9201

    server:
    port: 9201 #Eureka server服务端口
    
    spring:
      application:
        name: hyq-cloud-eureka-server # 应用名称,会在Eureka中作为服务的id标识(serviceId)
    
    eureka:
      instance:
        hostname: HyqCloudEurekaServerB
      client:
        service-url: # 客户端与EurekaServer交互的地址,如果是集群,也需要写其它Server的地址
          defaultZone: http://HyqCloudEurekaServerB:9200/eureka
        register-with-eureka: true
        fetch-registry: true
    

    商品微服务:

    server:
      port: 9000 # 后期该微服务多实例,9000(10个以内)
    spring:
      application:
        name: hyq-service-product
      datasource:
        url: jdbc:mysql://localhost:3306/hyq?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useSSL=false
        driver-class-name: com.mysql.jdbc.Driver
        username: root
        password: root
    eureka:
      client:
        service-url:
          #把 eureka 集群中的所有 url 都填写了进来,也可以只写一台,因为各个 eureka server 可以同步注册表
          defaultZone: http://hyqcloudeurekaservera:9200/eureka/,http://hyqcloudeurekaserverb:9201/eureka/
        fetch-registry: true
        register-with-eureka: true
      instance:
        #使用ip注册,否则会使用主机名注册了(此处考虑到对老版本的兼容,新版本经过实验都是ip)
        prefer-ip-address: true
        #自定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
        instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
    

    页面静态化微服务:

    server:
      port: 9100 # 后期该微服务多实例,端口从9100递增(10个以内)
    
    spring:
      application:
        name: hyq-service-page
      datasource:
        url: jdbc:mysql://localhost:3306/hyq?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useSSL=false
        driver-class-name: com.mysql.jdbc.Driver
        username: root
        password: root
    
    eureka:
      client:
        service-url:
          #把 eureka 集群中的所有 url 都填写了进来,也可以只写一台,因为各个 eureka server 可以同步注册表
          defaultZone: http://hyqcloudeurekaservera:9200/eureka/,http://hyqcloudeurekaserverb:9201/eureka/
        register-with-eureka: true
        fetch-registry: true
      instance:
        #使用ip注册,否则会使用主机名注册了(此处考虑到对老版本的兼容,新版本经过实验都是ip)
        prefer-ip-address: true
        #自定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
        instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
    
  • 服务消费者调用服务提供者
    改造页面静态化微服务:之前是直接通过RestTemplate写死URL进行调用,现在通过Eureka方式进行调用。

    package com.hyq.page.controller;
    
    import com.hyq.common.pojo.Products;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.cloud.client.ServiceInstance;
    import org.springframework.cloud.client.discovery.DiscoveryClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.client.RestTemplate;
    
    import java.util.List;
    
    @RestController
    @RequestMapping("/page")
    public class PageController {
        @Autowired
        private RestTemplate restTemplate;
    
        @Autowired
        private DiscoveryClient discoveryClient;
    
        @GetMapping("/getData/{id}")
        public Products findDataById(@PathVariable Integer id) {
            //1.获得Eureka中注册的hyq-service-product实例集合
            List<ServiceInstance> instances = discoveryClient.getInstances("hyq-service-product");
            //2.获得实例集合中的第一个
            ServiceInstance instance = instances.get(0);
            //3.根据实例信息拼接IP地址
            String host = instance.getHost();
            int port = instance.getPort();
            String url = "http://" + host + ":" + port + "/product/query/" + id;
            //4.调用
            Products products = restTemplate.getForObject(url, Products.class);
            System.out.println("从hyq-service-product获得product对象:" + products);
            return products;
        }
    }
    
    

1.5 Eureka细节详解

1.5.1 Eureka元数据详解

Eureka的元数据有两种:标准元数据和自定义元数据。
标准元数据:主机名、IP地址、端口号等信息,这些信息都会被发布在服务注册表中,用于服务之间的调用。
自定义元数据:可以使用eureka.instance.metadata-map配置,符合KEY/VALUE的存储格式。这些元数据可以在远程客户端中访问。

类似于

eureka:
  # 标准元数据
  client:
    service-url:
      defaultZone: http://hyqcloudeurekaservera:9200/eureka/,http://hyqcloudeurekaserverb:9201/eureka/
    register-with-eureka: true
    fetch-registry: true
  instance:
    prefer-ip-address: true
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
    # 自定义元数据
    metadata-map:
      ip: 192.168.10.123
      port: 1010
      user: admin
      password: 123456

我们可以在程序中可以使用DiscoveryClient 获取指定微服务的所有元数据信息

package com.hyq.page.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Map;
import java.util.Set;

@RestController
@RequestMapping("/metadata")
public class MetadataController {

    @Autowired
    private DiscoveryClient discoveryClient;

    @RequestMapping("/show")
    public String showMetaData(){
        String result = "";
        List<ServiceInstance> instances = discoveryClient.getInstances("hyq-service-page");
        for (ServiceInstance instance:instances) {
            //获取服务元数据
            Map<String, String> metadata = instance.getMetadata();
            Set<Map.Entry<String, String>> entries = metadata.entrySet();
            for (Map.Entry<String,String> entry : entries){
                String key = entry.getKey();
                String value = entry.getValue();
                result+="key:"+key+",value:"+value;
            }
        }
        return result;
    }

}

标准元数据
在这里插入图片描述

自定义元数据
在这里插入图片描述

1.5.2 Eureka客户端详解

服务提供者(也是Eureka客户端)要向EurekaServer注册服务,并完成服务续约等工作

  • 服务注册详解(服务提供者)

    1. 当我们导入了eureka-client依赖坐标,配置Eureka服务注册中心地址
    2. 服务在启动时会向注册中心发起注册请求,携带服务元数据信息
    3. Eureka注册中心会把服务的信息保存在Map中。
  • 服务续约详解(服务提供者)
    服务每隔30秒会向注册中心续约(心跳)一次(也称为报活),如果没有续约,租约在90秒后到期,然后服务会被失效。每隔30秒的续约操作我们称之为心跳检测

    1. Eureka Client :30S续约一次,在Eureka Server更新自己的状态 (Client端进行配置)
    2. Eureka Server:90S还没有进行续约,将该微服务实例从服务注册表(Map)剔除 (Client端进行配置)
    3. Eureka Client: 30S拉取服务最新的注册表并缓存到本地 (Client端进行配置)

    往往不需要我们调整这两个配置

    eureka:
      instance:
        # 租约到期,服务时效时间,默认值90秒,服务超过90秒没有发生心跳,EurekaServer会将服务从列表移除
        lease-expiration-duration-in-seconds: 30
        # 租约续约间隔时间,默认30秒
        lease-renewal-interval-in-seconds: 30
    
  • 获取服务列表(服务注册表)详解(服务消费者)
    每隔30秒服务会从注册中心中拉取一份服务列表,这个时间可以通过配置修改。往往不需要我们调整

    eureka:
      client:
        # 每隔多久拉取一次服务列表
        registry-fetch-interval-seconds: 30
    
    1. 服务消费者启动时,从 EurekaServer服务列表获取只读备份,缓存到本地
    2. 每隔30秒,会重新获取并更新数据
    3. 每隔30秒的时间可以通过配置eureka.client.registry-fetch-interval-seconds修改

1.5.3 Eureka服务端详解

  • 服务下线:

    1. 当服务正常关闭操作时,会发送服务下线的REST请求给EurekaServer。
    2. 服务中心接受到请求后,将该服务置为下线状态
  • 失效剔除:
    Eureka Server会定时(间隔值是eureka.server.eviction-interval-timer-in-ms,默认60s)进行检查,如果发现实例在在一定时间(此值由客户端设置的eureka.instance.lease-expiration-duration-in-seconds定义,默认值为90s)内没有收到心跳,则会注销此实例。

  • 自我保护机制:
    自我保护模式正是一种针对网络异常波动的安全保护措施,使用自我保护模式能使Eureka集群更加的健壮、稳定的运行。

    自我保护机制的工作机制是:如果在15分钟内超过85%的客户端节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,Eureka Server自动进入自我保护机制,此时会出现以下几种情况:

    1. Eureka Server不再从注册列表中移除因为长时间没收到心跳而应该过期的服务。
    2. Eureka Server仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上,保证当前节点依然可用。
    3. 当网络稳定时,当前Eureka Server新的注册信息会被同步到其它节点中。

    因此Eureka Server可以很好的应对因网络故障导致部分节点失联的情况,而不会像ZK那样如果有一半不可用的情况会导致整个集群不可用而变成瘫痪。

  • 为什么会有自我保护机制?
    默认情况下,如果Eureka Server在一定时间内(默认90秒)没有接收到某个微服务实例的心跳,Eureka Server将会移除该实例。但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通信,而微服务本身是正常运行的,此时不应该移除这个微服务,所以引入了自我保护机制。

    服务中心页面会显示如下提示信息
    在这里插入图片描述
    我们在单机测试的时候很容易满足心跳失败比例在 15 分钟之内低于 85%,这个时候就会触发 Eureka的保护机制,一旦开启了保护机制(默认开启),则服务注册中心维护的服务实例就不是那么准确了,此时我们通过修改Eureka Server的配置文件来关闭保护机制,这样可以确保注册中心中不可用的实例被及时的剔除(不推荐)。

    eureka:
      server:
        enable-self-preservation: false # 关闭自我保护模式(缺省为打开)
    

    经验:建议生产环境打开自我保护机制

第 2 节 Ribbon负载均衡

2.1 关于负载均衡

负载均衡一般分为服务器端负载均衡和客户端负载均衡

所谓服务器端负载均衡,比如Nginx、F5这些,请求到达服务器之后由这些负载均衡器根据一定的算法将请求路由到目标服务器处理。

所谓客户端负载均衡,比如我们要说的Ribbon,服务消费者客户端会有一个服务器地址列表,调用方在请求前通过一定的负载均衡算法选择一个服务器进行访问,负载均衡算法的执行是在请求客户端进行。

Ribbon是Netflix发布的负载均衡器。Eureka一般配合Ribbon进行使用,Ribbon利用从Eureka中读取到服务信息,在调用服务提供者提供的服务时,会根据一定的算法进行负载。

在这里插入图片描述

2.2 Ribbon高级应用

需求:
复制商品微服务9001,在9000和9001编写Controller,返回服务实例端口。
Page微服务中通过负载均衡策略调用hyq-service-product的controller

在这里插入图片描述
在微服务中使用Ribbon不需要额外导入依赖坐标,微服务中引入过eureka-client相关依赖,会自动引入Ribbon相关依赖坐标。

代码中使用如下,在RestTemplate上添加对应注解即可

package com.hyq.page;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableEurekaClient //@EnableDiscoveryClient
public class PageApplication {
    public static void main(String[] args) {
        SpringApplication.run(PageApplication.class);
    }

    @Bean
    @LoadBalanced  //Ribbon负载均衡
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

创建hyq-serivce-product-9001微服务,创建ServerConfigController,定义方法返回当前微服务所使用的容器端口号

修改服务提供者api返回值,返回当前实例的端口号,便于观察负载情况

package com.hyq.product.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/server")
public class ServerConfigController {

    @Value("${server.port}")
    private String serverPort;

    @RequestMapping("/query")
    public String findServerPort(){
        return serverPort ;
    }
}

在页面静态化微服务中调用hyq-service-product下的资源路径:http://hyq-server-product/ser
ver/query

@RequestMapping("/getPort")
public String getProductServerPort(){
    String url ="http://hyq-service-product/server/query";
    return restTemplate.getForObject(url , String.class);
}

第一次访问:
在这里插入图片描述
第二次访问:
在这里插入图片描述

2.3 Ribbon负载均衡策略

Ribbon内置了多种负载均衡策略,内部负责复杂均衡的顶级接口为:com.netflix.loadbalancer.IRule

接口代码:

package com.netflix.loadbalancer;

public interface IRule {
    Server choose(Object var1);

    void setLoadBalancer(ILoadBalancer var1);

    ILoadBalancer getLoadBalancer();
}

负载均衡策略描述
RoundRobinRule:轮询策略默认超过10次获取到的server都不可用,会返回一个空的server
RandomRule:随机策略如果随机到的server为null或者不可用的话,会while不停的循环选取
RetryRule:重试策略一定时限内循环重试。默认继承RoundRobinRule,也支持自定义注入,RetryRule会在每次选取之后,对选举的server进行判断,是否为null,是否alive,并且在500ms内会不停的选取判断。而RoundRobinRule失效的策略是超过10次,RandomRule是没有失效时间的概念,只要serverList没都挂。
BestAvailableRule:最小连接数策略遍历serverList,选取出可用的且连接数最小的一个server。该算法里面有一个LoadBalancerStats的成员变量,会存储所有server的运行状况和连接数。如果选取到的server为null,那么会调用RoundRobinRule重新选取。
AvailabilityFilteringRule:可用过滤策略扩展了轮询策略,会先通过默认的轮询选取一个server,再去判断该server是否超时可用,当前连接数是否超限,都成功再返回。
ZoneAvoidanceRule:区域权衡策略(默认策略)扩展了轮询策略,继承了2个过滤器:ZoneAvoidancePredicate和AvailabilityPredicate,除了过滤超时和链接数过多的server,还会过滤掉不符合要求的zone区域里面的所有节点, 在一个区域/机房内的服务实例中轮询。先过滤再轮询

修改负载均衡策略:

  • 配置文件方式

    # 给某个微服务配置负载均衡规则
    hyq-service-product:
      ribbon:
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #随机策略
    
  • 代码方式

    @Bean
    public IRule randomRule(){
        return new RandomRule();  
    }
    

    注意,一般用默认的负载均衡规则,不做修改

2.4 Ribbon核心源码剖析

Ribbon工作原理:
在这里插入图片描述

  • 老规矩:SpringCloud充分利用了SpringBoot的自动装配特点,找spring.factories配置文件

    在这里插入图片描述
    ctrl + 单击 进入RibbonAutoConfiguration类的源码:
    在这里插入图片描述

  • LoadBalancerAutoConfiguration 类中配置
    1、装配验证:
    在这里插入图片描述
    2、自动注入:
    在这里插入图片描述
    3、注入restTemplate定制器:

    为restTemplate对象设置loadBalancerInterceptor
    在这里插入图片描述

到这里,我们明白,添加了注解的RestTemplate对象会被添加一个拦截器LoadBalancerInterceptor,该拦截器就是后续拦截请求进行负载处理的。

第 3 节Hystrix熔断器

属于一种容错机制

3.1 微服务中的雪崩效应

当山坡积雪内部的内聚力抗拒不了它所受到的重力拉引时,便向下滑动,引起大量雪体崩塌,人们把这种自然现象称作雪崩。

微服务中,一个请求可能需要多个微服务接口才能实现,会形成复杂的调用链路。

服务雪崩效应: 是一种因“服务提供者的不可用”(原因)导致“服务调用者不可用”(结果),并将不可用逐渐放大的现象。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

扇入:代表着该微服务被调用的次数,扇入大,说明该模块复用性好
扇出:该微服务调用其他微服务的个数,扇出大,说明业务逻辑复杂
扇入大是一个好事,扇出大不一定是好事

在微服务架构中,一个应用可能会有多个微服务组成,微服务之间的数据交互通过远程过程调用完成。这就带来一个问题,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”。

如图中所示,最下游商品微服务响应时间过长,大量请求阻塞,大量线程不会释放,会导致服务器资源耗尽,最终导致上游服务甚至整个系统瘫痪。

形成原因:
服务雪崩的过程可以分为三个阶段:

  1. 服务提供者不可用
  2. 重试加大请求流量
  3. 服务调用者不可用

服务雪崩的每个阶段都可能由不同的原因造成:
在这里插入图片描述

3.2 雪崩效应解决方案

从可用性可靠性着想,为防止系统的整体缓慢甚至崩溃,采用的技术手段;

下面,我们介绍三种技术手段应对微服务中的雪崩效应,这三种手段都是从系统可用性、可靠性角度出发,尽量防止系统整体缓慢甚至瘫痪。

  • 服务熔断
    熔断机制是应对雪崩效应的一种微服务链路保护机制。我们在各种场景下都会接触到熔断这两个字。高压电路中,如果某个地方的电压过高,熔断器就会熔断,对电路进行保护。股票交易中,如果股票指数过高,也会采用熔断机制,暂停股票的交易。同样,在微服务架构中,熔断机制也是起着类似的作用。当扇出链路的某个微服务不可用或者响应时间太长时,熔断该节点微服务的调用,进行服务的降级,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路。
    注意:

    1. 服务熔断重点在“”,切断对下游服务的调用
    2. 服务熔断和服务降级往往是一起使用的,Hystrix就是这样。
  • 服务降级
    通俗讲就是整体资源不够用了,先将一些不关紧的服务停掉(调用我的时候,给你返回一个预留的值,也叫做兜底数据),待渡过难关高峰过去,再把那些服务打开。

    服务降级一般是从整体考虑,就是当某个服务熔断之后,服务器将不再被调用,此刻客户端可以自己准备一个本地的fallback回调,返回一个缺省值,这样做,虽然服务水平下降,但好歹可用,比直接挂掉要强。

  • 服务限流
    服务降级是当服务出问题或者影响到核心流程的性能时,暂时将服务屏蔽掉,待高峰或者问题解决后再打开;但是有些场景并不能用服务降级来解决,比如秒杀业务这样的核心功能,这个时候可以结合服务限流来限制这些场景的并发/请求量

    限流措施也很多,比如

    • 限制总并发数(比如数据库连接池、线程池)
    • 限制瞬时并发数(如nginx限制瞬时并发连接数)
    • 限制时间窗口内的平均速率(如Guava的RateLimiter、nginx的limit_req模块,限制每秒的平均速率)
    • 限制远程接口调用速率、限制MQ的消费速率等

3.3 Hystrix简介

在这里插入图片描述
[来自官网] Hystrix(豪猪),宣言“defend your application”是由Netflix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性。Hystrix主要通过以下几点实现延迟和容错。

  • 包裹请求:使用HystrixCommand包裹对依赖的调用逻辑。 页面静态化微服务方法(@HystrixCommand 添加Hystrix控制)
  • 跳闸机制:当某服务的错误率超过一定的阈值时,Hystrix可以跳闸,停止请求该服务一段时间。
  • 资源隔离:Hystrix为每个依赖都维护了一个小型的线程池(舱壁模式)。如果该线程池已满, 发往该依赖的请求就被立即拒绝,而不是排队等待,从而加速失败判定。
  • 监控:Hystrix可以近乎实时地监控运行指标和配置的变化,例如成功、失败、超时、以及被拒绝的请求等。
  • 回退机制:当请求失败、超时、被拒绝,或当断路器打开时,执行回退逻辑。回退逻辑由开发人员自行提供,例如返回一个缺省值。
  • 自我修复:断路器打开一段时间后,会自动进入“半开”状态(探测服务是否可用,如还是不可用,再次退回打开状态)。

3.4 Hystrix应用

3.4.1.熔断处理

目的: 商品微服务长时间没有响应,服务消费者—>页面静态化微服务快速失败给用户提示

  • 引入依赖: 服务消费者工程(静态化微服务)中引入Hystrix依赖坐标(也可以添加在父工程中)
    <!--熔断器Hystrix-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
    
  • 开启熔断: 服务消费者工程(静态化微服务)的启动类中添加熔断器开启注解
    @EnableCircuitBreaker
    package com.hyq.page;
    
    import com.netflix.loadbalancer.IRule;
    import com.netflix.loadbalancer.RandomRule;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.client.loadbalancer.LoadBalanced;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    import org.springframework.context.annotation.Bean;
    import org.springframework.web.client.RestTemplate;
    
    /**
     * 注解简化写法
     * @SpringCloudApplication = @SpringBootApplication+@EnableDiscoveryClient+@EnableCircuitBreaker
     */
    @SpringBootApplication
    @EnableEurekaClient //@EnableDiscoveryClient
    @EnableCircuitBreaker // 开启熔断
    public class PageApplication {
        public static void main(String[] args) {
            SpringApplication.run(PageApplication.class);
        }
    
        @Bean
        @LoadBalanced  //Ribbon负载均衡
        public RestTemplate restTemplate(){
            return new RestTemplate();
        }
    
    //    @Bean
    //    public IRule randomRule(){
    //        return new RandomRule();
    //    }
    
    }
    
    
  • 定义服务降级处理方法: 业务方法上使用@HystrixCommand的fallbackMethod属性关联到服务降级处理方法
    /**
     * 提供者模拟处理超时,调用方法添加Hystrix控制
     */
    // 使用@HystrixCommand注解进行熔断控制
    @HystrixCommand(
            // 线程池标识,要保持唯一,不唯一的话就共用了
            threadPoolKey = "getProductServerPort2",
            // 线程池细节属性配置
            threadPoolProperties = {
                    @HystrixProperty(name="coreSize",value = "1"), // 线程数
                    @HystrixProperty(name="maxQueueSize",value="20") // 等待队列长度
            },
            // commandProperties熔断的一些细节属性配置
            commandProperties = {
                    // 每一个属性都是一个HystrixProperty
                    @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds" , value = "2000")
            }
    )
    @RequestMapping("/getPort2")
    public String getProductServerPort2(){
        String url ="http://hyq-service-product/server/query";
        return restTemplate.getForObject(url , String.class);
    }
    
  • 商品微服务模拟超时操作
    package com.hyq.product.controller;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RequestMapping("/server")
    public class ServerConfigController {
    
        @Value("${server.port}")
        private String serverPort;
    
        @RequestMapping("/query")
        public String findServerPort(){
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return serverPort ;
        }
    
    }
    
    

3.4.2降级处理

配置@HystrixCommand注解,定义降级处理方法

/**
 * 提供者模拟处理超时,调用方法添加Hystrix控制
 */
// 使用@HystrixCommand注解进行熔断控制
@HystrixCommand(
        // 线程池标识,要保持唯一,不唯一的话就共用了
        threadPoolKey = "getProductServerPort2",
        // 线程池细节属性配置
        threadPoolProperties = {
                @HystrixProperty(name="coreSize",value = "1"), // 线程数
                @HystrixProperty(name="maxQueueSize",value="20") // 等待队列长度
        },
        // commandProperties熔断的一些细节属性配置
        commandProperties = {
                // 每一个属性都是一个HystrixProperty
                @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds" , value = "2000"),
                // hystrix高级配置,定制工作过程细节
                // 统计时间窗口定义
                @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "8000"),
                // 统计时间窗口内的最小请求数
                @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "2"),
                // 统计时间窗口内的错误数量百分比阈值
                @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),
                // 自我修复时的活动窗口长度
                @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "3000")
        },
        fallbackMethod = "myFallBack" // 回退方法
)
@RequestMapping("/getPort2")
public String getProductServerPort2(){
    String url ="http://hyq-service-product/server/query";
    return restTemplate.getForObject(url , String.class);
}
/** 
 * 定义回退方法,返回预设默认值
 * 注意:该方法形参和返回值与原始方法保持一致
 */
public String myFallBack() {
    return "-1"; // 兜底数据
}

3.5 Hystrix舱壁模式

即:线程池隔离策略

如果不进行任何设置,所有熔断方法使用一个Hystrix线程池(10个线程),那么这样的话会导致问题,这个问题并不是扇出链路微服务不可用导致的,而是我们的线程机制导致的,如果方法A的请求把10个线程都用了,方法2请求处理的时候压根都没法去访问B,因为没有线程可用,并不是B服务不可用。
在这里插入图片描述
为了避免问题服务请求过多导致正常服务无法访问,Hystrix 不是采用增加线程数,而是单独的为每一个控制方法创建一个线程池的方式,这种模式叫做“舱壁模式",也是线程隔离的手段。

3.6 Hystrix工作流程与高级应用

在这里插入图片描述

1、当调用出现问题时,开启一个时间窗(10s)
2、在这个时间窗内,统计调用次数是否达到最小请求数?
	 如果没有达到,则重置统计信息,回到第1步
 	 如果达到了,则统计失败的请求数占所有请求数的百分比,是否达到阈值?
		如果达到,则跳闸(不再请求对应服务)
		如果没有达到,则重置统计信息,回到第1步
3、如果跳闸,则会开启一个活动窗口(默认5s),每隔5s,Hystrix会让一个请求通过,到达那个问题服务,
   看是否调用成功,如果成功,重置断路器回到第1步,如果失败,回到第3步
/**
 * 8秒钟内,请求次数达到2个,并且失败率在50%以上,就跳闸
 * 跳闸后活动窗口设置为3s
 */
@HystrixCommand(
        commandProperties = {
                //统计窗口时间的设置
                @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "8000"),
                //统计窗口内的最小请求数
                @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "2"),
                //统计窗口内错误请求阈值的设置 50%
                @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),
                //自我修复的活动窗口时间
                @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "3000")
        }
)

我们上述通过注解进行的配置也可以配置在配置文件中:

# 配置熔断策略:
hystrix:
  command:
    default:
      circuitBreaker:
        # 强制打开熔断器,如果该属性设置为true,强制断路器进入打开状态,将会拒绝所有的请求。默认false关闭的
        forceOpen: false
        # 触发熔断错误比例阈值,默认值50%
        errorThresholdPercentage: 50
        # 熔断后休眠时长,默认值5秒
        sleepWindowInMilliseconds: 3000
        # 熔断触发最小请求次数,默认值是20
        requestVolumeThreshold: 2
      execution:
        isolation:
          thread:
            # 熔断超时设置,默认为1秒
            timeoutInMilliseconds: 2000

基于springboot的健康检查观察跳闸状态(自动投递微服务暴露健康检查细节)

#springboot中暴露健康检查等断点接口
management:
  endpoints:
    web:
      exposure:
        exclude: *
  #暴露健康接口的细节
  endpoint:
    health:
      show-details: always

访问健康检查接口:http://localhost:9100/actuator/health

Hystrix 线程池队列配置案例:
有一次在生产环境,突然出现了很多笔还款单被挂起,后来排查原因,发现是内部系统调用时出现了Hystrix调用异常。在开发过程中,因为核心线程数设置的比较大,没有出现这种异常。放到了测试环境,偶尔有出现这种情况。

后来调整maxQueueSize属性,确实有所改善。可没想到在生产环境跑了一段时间后却又出现这种了情况,此时我第一想法就是去查看maxQueueSize属性,可是maxQueueSize属性是设置值了。

当时就比较纳闷了,为什么maxQueueSize属性不起作用,后来通过查看官方文档发现Hystrix还有一个queueSizeRejectionThreshold属性,这个属性是控制队列最大阈值的,而Hystrix默认只配置了5个,因此就算我们把maxQueueSize的值设置再大,也是不起作用的。两个属性必须同时配置

hystrix:
  threadpool:
    default:
      coreSize: 10 #并发执行的最大线程数,默认10
      maxQueueSize: 1000 #BlockingQueue的最大队列数,默认值-1
      queueSizeRejectionThreshold: 800 #即使maxQueueSize没有达到,达到queueSizeRejectionThreshold该值后,请求也会被拒绝,默认值5

正确的配置案例:

  • 将核心线程数调低,最大队列数和队列拒绝阈值的值都设置大一点:
hystrix:
  threadpool:
    default:
      coreSize: 10 
      maxQueueSize: 1500
      queueSizeRejectionThreshold: 1000 

第 4 节 Feign远程调用组件

在之前的案例中,服务消费者调用服务提供者的时候使用RestTemplate技术。
在这里插入图片描述

4.1 Feign简介

Feign是Netflix开发的一个轻量级RESTful的HTTP服务客户端(用它来发起请求,远程调用的),是以Java接口注解的方式调用Http请求,而不用像Java中通过封装HTTP请求报文的方式直接调用,Feign被广泛应用在Spring Cloud 的解决方案中。

类似于Dubbo,服务消费者拿到服务提供者的接口,然后像调用本地接口方法一样去调用,实际发出的是远程的请求。

Feign可帮助我们更加便捷,优雅的调用HTTP API:不需要我们去拼接url然后呢调用restTemplate的api,在SpringCloud中,使用Feign非常简单,创建一个接口(在消费者–服务调用方这一端),并在接口上添加一些注解,代码就完成了

SpringCloud对Feign进行了增强,使Feign支持了SpringMVC注解(OpenFeign)

本质:封装了Http调用流程,更符合面向接口化的编程习惯,类似于Dubbo的服务调用

4.2 Feign配置应用

在服务调用者工程(消费)创建接口(添加注解)
(效果)Feign = RestTemplate+Ribbon+Hystrix

  • 服务消费者工程(页面静态化微服务)中引入Feign依赖(或者父类工程)

    <!-- Feign依赖       -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    
  • 服务消费者工程(静态化微服务)启动类使用注解@EnableFeignClients添加Feign支持

    @SpringBootApplication
    @EnableEurekaClient //@EnableDiscoveryClient
    @EnableFeignClients // 开启Feign 
    public class PageApplication {
        public static void main(String[] args) {
            SpringApplication.run(PageApplication.class);
        }
    }
    

    注意:此时去掉Hystrix熔断的支持注解@EnableCircuitBreaker即可包括引入的依赖,因为Feign会自动引入

  • 在消费者微服务中创建Feign接口

    package com.hyq.page.feign;
    
    import com.hyq.common.pojo.Products;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @FeignClient("hyq-service-product")
    public interface ProductFeign {
        /**
         * 通过id获取商品信息
         * @param id
         * @return
         */
        @RequestMapping("/product/query/{id}")
        public Products query(@PathVariable Integer id);
        /**
         * 获得端口号
         * @return
         */
        @RequestMapping("/server/query")
        public String findServerPort();
    }
    

    注意:

    1. @FeignClient注解的name属性用于指定要调用的服务提供者名称,和服务提供者yml文件中spring.application.name保持一致
    2. 接口中的接口方法,就好比是远程服务提供者Controller中的Hander方法(只不过如同本地调用了),那么在进行参数绑定的时,可以使用@PathVariable、@RequestParam、@RequestHeader等,这也是OpenFeign对SpringMVC注解的支持,但是需要注意value必须设置,否则会抛出异常
    3. @FeignClient(name = “hyq-service-product”),name在消费者微服务中只能出现一次。(升级Spring Boot 2.1.0 Spring Cloud Greenwich.M1 版本后,在2个Feign接口类内定义相同的名字, @FeignClient(name = 相同的名字 就会出现报错,在之前的版本不会提示报错),所以最好将调用一个微服务的信息都定义在一个Feign接口中
  • 改造PageController中原有的调用方式

    package com.hyq.page.controller;
    
    import com.hyq.common.pojo.Products;
    import com.hyq.page.feign.ProductFeign;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.cloud.client.ServiceInstance;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.List;
    
    @RestController
    @RequestMapping("/page")
    public class PageController {
    
        @Autowired
        private ProductFeign productFeign ;
    
        @GetMapping("/getData/{id}")
        public Products findDataById(@PathVariable Integer id) {
            return productFeign.query(id);
        }
    
        @RequestMapping("/getPort")
        public String getProductServerPort() {
            return productFeign.findServerPort();
        }
    }
    

4.3 Feign对负载均衡的支持

Feign 本身已经集成了Ribbon依赖和自动配置,因此我们不需要额外引入依赖,可以通过
ribbon.xx 来进 行全局配置,也可以通过服务名.ribbon.xx 来对指定服务进行细节配置配置(参考之前,此处略)

Feign默认的请求处理超时时长1s,有时候我们的业务确实执行的需要一定时间,那么这个时候,我们就需要调整请求处理超时时长,Feign自己有超时设置,如果配置Ribbon的超时,则会以Ribbon的为准

hyq-service-product:
  ribbon:
    ConnectTimeout: 2000 #请求连接超时时间
    ReadTimeout: 5000 #请求处理超时时间
    OkToRetryOnAllOperations: true #对所有操作都进行重试
    ####根据如上配置,当访问到故障请求的时候,它会再尝试访问一次当前实例(次数由MaxAutoRetries配置),
    ####如果不行,就换一个实例进行访问,如果还不行,再换一次实例访问(更换次数由MaxAutoRetriesNextServer配置),
    ####如果依然不行,返回失败信息。
    MaxAutoRetries: 0 #对当前选中实例重试次数,不包括第一次调用
    MaxAutoRetriesNextServer: 0 #切换实例的重试次数
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #随机策略

4.4 Feign对熔断器的支持

  1. 在Feign客户端工程配置文件(application.yml)中开启Feign对熔断器的支持

    # 开启Feign的熔断功能
    feign:
      hystrix:
        enabled: true
    

    Feign的超时时长设置那其实就上面Ribbon的超时时长设置
    Hystrix超时设置(就按照之前Hystrix设置的方式就OK了)
    注意:

    1. 开启Hystrix之后,Feign中的方法都会被进行一个管理了,一旦出现问题就进入对应的回退逻辑处理
    2. 针对超时这一点,当前有两个超时时间设置(Feign/hystrix),熔断的时候是根据这两个时间的最小值来进行的,即处理时长超过最短的那个超时时间了就熔断进入回退降级逻辑
    # 配置熔断策略:
    hystrix:
      command:
        default:
          circuitBreaker:
            # 强制打开熔断器,如果该属性设置为true,强制断路器进入打开状态,将会拒绝所有的请求。默认false关闭的
            forceOpen: false
            # 触发熔断错误比例阈值,默认值50%
            errorThresholdPercentage: 50
            # 熔断后休眠时长,默认值5秒
            sleepWindowInMilliseconds: 3000
            # 熔断触发最小请求次数,默认值是20
            requestVolumeThreshold: 2
          execution:
            isolation:
              thread:
                # 熔断超时设置,默认为1秒
                timeoutInMilliseconds: 2000
    
  2. 自定义FallBack处理类(需要实现FeignClient接口)

    package com.hyq.page.feign.fallback;
    
    import com.hyq.common.pojo.Products;
    import com.hyq.page.feign.ProductFeign;
    import org.springframework.stereotype.Component;
    
    @Component
    public class ProductFeignFallBack implements ProductFeign {
        @Override
        public Products query(Integer id) {
            return null;
        }
    
        @Override
        public String findServerPort() {
            return "-1";
        }
    }
    
    @FeignClient(name = "hyq-service-product" , fallback = ProductFeignFallBack.class)
    public interface ProductFeign {
     //其余代码无变动  省略
    }
    

4.5 Feign对请求压缩和响应压缩的支持

Feign 支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。通过下面的参数 即可开启请求与响应的压缩功能:

feign:
  hystrix:
    enabled: true
  #开启请求和响应压缩
  compression:
    request:
      mime-types: text/html,application/xml,application/json # 设置压缩的数据类型,此处也是默认值
      enabled: true # 默认不开启
      min-request-size: 2048 # 设置触发压缩的大小下限,此处也是默认值
    response:
      enabled: true # 默认不开启

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/66578.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CCES软件做开发,如果仿真器连不进目标板怎么解决?(Failed to connect to processor)

ADI的DSP调试&#xff0c;我在Visual DSP软件下写过一个详细的帖子&#xff0c;来说明仿真器如果连不进目标板&#xff0c;可能存在的几种问题以及解决办法&#xff0c;现在在CCES软件下遇到了同样的问题&#xff0c;所以准备再写一个帖子说明一下。 我们都知道ADI的DSP&#…

智慧工地管理平台系统厂家哪家强|喜讯科技

喜讯科技针对施工现场涉及面广&#xff0c;多种元素交叉&#xff0c;状况较为复杂&#xff0c;如人员出入、机械运行、物料运输等工程项目管理在一定程度上存在着决策层看不清、管理层管不住、执行层做不好的问题。 围绕施工现场管理&#xff0c;构建全方位的智能监控防范体系弥…

Redis——Linux下安装以及命令操作

一、概述 redis是什么&#xff1f; Redis&#xff08;Remote Dictionary Server )&#xff0c;即远程字典服务 是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库&#xff0c;并提供多种语言的API。 是一款高性能的NOSQL系列的非关系型…

每日一题:冒泡排序

每日一题&#xff1a;冒泡排序每日一题:冒泡排序第一种写法&#xff1a;第二种写法&#xff1a;每日一题:冒泡排序 冒泡排序是八大排序中较为简单的一种&#xff0c;具体详细可见&#xff1a;冒泡排序_百度百科 (baidu.com) 我们重点来看冒泡排序的步骤&#xff1a; 冒泡排序…

程序员如何写游戏搞钱?

ConcernedApe&#xff0c;一个叫做Eric Barone的程序员研发了一款叫做星露谷的小游戏&#xff0c;以乡村经营生活为核心&#xff0c;打造了一个虚拟的小世界&#xff0c;在这个小世界&#xff0c;你可以种植农作物&#xff0c;经营农场并挖矿钓鱼。 其中钓鱼的玩法是十分新颖的…

Git常见问题

1.拉取的项目很大&#xff0c;如1G以上&#xff0c;此时报错early EOF 具体报错如下&#xff1a; Cloning into csp-doc... remote: Counting objects: 6061, done. remote: Compressing objects: 100% (4777/4777), done. error: RPC failed; curl 18 transfer closed with …

Spring - FactoryBean扩展实战_MyBatis-Spring 启动过程源码解读

文章目录PrePreMyBatis-Spring 组件扩展点org.mybatis.spring.SqlSessionFactoryBeanInitializingBean扩展接口 afterPropertiesSetFactoryBean 扩展接口 getObjectApplicationListener扩展接口 onApplicationEvent扩展点org.mybatis.spring.mapper.MapperFactoryBeanSqlSessio…

【Linux基本命令归纳整理】

Linux 是一套免费使用和自由传播的类 Unix 操作系统&#xff0c;是一个基于 POSIX 和 UNIX 的多用户、多任务、支持多线程和多 CPU 的操作系统。严格来讲&#xff0c;Linux 这个词本身只表示 Linux 内核&#xff0c;但实际上人们已经习惯了用 Linux 来形容整个基于 Linux 内核&…

Day40——Dp专题

文章目录三、01背包8.分割等和子集9.最后一块石头的重量 II10.目标和11. 一和零三、01背包 8.分割等和子集 题目链接&#xff1a;416. 分割等和子集 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a;我们构造两个子集使得两个子集的和相等&#xff0c;其实就是让我…

JavaScript:初始JS 以及 基础语法

前端三件套&#xff1a; HTML: 生成网页控件 例如&#xff1a;生成 文本框 多选框 下拉列表 等 (人的身体) CSS: 修饰网页上的控件 例如&#xff1a;修饰文本框为圆形 &#xff08;人的衣服&#xff09; JavaSript: 在这些控件上添加逻辑 例如&#xff1a;获取文本框的值 然…

哈工大体系结构lab3 —— 流水线处理器的verilog实现

流水线处理器的verilog实现 是的我刚刚验收完最后一个实验&#xff0c;所以怀着激动的心情&#xff0c;把当时其中一个留档的代码发出来&#xff0c;还算较为清晰&#xff0c;仅供没有思路的同学参考。造完cache&#xff0c;我的生活终于可以恢复正轨了&#xff0c;这几天折磨的…

web安全之SQL盲注的靶场练习和分析

目录 SQL盲注-报错回显盲注 SQL盲注-时间盲注 SQL盲注-布尔盲注 SQL盲注-报错回显盲注 在burp里面进行动态抓包&#xff0c;判断符号闭环&#xff0c;如图明显为闭环 列数3时报错&#xff0c;判断当前列数为2 强行报错注入 &#xff0c;如图获取到版本号 uname1212 unio…

h5视频落地页知识点整理

一、视频在苹果中自动播放&#xff08;借助微信SDK&#xff09; 1.引入微信SDK <script src"http://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script> 2. document.addEventListener(WeixinJSBridgeReady, function() { const timer setInte…

如何签署exe或Windows应用程序?

本文您将了解为什么要签署Windows应用程序以及如何签署EXE或Windows应用程序的步骤指南。 代码签名是一种确保软件来自经过验证的正版软件发行商的方法。使用代码签名证书唱WindowsEXE文件可确保可执行文件或Windows应用程序不会被恶意行为者更改或修改。 Windows应用程序签名…

2022年NPDP新版教材知识集锦--【第五章节】(2)

《产品经理认证(NPDP)知识体系指南(第2版)》已于2022年4月正式上架发行&#xff0c;新版教材自2022年11月NPDP考试起使用。将新版NPDP教材中的相关知识点进行了整理汇总&#xff0c;包括详细设计与规格阶段相关内容&#xff0c;快来看看吧。 【市场研究工具】(全部内容获取文末…

华为机试 - 无向图染色

目录 题目描述 输入描述 输出描述 用例 题目解析 算法源码 题目描述 给一个无向图染色&#xff0c;可以填红黑两种颜色&#xff0c;必须保证相邻两个节点不能同时为红色&#xff0c;输出有多少种不同的染色方案&#xff1f; 输入描述 第一行输入M(图中节点数) N(边数) …

使用reshape2 R包进行在线长数据和宽数据相互转化

数据是数据分析的基础。我们常见的数据一般存储在excel表格&#xff0c;或者txt文档中。今天我们来看看长数据和宽数据&#xff0c;以及如何进行两者之间的相互转换。 1&#xff0e;宽数据和长数据 宽数据 如图1所示&#xff0c;宽数据是我们最常见的数据存储形式&#xff0c…

[附源码]Python计算机毕业设计Django校园订餐管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

nginx配置文件组成

1.配置文件的组成 ​ 配置文件由全局块events块http块组成 1.1 全局块 ​ 从配置文件开始到events之间的内容&#xff0c;主要会设置一些影响Nginx服务器整体运行的配置指令&#xff0c;主要包括配置运行Nginx服务器的用户(组)、允许生成的worker process数&#xff0c;进程pid…

Java数据结构与Java算法学习Day06---堆(简略笔记记录)

目录 一、堆 96 1.1堆的定义 96 1.2堆的API设计 97 1.3堆---堆的插入方法 98 1.4堆---堆的删除最大元素方法 99 1.5堆---堆的测试 100 二、堆排序 101 2.1堆排序 101 一、堆 96 1.1堆的定义 96 堆实际上也是利用数据结构实现的&#xff0c;用树实现的特殊结构&…