• 89-Spring Cloud 微服务详解


    Spring Cloud 微服务详解

    微服务架构:
    在说明该架构之前,再次的说明互联网应用架构演进(虽然之前说明过了)
    随着互联网的发展,用户群体逐渐扩大,网站的流量成倍增长,常规的单体架构已无法满足请求压力 和业务的快速迭代
    架构的变化势在必行,下面我们就以拉勾网的架构演进为例,从最开始的单体架构 分析,⼀步步的到现在的微服务架构
    比如早期的淘宝:LAMP(Linux、Apache、MySQL、PHP)
    后面的可能比较繁琐,可以看看79章博客的简单的说明(微服务是类似于RPC基础上的升级)
    单体应用架构:
    在诞生之初,拉勾的用户量、数据量规模都比较小
    项目所有的功能模块都放在一个工程中编码、编译、打包并且部署在一个Tomcat容器中的架构模式就是单体应用架构
    这样的架构既简单实用、便于维护,成本又低,成为了那个时代的主流架构方式

    在这里插入图片描述

    在这里插入图片描述

    优点:
    高效开发:项目前期开发节奏快,团队成员少的时候能够快速迭代
    架构简单:MVC架构,只需要借助IDE开发、调试即可
    易于测试:只需要通过单元测试或者浏览器完成
    易于部署:打包成单⼀可执行的jar或者打成war包放到容器内启动
    单体架构的应用比较容易部署、测试, 在项目的初期,单体应用可以很好地运行
    然而,随着需求的不断增加, 越来越多的人加入开发团队,代码库也在飞速地膨胀
    慢慢地,单体应用变得越来越臃肿,可维护性、灵活性逐渐降低,维护成本越来越高,特别的是打开idea初始化时非常的慢
    缺点:
    可靠性差: 某个应用Bug,例如死循环、内存溢出等, 可能会导致整个应用的崩溃
    复杂性高: 以一个百万行级别的单体应用为例
    整个项目包含的模块多、模块的边界模糊、 依赖关系不清晰、 代码质量参差不齐、 混乱地堆砌在一起。使得整个项目非常复杂
    扩展能力受限: 单体应用只能作为一个整体进行扩展,无法根据业务模块的需要进行伸缩
    例如,应用中有的模块是计算密集型的,它需要强劲的CPU
    有的模块则是IO密集型的,需要更大的内存,由于这些模块部署在一起(同一个服务器,所以都要考虑)
    不得不在硬件的选择上做出妥协
    业务量上涨之后,单体应用架构进一步丰富变化
    比如应用集群部署、使用Nginx进行负载均衡、增加缓存服务器、增加文件服务器、数据库集群并做读写分离等
    通过以上措施增强应对高并发的能力、应对一定的复杂业务场景,但依然属于单体应用架构

    在这里插入图片描述

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

    在这里插入图片描述

    优点:
    系统拆分实现了流量分担,解决了一定并发问题 ,虽然单体可以做集群,但对机器来说,是不好的,且浪费一定空间
    总不能为了一部分的访问慢,而加上服务器吧
    可以针对不同模块进行优化
    方便水平扩展,负载均衡,容错率提⾼
    系统间相互独立(业务不在同一个服务器了,比如可以单独的考虑硬件了),互不影响,新的业务迭代时更加高效
    缺点:
    服务之间相互调用(基本是这样的),如果某个服务的端口或者ip地址发生改变,调用的系统得手动改变
    搭建集群之后,实现负载均衡比较复杂,如:内网负载,在迁移机器时会影响调用方的路 由,导致线上故障
    服务之间调用方式不统⼀,基于 httpclient 、 webservice ,接⼝协议不统⼀
    服务监控不到位:除了依靠端⼝、进程的监控,调用的成功率、失败率、总耗时等等这些监 控指标是没有的
    SOA应用架构:
    在做了垂直划分以后,模块随之增多,维护的成本在也变⾼
    ⼀些通用的业务和模块(如相同业务部分不同的模块)重复的越来越多
    为了解决上⾯提到的接口协议不统⼀、服务无法监控、服务的负载均衡,引入了阿里巴巴开源的 Dubbo
    ⼀款高性能、轻量级的开源Java RPC框架,可以和Spring框架无缝集成
    它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现
    SOA (Service-Oriented Architecture),即面向服务的架构,操作了RPC,所以以RPC为主
    根据实际业务(业务的拆分,实际上我们只是拆分出生产者,即产生数据的)
    把系统拆分成合适的、独立部署的模块,模块之间相互独立(通过Webservice/Dubbo等技术进行通信)
    优点:分布式、松耦合、扩展灵活、可重用
    缺点:服务抽取粒度较大(需要抽取很多)
    服务调用方和提供方耦合度较高(接口耦合度,因为要进行调用,需要对应的接口编写和对应)
    主要是接口对应,因为需要保证地址对应

    在这里插入图片描述

    微服务应用架构:
    微服务架构可以说是SOA架构的一种拓展,这种架构模式下它拆分粒度更小、服务更独立
    把应用拆分成为一个个微小的服务,不同的服务可以使用不同的开发语言和存储
    服务之间往往通过Restful等轻量级通信,微服务架构关键在于微小、独立、轻量级通信
    微服务是在 SOA 上做的升华粒度更加细致,微服务架构强调的⼀个重点是业务需要彻底的组件化和服务化
    通过通信降低了服务调用方和提供方耦合度较高,而不用去写接口对应了

    在这里插入图片描述

    微服务架构和SOA架构相似又不同:
    微服务架构和SOA架构很明显的一个区别就是服务拆分粒度的不同,但是对于拉勾的架构发展来说
    我们所看到的SOA阶段其实服务拆分粒度相对来说已经比较细了(超前哦!)
    所以上述拉勾SOA到拉勾微服务,从服务拆分上来说变化并不大,只是引入了相对完整的新一代Spring Cloud微服务技术
    自然,上述我们看到的都是拉勾架构演变的阶段结果,每一个阶段其实都经历了很多变化
    拉勾的服务拆分其实也是走过了从粗到细,并非绝对的一步到位
    举个拉勾案例来说明SOA和微服务拆分粒度不同:
    我们在SOA架构的初期,"简历投递模块"和"人才搜索模块"都有简历内容展示的需求,只不过说可能略有区别
    一开始在两个模块中各维护了一套简历查询和展示的代码,后续我们将服务更细粒度拆分
    拆分出简历基础服务,那么不同模块调用这个基础服务即可
    因为我们的SOA可能并不是非常的细,就如登录来说,一个登录我们操作SOA时,可能会给出多个生成者
    因为登录有验证,是否登录,等等操作,而微服务就是将他们再次的拆分,实际上SOA也可以
    所以说,微服务与SOA相似,那么为什么不(都)使用SOA继续拆分呢
    因为在SOA的情况下,既然有登录了,那么并不需要继续拆分,浪费人力成本
    而微服务,因为有他的各种更加细腻的功能,所以导致,拆分为更小的服务时,可以更好的维护
    虽然也浪费人力成本,但收获更大
    微服务架构体现的思想及优缺点:
    微服务架构设计的核心思想就是"微",拆分的粒度相对比较小
    这样的话单一职责、开发的耦合度就会降低、微小的功能可以独立部署扩展、灵活性强,升级改造影响范围小
    微服务架构的优点:微服务架构和微服务
    微服务很小,便于特定业务功能的聚焦
    微服务很小,每个微服务都可以被一个小团队单独实施(开发、测试、部署上线、运维)
    团队合作一定程度解耦,便于实施敏捷开发
    微服务很小,便于重用和模块之间的组装
    微服务很独立,那么不同的微服务可以使用不同的语言开发(因为基本是http访问),松耦合
    类似于比如Dubbo也可以操作不同的语言调用,因为好像只操作返回值
    微服务架构下,我们更容易引入新技术
    当然,并没有完美的架构,即通常都会有缺点,有失必有得
    微服务架构的缺点:
    微服务架构下,分布式复杂难以管理,当服务数量增加,管理将越加复杂
    微服务架构下,分布式链路跟踪难等
    而正是因为这些缺点,所以并不是必须要微服务,如并发少的,那么使用前三个架构即可解决
    当然,若是太多的,则可以使用微服务
    在79章Dubbo就说过,(完全分开,或者留下少数)分开后就是微服务,所以也的确分开了,但无论是不分开还是分开
    都会有对应的缺点,如SOA访问优化了RPC的平均,有提示,微服务虽然使得优化更加的细腻些,但更复杂等等
    微服务架构中的核心概念:
    服务注册与服务发现
    例如:职位搜索 ->(调用) 简历服务
    服务提供者(生产者):简历服务
    服务消费者:职位搜索
    服务注册:服务提供者将所提供服务的信息(服务器IP地址和端口、服务访问协议等)注册/登记到注册中心
    服务消费者一般也会注册
    服务发现:服务消费者能够从注册中心获取到较为实时的服务列表,然后根究一定的策略选择一个服务访问

    在这里插入图片描述

    在这里插入图片描述

    通过注册中心,消费者可以得到提供者的列表(一般包括提供者的ip地址,端口号等等信息,使得调用不会出错)
    从而可以选择操作对应的提供者
    通常该注册中心中该列表若有对应的服务宕机了(根据心跳)
    就会操作移除列表,使得消费者访问集群或者其他的服务时基本不会使得访问失败
    注意:这里的微服务可以看成是一台服务器,因为划分的更细了,当然也有可能不是,因为在一个服务器里面也是可以的
    但这样就使得没有更加细了
    负载均衡:
    负载均衡即将请求压力分配到多个服务器(应用服务器、数据库服务器等),以此来提高服务的性能、可靠性

    在这里插入图片描述

    熔断:
    熔断即断路保护,微服务架构中
    如果下游服务(如下面的调用服务B和调用服务C)因访问压力过大而响应变慢(如后面多次说明的延时)或失败
    上游服务为了保护系统整体可用性
    可以暂时切断对下游服务的调用,这种牺牲局部,保全整体的措施就叫做熔断
    假设服务C宕机了,那么一般操作服务B中的默认服务C数据或者自己的默认数据给服务A

    在这里插入图片描述

    链路追踪:
    微服务架构越发流行,一个项目往往拆分成很多个服务,那么一次请求就需要涉及到很多个服务
    不同的微服务可能是由不同的团队开发、可能使用不同的编程语言实现
    整个项目也有可能部署在了很多服务器上(甚至百台、千台)横跨多个不同的数据中心
    所谓链路追踪,就是对一次请求涉及的很多个服务链路进行日志记录、性能监控
    从而可以知道优化什么地方或者访问在那个地方出问题了等等操作或者信息

    在这里插入图片描述

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

    在这里插入图片描述

    Spring Cloud 综述:
    Spring Cloud 是什么:
    Spring Cloud是一系列框架的有序集合(框架集合,一般是有序的,即框架之间的执行顺序是有序的,这是必然的)
    因为一环通常套一环,虽然可能可以单独存在,但多个框架之间是有一定的联系来连接,从而出现执行顺序
    就如过滤器和Servlet一样,会先操作过滤器,而没有过滤器就操作Servlet
    这个Servlet就相当于单独存在,而加上过滤器就是多个了,上面只是一种解释而已
    它利用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 (或者说Spring官方) 吸收了Netflix公司的产品基础之上自己也搞了几个组件
    阿里巴巴在之前的基础上搞出了一堆微服务组件,如Spring Cloud Alibaba(SCA)
    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 (分布式消息传递平台)
    Spring Cloud 架构:
    如前所述,Spring Cloud是一个微服务相关规范,这个规范意图为搭建微服务架构提供一站式服务
    采用组件(框架)化机制定义一系列组件,各类组件针对性的处理微服务中的特定问题
    这些组件共同来构成Spring Cloud微服务技术栈
    Spring Cloud 核心组件:
    Spring Cloud 生态圈中的组件,按照发展可以分为第一代 Spring Cloud组件和第二代 Spring Cloud组件

    在这里插入图片描述

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

    在这里插入图片描述

    Spring Cloud中的各组件协同工作,才能够支持一个完整的微服务架构,比如:
    注册中心负责服务的注册与发现,很好将各服务连接起来
    API网关负责转发所有外来的请求
    断路器负责监控服务之间的调用情况,连续多次失败进行熔断保护
    配置中心提供了统一的配置信息管理服务,可以实时的通知各个服务获取最新的配置信息
    Spring Cloud 与 Dubbo 对比:
    Dubbo是阿里巴巴公司开源的一个高性能优秀的服务框架,基于RPC调用,对于目前使用率较高的
    对于Spring Cloud Netflix来说,它是基于HTTP的,且他的效率没有Dubbo高(并不是说http就比tcp慢,只是大多数来说tcp的形式更加快点,即他们并没有可比性),虽然dubbo效率通常高点,但是一个功能最少需要两个(项目),而微服务就单纯一个,并且也可以互相调用,这也是一个问题(不是主要的)
    其余主要问题在于Dubbo体系的组件不全,不能够提供一站式解决方案,比如服务注册与发现需要借助于Zookeeper等实现
    而Spring Cloud Netflix则是真正的提供了一站式服务化解决方案,且有Spring大家族背景
    前些年, Dubbo使用率高于SpringCloud,但目前Spring Cloud在服务化/微服务解决方案中已经有 了非常好的发展趋势

    在这里插入图片描述

    Spring Cloud 与 Spring Boot 的关系:
    Spring Cloud 只是利用了Spring Boot 的特点,让我们能够快速的实现微服务组件开发
    否则不使 用Spring Boot的话,我们在使用Spring Cloud时
    每一个组件的相关Jar包都需要我们自己导入配置以 及需要开发人员考虑兼容性等各种情况
    所以Spring Boot是我们快速把Spring Cloud微服务技术应用起 来的一种方式
    案例准备:
    本部分我们按照普通方式模拟一个微服务之间的调用,后续我们将一步步使用Spring Cloud的组件对案 例进行改造
    需求:

    在这里插入图片描述

    完整业务流程图:

    在这里插入图片描述

    案例数据库环境准备:
    这里使用mysql 5.7.x版本
    商品信息表:
    CREATE DATABASE lagou CHARACTER SET utf8;
    
    USE lagou;
    
    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) #商品类型 
    );
    
    INSERT INTO products (id,NAME,price,flag,goods_desc,images,goods_stock,goods_type)
    VALUE (1,'aa',1.1,1,1,1,1,1)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    案例工程:
    我们基于SpringBoot来构造工程环境,我们的工程模块关系如下所示:

    在这里插入图片描述

    父工程 lagou-parent:
    在Idea中新建一个项目,命名为lagou-parent(Maven项目)
    操作spring boot并不是一定要创建spring boot项目(自动加依赖),只需要有对应的依赖即可
    对应的依赖:
        
         <packaging>pompackaging>
        
    <parent>      
        <groupId>org.springframework.bootgroupId>        
        <artifactId>spring-boot-starter-parentartifactId>   
        <version>2.1.6.RELEASEversion> 
    parent>
    <dependencies>    
              
        <dependency>        
            <groupId>org.springframework.bootgroupId>    
            <artifactId>spring-boot-starter-webartifactId>   
        dependency>    
          
        <dependency>           
            <groupId>org.springframework.bootgroupId>      
            <artifactId>spring-boot-starter-loggingartifactId>
        dependency>     
           
        <dependency>          
            <groupId>org.springframework.bootgroupId>      
            <artifactId>spring-boot-starter-testartifactId>    
            <scope>testscope>       
        dependency>        
            
        <dependency>        
            <groupId>org.projectlombokgroupId>       
            <artifactId>lombokartifactId>       
            <version>1.18.4version>   
            <scope>providedscope>  
        dependency>   
            
        <dependency>
            <groupId>org.springframework.bootgroupId>       
            <artifactId>spring-boot-starter-actuatorartifactId> 
        dependency>     
            
        <dependency>        
            <groupId>org.springframework.bootgroupId>     
            <artifactId>spring-boot-devtoolsartifactId>     
            <optional>trueoptional>   
        dependency>
        dependencies>
     <build>      
         <plugins>    
               
             <plugin>           
                 <groupId>org.apache.maven.pluginsgroupId>   
                 <artifactId>maven-compiler-pluginartifactId>       
                 <configuration>              
                     <source>11source>    
                     <target>11target>        
                     <encoding>utf-8encoding>  
                 configuration>          
             plugin>         
                  
             <plugin>            
                 <groupId>org.springframework.bootgroupId>     
                 <artifactId>spring-boot-maven-pluginartifactId>    
                 <executions>            
                     <execution>        
                         <goals>        
                             <goal>repackagegoal>      
                         goals>       
                     execution>       
                 executions>          
             plugin>    
         plugins> 
    build>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    公共组件微服务(lagou-parent的子模块lagou-service-common):
    在公共组件微服务中引入数据库驱动及mybatis-plus:
    <dependencies>       
        <dependency>          
            <groupId>com.baomidougroupId>    
            <artifactId>mybatis-plus-boot-starterartifactId>   
            <version>3.3.2version>   
        dependency>         
            
        <dependency>           
            <groupId>javax.persistencegroupId>  
            <artifactId>javax.persistence-apiartifactId>  
            <version>2.2version>      
        dependency>
        <dependency>       
            <groupId>mysqlgroupId>    
            <artifactId>mysql-connector-javaartifactId>  
            <scope>runtimescope>      
        dependency>  
    dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    生成数据库实体类
    在java资源文件下,创建com包,然后再在里面创建lagou包,然后再在里面创建common包,然后再在里面创建pojo
    然后再在里面创建Products类,总体来说就是创建com.lagou.common.pojo.Products类
    除了最后面的Products是类外,其他的都是接口或者类,为了防止过于啰嗦的介绍,后面就以这样的进行说明
    我们可以使用idea自带的操作(一般是自带的,可能需要插件),点击如下:

    在这里插入图片描述

    生成表对应的类,然后自己检查修改即可,修改成如下:
    package com.lagou.common.pojo;
    
    
    import lombok.Data;
    
    import javax.persistence.Id;
    import javax.persistence.Table;
    
    @Data
    @Table(name = "products")
    public class Products {
    
      @Id
      private long id;
      private String name;
      private double price;
      private String flag;
      private String goodsDesc;
      private String images;
      private long goodsStock;
      private String goodsType;
    
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    商品微服务(后面我们也将lagou-service-product称为商品微服务,注意即可):
    商品微服务是服务提供者,页面静态化微服务是服务的消费者
    创建商品微服务lagou-service-product,继承lagou-parent
    对应的依赖:
     <dependencies>      
         <dependency>          
             <groupId>com.lagougroupId> 
             <artifactId>lagou-service-commonartifactId>    
             <version>1.0-SNAPSHOTversion>      
         dependency>  
    dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    在资源文件夹下,创建application.yml文件,内容如下:
    server:  
    	port: 9000  
    	# 后期该微服务多实例(一般有很多),9000(如10个以内),即每个微服务基本都会占用一个端口,所以最好大一点
    Spring:  
    	application:    
    		name: lagou-service-product  
    		#为项目起个名称,当然不一致也可以,只是见名知意而已,就如dubbo的名称一样,可以一致
    	datasource:    
    		driver-class-name: com.mysql.jdbc.Driver    
    		url: jdbc:mysql://localhost:3306/lagou?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC    
    		username: root    
    		password: 123456
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    创建一个启动类com.lagou.product.ProductApplication:
    package com.lagou.product;
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    /**
     *
     */
    @SpringBootApplication
    @MapperScan("com.lagou.product") 
    public class ProductApplication {
        public static void main(String[] args) {
            SpringApplication.run(ProductApplication.class,args);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    mapper层开发:
    在product包下,创建接口mapper.ProductMapper:
    package com.lagou.product.mapper;
    
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.baomidou.mybatisplus.core.mapper.Mapper;
    import com.lagou.common.pojo.Products;
    
    /*
    现在使用的Mybatis-plus组件(组件可以称为框架),该组件是Mybatis的加强版
    能够与SpringBoot进行非常友好的整合,对比Mybatis框架只有使用便捷的改变,没有具体功能的改变
    具体使用:让具体的Mapper接口继承BaseMapper即可 
    
    若需要更加的知道有什么作用,可以到77章博客进行学习
    */
    
    public interface ProductMapper extends BaseMapper<Products> {
        
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    serive层开发:
    在product包下,创建接口service.ProductService:
    package com.lagou.product.service;
    
    import com.lagou.common.pojo.Products;
    
    /**
     *
     */
    public interface ProductService {
    
        /**
         * 通过商品id查询商品信息
         * @param id
         * @return
         */
        public Products queryById(Integer id);
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    对应的实现类(实现类一般是在当前接口的impl包下的),名称一般是接口加上后缀"Impl",没有包或者该实现类,创建即可:
    package com.lagou.product.service.impl;
    
    import com.lagou.common.pojo.Products;
    import com.lagou.product.mapper.ProductMapper;
    import com.lagou.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;
    
        @Override
        public Products queryById(Integer id) {
            return productMapper.selectById(id);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    controller层开发:
    在product包下,创建类controller.ProductController:
    package com.lagou.product.controller;
    
    import com.lagou.common.pojo.Products;
    import com.lagou.product.service.ProductService;
    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;
    
    /**
     *
     */
    @RestController
    @RequestMapping("/product")
    public class ProductController {
    
        @Autowired
        private ProductService productService;
    
        @GetMapping("/query/{id}")
        public Products queryById(@PathVariable Integer id){
            return productService.queryById(id);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    启动启动类,访问"localhost:9000/product/query/1",发现得到了数据,至此操作成功
    页面静态化微服务(lagou-parent的子模块lagou-service-page)
    后面我们也将lagou-service-page称为静态化微服务或者页面静态化微服务,注意即可:
    对应的依赖:
    <dependencies>     
    	<dependency>           
            <groupId>com.lagougroupId>   
            <artifactId>lagou-service-commonartifactId>         
            <version>1.0-SNAPSHOTversion>     
        dependency>    
    dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    在资源文件夹下,创建application.yml文件,内容如下
    因为spring boot自动配置的原因,需要写上对应没有默认的配置,所以这里需要写上配置
    当然有默认的可以省略,比如下面的驱动:
    server:
      port: 9100 
      # 后期该微服务多实例(一般有很多),9100(如10个以内),即每个微服务基本都会占用一个端口,所以最好大一点
    Spring:
      application: 
        name: lagou-service-page
        #为项目起个名称,当然不一致也可以,只是见名知意而已,就如dubbo的名称一样,可以一致
      datasource: 
        driver-class-name: com.mysql.jdbc.Driver 
        url: jdbc:mysql://localhost:3306/lagou?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC 
        username: root 
        password: 123456
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    创建启动类com.lagou.page.PageApplication:
    package com.lagou.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,args);
        }
    
        //向容器中注入一个RestTemplete,封装了HttpClient
        @Bean //因为对应的注解@SpringBootApplication有配置类的注解,所以可以写上@Bean
        //扫描当前所在包和子包,那么自然也会扫描到自己
        public RestTemplate restTemplate(){
            return new RestTemplate();
            
            //RestTemplate是spring-web依赖里面的,即spring的类,一般spring-mvc依赖里面就有
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    在page包下,创建controller.PageController类:
    package com.lagou.page.controller;
    
    import com.lagou.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("/getProduct/{id}")
        public Products getProduct(@PathVariable Integer id){
            //发生Http请求给商品微服务,将id传递过去,获取到id所对应的products对象
            //通过restTemplate发生请求,得到数据
            String url = "http://localhost:9000/product/query/";
            //注意:这个http://需要写(必须写http://,其中https://不可以写,否则访问不了)
            //使得不是添加在当前项目路径后面访问访问(这里与//或者/无关,直接的加在后面,而不是使得跳级别)
            //好像只识别http://
            
            //将返回的json变成对应的对象,因为json也是该对象变成了,只是反过来而已
            Products forObject = restTemplate.getForObject(url + id, Products.class); //get的请求
    
            return forObject;
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    启动启动类,浏览器访问"localhost:9100/page/getProduct/1",发现得到了数据,至此操作成功
    当然可以操作Postman来进行测试,56章博客里有
    我们可以发现,我们访问他,然后他去访问其他服务得到信息,有点像dubbo一样的操作
    案例代码问题分析:
    我们在页面静态化微服务中使用RestTemplate调用商品微服务的商品状态接口时(Restful API 接口)
    在微服务分布式集群环境下会存在什么问题呢?怎么解决?
    存在的问题:
    1:在服务消费者中,我们把url地址硬编码到代码中,不方便后期维护
    2:服务提供者只有一个服务,即便服务提供者形成集群,服务消费者还需要自己实现负载均衡
    3:在服务消费者中,不清楚服务提供者的状态
    4:服务消费者调用服务提供者时候,如果出现故障能否及时发现不向用户抛出异常页面?
    5:RestTemplate这种请求调用方式是否还有优化空间?能不能类似于Dubbo那样玩?
    6:这么多的微服务统一认证如何实现?
    7:配置文件每次都修改好多个很麻烦!?
    上述分析出的问题,其实就是微服务架构中必然面临的一些问题:
    我们通常使用下面几种情况对应来解决上面的情况,当然并不是必须对应的
    1:服务管理:自动注册与发现、状态监管
    2:服务负载均衡
    3:熔断
    4:远程过程调用
    5:网关拦截、路由转发
    6:统一认证
    7:集中式配置管理,配置信息实时自动更新
    这些问题,Spring Cloud 体系都有解决方案,后续我们会逐个学习
    第一代 Spring Cloud 核心组件(Netflix,SCN):
    说明:上面提到网关组件Zuul性能一般,未来将退出Spring Cloud 生态圈,所以我们直接讲解GateWay
    在课程章节规划时,我们就把GateWay划分到第一代Spring Cloud 核心组件这一部分了
    各组件整体结构如下:

    在这里插入图片描述

    从形式上来说,Feign一个顶三,Feign = RestTemplate + Ribbon + Hystrix

    在这里插入图片描述

    服务降级(一般在熔断后给出的操作),一般可以理解为给你一个默认值,或者其他的使得变得简单的方式来释放资源等等
    Eureka服务注册中心:
    常用的服务注册中心:Eureka、Nacos、Zookeeper、Consul
    关于服务注册中心:
    注意:服务注册中心本质上是为了解耦服务提供者和服务消费者
    服务消费者 --> 服务提供者
    服务消费者 --> 服务注册中心 --> 服务提供者
    对于任何一个微服务,原则上都应存在或者支持多个提供者(比如商品微服务部署多个实例),这是由微服务的分布式属性决定的
    更进一步,为了支持弹性扩、缩容特性,一个微服务的提供者的数量和分布往往是动态变化的,也是无法预先确定的
    因此,原本在单体应用阶段常用的静态LB机制就不再适用了,需要引入额外的组件来管理微服务提供者的注册与发现
    而这个组件就是服务注册中心
    注册中心实现原理:

    在这里插入图片描述

    在这里插入图片描述

    分布式微服务架构中,服务注册中心用于存储服务提供者地址信息、服务发布相关的属性信息
    消费者通过主动查询和被动通知的方式获取服务提供者的地址信息,而不再需要通过硬编码方式得到提供者的地址信息
    消费者只需要知道当前系统发布了那些服务,而不需要知道服务具体存在于什么位置,这就是透明化路由
    具体的步骤(注册中心是看成已经启动的):
    1:服务提供者启动
    2:服务提供者将相关服务信息主动注册到注册中心
    3:服务消费者获取服务注册信息
    通常情况下,消费者也需要注册到注册中心的,但那是内部的,外部的调用一般是不会注册
    实际上也要看你是否想要注册到里面去,一般情况下都会注册(虽然前面操作Zookeeper时,消费者没有注册):
    pull模式:服务消费者可以主动拉取可用的服务提供者清单
    push模式:服务消费者订阅服务(当服务提供者有变化时,注册中心也会主动推送更新后的服务清单给消费者)
    4:服务消费者直接调用服务提供者
    另外,注册中心也通常需要完成服务提供者的健康监控,当发现服务提供者失效时需要及时剔除
    主流服务中心对比:
    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定理(又称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
    那么为什么不能同时满足呢,具体的介绍80章博客里有,这里就不做说明了,可以使用ctrl+f,然后输入CAP查找即可
    注意:只是不能同时而已,实际上可以分开进行
    服务注册中心组件 Eureka :
    服务注册中心的一般原理、对比了主流的服务注册中心方案,目光聚焦Eureka
    Eureka 基础架构:

    在这里插入图片描述

    Eureka 交互流程及原理:

    在这里插入图片描述

    在这里插入图片描述

    Eureka 包含两个组件: Eureka Server (集群)和 Eureka Client(注册者,即上面的Application Client或者Application Service)
    Eureka Client是一个Java客户端,用于简化 与Eureka Server的交互
    Eureka Server提供服务发现的能力,各个微服务启动时
    会通过Eureka Client向Eureka Server 进行注册自己的信息(例如网络信息,如ip地址,端口号等等)
    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秒,若90秒还没有回应(即续约,不是下线的情况,下线就直接没有了)
    会将还没有续约的给剔除,以更新自己的信息
    这个90秒一般是客户端(客户端在这里对注册中心来说一般是消费者和提供者,统称为注册者)的访问
    虽然注册中心也可以是客户端(也回应)
    没有访问会通知注册中心,从而剔除对应的提供者
    5:Eureka Server在一定时间内没有接收到某个微服务节点的心跳, Eureka Server将会注销该微 服务节点(默认90秒)
    6:每个Eureka Server同时也是Eureka Client,也需要注册到所有对方里面
    因为多个Eureka Server之间通过复制的方式完成服务 注册列表的同步,自然需要对应的信息(如ip地址,端口号)来完成同步操作
    所以互相之间也可以称为Eureka Client
    但实际上却并不是注册得到同步的信息(如ip地址,端口号)
    而是一开始的列表操作的,有对应的信息了(如ip地址,端口号),后面说明两个true时就知道了
    即分开操作高可用和一致性,而复制时,那一瞬间是会出现高可用不了的,不能同时存在,复制后,那么就基本只有高可用了
    7:Eureka Client会缓存Eureka Server中的信息
    即使所有的Eureka Server节点都宕掉,服务消 费者依然可以使用缓存中的信息找到服务提供者
    一般缓存在本地,通常在访问不了Eureka Server节点时,才会使用缓存的,否则一般使用访问的
    且每次的读取信息,一般都会缓存,但统称都是根据续约(回应)时间来缓存的,而不是根据读取
    Eureka通过心跳检测、健康检查和客户端缓存等机制,提高系统的灵活性、可伸缩性和高可用性
    高可用:基本任何时候,我们都可以访问
    且可以得到正确的数据(通常一致性后的访问,因为在复制中间的这段时间,可能数据不高可用)
    一致性:基本任何时候,对应的集群的信息都是同步的,由于保持一致性,那么对应的就不会读取
    为了更加的解释高可用和一致性不能共存的原因,来个简单的解释:
    假设有服务器a和服务器b,简称为a,b,他们是集群
    如果是高可用,那么我们给a加数据,那么这时b读取数据,必然不是同步的,即这个时候是不一致
    如果是一致,那么我们给a加数据,那么这时b读取必然不能给读,必须复制后才可,即这个时候不可用
    那么操作之后,不就是共存了吗,实际上只是结果共存,高可用和一致性,并不是对结果来说明的,而是对操作来说明的
    即整体来说,就是不会共存
    因为有不共存的地方,这里是延时(服务器是可以访问的,即没有程序方向),当然挂掉或者不可连接,也是不能共存的
    具体在80章博客已经说明了挂掉,不可连接与挂掉基本是一样的,所以不可连接就没有解释了
    搭建单例Eureka Server服务注册中心 :
    实现过程:
    1:单实例Eureka Server—>访问管理界面
    2:服务提供者(商品微服务注册到集群)
    3:服务消费者(页面静态化微服务注册到Eureka/从Eureka Server获取服务信息)
    4:完成调用
    搭建Eureka Server服务lagou-cloud-eureka(是lagou-parent工程的子模块)
    lagou-parent中引入Spring Cloud 依赖:
    Spring Cloud 是一个综合的项目,下面有很多子项目,比如这里的eureka子项目
    对应的依赖:
    
    <dependencyManagement>       
        <dependencies>          
            <dependency>         
                <groupId>org.springframework.cloudgroupId>    
                <artifactId>spring-cloud-dependenciesartifactId>   
                
                <version>Greenwich.RELEASEversion>   
                
                <type>pomtype>              
                <scope>importscope>     
                 
            dependency>      
        dependencies>   
    dependencyManagement>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    我们发现,他的版本是Greenwich.RELEASE,一般需要根据如下图来对应(spring的官网,以后会改变的):

    在这里插入图片描述

    即,你的Spring Boot是什么的版本,那么这个Spring Cloud 依赖版本就要对应
    这是兼容性的问题,虽然可以不对应,但可能在某些方面会出现问题,如启动报错,所以最好还是对应
    具体为什么,大概是里面包含的依赖冲突问题吧,只要版本对应,基本不会发生依赖冲突(会报错的冲突,因为可能因为冲突,导致版本不一致,使得依赖之间可能导致不支持,从而报错,在70章博客有具体说明)
    其中可能并没有其他对应的依赖了,所以这里使用Greenwich
    这时,我们需要将Spring Boot版本改成2.1.x版本,如2.1.6.RELEASE版本
    否则的话,可能出现问题,如启动报错,因为兼容性的问题
    注意:导入时,根据名称来的,我们可以看上面的首字母,对应D,E,F,G,H,I(i的大写,不是l的大写L),J
    仔细观察,iIlL(有明显的不同)
    lagou-cloud-eureka工程pom.xml中引入依赖:
    <dependencies>   
         
        <dependency>  
            <groupId>org.springframework.cloudgroupId> 
            <artifactId>spring-cloud-starter-netflix-eureka-serverartifactId> 
            
                    
        dependency>
    dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    注意:这里要在父工程的pom文件中手动引入jaxb的jar
    因为Jdk9之后(像这样说明的通常包括本身,即这里包括Jdk9)默认没有加载该模块,或者不包括了
    但Eureka Server却使用到了,所以需要手动导入
    否则EurekaServer服务可能无法启动(通常启动不了,且使用@EnableEurekaServer注解的情况下)
    所以若你的Jdk版本是Jdk9之前的,一般就不需要引入(但是之前的可能也没有,所以最好也进行引入,因为随着时间的推移,可能会有所改动,虽然基本不会,测试一下就知道了,如果仍然启动不了,再加上就可以了)
    但之所以说是这里,是因为该版本没用jaxb依赖,如果是其他版本可能会自带
    比如Eureka Server的3.0.0版本就自带了,那么他就可以不用写(对应于cloud使用2020.0.0版本,在97章博客里操作的就是这个)
    这里需要注意:通过测试,实际上是@EnableEurekaServer注解需要使用jaxb,所以我们也认为需要导入Eureka Server
    即只要不是操作该@EnableEurekaServer注解,那么就不会可能无法启动
    所以我们也认为Eureka Server需要使用jaxb,而Eureka Client不需要(因为没有@EnableEurekaServer注解)
    父工程lagou-parent:
     
    <dependency>   
        <groupId>com.sun.xml.bindgroupId> 
        <artifactId>jaxb-coreartifactId> 
        <version>2.2.11version> 
    dependency> <dependency>  
        <groupId>javax.xml.bindgroupId> 
        <artifactId>jaxb-apiartifactId> 
    dependency> <dependency>   
        <groupId>com.sun.xml.bindgroupId>  
        <artifactId>jaxb-implartifactId> 
        <version>2.2.11version> 
    dependency> 
    <dependency>  
        <groupId>org.glassfish.jaxbgroupId>  
        <artifactId>jaxb-runtimeartifactId> 
        <version>2.2.10-b140310.1920version>
    dependency> <dependency>  
        <groupId>javax.activationgroupId> 
        <artifactId>activationartifactId>  
        <version>1.1.1version>
    dependency>
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    回到lagou-cloud-eureka工程,在资源文件下
    创建application.yml文件(一般是创建配置文件的,那么说明是在resources资源文件里,而不是java资源文件里)
    #Eureka server服务端口
    server:
      port: 9200
    spring:
      application: 
        name: lagou-cloud-eureka # 应用名称,会在Eureka中作为服务的id标识(serviceId)
    eureka:  #自己注册中心,也是可以看成一个客户端的
      instance: 
        hostname: localhost  #表示当前eureka实例的主机名为localhost,一般该名称可以随便写,但通常写localhost
      #Eureka server本身也是eureka的一个客户端,因为在集群下
      #需要与其他的Eureka server进行同步,所以需要初始化注册
      #下面就是注册的配置,操作对应的集群,虽然这里是单例的
      client: #这里记得要看成是客户端,而不是服务端,页面的出现是因为对应的注解造成的,而不是这里
        service-url: # 客户端与EurekaServer交互的地址,如果是集群,也需要写其它Server的地址,这里就指定了自己
          defaultZone: http://localhost:9200/eureka/ 
          #该地址要存在,且对应的到端口的可以访问,即可以访问http://localhost:9200/
          #后面的eureka/ 是为了注册的,不写,不会注册,当然,既然是访问,那么结尾的/可以去掉
          #并且必须写至少一个地址,只要不满足,则启动时会报错,所以不写或者写了但不存在,则报错
          #若是不写地址出现的报错(service-url:后面的不写,即也包括defaultZone)
          #会导致关闭,需要重新启动(没有对应的页面)
          #若是写的或者删掉该属性(service-url:下面的,也包括自己)导致的报错
          #只是没有注册的提示而已,所以并不会关闭,有对应的页面
          #写的报错会监听,即只要对方启动了,那么自然会注册,只是报错提示一下
          #与热部署无关,热部署是我们改变而重新部署的,而这里他并没有改变,就注册了,所以与热部署无关
          #经过测试,删除热部署代码时,也操作了监听,所以的确无关
          #所以无论是client还是server(他们里面的client,如这里的注册中心和后面的客户端)都不需要重启
          #只需要对方启动即可(即互相有心跳,实际上通常只是客户端心跳服务端而已),其中客户端直接也分为客户端和服务端,但对比注册者来说都是客户端
        #因为指定自己,所以下面就都设置为false,如果是集群的话
        #一般就会设置为true来使得操作同步
        #但单机的话,我们就不需要同步了,所以设置为false,虽然true也可,但没必要,虽然也可以自己注册自己
        #表示是否向Eureka中心(defaultZone指定的地址)注册自己的信息
        #因为自己就是服务,所以不需要注册(虽然没有显示本身,但并不需要),默认为true,这里设置为false 
        register-with-eureka: false 
        #是否获取服务注册列表信息,列表可以得到,但不能获取列表的信息
        #因为自己就是服务,所以不需要获取服务信息,默认为true,这里设置为false
        fetch-registry: false 
        
    
        #对于同步来说,register-with-eureka和fetch-registry这两个信息与同步无关,只与defaultZone对应的列表有关
        #他们两个只是操作注册的,而不是操作同步的
        #这就是为什么需要写上正确的地址的原因,当有人注册时,他会同步到所有的defaultZone列表
        #这也导致了,若集群中,只要有一个是完整的列表(被客户端注册的那个服务器)
        #那么其他的集群就算不是完整的也会同步
        #当然,第一个的注册的true会有对应的显示的,设置为false,则没有对应的显示了
        #但对于同步在集群时,需要将register-with-eureka设置为true,来使得注册自己
        
        #即后面的两个设置相当于是操作客户端的注册,就算不写也是没有问题的(有默认),如后面说明的客户端
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    编写启动类com.lagou.eureka.EurekaApplication,声明当前服务为Eureka注册中心
    package com.lagou.eureka;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
    
    /**
     *
     */
    @SpringBootApplication
    //表示当前项目是Eureka Server
    @EnableEurekaServer
    public class EurekaApplication {
        public static void main(String[] args) {
            SpringApplication.run(EurekaApplication.class,args);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    启动(运行)启动类,访问http://localhost:9200
    如果看到如下页面(Eureka注册中心后台),则表明EurekaServer发布成功

    在这里插入图片描述

    上面的192.168.194.1代表当前实例主机地址(这里是局域网ip),即这里是本机地址
    我们可以看到,与普通的Spring Boot项目相比,就是一个@EnableEurekaServer注解的添加,我们试着将该注解删除
    继续启动,发现,启动不会报错,且访问,没有页面,即对应的依赖,主要是为了操作@EnableEurekaServer注解的
    很明显,该注解操作了一系列的初始化操作,而出现对应的页面,即@EnableEurekaServer注解是核心
    每一次的启动,页面的显示一般是不同的
    我们来解释如下(给出部分显示):

    在这里插入图片描述

    我们将对应的false设置为true,再次查看,可以看到
    Instances currently registered with Eureka(当前注册到这里的实例,虽然是本身注册本身)下有对应的实例了
    在启动日志里面也可以看到对应的注册信息,我们可以看到对应的实例状态信息是localhost:lagou-cloud-eureka-server:9200
    那么如何改变这样的格式呢,修改部分application.yml文件:
    eureka:  #自己注册中心,也是可以看成一个客户端的
      instance:
        #hostname: localhost  #表示当前eureka实例的主机名
        #使用ip注册,否则会使用主机名注册了
        #此处考虑到对老版本的兼容,新版本经过实验都是ip,所以上面hostname: localhost与这个一样
        prefer-ip-address: true #使用这个,会导致DS Replicas下面多出来了本机,以为是集群
        #自定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
        instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
        
        
      #操作结果由原来的localhost:lagou-cloud-eureka-server:9200变成了
      		    #192.168.194.1:lagou-cloud-eureka-server:9200:1.0-SNAPSHOT 
      
      
      #即${spring.cloud.client.ip-address}代表192.168.194.1
      #${spring.application.name}代表lagou-cloud-eureka-server
      #${server.port}代表9200
      #@project.version@代表1.0-SNAPSHOT
      #根据名称可以明显的看到ip-address是ip地址
      #name是自己设置的name名称,port是设置的端口号,这两个是配置文件里面设置的,可以修改进行验证
      #project.version是项目的版本号
      
      #实际项目中,我们通常操作instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
      
      #实际上instance-id就是设置对应的显示了,就算值为1,那么就会显示1,只是这个值,我们进行了操作而已
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    至此操作完毕
    商品微服务和页面静态化微服务注册到Eureka:
    商品微服务lagou-service-product:
    添加对应的依赖:
            
    <dependency>            
        <groupId>org.springframework.cloudgroupId>     
        <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>  
    dependency>
            
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    对应的application.yml里添加如下信息:
    eureka:
      client:
        serviceUrl: # eureka server的路径
          defaultZone: http://localhost:9200/eureka/
      instance:
        #使用ip注册,否则会使用主机名注册了(此处考虑到对老版本的兼容,新版本经过实验都是ip)
        prefer-ip-address: true
        #自定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
        instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    我们发现,与前面的注册server一样的配置,但依赖不同,而正是依赖不同,导致对应的角色不同,使得这里是注册者
    其中也指定了注册路径,自然也是注册进去的,但我们发现,并没有写对应的register-with-eurek属性和fetch-registry属性
    虽然这些属性是操作注册中心的(Eureka server我称为注册中心),使得他们进行同步的主要操作,但客户端并不需要这些配置
    因为可以说是默认都是true,所以我们并不需要修改
    当然也是可以修改的
    如使得不进行注册和获取,或者只注册,而不操作获取(大概获取的操作不可进行),或者只获取,而不注册
    但注册中心之间的同步,与他们并没有关系,只与defaultZone列表有关
    接下来我们修改启动类:
    package com.lagou.product;
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    
    /**
     *
     */
    @SpringBootApplication
    @MapperScan("com.lagou.product.mapper")
    //@EnableEurekaClient //将当前项目作为Eureka Client注册到Eureka Server
    @EnableDiscoveryClient
    //@EnableDiscoveryClient注解也是将当前项目注册到注册中心,那么与上面的注解的注册有什么区别呢
    //实际上上面的注解@EnableEurekaClient是只能操作Eureka环境
    //而这个注解,是可以在所有的服务注册中心环境下使用,比如nacos等等
    //也就是说,以后我们没有对应的配置,而是使用其他注册中心的配置时,也可以进行注册
    //那么环境是什么意思:可以说,有对应的依赖配置就是对应的环境,就如application.yml里面的eureka:的配置
    //而有些注解是指定环境的注册,有些注解是不指定环境的至此
    //就如springmvc里面的@GetMapping只能操作get,而@RequestMapping基本都可以操作的类似意思
    //当部分的和全部的都写上时,一般是包含全部的会优先
    
    //而正是因为@EnableDiscoveryClient是包括全部的,所以我们通常操作@EnableDiscoveryClient
    //使得以后升级时或者修改时,基本不会出现对应的问题(环境不对问题)
    //当然,该注解,只有客户端的这个依赖有,服务端的没有,因为他主要针对注册,而不是服务端的创建注册中心
    public class ProductApplication {
        public static void main(String[] args) {
            SpringApplication.run(ProductApplication.class,args);
        }
    }
    
    //那么@EnableEurekaClient注解,在前面的那个服务端依赖可以写吗
    //答:可以,因为说过,对应的服务注册,可以当server,也可以当客户端
    //所以@EnableEurekaClient和@EnableEurekaServer都可以写
    //而正是因为操作了defaultZone,即必须指定服务端,所以才写上@EnableEurekaServer注解的
    //那么这里可以写上@EnableEurekaServer注解吗
    //答:不可以,没有该注解,因为我们只可以操作客户端(可以称为或者Enreka Client)
    //而没有服务端(也可称为注册中心或者Enreka Server)的操作
    
    //那么如果修改成了@EnableEurekaClient会导致找不到服务端的该地址,而导致报错吗,答:不会
    //实际上@EnableEurekaServer使得可以操作当前地址的初始化页面
    //而报错是与访问有关,访问成功是不会报错的(访问是注册相关,不是初始化地址)
    //与是否是服务端(可以称为注册中心或者Enreka Server)没有关系,而若修改成了@EnableEurekaClient
    //那么初始化必然失败,即相当于普通的Spring Boot项目了,即相当于没有注册,没有注册是不会导致项目报错的
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    页面静态化微服务lagou-service-page:
    与上面一样的操作即可,就不做说明了
    那么有个疑问,为什么不将对应的依赖放入父工程里呢:
    这是为了具体显示(给需要的),且为了依赖的空间(少传递)
    接下来我们启动对应的工程,回到页面,发现出现了对应的数据了
    搭建Eureka Server 高可用集群:
    在互联网应用中,服务实例很少有单个的
    因为如果EurekaServer只有一个实例,该实例挂掉,正好微服务消费者本地缓存列表中的服务实例也不可用
    那么这个时候整个系统都受影响
    在生产环境中,我们会配置Eureka Server集群实现高可用
    Eureka Server集群之中的节点通过点对点(P2P)通信的方式共享服务注册表,我们开启两台 Eureka Server 以搭建集群
    由于是在个人计算机中进行测试很难模拟多主机的情况,所以Eureka配置server集群时需要执行操作hosts文件里的地址
    所以需要修改个人电脑中hosts地址:
    一般在C:\Windows\System32\drivers\etc路径下修改hosts文件来改变本机的域名
    添加如下:
    127.0.0.1 LagouCloudEurekaServerA
    127.0.0.1 LagouCloudEurekaServerB
    #在url中,都会是小写(实际上忽略大小写),即小写的也可以访问,所以一般写在配置中,基本可以随便写,即忽略大小写
    #所以这样也可以http://LagouclOUdEurekaServera:9200/eureka/
    #既有小写也有大写,对结果没有影响,但为了规范,最好不要这样写
    
    • 1
    • 2
    • 3
    • 4
    • 5
    由于是单机,所以我们假设域名是不同的机子,虽然是同一个
    将lagou-cloud-eureka复制一份为lagou-cloud-eureka-9201
    记得将对应的配置文件信息由原来的lagou-cloud-eureka修改成lagou-cloud-eureka-9201
    比如pom.xml里面的,application.yml里面的(这个我们不进行修改,即让对应名称相同,即应用名称相同)
    虽然也可以都不用修改(pom.xml最好修改不同的id,否则可能被覆盖的是会报错的,即两个同样的类出现)
    其中父类需要添加子模块标签
    即在父工程里加上< module>lagou-cloud-eureka-9201来导入子工程,否则不会看成项目,而是文件夹,注意即可
    到那时你就知道了,除非你自己重新搞,如自己创建子工程,或者删除对应文件操作或直接创建
    但删除和直接创建的是单独项目,而不是子工程
    当然,为了更好的区别,最好将启动类也进行修改,加上后缀9201即可,一般会出现不同的启动名称(根据类名来区别)
    若是相同的,那么就在右上角进行修改启动名称来手动区别或者全部删除再次启动(这时又会根据类名来区别)
    主要看你如何操作,当然你也可以不修改
    修改9200和9201的application.yml文件,让他们之间进行注册:
    9200的application.yml文件:
    #将defaultZone: http://localhost:9200/eureka/修改成如下:
    defaultZone: http://LagouCloudEurekaServerB:9201/eureka/
    
    • 1
    • 2
    9201的application.yml文件:
    #将defaultZone: http://localhost:9200/eureka/修改成如下:
    defaultZone: http://LagouCloudEurekaServerA:9200/eureka/
    
    • 1
    • 2
    页面中的DS Replicas下面显示的是defaultZone对应注册地址的域名,即操作的注册地址的域名
    如上面的LagouCloudEurekaServerB或者LagouCloudEurekaServerA,不是其他服务器注册到这里的地址
    而是defaultZone的域名,无论是否存在,都会显示
    所以若是上面的配置,那么只是一个,若是下面的配置,则是两个
    而正是因为同步,即虽然注册到对方,但还是会复制没有的信息给对方,使得相当于操作下面的配置
    注意:上面操作时,如果是集群的话,则基本不会写自己的地址,而是其他服务器的地址,若是多个地址,那么用","逗号隔开
    当然,他们并不需要注册自己的,因为复制自己的数据到自己,并不需要这样,这不是多此一举吗
    当然你也可以写上自己的地址,也并没有问题,即9200和9201都可以写上如下:
    defaultZone: http://LagouCloudEurekaServerA:9200/eureka/,http://LagouCloudEurekaServerB:9201/eureka/
    #其中,后面可以加空格(前面不能加),因为,后面空格并不会识别,而前面会识别,基本其他操作逗号的配置也是如此
    #不能只加逗号(即逗号后面没有值),虽然页面会显示,但有特殊的问题,自己看看页面就知道了,出现一些特殊的显示的提示信息
    #注意:只有注册中心可以用,分开,其他的消费这或者提供者都只会识别第一个注册者
    #即若也是上面的,则只有9200起作用,而9201不会注册,这里要注意一下
    
    • 1
    • 2
    • 3
    • 4
    • 5
    那么对应的商品微服务(lagou-service-product)和页面静态化微服务(lagou-service-page)需要修改吗,答:这里不需要
    因为只要他们defaultZone的server操作的复制同步中有其他集群,就会同步给他们
    那么我们只需要写上该server即可,否则同步不过去的(同步不会同步注册者,这里要注意)
    同步与两个true是无关的,这里要注意,只与defaultZone有关
    很明显,这里指定了9201,所以会同步到9201去,所以这里不需要
    当然,自己的指不指定并无所谓,反正自己是有,也没有必要复制自己给自己
    至此,我们访问http://localhost:9200/和http://localhost:9201/,发现,都有对应的商品微服务和页面静态化微服务
    即的确同步了,很明显,对应的地址那里,就是同步的操作,为了验证之前的两个true是否与同步有关系
    我们将对应的true修改成false,看看是否可以同步,发现,双方都有同步,即的确,对应的两个设置与同步没有关系
    只是自己的注册信息没有了,这是自然的,因为他们操作客户端的
    操作完毕后,我们先再次访问"localhost:9100/page/getProduct/1",先确定没有出错
    但是我们看代码时,很明显是操作了商品微服务的地址,在前面我们说过,注册一般会有对应的信息
    所以我们需要得到对应的注册信息,即该商品微服务的地址
    所以我们修改对应的页面静态化微服务(lagou-service-page)的PageController:
    package com.lagou.page.controller;
    
    import com.lagou.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;
    
        //包名记得对应,import org.springframework.cloud.client.discovery.DiscoveryClient;
        @Autowired
        //服务注册中心的客户端对象,正是因为我们操作了注册,那么会注入客户端对应,来操作对应的注册中心
        private DiscoveryClient discoveryClient; //相当于dubbo里面的注入,都是操作注册中心
        //只是dubbo的的注解不同,是获取然后注入,而这里是注入然后获取
    
        @GetMapping("/getProduct/{id}")
        public Products getProduct(@PathVariable Integer id){
    
            //通过客户端注入的对象,指定获取名称
            //那么客户端的对象,就会去他指定的注册server里去获取注册的列表
            //通过defaultZone信息操作的同步
            //从而获取对应的商品微服务(lagou-service-product)在注册中心的服务列表
            //由于这里我们的lagou-service-product只有一个
            //那么为什么是一个呢,因为只有一个name: lagou-service-product,所以只有一个
            //在页面上面也可以看到UP (1) - 192.168.194.1:lagou-service-product:9000:1.0-SNAPSHOT 
            //即这个UP (1)就是一个
            //所以虽然一般返回的是集合,但我们只能取的第一个,该第一个自然包括很多信息,如ip地址,端口号等等,
            List<ServiceInstance> instances = discoveryClient.getInstances("lagou-service-product");
    
            //因为只有一个,所以不能越界,否则报错
            ServiceInstance serviceInstance = instances.get(0);
            //ServiceInstance serviceInstance1 = instances.get(1); 写上这个会报错,越界了
            //获得商品微服务的主机地址
            String host = serviceInstance.getHost();
            System.out.println(host);
            //获取商品微服务的端口号
            int port = serviceInstance.getPort();
            System.out.println(port);
            //拼url地址
            String url = "http://"+host+":"+port+"/product/query/"+id;
    
            //将返回的json变成对应的对象,因为json也是该对象变成了,只是反过来而已
            Products forObject = restTemplate.getForObject(url, Products.class);
    
            return forObject;
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    给出对应的页面图片,看看对应的UP (1) - 192.168.194.1:lagou-service-product:9000:1.0-SNAPSHOT

    在这里插入图片描述

    其中的UP后面的数字就是参数的名称对应的集合的大小,注意即可,当然,你可以进行测试,让参数是lagou-cloud-eureka
    如:
      List<ServiceInstance> instancess = discoveryClient.getInstances("lagou-cloud-eureka");
    
            System.out.println(instancess.size());
    
    
    • 1
    • 2
    • 3
    • 4
    然后修改其中一个名称,再次测试,我经过测试,第一次是返回2,第二次是返回1,即的确是集合的大小,即是对应的数据
    那么对应的get(0),是得到其中2中的那一个呢,即顺序是排列的呢
    答:顺序是时刻变化的,即更新信息(30秒更新一次)时,这个顺序就会变化,所以没有固定的顺序
    那么页面的顺序是怎么顺序的呢,答:根据某个计算来排列,但却是固定,而不是变化,当然这些并不需要死磕,虽然我死磕了
    我们重启,继续测试,发现,也得到了数据,这就使得硬编码问题解决
    以后,我们改变商品微服务地址(地址:包括主机地址,IP端口等等)时,只需要注册一下即可(名称不要改变)
    而不是去代码里进行改变
    接下来我们操作页面静态化微服务(lagou-service-page)的fetch-registry属性,设置为false,代表他不能去获取信息
    再次启动,会发现,在get(0)这里报错,即获取报错,不能获取
    当然,与其他的服务器是否获取是没有关系的,因为这里是自己的配置,而不是操作其他服务器的配置
    即其他服务器设置的false(如注册中心),他若是设置true,还是可以获取,只有他是false,才不能获取
    即,的确是不能操作对应的列表的,即不能获取信息,至此说明,注册(对应的false代表不注册)和获取的确是互相作用的
    那么这里与dubbo的操作的区别是什么,差不多类似,都是远程获取返回信息,都是操作地址获取
    只是这里写出来需要自己调用而已,而dubbo他去调用
    且他们保存的信息,在启动时,就保存了,都是先注册,然后可以得到信息
    主要的不同是访问注册中心信息得到信息后,访问对应提供者得到信息的协议不同,一个是tcp协议(dubbo)
    这里好像是http协议,所以dubbo效率高点
    但前面也说过了,只是Dubbo体系的组 件不全,操作注册时,一般需要其他的注册组件来辅助,如zookeeper,所以也是有缺点了
    至此操作完毕
    Eureka细节详解:
    Eureka元数据详解:
    Eureka的元数据有两种:标准元数据(默认的)和自定义元数据
    标准元数据:主机名、 IP地址、端口号等信息,这些信息都会被发布在服务注册表中,用于服务之 间的调用,如前面的获取信息
    自定义元数据:可以使用eureka.instance.metadata-map配置,符合KEY/VALUE的存储格式,这 些元数据可以在远程客户端中访问
    类似于:
      instance:
        #使用ip注册,否则会使用主机名注册了(此处考虑到对老版本的兼容,新版本经过实验都是ip)
        prefer-ip-address: true
        #自定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
        instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
        #下面就是添加的属性,就是自定义的元数据,与标准元数据一起,注册到注册中心
        #可以被注册中心的client获取(看后面的获取方式就知道了)
        metadata-map:
          ip: 192.168.200.128
          port: 10000
          user: YuanJing
          pwd: 123456
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    我们在商品微服务lagou-service-product里面加上面的新增的属性
    再次回到对应的lagou-service-page里面的PageController进行打上断点,看看获取的信息是否在注册中心里面
    如图所示:

    在这里插入图片描述

    在List< ServiceInstance> instances里面或者ServiceInstance serviceInstance里面找,自己查找即可),其他的可以说是标准的元数据
    如果不加,那么只有对应的management.port这个属性了,这是默认的,与标准的元数据的服务器端口对应
    那么如何获取自定义的元数据呢,看如下部分的PageController类:
    //因为只有一个,所以不能越界,否则报错
            ServiceInstance serviceInstance = instances.get(0);
    //        ServiceInstance serviceInstance1 = instances.get(1);
    
            //获得自定义的元数据
            Map<String, String> metadata = serviceInstance.getMetadata();
            Set<String> strings = metadata.keySet();
            Object[] objects = strings.toArray();
            Collection<String> values = metadata.values();
            Object[] objects1 = values.toArray();
    
            for(int a = 0;a<strings.size();a++){
                System.out.println(objects[a]+":"+objects1[a]);
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    重新启动,访问后,看看打印结果即可
    Eureka客户端详解:
    服务提供者(也是Eureka客户端)要向EurekaServer注册服务,并完成服务续约等工作
    服务注册详解(服务提供者)
    1:当我们导入了eureka-client依赖坐标,配置Eureka服务注册中心地址
    2:服务在启动时会向注册中心发起注册请求,携带服务元数据信息
    3:Eureka注册中心会把服务的信息主要保存在Map(总列表是map,而不是单独的list,因为名称对应list)中
    服务续约详解(服务提供者):
    服务每隔30秒会向注册中心续约(心跳)一次(也称为报活),如果没有续约,租约在90秒后到期,然后服务会被失效
    每隔30秒的续约操作我们称之为心跳检测
    Eureka Client(提供者和消费者在这里都称为客户端,虽然他们在调用中,又可以称为对应的客户端和服务端):
    30S续约一次,在Eureka Server更新自己的状态 (Client端进行配置)
    Eureka Server:若有客户端90S还没有进行续约的(续约一般是会操作服务没有下线的,后面会说明)
    则将该微服务实例从服务注册表(Map)剔除
    在Client端进行配置,通知Eureka Server删除对应的服务信息,因为是在Client端进行配置的
    Eureka Client: 30S拉取服务最新的注册表并缓存到本地 (Client端进行配置),一般续约成功后,再操作,即更新时进行缓存
    当然,可能是分开的,即续约(回应)和拉取的各操作各的,实际上也是如此,而不是每次的回应都会续约
    那么如何修改这些默认的呢,看如下:
    #向Eureka服务中心集群注册服务
    eureka:  
      instance:    
        # 租约续约间隔时间,默认30秒    
        lease-renewal-interval-in-seconds: 30     
        # 租约到期,服务时效时间,默认值90秒,服务超过90秒没有发生心跳,EurekaServer会将服务从列表移除    
        lease-expiration-duration-in-seconds: 90
        
        #一般往往不需要我们调整这两个配置,使用默认即可
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    获取服务列表(服务注册表)详解(服务消费者):
    每隔30秒服务会从注册中心中拉取一份服务列表,这个时间可以通过配置修改,往往不需要我们调整
    #向Eureka服务中心集群注册服务
    eureka:  
      client:    
        # 每隔多久拉取一次服务列表(会到缓存里面),默认30秒
        registry-fetch-interval-seconds: 30
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1:服务消费者启动时,从 EurekaServer服务列表获取只读备份,缓存到本地
    2:每隔30秒,会重新获取并更新数据
    3:每隔30秒的时间可以通过配置eureka.client.registry-fetch-interval-seconds修改
    Eureka服务端详解:
    服务下线:
    1:当服务正常关闭操作时(如我们手动的关闭),会发送服务下线的REST请求给EurekaServer
    2:服务中心接受到请求后,将该服务置为下线状态,那么一般也就会看不到了(保存信息使得操作心跳)
    那么下线的也就不会更新和获取了
    失效剔除:
    Eureka Server会定时(间隔值是eureka.server.eviction-interval-timer-in-ms,默认60s)进行检查
    如果发现实例在一定时间(此值由客户端设置的eureka.instance.lease-expiration-duration-in-seconds定义,默认值为90s)内
    没有收到心跳(一般不是下线的情况,因为下线他们基本就没有了),则会注销此实例
    所以说,并不是一定过了90秒就会被剔除,还需要这个60秒的检查到才会剔除
    自我保护机制:
    自我保护模式正是一种针对网络异常波动的安全保护措施,使用自我保护模式能使Eureka集群更加的健壮、稳定的运行
    自我保护机制的工作机制是:如果在15分钟内超过85%的客户端节点都没有正常的心跳(无论是下线还是网络问题),那么
    Eureka就认为客户端与注册中心出现了网络故障,Eureka Server自动进入自我保护机制
    我们可以将除了9200的其他项目都停止,虽然下线了,但我们等待一会,刷新9200页面,那么就会出现保护机制起作用的提示
    此时会出现以下几种情况:
    1:Eureka Server不再从注册列表中移除因为长时间没收到大多数的心跳而应该过期的服务
    2:Eureka Server仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上,保证当前节点依然可用,节省网络带宽
    使得不会拥挤,导致缓慢,因为需要保证自己的网络通信,直到好了为止
    3:当网络稳定时,当前Eureka Server新的注册信息会被同步到其它节点中
    这时前面两步,基本都会回到原来的操作,即会移除会同步(的操作)
    因此Eureka Server可以很好的应对因网络故障导致部分节点失联的情况
    而不会像ZK(zookeeper)那样如果有一半不可用的情况会导致整个集群不可用而变成瘫痪(半数机制)
    为什么会有自我保护机制?
    默认情况下,如果Eureka Server在一定时间内(默认90秒)没有接收到某个微服务实例的心跳,Eureka Server将会移除该实例
    但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通信或者微服务与客户端无法正常通信
    而微服务本身是正常运行的,此时不应该移除这个微服务,所以引入了自我保护机制
    这时服务中心页面会显示如下提示信息:

    在这里插入图片描述

    我们在单机测试的时候很容易满足心跳失败比例在 15 分钟之内低于 85%,这个时候就会触发 Eureka 的保护机制
    一旦开启了保护机制(默认情况下是开启的,当你触发了保护条件,就会出现对应的作用)
    则服务注册中心维护的服务实例就不是那么准确了
    此时我们通过修改Eureka Server的配置文件来关闭保护机制,这样可以确保注册中心中不可用的实例被及时的剔除(不推荐)
    如下,这里需要在服务端进行关闭,客户端没有该属性,虽然写上不会报错,但并不会起作用(若可以识别,也会起作用)
    即只会识别可以操作的,如后面的负载均衡策略的定义,我们点击时,他并没有对应的属性,但也操作了,因为识别了
    而识别,也就是读取时,正好找对应的属性,而进行得对应值的操作
    yml,properties,yaml基本都是如此,都可以自定义值,虽然识别时,只操作可以操作的:
    eureka:  
      server:    
        enable-self-preservation: false # 关闭自我保护模式(缺省为打开,即默认打开的)
    
    • 1
    • 2
    • 3
    建议生产环境打开自我保护机制(因为需要部署的,肯定需要开启,防止因为网络问题,但服务没有问题,而造成的移除影响)
    开发环境可以不打开
    打开后,只要满足了保护条件,就会提示并进行相关操作,没有满足保护条件,就会没有提示且没有相关操作
    如果关闭的话,无论是否满足保护条件,都没有提示且没有相关操作
    至此,注册中心操作完毕
    Ribbon负载均衡:
    关于负载均衡:
    负载均衡一般分为服务器端负载均衡和客户端负载均衡
    所谓服务器端负载均衡,比如Nginx、F5这些
    请求到达服务器之后由这些负载均衡器根据一定的算法将请求路由到目标服务器处理
    所谓客户端负载均衡,比如我们要说的Ribbon,服务消费者客户端会有一个服务器地址列表
    调用方在请求前通过一定的负载均衡算法选择一个服务器进行访问
    因为负载均衡算法的执行是在请求客户端进行而不是服务端,所以称为客户端负载均衡
    Ribbon是Netflix发布的负载均衡器,Eureka一般配合Ribbon进行使用
    Ribbon利用从Eureka中读取到服务信息,在调用服务提供者提供的服务时,会根据一定的算法进行负载

    在这里插入图片描述

    Ribbon高级应用:
    需求:
    将商品微服务9000复制一份为9001,在9000和9001编写Controller,返回服务实例端口
    Page微服务中通过负载均衡策略调用lagou-service-product的controller
    复制的操作与前面的复制注册中心一样的就可
    但在复制之前,我们首先先操作lagou-service-productd的controller包下创建ServiceInfoController类:
    package com.lagou.product.controller;
    
    import com.netflix.discovery.converters.Auto;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     *
     */
    @RestController
    @RequestMapping("/service")
    public class ServiceInfoController {
    
        //在spring中,我们知道,读取了properties是可以注入的,而由于yml和yaml也是与properties一样
        //所以这里也可以进行注入
        //这是Spring Boot的缘故,因为读取了文件
        @Value("${server.port}")
        private String port;
    
        @GetMapping("port")
        public String getPort(){
            return port;
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    这时,我们再进行复制一份即可,名称是lagou-service-product-9001
    yml的名称,我们不变,虽然也可以修改,但为了得到注册信息是,在一个集合里,所以就不修改了,其他自行修改
    在微服务中使用Ribbon不需要额外导入依赖坐标
    因为若微服务中引入过eureka-client或者eureka-server相关依赖,会自动引入Ribbon相关依赖坐标
    而由于对应的Ribbon是操作消费方的,所以我们在页面静态化服务使用该负载均衡

    在这里插入图片描述

    接下来在lagou-service-page修改对应的启动类部分来操作负载均衡 :
       @Bean //因为对应的注解@SpringBootApplication有配置类的注解,所以可以写上@Bean
        @LoadBalanced 
    //启用请求的负载均衡,注解可以获得很多信息,但实际上注解最主要的是获取地址信息,来操作反射,即主要是反射的操作
    //部分会使用信息,但很明显,这里的信息基本没有,即是操作反射的,且注解基本都会(元注解一般也是操作反射)
        //我们可以通过反射,对该返回的实例进行操作,比如他在请求时,会操作负载均衡,一般是通过拦截即过滤器
    //可能是该实例自带的操作方式,因为一般注解操作不了,只是注解解除了他的限制,或者增加了拦截器
        public RestTemplate restTemplate(){
            return new RestTemplate();
        }
    
    //简单来说,就是一个@LoadBalanced注解(不看为什么的话)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    到PageController类里面添加部分代码:
    @GetMapping("loadProductServicePort")
        public String getProductServerPort(){
            List<ServiceInstance> instances = discoveryClient.getInstances("lagou-service-product");
            ServiceInstance serviceInstance = instances.get(0);
    
            String host = serviceInstance.getHost();
            int port = serviceInstance.getPort();
            String url = "http://"+host+":"+port+"/service/port/";
            String forObject = restTemplate.getForObject(url, String.class);
    
            return forObject;
        }
    
    //注意,若你使用上面的,那么会报错,因为在这里的负载均衡需要将地址和端口号修改成项目的名称才可
    //而不是具体的地址和端口
    //报错信息一般是:"message": "No instances available for 192.168.194.1",
    //即修改如下
      @GetMapping("loadProductServicePort")
        public String getProductServerPort(){
         
            String url = "http://lagou-service-product/service/port/";
            String forObject = restTemplate.getForObject(url, String.class);
    
            return forObject;
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    这时,我们去访问,多次的访问,会发现,循环的访问,根据get得到的顺序来(前面说的,是没有顺序的,因为会更新)
    至此,我们的负载均衡操作完毕,实际上就是操作对应的集合来操作,我们直接传递项目名称
    那么负载均衡就会根据这个名称按照负载均衡的操作进行访问
    这里默认是类似于轮询算法的(就是后面说明的区域权衡策略),但好像并不是轮询的算法,具体看后面的解释
    从get(0)开始,即第一个开始,然后解析成对应的地址和端口,然后访问
    当再次访问时,就会解析另外一个,然后访问,所以主要是名称对应的注册信息的多少
    那么很明显,从原来的,指定服务器的地址,变成了选择服务器的地址访问
    即这里的确是客户端的负载均衡,而不是服务端的负载均衡
    Ribbon负载均衡策略:
    Ribbon内置了多种负载均衡策略,内部负责复杂均衡的顶级接口为 com.netflix.loadbalancer.IRule
    接口简介:
    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by FernFlower decompiler)
    //
    
    package com.netflix.loadbalancer;
    
    public interface IRule {
        Server choose(Object var1); //根据对应的var1,选择要调用的Server(目标微服务的实例)
    
        void setLoadBalancer(ILoadBalancer var1);
    
        ILoadBalancer getLoadBalancer();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这里插入图片描述

    实际上上面这个图可以手动的生成,我们双击IRule,然后按右键,点击如下:

    在这里插入图片描述

    其中最右边的两个选择,上面一个是新的窗口,下面一个是小窗口(一般不会出现默认的显示),你可以自己测试一下
    所以我们点击上面一个,然后若出现如下:

    在这里插入图片描述

    点击第一个即可,然后到这里:

    在这里插入图片描述

    我们点击他,然后使用ctrl+alt+b,出现如下:

    在这里插入图片描述

    然后使用ctrl+a,鼠标不要动,动了需要重新选择,然后按回车,出现如下:

    在这里插入图片描述

    我们进行对比,虽然位置可能不同,但是连接基本相同,至此操作完毕

    在这里插入图片描述

    之所以我们之前操作轮询,是因为过滤器的操作,并没有过滤掉
    即我们没有超时,连接数也不多,且在一个区域,那么自然也是轮询,提一个小知识,如果是输出(1),那么结果是1,吗
    答:结果是1,()可以看成运算的符号,只是提高优先运算而已,只是单纯的他并不会操作值
    那么如何修改负载均衡策略,操作如下:
    修改负载均衡策略:
    #针对的被调用方微服务名称,不加就是全局生效,即所有的访问都操作该策略,而不是只是操作这一个
    #即这里只操作lagou-service-product这个名称的调用
    lagou-service-product: #最上面的层级
      ribbon:
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #随机策略
    #全局的生效,可能并不会立即生效,虽然并不知道原因,但总体来说是随机的
    #ribbon:
    #   NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    lagou-service-product:   
      ribbon:    
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule #轮询策略,负载均衡的轮询
    
    • 1
    • 2
    • 3
    Ribbon核心源码剖析:
    Ribbon工作原理:

    在这里插入图片描述

    SpringCloud充分利用了SpringBoot的自动装配特点,我们Spring Boot启动时,由于有Spring Cloud
    那么除了操作自己的对应的配置文件外,也会操作Spring Cloud的配置文件,所以我们找Spring Cloud对应的spring.factories配置文件

    在这里插入图片描述

    点击,找到如下(LoadBalancerAutoConfiguration):

    在这里插入图片描述

    使用ctrl+n,找LoadBalancerAutoConfiguration类(部分代码):
    @Configuration //标识当前类为配置类
    //需要这个实例,才会操作,所以我们注入了RestTemplate,那么负载均衡才会操作
    @ConditionalOnClass({RestTemplate.class}) 
    //也需要负载均衡对应的客户端
    @ConditionalOnBean({LoadBalancerClient.class})
    //上面两个使得进行验证,即在满足条件时,我们的负载均衡才会有作用,即自动配置前验证的条件
    @EnableConfigurationProperties({LoadBalancerRetryProperties.class})
    public class LoadBalancerAutoConfiguration {
        @LoadBalanced
        @Autowired( 
            //进行了注入,注入可以注入到集合的,从对应的下标0开始
            //前面说过,若是类,那么不能注入两个以上的(对应的id不是变量名称,可能之前并没有说明)
            //那么会报错,而当是集合时,而是直接的添加,而不会报错
            required = false
        )
        private List<RestTemplate> restTemplates = Collections.emptyList(); 
        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    再看LoadBalancerAutoConfiguration类的如下代码:
      @Bean
            @ConditionalOnMissingBean
            public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor 
                                                                 loadBalancerInterceptor) {
                return (restTemplate) -> {
                    List<ClientHttpRequestInterceptor> list = new 
                        ArrayList(restTemplate.getInterceptors());
                    list.add(loadBalancerInterceptor);
                    restTemplate.setInterceptors(list); //拦截器放入我们的注入的restTemplate里面
                };
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    我们看看拦截器LoadBalancerInterceptor类,找到如下:
    public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
            URI originalUri = request.getURI();
            String serviceName = originalUri.getHost();
            Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + 
                         originalUri);
            return (ClientHttpResponse)this.loadBalancer.execute(
                serviceName, this.requestFactory.createRequest(request, body, execution));
        }
    
    //很明显,我们看看对应的execute方法,参数传递了serviceName,即我们的主机名称
    //我们可以通过这个名称来得到对应的所有的ip地址和端口号,从而可以操作负载均衡
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    我们看看execute方法,发现是个接口,我们看看实现类,发现,只有一个实现类
    即RibbonLoadBalancerClient类(很明显就是对应的负载均衡操作的类,即Ribbon的操作):
    该方法如下:
    public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
            return this.execute(serviceId, (LoadBalancerRequest)request, (Object)null);
        }
    
    • 1
    • 2
    • 3
    我们进入execute方法:
    public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws 
        IOException {
            ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId);
            Server server = this.getServer(loadBalancer, hint);
        //上面两个首先通过名称得到对应的ip地址和端口信息,然后通过算法,得到对应的的访问,即server
            if (server == null) {
                throw new IllegalStateException("No instances available for " + serviceId);
            } else {
                RibbonLoadBalancerClient.RibbonServer ribbonServer = new 
                    RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
                return this.execute(serviceId, (ServiceInstance)ribbonServer, 
                                    (LoadBalancerRequest)request);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    至此大致了解完毕
    实际上微服务也就是一个调用者或者提供者,只是更加的细一点,而称为微服务
    Hystrix熔断器:
    他属于一种容错机制
    微服务中的雪崩效应:
    当山坡积雪内部的内聚力抗拒不了它所受到的重力拉引时,便向下滑动,引起大量雪体崩塌,人们把这种自然现象称作雪崩
    微服务中,一个请求可能需要多个微服务接口才能实现(他们互相调用),会形成复杂的调用链路
    服务雪崩效应:是一种因"服务提供者的不可用"(原因)导致"服务调用者不可用"(结果),并将不可用逐渐放大的现象
    下面的红色代表不可用或者出现问题

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    扇入:代表着该微服务被调用的次数,扇入大,说明该模块复用性好
    扇出:该微服务调用其他微服务的个数,扇出大,说明业务逻辑复杂
    扇入大是一个好事,扇出大不一定是好事
    在微服务架构中,一个应用可能会有多个微服务组成,微服务之间的数据交互通过远程过程调用完成
    这就带来一个问题,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的"扇出"
    如果扇出的链路上某个微服务的调用响应时间过长或者不可用
    对微服务A的调用就会占用越来越多的系统资源(如持续的等待,或者一直的执行中),进而引起系统崩溃,所谓的"雪崩效应"
    如上面的图中所示,最下游商品微服务响应时间过长,大量请求阻塞,大量线程不会释放,会导致服务器资源耗尽
    最终导致上游服务甚至整个系统瘫痪
    形成原因:
    服务雪崩的过程可以分为三个阶段:
    1:服务提供者不可用
    2:重试加大请求流量
    3:服务调用者不可用
    我们先出现1,然后再出现2,最后出现3,就形成了提供者不可用,导致调用者不可用的原因
    服务雪崩的每个阶段都可能由不同的原因造成:
    当然有些具体的原因是可以解决的,如用户大量请求(可以采用限流)

    在这里插入图片描述

    上面的用户重试是对于提供者说的消费者(调用者),即一般是这个的服务调用者,用户大量请求一般也是如此
    当然,也可以说用户就是用户,而不是调用者,虽然也没有问题
    雪崩效应解决方案:
    从可用性可靠性着想,为防止系统的整体缓慢甚至崩溃,采用的技术手段
    下面,我们介绍三种技术手段应对微服务中的雪崩效应
    这三种手段都是从系统可用性、可靠性角度出发,尽量防止系统整体缓慢甚至瘫痪
    服务熔断:
    熔断机制是应对雪崩效应的一种微服务链路保护机制,我们在各种场景下都会接触到熔断这两个字
    如高压电路中,如果某个地方的电压过高,熔断器就会熔断,对电路进行保护
    如股票交易中,如果股票指数过高,也会采用熔断机制,暂停股票的交易
    同样,在微服务架构中,熔断机制也是起着类似的作用
    当扇出链路的某个微服务不可用或者响应时间太长时,熔断该节点微服务的调用,进行服务的降级,快速返回错误的响应信息
    当检测到该节点微服务调用响应正常后,恢复调用链路
    注意:
    1:服务熔断重点在"断",切断对下游服务的调用
    这个断与普通的访问失败不同,他是由一定的显示失败的,并与降级连接操作,所以通常与降级一起使用
    或者说降级加熔断,就是一个整体熔断,简单来说,熔断是具体说明或者断的操作(实现了断),而降级是实现断后面的操作
    2:服务熔断和服务降级往往是一起使用的,Hystrix就是这样
    服务降级:
    通俗讲就是整体资源不够用了,先将一些不关紧的服务停掉(调用我的时候,给你返回一个预留的值,也叫做兜底数据)
    待渡过难关高峰过去,再把那些服务打开
    服务降级一般是从整体考虑,就是当某个服务熔断之后,服务器将不再被调用
    此刻客户端可以自己准备一个本地的fallback回调,返回一个缺省值(即默认值)
    这样做,虽然服务水平下降,但好歹可用,比直接挂掉要强
    服务限流:
    服务降级是当服务出问题或者影响到核心流程的性能时,暂时将服务屏蔽掉,待高峰或者问题解决后再打开
    但是有些场景并不能用服务降级来解决,比如秒杀业务这样的核心功能,这个时候可以结合服务限流来限制这些场景的并发/请求量
    限流措施也很多,比如:
    限制总并发数(比如数据库连接池、线程池)
    限制瞬时并发数(如nginx限制瞬时并发连接数)
    限制时间窗口内的平均速率(如Guava的RateLimiter、nginx的limit_req模块,限制每秒的平均速率)
    限制远程接口调用速率、限制MQ的消费速率等
    至于更加的底层限制,可以理解为,我只给出部分窗口,该窗口可以是底层的TCP或者UDP的数量
    Hystrix简介:

    在这里插入图片描述

    Hystrix(豪猪),宣言"defend your application(保护你的应用)"是由Netflix开源的一个延迟和容错库
    用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性
    Hystrix主要通过以下几点实现延迟和容错:
    包裹请求:使用HystrixCommand包裹对依赖的调用逻辑,页面静态化微服务方法(@HystrixCommand 添加Hystrix控制)
    跳闸机制:当某服务的错误率超过一定的阈值时,Hystrix可以跳闸,停止请求该服务一段时间
    资源隔离:Hystrix为每个依赖都维护了一个小型的线程池(舱壁模式)
    如果该线程池已满, 发往该依赖的请求就被立即拒绝,而不是排队等待,从而加速失败判定
    监控:Hystrix可以近乎实时地监控运行指标和配置的变化,例如成功、失败、超时、以及被拒绝 的请求等
    回退机制:当请求失败、超时、被拒绝,或当断路器(即熔断)打开时
    执行回退逻辑,回退逻辑由开发人员自行提供,例如返回一个缺省值(兜底数据,即默认值)
    自我修复:断路器打开一段时间后,会自动进入"半开"状态(探测服务是否可用,如还是不可用,再次退回打开状态)
    Hystrix应用:
    熔断处理:
    需求目的:商品微服务长时间没有响应,服务消费者(用户调用) -> 页面静态化微服务快速失败给用户提示
    引入依赖:服务消费者工程(静态化微服务)中引入Hystrix依赖坐标(也可以添加在父工程中)
         
    <dependency>      
        
        <groupId>org.springframework.cloudgroupId>     
        <artifactId>spring-cloud-starter-netflix-hystrixartifactId>  
               
    dependency>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    开启熔断:服务消费者工程(静态化微服务)的启动类中添加熔断器开启注解,部分启动类代码:
    @SpringBootApplication
    //@EnableEurekaClient
    @EnableDiscoveryClient
    @EnableCircuitBreaker //启用熔断服务
    public class PageApplication {
    
    • 1
    • 2
    • 3
    • 4
    • 5
    也可以这样:
    @SpringCloudApplication
    public class PageApplication {
        //代替上面的三个注解
        
        //我们看看@SpringCloudApplication的信息
        /*
        
        @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @SpringBootApplication
    @EnableDiscoveryClient
    @EnableCircuitBreaker
    public @interface SpringCloudApplication {
    }
    
    可以发现,他的确有
    @SpringBootApplication
    @EnableDiscoveryClient
    @EnableCircuitBreaker
    这三个注解,即他一个可以代替这三个
    
        */
        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    我们回到商品微服务的ServiceInfoController类(9000和9001都进行操作):
    修改部分代码如下:
     @GetMapping("port")
        public String getPort(){
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return port;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    再回到静态化微服务项目里的PageController类,加上如下方法:
       /*
        模拟服务超时,进行熔断处理的方法
        针对熔断处理,Hystrix默认维护一个线程池,默认大小为10个线程
        只要加上了@HystrixCommand注解,那么就操作这个线程池
         */
        //@HystrixCommand注解:当我们启用熔断服务时,指定操作那个方法进行处理熔断
        //为了我们不使得操作默认的线程池,我们需要一个新的线程池
    //名称是getProductServerPort2,即维护这个名称的线程池,使得我们不会使用默认的线程池
        //当然,若其他的方法也是这个名称,那么他们维护的是同一个该名称的线程池
    //一般这时以 后操作的配置为主,即写在后面的为主(因为初始化的覆盖)
        //所以默认所有的请求共同维护默认的线程池,而加上threadPoolKey
    //使得该方法维护他指定的创建的线程池,而不是默认了
        @HystrixCommand(
                threadPoolKey = "getProductServerPort2",
                //每一个属性都对应一个HystrixProperty
                threadPoolProperties =  {
                        //同时能够执行线程的个数,这里指定1,即并发的线程数
                        //但在生成环境下(部署后),自然肯定不是1,因为总得不是只有一个吧
                        @HystrixProperty(name="coreSize",value = "1"), //并发线程数,默认为10
                        //设置没有再执行的线程的存放位置(并发线程数的其他线程),即队列
                        //默认的线程队列值是-1,即默认不开启,即其他的都会去除掉,而没有等待
                    //即这也就使得访问不了了,通常需要重新的访问熔断方法,即这个@HystrixCommand注解操作的方法
                        //允许等待20个线程,当然,实际开发中,通常不是20,可能更多
                        @HystrixProperty(name="maxQueueSize",value = "20"),
                },
                //超时时间的设置
                //每一个属性也都对应一个HystrixProperty
                commandProperties={
                        //设置请求超时时间,一旦请求超过此时间,那么都按照超时处理,这里设置为2000毫秒,即2秒
                    //默认好像是1秒
                @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "2000")
    
                }
    
                //最后注意:上面的name的值,最好不要写错
            //否则可能是使用默认的值,而不是自己的对应设置,当然,可能也会报错
        )
        @GetMapping("loadProductServicePort2")
        public String getProductServerPort2(){
            String url = "http://lagou-service-product/service/port/";
            System.out.println(1);
            String forObject = restTemplate.getForObject(url, String.class);
            System.out.println(forObject);
    
            return forObject;
        }
    
    //我们知道,我们给前面的商品微服务设置了停顿,5秒,那么由于这里的超时时间是2秒,那么自然超时
    //从而导致超时处理,这里的处理就是熔断处理
    //最后要注意:
    //熔断处理是一个统称的处理
    //即请求失败、超时、被拒绝,或当断路器(虽然也称为熔断)打开等等处理都可以称为熔断处理(返回错误信息)
    //或者是熔断
    //即使得处理不操作调用的统称,称为熔断处理,熔断处理操作他们,使得处理了他们
    //所以这里的超时处理,也可以说是熔断处理,因为处理了超时
    //使得返回值不是对应的调用了,被赋值了
    
    
    //最后注意:超时的是这个方法的超时,而不是我们去调用提供者的超时,而之所以调用提供者会出现超时
    //是因为是这个方法在调用,所以也就相当于该方法的超时了
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    接下来,我们重启对应的两台商品微服务,和这里的静态化微服务
    对localhost:9100/page/loadProductServicePort2和localhost:9100/page/loadProductServicePort进行访问,可以发现
    localhost:9100/page/loadProductServicePort2访问失败,localhost:9100/page/loadProductServicePort访问成功
    失败原因:超时,即:
     "message": "getProductServerPort2 timed-out and fallback failed.", 
     #熔断进行的操作,即熔断的错误返回,熔断处理了很多使得不调用的操作
     #服务器返回:java.util.concurrent.TimeoutException: null
    
    • 1
    • 2
    • 3
    上面打印了1,那么后面的会不会打印,答:会的,虽然超时报错
    但程序还是在执行(因为是超时,而不是程序错误,程序错误自然不会打印后面的了)的
    只是首先给前端数据了,即虽然前端有显示数据,但我们回到服务器等待,就会有对应的打印
    即先返回默认数据,之后打印出来信息,只是这时,不会在返回了
    即只是对应的商品微服务需要执行完才可,所以等待一会,对应的System.out.println(forObject)就打印信息了
    但也正是由于报错,所以导致页面返回的数据不会是对应的return forObject;
    因为他返回之前,可以说是重新赋值返回数据,再给前端
    然后他的返回不会给前端了
    我们现在将两个商品微服务的5000修改成1000,继续测试,发现,得到了数据,即返回了
    当然,调用的时间未必是设置的时间,即就算你设置了1999,但可能也会报错
    因为调用也需要时间,那么总体来说可能超过了2秒
    所以说,对于程序来说,有些时候,最好要考虑更加长远一些,来消除调用时间,而不是刚刚好的设置
    所以上面就将5000修改成1000来测试成功的案例,而不是1999,更加长远的设置了
    现在,我们去除一个商品微服务的休眠,如9000,进行测试
    可以发现,有时报错,有时出现数据,这是正常的,那么现在有个问题
    如果直接的返回错误数据,那么对用户体验肯定是不行的,那么如何操作返回的数据呢
    即这里就是在前面熔断后的操作服务降级的处理了,具体处理如下:
    回到静态化微服务项目里的PageController类,加上如下方法:
      /*
        服务降级处理演示:是在服务熔断后的兜底操作
         */
    
        @HystrixCommand(
                commandProperties={
                        //设置请求超时时间,一旦请求超过此时间,那么都按照超时处理,这里设置为2000毫秒,即2秒
                @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "2000")
    
                },
                //设置回退的方法
                fallbackMethod = "fallBack"
        )
        @GetMapping("loadProductServicePort3")
        public String getProductServerPort3(){
            String url = "http://lagou-service-product/service/port/";
            System.out.println(1);
            String forObject = restTemplate.getForObject(url, String.class);
    
            System.out.println(5);
            System.out.println(forObject);
            return forObject;
        }
    
        /*
        定义回退方法,即兜底的操作,当我们的请求出现熔断后执行,算是一种补救的措施
        注意:
         这个方法的返回参数类型以及参数列表类型,需要与调用的方法返回值类型以及参数列表类型一致
        否则会报错的,类型不一致的错误
    正是因为这样,所以才叫做兜底数据,即参数列表得到的值,是我们传递的,且返回类型一样,所以也就进行了操作补救
        
         */
    
        public String fallBack(){
    
            return "-1";
        }
    
    //启动测试
    //那么你认为会不会打印5呢,且对应的System.out.println(forObject);的结果是什么呢
    //答:根据前面说明的,会打印了5,且结果是9001(前面去除了9000的休眠),虽然没有报错,但返回的值却是-1
    //即以原来的错误信息赋值,变成了对应的返回信息赋值,所以需要与调用的方法返回值类型一致
    //经过测试发现,的确需要一致,如果不一致,会出现错误,在调用提供者之前,即这时,无论你怎么调用,都会报错
    //所以是先检查的
    //当然,对应的值若都是null,但是赋值也是不行的,这是基础知识
    //所以说类型也需要一致,不信的话,可以自己创建类来测试
    /*
    报错信息:
    "message": "Incompatible return types. \nCommand method: public java.lang.String com.lagou.page.controller.PageController.getProductServerPort3();\nFallback method: public java.lang.Integer com.lagou.page.controller.PageController.fallBack();\nHint: Fallback method 'public java.lang.Integer com.lagou.page.controller.PageController.fallBack()' must return: class java.lang.String or its subclass",
    
    服务器打印信息差不多一样
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    为了证明当我们的name没有对应正确时,是否使用默认的,比如:
    @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "2000")
    /*
    其中execution.isolation.thread.timeoutInMilliseconds变成
    execution.isolation.thread.timeoutInMillisecondss
    最后多一个s
    
    然后将5000修改成1000,在前面是成功的,由于默认超时是1秒,所以设置为1000
    
    进行测试,发现,如果name不一致,那么会报错,即
    "message": "Failed to set Command properties. groupKey: 'PageController', commandKey: 'getProductServerPort3', threadPoolKey: 'null'",
    服务器打印的报错(之前说的都是对应的页面信息报错):
    unknown command property: execution.isolation.thread.timeoutInMillisecondss
    
    当然,报错的信息我们并不需要注意,因为并没有很大的意义,且报错的信息主要知道说明即可知道是什么问题了
    而不用去记住
    
    
    而删除时,就是使用默认的,这时1000毫秒的确报错或者操作了回退方法,而不是成功,即的确使用了默认
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    至此,我们的对应熔断和降级操作完毕
    Hystrix舱壁模式:
    即前面的如下:
    资源隔离:Hystrix为每个依赖都维护了一个小型的线程池(舱壁模式
    如果该线程池已满, 发往该依赖的请求就被立即拒绝,而不是排队等待,从而加速失败判定
    即:线程池隔离策略
    如果不进行任何设置,所有熔断方法使用一个Hystrix线程池(10个线程)
    那么这样的话会导致问题,这个问题并不是扇出链路微服务不可用导致的,而是我们的线程机制导致的
    如果方法A的请求把10个线程都用了,方法2请求处理的时候压根都没法去访问B,因为没有线程可用,并不是B服务不可用

    在这里插入图片描述

    在这里插入图片描述

    为了避免问题服务请求过多导致正常服务无法访问,Hystrix 不是采用增加线程数
    而是单独的为每一个控制方法创建一个线程池的方式,这种模式叫做“舱壁模式",也是线程隔离的手段
    即只要我们操作了如下:
    threadPoolKey = "getProductServerPort2",
    //即只要在@HystrixCommand注解里面定义了threadPoolKey
    //就意味着开启了舱壁模式(线程隔离),该操作的方法就会自己维护一个线程池
    //使得该方法可以不会与其他方法共同操作默认的线程池,而容易导致访问不了
    
    • 1
    • 2
    • 3
    • 4
    Hystrix工作流程与高级应用:

    在这里插入图片描述

    这里是可以自定义设置的一个熔断机制,如果是超时的,那么按照超时的处理,而不是这里的处理
    1:当调用出现问题时,开启一个时间窗(10s)
    2:在这个时间窗内,统计调用次数是否达到最小请求数:
    如果没有达到,则重置统计信息,回到第1步
    如果达到了,则统计失败的请求数占所有请求数的百分比,这时查看是否达到阈值:
    如果达到,则熔断,即则跳闸(请求对应服务基本失败)
    如果没有达到,则重置统计信息,回到第1步
    一般来说,这里就是熔断中的处理,即熔断处理,那么对应的调用基本是调用不了的
    虽然可能也可以调用(如超时造成的熔断,而正是因为这样,所以有自我修复机制)
    虽然熔断的意思是操作不可调用(超时算作一种特殊的不可调用,因为最终会调用)
    3:如果跳闸(到这里代表即熔断,因为前面的阈值,也可称他为熔断,后面会说明为什么称他为熔断)
    则会开启一个活动窗口(默认5s),每隔5s,Hystrix会让一个请求通过到达那个问题服务,看是否调用成功
    如果成功,重置断路器(重置跳闸处理,即关闭跳闸,也就没有活动窗口了)回到第1步(即上面的1),如果失败,回到第3步
    通常处理超时外,基本都是访问失败,即后面的不会执行
    上面的三个步骤,整体就是熔断中的工作流程,简称为熔断(虽然与前面介绍的熔断的意思并不是非常符合)
    因为并没有完全的不能访问,如延时,实际上介绍的熔断也是如此,但针对于显示来说的,自然是全部断掉
    但针对于程序并不是,如延时
    而跳闸可以说明是真正的熔断,针对于这个熔断来说
    当然,无论是说明跳闸还是熔断,上面的整体都是对访问失效的一种操作而已
    所以说,你认为跳闸不是熔断也是可以的,而我之所以说跳闸是真正的熔断而不是处理的原因是
    到了跳闸这里,代表访问基本(并不是全部)是失效的(虽然受自己的影响)
    如何设置对应的值:
      commandProperties={
           //设置请求超时时间,一旦请求超过此时间,那么都按照超时处理,这里设置为2000毫秒,即2秒,默认为1秒
           @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "2000"),
           //统计窗口时间的设置,这里是上面图片中的10s的地方(默认好像是10s),这里设置他为8秒,即8000毫秒     
           @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds",value = "8000"),          
           //统计窗口内的最小请求数,默认是20          
           @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "2"),
           //统计窗口内错误请求阈值的设置,这里设置为50,也就是50%
           //即只要有一半是经过处理的,那么就进行真正的熔断,默认是50%     
           @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "50"),    
           //自我修复的活动窗口时间,即上面5s的地方(默认好像是5s),这里设置他为3秒,即3000毫秒
           @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "3000")
                },
    
    //总体来说就是8秒钟内,请求次数达到2个,并且失败率在50%以上,就跳闸,且跳闸后活动窗口设置为3s
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    那么我们如何查看对应的设置是否成功呢,首先根据需求,自然是先要操作熔断,再次发送一个成功的请求
    那么当对应的自我修复机制拿到该请求时,就会重置熔断,在发送之前
    可以先访问下面的健康机制,来看看对应的的状态,比如:
    "hystrix": {
                "status": "UP"
            }
    
    • 1
    • 2
    • 3
    这里我们使用Postman来进行测试,首先,需要在8秒的窗口中,只要调用的问题数是正常的一半,那么就会开启熔断(跳闸)
    那么如何进行多次的访问呢,在Postman里面看如下:

    在这里插入图片描述

    该熔断测试里面只有这localhost:9100/page/loadProductServicePort3一个请求,至于为什么后面会说明
    点击右边的Run,到如下:

    在这里插入图片描述

    其中这个5代表执行次数,0代表间隔,即执行完,立即执行另外一个请求
    从这里可以看出,他们是逐步的,而不是异步的,即操作同步,且是执行熔断测试里面的所有请求
    即执行5次熔断测试的所有请求(所有请求执行完,才算一次,且是从上到下的执行,即这个5,可以理解为5次循环的执行)
    这就是为什么最好只有一个请求的原因,因为很方便,我们点击Run熔断测试
    然后访问http://localhost:9100/actuator/health,如果出现如下(因为是随机的策略,所以可能不会是开启跳闸):
    "hystrix": {
                "status": "CIRCUIT_OPEN",
                "details": {
                    "openCircuitBreakers": [
                        "PageController::getProductServerPort3"
                    ]
                }
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    代表开启了跳闸(也可以说是熔断),即进行跳闸中的操作了
    接下来我们手动的访问localhost:9100/page/loadProductServicePort3(虽然他并不知道使用了负载均衡)
    而正是因为负载均衡,所以可以成功,即没有操作熔断处理的成功会出现如下(虽然对应并没有修复)
    那么访问http://localhost:9100/actuator/health,就会出现如下:
    "hystrix": {
                "status": "UP"
            }
    
    //因为对于的自我修复时间是每隔几秒进行的,所以只要成功即可,无论是否需要多少时间
    
    • 1
    • 2
    • 3
    • 4
    • 5
    代表没有使用熔断,至此测试完毕,当然了,因为是负载均衡,所以手动的访问是可以成功的,否则一般不会访问成功
    我们前面通过注解进行的配置也可以配置在配置文件中(静态化微服务的配置文件,因为他操作了Hystrix):
    # 配置熔断策略:
    hystrix:  
      command:    
        default:      
          circuitBreaker:        
            #强制打开熔断器,如果该属性设置为true,强制断路器进入打开状态,将会拒绝所有的请求,默认false关闭的 
            forceOpen: false      #设置为true,那么基本只操作回退返回值,对应的方法内容不会执行  
            # 触发熔断错误比例阈值,默认值50%        
            errorThresholdPercentage: 50        
            # 熔断后休眠时长,默认值5秒        
            sleepWindowInMilliseconds: 3000        
            # 熔断触发最小请求次数,默认值是20        
            requestVolumeThreshold: 2      
          execution:        
            isolation:          
              thread:            
              # 熔断超时设置,默认为1秒            
              timeoutInMilliseconds: 2000
    #那么注解优先,还是这个配置优先呢,答:注解优先
    #一般情况下,配置是先读取的,那么注解必然是覆盖,虽然他操作的是全局,即所有的方法生效
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    基于springboot的健康检查观察跳闸状态(自动投递微服务暴露健康检查细节):
    # springboot中暴露健康检查等断点端口
    management:  
      endpoints:    
        web:      
          exposure:        
            include: "*"  #表示所有的端口都放开,包括下面的健康检查端口,但也只是放开,并没有细节显示出现
      # 暴露健康端口的细节显示
      #没有这个,一般只会显示如下(无论是否放开,即就算没有management也是一样的显示):
      #{
      #  "status": "UP"
      #}
      endpoint:    
        health:    #  health:健康
          show-details: always #  always:总是
    #上面的端口,代表对应于Cloud组件的是否启动(使用或者开启)的意思
    #你访问时就知道了,出现这个"status": "UP"代表是开启的
    #当然,启用和开启(使用)是有区别的,先要启用,才会有对应的启动
    #如需要启用熔断,才可以开启(使用)熔断操作
    #但是启用和开启是相对于的,即启用也可以说是开启,后面的开启则是代表使用等等意思,只是启用和开启更好的表示
    
    #上面的操作,前提需要有spring-boot-starter-actuator依赖,否则访问失败
    #页面的错误显示:
    #	{
    #    	"timestamp": "2022-09-03T07:49:48.889+0000",
    #    	"status": 404,
    #    	"error": "Not Found",
    #    	"message": "No message available",
    #    	"path": "/actuator/health"
    #	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    访问健康检查接口:http://localhost:9100/actuator/health,不能是post,否则访问失败,基本只能是get
    post请求的部分错误信息:
    {
        "timestamp": "2022-09-04T03:10:43.083+0000",
        "status": 405,
        "error": "Method Not Allowed",
        "message": "Request method 'POST' not supported",
        "trace": "org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported\r\n\tat org.springframewo
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    Hystrix 线程池队列配置案例:
    有一次在生产环境,突然出现了很多笔还款单被挂起,后来排查原因,发现是内部系统调用时出现了Hystrix调用异常
    在开发过程中,因为核心线程数设置的比较大,没有出现这种异常,放到了测试环境,偶尔有出现这种情况
    后来调整maxQueueSize属性,确实有所改善,可没想到在生产环境跑了一段时间后却又出现这种了情况
    此时我第一想法就是去查看maxQueueSize属性,可是maxQueueSize属性是设置值了
    当时就比较纳闷了,为什么maxQueueSize属性不起作用
    后来通过查看官方文档发现Hystrix还有一个queueSizeRejectionThreshold属性,这个属性是控制队列最大阈值的
    而Hystrix默认只配置了5个,因此就算我们把maxQueueSize的值设置再大,也是不起作用的。两个属性必须同时配置
    hystrix:  
      threadpool:    
        default:      
          coreSize: 10 #并发执行的最大线程数,默认10 
          #这个属性,一般最好设置为自己主机的cpu核数,因为太大影响性能     
          maxQueueSize: 1000 #BlockingQueue的最大队列数,默认值-1      
          queueSizeRejectionThreshold: 800 
          #即使maxQueueSize没有达到,达到queueSizeRejectionThreshold该值后,请求也会被拒绝,默认值5
          #可以理解为他是设置了队列存放上限
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    正确的配置案例:
    将核心线程数调低,最大队列数和队列拒绝阈值的值都设置大一点:
    hystrix:  
      threadpool:    
        default:      
          coreSize: 10 #并发执行的最大线程数,默认10 
          #这个属性,一般最好设置为自己主机的cpu核数,因为太大影响性能     
          maxQueueSize: 1500 #BlockingQueue的最大队列数,默认值-1      
          queueSizeRejectionThreshold: 1000 
          #即使maxQueueSize没有达到,达到queueSizeRejectionThreshold该值后,请求也会被拒绝,默认值5
          #可以理解为他是设置了队列存放上限,即你虽然允许可以存放1500个队列,但我的上限只有1000个
          #当然若没有超过上限,但也不能超过允许的
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    至此,熔断操作完毕,最后说明,在显示时,熔断代表断开,但在程序里,并不是的,因为有延时的存在
    虽然是断开,但其中还是有一系列的操作的,针对于调用的维护的操作,如自我修复机制
    Feign远程调用组件,一般我们指定提供者的信息,然后调用
    在之前的案例中,服务消费者调用服务提供者的时候使用RestTemplate技术
    如:
      @GetMapping("loadProductServicePort3")
        public String getProductServerPort3(){
            List<String> services = discoveryClient.getServices();
            System.out.println(services); 
            //可以得到除了当前工程的其他名称,从上到下的(一般按照启动先后来排顺序)
            //[lagou-cloud-eureka, lagou-service-product],我们虽然可以将该值放入下面
            //但是如果以后扩展,可能会出现问题
            String url = "http://lagou-service-product/service/port/";
            System.out.println(1);
            String forObject = restTemplate.getForObject(url, String.class);
    
            System.out.println(5);
            System.out.println(forObject);
            return forObject;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    在操作Feign之前,首先先介绍一下他
    Feign简介:
    Feign是Netflix开发的一个轻量级RESTful的HTTP服务客户端(用它来发起请求,远程调用的)
    是以Java接口注解的方式调用Http请求,而不用像Java中通过封装HTTP请求报文的方式直接调用
    Feign被广泛应用在Spring Cloud 的解决方案中
    也类似于Dubbo,服务消费者拿到服务提供者的接口,然后像调用本地接口方法一样去调用,实际发出的是远程的请求
    对于Feign的作用:
    Feign可帮助我们更加便捷,优雅的调用HTTP API:
    不需要我们去拼接url然后呢调用restTemplate的api,在SpringCloud中,使用Feign非常简单
    创建一个接口(在消费者,即服务调用方这一端),并在接口上添加一些注解,代码就完成了
    SpringCloud对Feign进行了增强,使Feign支持了SpringMVC注解(OpenFeign)
    本质:封装了Http调用流程,更符合面向接口化的编程习惯,类似于Dubbo的服务调用
    Feign配置应用:
    在服务调用者工程(消费)创建接口(添加注解)
    (效果)Feign = RestTemplate+Ribbon+Hystrix,他可以操作对应的三个功能,前提是需要有对应的依赖
    在服务消费者工程(页面静态化微服务)中引入Feign依赖(或者父类工程):
    <dependency>      
        
        <groupId>org.springframework.cloudgroupId>           
        <artifactId>spring-cloud-starter-openfeignartifactId>  
            
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    要想使用,首先需要启用,回到静态化微服务的启动类:
    @SpringCloudApplication
    @EnableFeignClients //表示启用Fegin客户端功能,有了这个,可以注释掉对应的熔断的启用,它会操作熔断的操作
    public class PageApplication {
        //注意:虽然他可以操作熔断的操作,但是其他没有操作他的方法的不会有熔断
        //且他并没有回退的操作,所以这个启用熔断也最好加上
        //到那时,因为回退是最后执行,那么就覆盖了,所以操作了回退
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    在消费者微服务中创建Feign接口:
    在page包下,创建feign.ProductFeign接口:
    package com.lagou.page.feign;
    
    import com.lagou.common.pojo.Products;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    
    /**
     * 自定义的Feign接口,调用Product商品微服务的所有的接口方法都在此进行定义
     */
    @FeignClient(name="lagou-service-product")
    public interface ProductFeign {
    
        /*
        通过商品id查询商品对象
         */
        @GetMapping("/product/query/{id}")
        public Products queryById(@PathVariable Integer id);
    
        /*
        获得端口号
        */
        @GetMapping("/service/port")
        public String getPort();
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    注意:
    1:@FeignClient注解的name属性用于指定要调用的服务提供者名称,和服务提供者yml文件中spring.application.name保持一致
    2:接口中的接口方法,就好比是远程服务提供者Controller中的Hander方法(只不过如同本地调用了)
    那么在进行参数绑定的时,可以使用@PathVariable、@RequestParam、@RequestHeader等
    这也是OpenFeign对SpringMVC注解的支持
    但是需要注意value必须设置,否则会抛出异常(包括这样的说明:但是上面的value必须设置,即{id}
    否则访问会抛出异常,这是基本的知识,68章博客有说明)
    3:@FeignClient(name = “lagou-service-product”),name在消费者微服务中只能出现一次
    升级Spring Boot 2.1.0 Spring Cloud Greenwich.M1 版本后,在2个Feign接口类内定义相同的名字
    即@FeignClient的name若是相同的名字 就会出现报错,在之前的版本不会提示报错,大概运行时报错,也有可能是覆盖了
    所以最好将调用一个微服务的信息都定义在一个Feign接口中
    改造PageController类中原有的调用方式(部分):
    @Autowired
        private ProductFeign productFeign;  
    
    @GetMapping("loadProductServicePort3")
        public String getProductServerPort3(){
    //        List services = discoveryClient.getServices();
    //        System.out.println(services);
    //        String url = "http://lagou-service-product/service/port/";
    //        System.out.println(1);
    //        String forObject = restTemplate.getForObject(url, String.class);
    //
    //        System.out.println(5);
    //        System.out.println(forObject);
    //        return forObject;
            String port = productFeign.getPort(); //一般对方微服务里面的方法报错,这里通常也会报错
            return port;
        }
    
    
    //我们发现,可以注入ProductFeign,因为@FeignClient(name="lagou-service-product")的存在,使得可以注入
    //且由于对应的@GetMapping的存在,当你执行对应的方法时,会自动的拼接name和@GetMapping的地址,从而操作返回
    //而正是因为拼接,所以方法名可以与提供者不一致,即可以getPor()
    //但自然返回值肯定需要一致的,这是自然的,否则就如前面的类型不一致报错一样
    //我们对比一下平常的拼接,可以说明,他只是将拼接的操作封装了,在外面启用Feign时
    //调用该方法,就会拼接调用,从而减少代码的使用,但本质上也就是操作url来访问的,只是拼接地址的编写在接口了
    //与dubbo不同的是,dubbo一般是利用数据是操作tcp(一般是这个)或者是udp的缘故,当然,因为需要定义注册信息
    //他那里一般操作接口对应,而不是这里的名称对应
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    启动继续访问,发现可以访问,至此操作成功
    Feign对负载均衡的支持:
    Feign 本身已经集成了Ribbon依赖和自动配置,即他的访问是操作负载均衡的
    因此我们不需要额外引入依赖或者使用@LoadBalanced注解,之前我们说过Feign = RestTemplate+Ribbon+Hystrix
    可以理解为他操作RestTemplate并给他加上@LoadBalanced注解,在操作部分熔断处理(之所以是部分,因为没有回退操作)
    拼接的操作给接口即可,所以简化了我们的编写
    可以通过 ribbon.xx 来进行全局配置,也可以通过 “服务名.ribbon.xx” 来对指定服务进行细节配置(参考之前的策略设置, 此处略)
    Feign默认的请求处理超时时长是1 s,有时候我们的业务确实执行的需要一定时间
    那么这个时候,我们就 需要调整请求处理超时时长, Feign自己有超时设置,如果配置Ribbon的超时,则会以Ribbon的为准
    #针对的被调用方微服务名称,不加就是全局生效
    lagou-service-product:  
      ribbon:    
        #请求连接超时时间    
        ConnectTimeout: 2000    
        #请求处理超时时间,默认是1秒  
        ReadTimeout: 5000    
        #对所有操作都进行重试    
        OkToRetryOnAllOperations: true    
        #根据如上配置,当访问到故障请求的时候,它会再尝试访问一次当前实例(次数由MaxAutoRetries配置)
        #如果不行,就换一个实例进行访问,如果还不行
        #再换一次实例访问(更换次数由MaxAutoRetriesNextServer配置)
        #如果依然不行,返回失败信息,该失败的超时信息会给前端,与熔断超时的信息不同,即
        #"message": "Read timed out executing GET http://lagou-service-product/service/port",
        #服务器返回:java.net.SocketTimeoutException: Read timed out
        #熔断的是:"message": "getProductServerPort2 timed-out and fallback failed.", 
        #但都是超时错误信息
        MaxAutoRetries: 0 #对当前选中实例重试次数,不包括第一次调用    
        MaxAutoRetriesNextServer: 0 #切换实例的重试次数    
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule 
        #不能有两个该属性的,所以没有覆盖一说
        #负载策略调整(这里是轮询策略)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    注意:在我们使用熔断启用时,如果出现超时错误,一般是对应的自己的错误(返回对应的错误信息)
    但若有回退,那么都操作回退
    但是,若不使用熔断启用,即使用Feign的熔断处理,那么只考虑请求超时,对应的访问超时无论怎么设置
    都只有请求的错误或者成功
    所以说,若单独的操作超时,那么只有该超时的操作,而没有其他的熔断超时
    你可以试着将@EnableCircuitBreaker(启用熔断服务),去除,将熔断超时设置为1000
    来测试即可,你会发现,只有请求的超时问题,他并不会操作,即因为并不是同一个熔断
    他是Feign的熔断,即只操作Feign的熔断了,但是若启用的话,则都会操作,且熔断的超时优先,只要满足就会返回
    所以说,通常情况下,@EnableCircuitBreaker最好加上,但是若只针对提供者,那么可以不加,实际上我们也只针对提供者
    所以有些时候是不加的,而实际上Feign操作了真正的熔断,因为超时的错误信息,使得不会让后面的程序执行
    而不像@EnableCircuitBreaker一样,可以执行,所以他们的共同操作,可以使得完全真正的熔断
    虽然要考虑很多细节,但只要跨大步即可
    Feign对熔断器的支持:
    在Feign客户端工程配置文件(application.yml)中开启Feign对熔断器的支持
    # 开启Feign的熔断功能
    feign:  
      hystrix:    
        enabled: true #默认是false,即不开启
    #在前面我说过,Feign只有部分的熔断处理,即没有回退逻辑,那是因为使用自己的熔断
    #而没有使用启用的熔断,若要使用,则将这里设置为true
    #这样,你也就可以真正的去除@EnableCircuitBreaker注解了
    #这也使得他是完整的真正的熔断了(因为不会执行后面的,且有回退),但一些的其他机制可能并没有,如自我修复机制
    #但若有这个注解,则注解优先,即只操作注解的对应信息(对应覆盖了)
    #这也使得返回的错误信息是不同的,因为设置为true的错误信息是(虽然启用,但有些并不是一定相同,因为启用自带的)
    #虽然之前用的是没有启用的
    #"message": "ProductFeign#getPort() timed-out and no fallback available.",
    #而不是"message": "getProductServerPort2 timed-out and fallback failed.", 
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    Feign的超时时长设置那其实就上面Ribbon的超时时长设置
    Hystrix超时设置(就按照之前Hystrix设置的方式就OK了)
    最后要注意,对应的超时是针对调用,而不是访问,与我们手动的熔断器的对象不同,所以会有如下:
    1:开启Hystrix之后,Feign中的方法都会被进行一个管理了,一旦出现问题就进入对应的回退逻辑处理
    2:针对超时这一点,当前有两个超时时间设置(Feign/hystrix),熔断的时候是根据这两个时间的最小值来进行的
    即处理时长超过最短的那个超时时间了就熔断进入回退降级逻辑
    但由于不是同一个熔断器,即原来的回退不会操作,因为不是同一个熔断器
    好像只操作自己的默认的了,并不会操作设置的(不是同一个),所以即1秒(最小的)
    且负载均衡的配置可能并不会使用(使用他自己的),所以他的默认设置为false,即最好不要使用这个,因为时间太短
    需要自己去配置他的信息,具体设置可以去百度(好像并没有),大概是因为是提供者吧
    那么这里说明一下,对应的回退逻辑方法操作如下:
    首先在feign包下,创建ProductFeignFallBack类:
    package com.lagou.page.feign;
    
    import com.lagou.common.pojo.Products;
    import org.springframework.stereotype.Component;
    
    /**
     * Feign自带熔断器触发后的回调逻辑操作
     */
    @Component
    public class ProductFeignFallBack implements ProductFeign{
        //定义一个方法,来实现对应的接口,只要我们调用对应的接口出现了问题
        //那么就调用这个下面的对应方法,将其返回的信息赋值给显示,即可以返回给前端
        //前提是需要配置
    
        @Override
        public Products queryById(Integer id) {
            return null;
        }
    
        @Override
        public String getPort() {
            return "-1";
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    我们需要指定,指定操作如下:
    回到ProductFeign接口,修改如下:
    @FeignClient(name="lagou-service-product",fallback = ProductFeignFallBack.class)
    public interface ProductFeign {
    
    • 1
    • 2
    至此,我们重新启动,进行测试,发现,对应的错误不是错误信息了,而是-1,至此他对提供的调用,实现了完整的熔断了
    注意:当操作了回退时,延时的对应的程序会使得继续运行,而不会直接的停止,没有回退不会,与手动的熔断器,就这里不同
    Feign对请求压缩和响应压缩的支持:
    Feign 支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗
    通过下面的参数 即可开启请求与响应的压缩功能:
    feign:  
      hystrix:    
        enabled: true  
        #开启请求和响应压缩的设置,默认是不开启的,开启后,才会操作压缩
      compression:    
        request:      
          enabled: true #默认不开启,即默认为false      
          mime-types: text/html,application/xml,application/json # 设置压缩的数据类型,此处也是默认值,
          #默认值也就是text/html,application/xml,application/json,所以可以不用设置,点击进去看就知道了
          min-request-size: 2048 
          # 设置触发压缩的大小下限,即大于等于这个大小(即2048k),就会触发压缩,此处也是默认值    
        response:      
          enabled: true #默认不开启,即默认为false    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    GateWay网关组件:
    网关:微服务架构中的重要组成部分
    局域网中就有网关这个概念,局域网接收或者发送数据出去通过这个网关
    比如用Vmware虚拟机软件搭建虚拟机集群的时候,往往我们需要选择IP段中的一个IP作为网关地址
    我们学习的GateWay–>Spring Cloud GateWay(它只是众多网关解决方案中的一种)
    GateWay简介:
    Spring Cloud GateWay是Spring Cloud的一个全新项目,目标是取代Netflix Zuul
    它基于Spring5.0+SpringBoot2.0+WebFlux(基于高性能的Reactor模式响应式通信框架Netty,异步非阻塞模型)等技术开发
    性能高于Zuul,官方测试,GateWay是Zuul的1.6倍,旨在为微服务架构提供一种简单有效的统一的API路由管理方式
    Spring Cloud GateWay不仅提供统一的路由方式(反向代理)
    并且基于 Filter(定义过滤器对请求过滤,完成一些功能)链的方式提供了网关基本的功能
    例如:鉴权、流量控制、熔断、路径重写、日志监控等
    网关在架构中的位置:

    在这里插入图片描述

    GateWay核心概念 :
    Spring Cloud GateWay天生就是异步非阻塞的,基于Reactor模型(同步非阻塞的I/O多路复用机 制)
    一个请求—>网关根据一定的条件匹配—匹配成功之后可以将请求转发到指定的服务地址
    而在这 个过程中,我们可以进行一些比较具体的控制(限流、日志、黑白名单)
    路由(route): 网关最基础的部分,也是网关比较基础的工作单元
    路由由一个ID、一个目标URL(最终路由到的地址)、一系列的断言(匹配条件判断)和Filter过滤器(精细化控制)组成
    如果断言为true,则匹配该路由
    断言(predicates):参考了Java8中的断言java.util.function.Predicate
    开发人员可以匹配Http请求中的所有内容(包括请求头、请求参数等,类似于nginx中的location匹配一样)
    如果断言 与请求相匹配则路由
    过滤器(fifilter):一个标准的Spring webFilter,使用过滤器,可以在请求之前或者之后执行业务 逻辑
    GateWay如何工作:

    在这里插入图片描述

    Spring 官方介绍:
    客户端向Spring Cloud GateWay发出请求,然后在GateWay Handler Mapping中找到与请求相匹配 的路由
    将其发送到GateWay Web Handler,Handler再通过指定的过滤器链来将请求发送到我们实际 的服务执行业务逻辑,然后返回
    过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前 (pre)或者之后(post)执行业务逻辑
    Filter在"pre"类型过滤器中可以做参数校验、权限校验、流量监控、日志输出、协议转换等
    在"post"类型的过滤器中可以做响应内容、响应头的修改、日志的输出、流量监控等
    GateWay应用:
    使用网关对静态化微服务进行代理(添加在它的上游,相当于隐藏了具体微服务的信息,对外暴露的 是网关)
    创建工程lagou-cloud-gateway并导入依赖
    最好是独立的项目,而不是不是子项目,因为网关主要是独立的,虽然也可以是子项目
    GateWay不需要使用web模块,它引入的是WebFlux(类似于SpringMVC)
        
    <parent>       
        <groupId>org.springframework.bootgroupId>   
        <artifactId>spring-boot-starter-parentartifactId>  
        <version>2.1.6.RELEASEversion>  
        <relativePath /> 
             
    parent>
    <dependencies>    
        <dependency>       
            
            <groupId>org.springframework.cloudgroupId>      
            <artifactId>spring-cloud-commonsartifactId>   
        dependency>   
        <dependency>     
                
            <groupId>org.springframework.cloudgroupId>    
            <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId> 
        dependency>     
            
        <dependency>        
            <groupId>org.springframework.cloudgroupId>   
            <artifactId>spring-cloud-starter-gatewayartifactId>   
        dependency>       
          
        <dependency>         
          
            
            <groupId>org.springframework.bootgroupId>  
            <artifactId>spring-boot-starter-webfluxartifactId>    
        dependency>   
            
        <dependency>           
            <groupId>org.springframework.bootgroupId> 
            <artifactId>spring-boot-starter-loggingartifactId> 
        dependency>      
             
        <dependency>           
            <groupId>org.springframework.bootgroupId>        
            <artifactId>spring-boot-starter-testartifactId> 
            <scope>testscope>       
        dependency>       
           
        <dependency>          
            <groupId>org.projectlombokgroupId>    
            <artifactId>lombokartifactId>         
            <version>1.18.4version>        
            <scope>providedscope>     
        dependency>
             
        <dependency>          
            <groupId>com.sun.xml.bindgroupId>    
            <artifactId>jaxb-coreartifactId>    
            <version>2.2.11version>      
        dependency>       
        <dependency>       
            <groupId>javax.xml.bindgroupId> 
            <artifactId>jaxb-apiartifactId> 
        dependency>      
        <dependency>        
            <groupId>com.sun.xml.bindgroupId>     
            <artifactId>jaxb-implartifactId>     
            <version>2.2.11version>    
        dependency>     
        <dependency>     
            <groupId>org.glassfish.jaxbgroupId> 
            <artifactId>jaxb-runtimeartifactId>  
            <version>2.2.10-b140310.1920version> 
        dependency>   
        <dependency>         
            <groupId>javax.activationgroupId>    
            <artifactId>activationartifactId>         
            <version>1.1.1version>  
        dependency>      
        
            
        <dependency>           
            <groupId>org.springframework.bootgroupId>     
            <artifactId>spring-boot-starter-actuatorartifactId>  
        dependency>      
            
        <dependency>      
            <groupId>org.springframework.bootgroupId>       
            <artifactId>spring-boot-devtoolsartifactId>       
            <optional>trueoptional>      
        dependency>
            
        <dependency>           
            <groupId>org.springframework.cloudgroupId>       
            <artifactId>spring-cloud-starter-sleuthartifactId>   
        dependency>
         <dependency>         
             <groupId>org.springframework.cloudgroupId>        
             <artifactId>spring-cloud-starter-zipkinartifactId>  
        dependency>
         dependencies>
     <dependencyManagement>   
               
         <dependencies>       
             <dependency>               
                 <groupId>org.springframework.cloudgroupId>      
                 <artifactId>spring-cloud-dependenciesartifactId>
                  <version>Greenwich.RELEASEversion>    
                 <type>pomtype>        
                 <scope>importscope>   
             dependency>     
         dependencies> 
    dependencyManagement>
     <build>      
         <plugins>    
                     
             <plugin>               
                 <groupId>org.apache.maven.pluginsgroupId>   
                 <artifactId>maven-compiler-pluginartifactId> 
                 <configuration>              
                     <source>11source>       
                     <target>11target>       
                     <encoding>utf-8encoding>      
                 configuration>    
             plugin>          
                  
             <plugin>           
                 <groupId>org.springframework.bootgroupId>
                 <artifactId>spring-boot-maven-pluginartifactId>      
             plugin>      
         plugins>  
    build>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    到资源文件夹下,创建com.lagou.gateway.GateWayServerApplication类(作为启动类):
    package com.lagou.gateway;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    
    /**
     *
     */
    @SpringBootApplication
    @EnableDiscoveryClient
    public class GateWayServerApplication {
        public static void main(String[] args) {
            SpringApplication.run(GateWayServerApplication.class,args);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    创建配置文件application.yml:
    server:  
      port: 9300
    eureka:  
      client:    
        serviceUrl: # eureka server的路径      
          defaultZone: http://LagouCloudEurekaServerA:9200/eureka,http://LagouCloudEurekaServerB:9201/eureka  
      instance:    
        prefer-ip-address: true    
        instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
    spring:  #一般没有说明是谁的下一级时,通常是最上一级
      application:    
        name: lagou-cloud-gateway  
        #下面是主要的部分
        #网关的配置  
      cloud:    
        gateway:      
          routes: #配置路由,根据路由可以知道你的请求是到那个微服务    
            - id: service-page-router        #- id是名称,即基本必须这样写,这是规定的   
              uri: http://127.0.0.1:9100          
              predicates: #当断言成功后(即下面匹配成功后)
              #交给某一个微服务处理时使用的是转发,这个配置可以说是断言          
                - Path=/page/** #**代表只要是/page/开头的url,就可以匹配,所以满足后,会转化成uri,操作转发
            - id: service-product-router          
              uri: http://127.0.0.1:9000          
              predicates:            
                - Path=/product/**        
              #上面整体,就是,当你传递一个路径
              #首先看除了"ip(或者域名):端口"的后面的整体路径是否是/page/**开头
              #一般**代表所有,而*代表一个路径
              #所以当我们访问product/query/1一般需要**,而不是*
              #否则会报错(他独有的404,网关给的),即访问失败
              #若是(回答是否是/page/**开头),则将地址变成uri,并且后面加上匹配成功的路径
              #然后操作转发,那这个- id后面的值是干什么的呢,答:现在并没有什么作用,即可以随便写
              filters:            
              # http://127.0.0.1:9300/product/service/port  -->  /service/port --> 商品微服务            
                - StripPrefix=1  #注意他操作的是- Path=/product/**  ,因为是写在他下面的,而不是操作所有
                #即- Path=/page/**不会被操作
                #这个1代表,去掉uri中的第一部分,2代表去掉前两个部分,以此类推,而0代表不去掉
                #不能设置为负数和小数,否则访问基本报错
                #这里因为是1,所以就要求我们通过网关访问的时候,把uri的第一部分设置为product
                #从uri的第二部分开始才是真正的uri,即原来的访问product/service/port 变成了service/port
                #那么这样的好处是上面,我们可以用第一部分代表对应的服务整体介绍,后面的代表真实路径
                #而不会与匹配路径进行必须一致了,但通常情况下,还是要看具体的操作,并不是一定操作这个
                #一般我们都不会操作这个,因为匹配路径也就是对应的服务介绍
                #而对于的请求中也一般有他做开头,而不是没有开头(这时使用这个,比如这里的获取端口)
                
                #但无论怎么设置,都是根据具体业务来的
                
                
                
                #注意:复制代码时,记得看看是否符合格式,可能复制过去了格式变了,改变即可
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    这时,我们访问localhost:9300/product/query/1,他会找根据除了"ip(或者域名):端口"的整体地址
    也就是product/query/1/进行匹配
    很明显,可以发现,匹配到了/product/**,即将对应的地址变成http://127.0.0.1:9000/product/query/1进行转发操作
    若是操作了过滤,则需要localhost:9300/product/product/query/1,因为地址变成之后
    会去除第一个的部分(因为是过滤器,自然会进行操作,即也是在地址变成后操作),也就是/product
    这里的/无论怎么加都是一个,除了第一部分的前面的/不能加\外,其他可以
    当然这是程序的作用,也有时因为浏览器的作用(浏览器的作用是主要的,有时再第一部分前面只能是一个/)
    当然,这些作用我们并不需要知道(一般都是省略变成一个"/",有些部分可能不会,如第一部分的前面)
    但我们只要按照规范写即可,就能防止这些问题,即写上一个"/“即可,所以我们也最好操作一个”/"
    这就是具体流程,那么我们也知道,上面进行了注册,如果不注册,可以操作吗
    答:可以操作,因为他只是注册,且我们并没有使用注册中心的操作,他只是给我们操作转发而已,所以可以使用
    上面的网关独有的404一般是这样的:
    /*
    部分(一般没有匹配成功或者断言成功的,就会显示这个):
    {
        "timestamp": "2022-09-03T06:55:30.007+0000",
        "path": "/product/query/1",
        "status": 404,
        "error": "Not Found",
        "message": null,
         "trace": "org.springframework.web.server.ResponseStatusException: 404 NOT_FOUND\r\n\tat org.springframework.web.reactive.resource.R 后面省略
        
        而通常的404一般是这样的:
      {
        "timestamp": "2022-09-03T06:55:21.576+0000",
        "status": 404,
        "error": "Not Found",
        "message": "No message available",
        "path": "/product/que" 对应的找不到的错误路径
    }
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    GateWay路由规则详解:
    Spring Cloud GateWay 帮我们内置了很多 Predicates功能
    实现了各种路由匹配规则(通过 Header、请求参数等作为条件)匹配到对应的路由

    在这里插入图片描述

    时间点后匹配:
    spring:  
      cloud:    
        gateway:      
          routes:        
            - id: after_route          
            uri: https://example.org          
            predicates:            
              - After=2017-01-20T17:42:47.789-07:00[America/Denver]
              #代表再该时间后,才可以访问,即断言成功(即匹配成功)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    时间点前匹配:
    spring:  
      cloud:    
        gateway:      
          routes:      
            - id: before_route        
            uri: https://example.org        
            predicates:        
              - Before=2017-01-20T17:42:47.789-07:00[America/Denver]
              #代表再该时间前,才可以访问,即断言成功(即匹配成功)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    时间区间匹配:
    spring:  
      cloud:    
        gateway:      
          routes:      
            - id: between_route        
            uri: https://example.org        
            predicates:        
              - Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
              #代表再该时间之间,才可以访问,即断言成功(即匹配成功)
              
              
              #逗号,后面的空格是忽略的,所以可以加上空格,基本其他操作逗号的配置也是如此
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    指定Cookie,且可以操作正则匹配指定值:
    spring:  
      cloud:    
        gateway:      
          routes:      
            - id: cookie_route        
            uri: https://example.org        
            predicates:        
              - Cookie=chocolate, ch.p
              #匹配cookie,可以操作正则
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    指定Header,且可以操作正则匹配指定值:
    spring:  
      cloud:    
        gateway:      
          routes:      
            - id: header_route        
            uri: https://example.org        
            predicates:        
              - Header=X-Request-Id, \d+
               #匹配header,操作了正则
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    请求Host匹配指定值:
    spring:  
      cloud:    
        gateway:      
          routes:      
            - id: host_route        
            uri: https://example.org        
            predicates:        
              - Host=**.somehost.org,**.anotherhost.org
              #匹配主机域名名称
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    请求Method匹配指定请求方式:
    spring:  
      cloud:    
        gateway:      
          routes:      
            - id: method_route       
            uri: https://example.org     
            predicates:     
              - Method=GET,POST
              #匹配方法
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    请求路径,且可以操作正则匹配:
    spring:  
      cloud:    
        gateway:      
          routes:     
            - id: path_route      
            uri: https://example.org    
            predicates:      
              - Path=/red/{segment},/blue/{segment}
              #匹配路径,逗号可以分开(可加空格),也可以只加逗号,并不是所有的配置都可以操作只加逗号
              #比如注册,就不能只加逗号,虽然不报错,但页面会出现一些特殊的显示的提示信息
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    请求包含某参数:
    spring:  
      cloud:    
        gateway:      
          routes:      
            - id: query_route        
            uri: https://example.org      
            predicates:      
              - Query=green
                  #匹配参数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    请求包含某参数,且可以操作参数值匹配正则表达式:
    spring:  
      cloud:    
        gateway:      
          routes:      
            - id: query_route       
            uri: https://example.org    
            predicates:       
              - Query=red, gree.
              #匹配参数,可以操作正则
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    远程地址匹配:
    spring:  
      cloud:    
        gateway:      
          routes:      
            - id: remoteaddr_route        
            uri: https://example.org      
            predicates:        
              - RemoteAddr=192.168.1.1/24
              #匹配地址
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    上面大致了解即可,一般我们会操作时间的区间来操作规定时间内的活动,和路径的匹配等等,其他的我们只要了解即可
    若出现了对应的业务环境,则可以去操作,但我们通常只会操作路径的匹配,以及特殊情况下的时间区间来匹配
    GateWay动态路由详解:
    GateWay支持自动从注册中心中获取服务列表并访问,即所谓的动态路由
    前面因为我们是固定写死的,所以我们并没有操作注册信息,现在我们需要对应的信息了
    实现步骤如下:
    1:pom.xml中添加注册中心客户端依赖(因为要获取注册中心服务列表,eureka客户端已经引入)
    2:动态路由配置
    spring:
      application:
        name: lagou-cloud-gateway
        #下面是主要的部分
        #网关的配置
      cloud:
        gateway:
          routes: #配置路由,根据路由可以知道你的请求是到那个微服务
            - id: servicrrr 
              uri: lb://lagou-service-page 
              predicates: #当断言成功后,交给某一个微服务处理时使用的是转发
                - Path=/page/** #**代表只要是/page/开头的url,就可以匹配,所以满足后,会转化成uri,操作转发
            - id: serviceee
              uri: lb://lagou-service-product #需要以lb://开头
              #若有多个地址(一个名称可以对应多个服务地址),会帮我们操作负载均衡,一般默认为随机
              predicates:
                - Path=/product/**,
              filters:
                - StripPrefix=1
                
                #且注意:不能是层级关系必须要写好,即如a: b:,那么会报错(启动报错,那么就需要重新启动了)
                #应该是
                #a:
                #  b:
                #这样才对
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    注意:动态路由设置时,uri以 lb://开头(lb代表从注册中心获取服务),后面是需要转发到的服务名称
    因为需要通过该服务获取信息,而操作地址和端口来访问,而正是因为如此,需要注册,这样才可以得到信息
    因为只有注册了,我们才基本可以获取对应的列表信息
    GateWay过滤器:
    GateWay过滤器简介:
    从过滤器生命周期(影响时机点)的角度来说,主要有两个pre和post:

    在这里插入图片描述

    他们简单的解释为,客户端发送请求信息到过滤器,过滤器到微服务,然后微服务将响应信息到过滤器,过滤器到客户端的意思
    其中过滤器就可以称为网关(多个过滤器链的操作)
    过滤器除了可以操作请求和响应外,也可以操作转发和重定向,即过滤器也可以看成一个servlet
    即可以只有过滤器也可,而不操作资源的存放,单独的将服务器当成过滤器,一般网关就是这样
    即request和response的走向,上面的去除就是request的走向
    最后到访问微服务,但他们都是在网关上的过滤
    从过滤器类型的角度, Spring Cloud GateWay的过滤器分为GateWayFilter和GlobalFilter两种:

    在这里插入图片描述

    如GatewayFilter可以去掉url中的占位后转发路由,比如:
      predicates:       
        - Path=/product/**        
      filters:        
        - StripPrefix=1  # 可以去掉product之后转发,但他只操作单个路由
        
        #没有如下:
        filters:        
          - StripPrefix=1
          predicates:       
            - Path=/product/**        
        
    #这个过滤器就是GatewayFilter操作单个路由,一般是通过配置操作的
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    注意:GlobalFilter全局过滤器是程序员使用比较多的过滤器,我们主要讲解这种类型
    与GatewayFilter不同的是,需要代码操作,即自定义类来实现,而不是操作配置文件
    自定义全局过滤器实现IP访问限制(黑白名单):
    请求过来时,判断发送请求的客户端的ip,如果在黑名单中,拒绝访问
    自定义GateWay全局过滤器时,我们实现Global Filter接口即可,通过全局过滤器可以实现黑白名单、限流等功能
    接下来我们在lagou-cloud-gateway项目中的gateway包下,创建filter.BlackListFilter类:
    //一般情况下,会说明实现了什么接口,但有时不会,自己注意看即可
    package com.lagou.gateway.filter;
    
    import org.springframework.boot.web.servlet.filter.OrderedFilter;
    import org.springframework.cloud.gateway.filter.GatewayFilter;
    import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    import org.springframework.cloud.gateway.filter.GlobalFilter;
    import org.springframework.core.Ordered;
    import org.springframework.core.io.buffer.DataBuffer;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.server.reactive.ServerHttpRequest;
    import org.springframework.http.server.reactive.ServerHttpResponse;
    import org.springframework.stereotype.Component;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;
    
    import java.net.InetSocketAddress;
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * 通常情况下进行网关自定义过滤器时
     * 需要实现两个接口:GlobalFilter 和 Ordered(指定过滤器的执行顺序)
     */
    @Component
    public class BlackListFilter implements GlobalFilter, Ordered {
    
        /*
        加载黑名单列表
        一般我们存在数据库的,一般情况下是mysql-redis-内存
        这里我们手动的设置了
         */
        private static List<String> blackList = new ArrayList<>();
        //静态变量需要写在静态块上面,有先后顺序的加载的
    
        static{
            //将本机地址加上到黑名单中
            blackList.add("127.0.0.1");
        }
    
        /*
        GlobalFilter接口的方法,是过滤器的核心逻辑
        exchange:封装了request和response上下文
        chain:网关过滤器链
    
         */
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            //获得请求和响应的对象
            ServerHttpRequest request = exchange.getRequest();
            ServerHttpResponse response = exchange.getResponse();
    
            //获取来访者的ip地址
            String hostString = request.getRemoteAddress().getHostString();
    
            //判断是否在黑名单中
            if(blackList.contains(hostString)){ //如果是黑名单
                //拒绝访问
                response.setStatusCode(HttpStatus.UNAUTHORIZED); //没有授权
                String data = "request be denied";
    
                //需要返回Mono,就需要下面的操作,并操作响应的数据
                DataBuffer wrap = response.bufferFactory().wrap(data.getBytes());
                return response.writeWith(Mono.just(wrap));
                 //返回了这个,就代表不往后面走了
            }
    
    
            return chain.filter(exchange); //返回这个,代表往后面走
    
            //由于我们操作了扫描,那么对应的过滤器可以操作(操作了注入),所以我们请求时,就会经过这个过滤器
        }
    
        /*
        Ordered接口的方法,用来定义过滤器的顺序
        这个方法的返回值的大小,决定了这个过滤器的优先级,越小优先级越高
         */
    
        @Override
        public int getOrder() {
            return 0;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    这时,我们去访问http:/127.0.0.1:9300/product/service/port和http://localhost:9300/product/service/port
    前者返回对应的提示信息request be denied,后面的可以正常访问,因为我们获取的是对应的地址(该地址不会是DNS变成的ip)
    所以是不同的,至此黑名单操作完毕
    GateWay高可用:
    网关作为非常核心的一个部件,如果挂掉,那么所有请求都可能无法路由处理,因此我们需要做GateWay的高可用
    GateWay的高可用很简单:可以启动多个GateWay实例来实现高可用
    在GateWay的上游使用Nginx等负载均衡设备进行负载转发以达到高可用的目的
    启动多个GateWay实例(假如说两个,一个端口9002,一个端口9003),剩下的就是使用Nginx等完成负载代理即可,示例如下:
    #配置多个GateWay实例
    upstream gateway {    
    server 127.0.0.1:9002;    
    server 127.0.0.1:9003; 
    } 
    location / {    
    proxy_pass http://gateway; 
    }
    #这里就大致的说明一下,若不知道怎么操作,可以回到75章博客进行学习即可
    #实际上两个端口,每个端口可以操作对应的路由,或者说,操作一样的,只是做个集群而已,这里就不进行说明了
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    Spring Cloud Config 分布式配置中心:
    分布式配置中心应用场景:
    往往,我们使用配置文件管理一些配置信息,比如application.yml
    单体应用架构,配置信息的管理、维护并不会显得特别麻烦,手动操作就可以,因为就一个工程
    微服务架构,因为我们的分布式集群环境中可能有很多个微服务
    我们不可能一个一个去修改配置然后重启生效(通过前面的学习和测试,我可以很直观的感受到这样的痛苦)
    在一定场景下我们还需要在运行期间动态调整配置信息
    比如:根据各个微服务的负载情况,动态调整数据源连接池大小,我们希望配置内容发生变化的时候,微服务可以自动更新
    大多数情况下,我们都需要如此,在之前的学习中,通常都是需要重新部署的,而不是自动更新,因为他们只会加载一次
    有些是为了保存数据的就不需要加载,如sql语句,可以部署时进行修改
    这里我考虑加载一次的(之前的学习中说明加载一次的地方也是这样的考虑,可能并没有说明),这里要注意
    虽然之前对应的xml可以不用我们去修改java代码,有效的操作了硬编码
    但我们虽然通常说解决硬编码使用xml,但实际上xml并没有解决
    只是更好的修改而已,还是需要重新的操作部署的,但是,若有再次的读取配置文件的话,才算是解决硬编码
    如自动的读取配置文件,或者每过一段时间读取一次配置文件,但这就要考虑整个系统的运行问题了
    所以我们也常说,xml是解决硬编码的,但现在,我们一般需要去除xml了
    因为太过于繁琐,且有些并不需要修改,也就使得,无效的使用xml,所以现在一般有Spring Boot的出现
    具体的场景总结如下:
    1:集中配置管理,一个微服务架构中可能有成百上千个微服务,所以集中配置管理是很重要的(一次修改、到处生效)
    2:不同环境不同配置,比如数据源配置在不同环境(开发dev,测试test,生产prod)中是不同的
    3:运行期间可动态调整,例如,可根据各个微服务的负载情况,动态调整数据源连接池大小等配置修改后可自动更新
    4:如配置内容发生变化,微服务可以自动更新配置
    那么,我们就需要对配置文件进行集中式管理,这也是分布式配置中心的作用
    Spring Cloud Config:
    Config简介:
    Spring Cloud Config是一个分布式配置管理方案,包含了 Server端和 Client端两个部分

    在这里插入图片描述

    Server 端:提供配置文件的存储、以接口的形式将配置文件的内容提供出去
    通过使用@EnableConfigServer注解在 Spring boot 应用中非常简单的嵌入
    Client 端:通过接口获取配置数据并初始化自己的应用
    Config分布式配置应用:
    说明:Config Server是集中式的配置服务,用于集中管理应用程序各个环境下的配置,默认使用Git存储配置文件内容,也可以SVN
    比如,我们要对"静态化微服务或者商品微服务"的application.yml进行管理
    区分开发环境(dev)、测试环境(test)、生产环境(prod)
    1:登录GitHub,创建项目lagou-config
    输入网址https://github.com/,然后创建仓库lagou-config
    2:上传yml配置文件,命名规则如下(之所以这样的命名,在后面的配置中就知道了):
    {application}-{profile}.yml 或者 {application}-{profile}.properties
    其中,application为应用名称,profile指的是环境(用于区分开发环境,测试环境、生产环境等)
    示例:
    lagou-service-page-dev.yml、lagou-service-page-test.yml、lagou-service-page-prod.yml
    lagou-service-page-dev.properties 、lagou-service-page-test.properties 、lagou-service-page-prod.properties等等
    比如我们上传一个lagou-service-page-dev.yml文件,具体内容如下:
    mysal:
      user: jetwu
    
    person:
      name: hahaha
    
    • 1
    • 2
    • 3
    • 4
    • 5
    内容最好是上面的格式,否则加载时,可能会出现问题,使得启动报错,正是因为可能,所以格式最好正确
    具体如何上传,可以到73章博客,查找远程仓库的操作的内容,就知道怎么操作了
    3:构建Config Server统一配置中心
    新建SpringBoot工程lagou-cloud-config(是lagou-parent项目的子项目),引入依赖坐标(需要注册自己到Eureka)
    <dependencies>        
             
        <dependency>          
            <groupId>org.springframework.cloudgroupId>      
            <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>  
        dependency>       
           
        <dependency>          
            <groupId>org.springframework.cloudgroupId>   
            <artifactId>spring-cloud-config-serverartifactId>    
        dependency>  
    dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    然后再资源文件下,创建com.lagou.config.ConfigServerApplication类:
    package com.lagou.config;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.config.server.EnableConfigServer;
    
    /**
     *
     */
    @SpringBootApplication
    @EnableDiscoveryClient
    @EnableConfigServer //开启配置服务器功能
    public class ConfigServerApplication {
        public static void main(String[] args) {
            SpringApplication.run(ConfigServerApplication.class,args);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    创建 application.yml文件,且配置如下:
    server:  
      port: 9400
      #注册到Eureka服务中心
    eureka:  
      client:    
        service-url:      
          defaultZone: http://LagouCloudEurekaServerA:9200/eureka,http://LagouCloudEurekaServerB:9201/eureka  
      instance:    
        prefer-ip-address: true    
        instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
    spring:  
      application:    
        name: lagou-service-config  
      cloud:    
        config:      
          server:        
            git:          
              #配置git服务地址(一般点击Code那里的地址,虽然url的地址也可以),这个.git可以不加   
              #注意:就算配置正确,可能也会因为网络连接问题导致访问不了,多试几次即可
              #且由于这里是获取文件信息,而不是推送,所以一般可以操作https
              #这里不能是ssh的地址,否则启动启动类是启动不了的,具体ssh操作,可以看百度
              uri: https://github.com/wobushigoudao/lagou-config.git  
              #注意:不要使用http,一般不会操作http的,若使用的话,可能会访问不了     
              username: wobushigoudao #配置git用户名          
              #配置git密码,当然,这个密码是错误的,总不能给你密码吧,要输入自己的密码
              password: wurtr564       
              search-paths:            
                - lagou-config #再次确认仓库,虽然我们连接到仓库了,这个也要写,否则也会报错
          # 读取分支      
          label: master
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    测试:访问http://127.0.0.1:9400/master/lagou-service-page-dev.yml,他会去git(github)里面得到信息,放在对应的响应里面
    这时可以发现得到了该文件的数据,页面显示如下:
    mysal:
      user: jetwu
    person:
      name: hahaha
    #中间的空行会帮我们去掉
    
    • 1
    • 2
    • 3
    • 4
    • 5
    代表去该master分支里进行读取lagou-service-page-dev.yml文件
    首先我们在启动时,他会根据我们的地址以及用户名和密码进行连接
    从而获取数据,但是该用户名和密码相当于直接的通过浏览器连接来获取的,不是git那样的不允许直接操作
    构建Client客户端(在已有页面静态化微服务基础上):
    案例实现:在lagou-service-page微服务中动态获取config server的配置信息
    在该微服务里面加上如下依赖:
    <dependency>  
           
        <groupId>org.springframework.cloudgroupId>  
        <artifactId>spring-cloud-config-clientartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    将该项目的application.yml修改为bootstrap.yml配置文件
    bootstrap.yml是系统级别的,优先级比application.yml(当然也包括yaml和properties)高,应用启动时会检查这个配置文件
    在这个配置文件中指定配置中心的服务地址,会自动拉取所有应用配置并且启用
    主要是把与统一配置中心连接的配置信息放到bootstrap.yml,这样就可以实现,读取git上的配置了
    相当于再外面读取application.yml之前,首先先得到文件
    然后才能读取application.yml(当然是有三个文件的,properties,yaml,yml,这里我们就以yml为主)
    这是后面的配置的作用,也是bootstrap.yml的作用(需要bootstrap.yml操作完毕)
    一般情况下,bootstrap.yml是什么都没有的,所以也就相当于他application.yml直接的跳过了bootstrap.yml了
    而这里因为有属性操作,所以需要等待他bootstrap.yml操作完,才可操作
    这里提一下:一般来说,该文件是属于cloud操作的而不是boot操作的,他也有后缀,也符合boot的那三个后缀优先级
    注意:需要统一读取的配置信息,从配置中心获取
    bootstrap.yml(部分)
    server:  
      port: 9100   # 通常来说,后期该微服务有多个实例,端口从9100递增(一般10个以内)
    Spring:  
      application:    
        name: lagou-service-page  
      datasource:    
        driver-class-name: com.mysql.jdbc.Driver    
        url: jdbc:mysql://localhost:3306/lagou?
        useUnicode=true&characterEncoding=utf8&serverTimezone=UTC    
        username: root    
        password: 123456  
      #这个一般需要bootstrap.yml文件才会读取,其他的与application.yml文件一样的操作
      #只是有些配置需要bootstrap.yml文件,除了这些配置,那么也就可以相当于application.yml文件一样了
      cloud:    #针对config分布式配置中心的配置,主要是针对配置了config server的信息
        config:      
        #config客户端配置,和ConfigServer通信,并告知ConfigServer希望获取的配置信息在哪个文件中      
          name: lagou-service-page       #代表前缀名称
          profile: dev   #后缀名称,不是文件的后缀(yml),是名称的后缀          
          #上面的操作,就使得可以指定配置文件,也就可以解决对应的环境的配置文件不同的问题了,只需要修改一下即可
          #可能有些地方可以根据运行时的环境来动态变化,这是可能的,具体可以去百度看看
          label: master  #分支名称   
          #所以命名需要这样lagou-service-page-dev.yml,这是规则,后缀名可以随便定义吗,答:基本不可以
          #所以最好是yml或者properties文件,可能yaml也行,但也要看对应的版本,也有可能有对应的配置
          #这里最好还是前面两个,其他的可能读取不到,当然,这里并不需要死磕,并没有意义
          #因为我们也可以操作让他可以,也可以操作让他不可以
          uri: http://localhost:9400   #ConfigServer配置中心地址
          #注意:就算配置正确,可能也会因为网络连接问题导致访问不了(虽然不会使得需要重启),多试几次即可
          #访问不了时,可能是操作缓存的,而网络的问题,在后面的测试中,有时会出现
          #形成对应的数据可能不准确或者得不到(通常是得不到)
          #因为通常访问github是缓慢的,所以在后面的各个细节中都会可能出现上面的问题
          #即注意即可,或者多测试几次,或者重启
    #当我们重启时,读取配置文件后,会自动去http://localhost:9400查找对应的信息,即相当于他去访问
    #http://127.0.0.1:9400/master/lagou-service-page-dev.yml
    #自然我们访问(不用我们手动的访问了,他进行拼接访问)这个,那么服务配置中心也就会去git里面得到信息
    #那么从而返回的信息中,就会到对应的返回值里面,被我们得到,操作的放在response
    #所以页面可以得到,我们也可以得到,得到后,那么会再次的读取得到的信息,即相当于读取了配置文件
    #反正读取配置文件也是进行存放的操作,只是我们直接存放了
    #所以也就相当于我们自己定义了该文件里的信息放在这个配置文件里面了
    
    #注意:上面并没有指定后缀(不是名称后缀,那么如果git的文件有多个,那么怎么操作)
    #答:操作优先级,与Spring boot基本一样,通常是yml,yaml,properties,yaml可能操作不了,但也不绝对,主要看java与github之间的联系了,我们通常只操作yml或者properties
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    我们回到lagou-service-page项目的(虽然前面说过了,这里提醒一下)controller包下,创建ConfigClientController类:
    package com.lagou.page.controller;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     *
     */
    @RestController
    @RequestMapping("/config")
    public class ConfigClientController {
    
        //既然已经读取了,那么自然可以注入进来
        //但如果没有被读取,可能是会报错的,虽然不会导致需要重启启动,但可能会影响访问
        //使得访问不了,虽然有些报错不会,所以最好是设置了bootstrap.yml文件
        @Value("${mysal.user}")
        private String username;
    
        @Value("${person.name}")
        private String name;
    
        @GetMapping("/query")
        public String getConfiginfo(){
            return username+":"+name;
    
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    访问http://127.0.0.1:9100/config/query,发现得到了数据,即jetwu:hahaha
    我们可以在启动日志里面看到如下:

    在这里插入图片描述

    发现,的确去读取信息了,至此操作读取配置文件信息操作完毕,即我们读取了配置中心的文件(虽然最终是github里面的)
    但却是配置中心返回给我们的,而正是因为他们是读取github里面的信息
    所以也基本可以操作实时的修改配置文件,而不用操作项目的重新部署,虽然主要操作自动加载的
    因为只会加载一次的而没有自动加载的,主要是没有自动加载的,如Spring的一些配置
    之前说明的只会加载一次的主要是这样没有自动加载的,且不是存放数据的配置(如sql)
    那么怎么修改都是没有用的,因为不会加载
    Config配置手动刷新:
    我们需要进行某些操作,使得不用重启微服务,只需要手动的做一些其他的操作(访问一个地址/refresh)进行刷新
    之后再访问即可
    此时,客户端取到了配置中心的值,可是没有操作之前,当我们修改GitHub上面的值时
    服务端(Config Server)能实时获取最新的值,但客户端(Config Client)读的是缓存,无法实时获取最新值
    即他并没有再次的去访问加载,使得重新赋值(存放的地方赋值,也就是可以使得更新)
    简单来说,他只是加载了启动时的信息,并没有再次的去加载,而第一次的加载的信息,就可以称为缓存信息,虽然也是在内存
    Spring Cloud已经为我们解决了这个问题,那就是客户端使用post去触发refresh(中文意思:刷新),获取最新数据
    而正是因为是我们使用,所以是手动的刷新,但也要注意:并不是所有的改变都会使用的,就如某些只会加载一次(比如端口)
    基本上,只有可以重新赋值的,才能操作,加载一次的没有重新赋值的操作
    那么如何操作呢,看如下:
    到lagou-service-page项目里操作如下:
    1:Client客户端添加依赖springboot-starter-actuator(记得添加)
    2:Client客户端bootstrap.yml中添加配置(暴露通信端点)
    management:    
      endpoints:        
        web:            
          exposure:                
            include: refresh    #暴露一个端口            
    #也可以暴露所有的端口
    management:  
      endpoints:    
        web:      
          exposure:        
            include: "*"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    回到ConfigClientController类修改部分如下:
    @RestController
    @RequestMapping("/config")
    @RefreshScope //手动刷新,后面的自动刷新也需要这个(虽然该自动实际上也需要手动)
    //如果不加,那么刷新是基本没有用的
    public class ConfigClientController {
    
    • 1
    • 2
    • 3
    • 4
    • 5
    重启项目,接下来当我们修改配置文件时,先访问http://127.0.0.1:9100/config/query得到缓存数据进行验证
    然后手动向Client客户端发起POST请求(注意:是post请求,之前的基本都是get请求,主要是因为@GetMapping)
    即方法http://localhost:9100/actuator/refresh,使得刷新配置信息
    即再次的加载一次,这时我们再次的访问http://127.0.0.1:9100/config/query,就不是缓存的数据(第一次的加载)了
    如果不是post请求,那么错误信息如下,如操作get:
    {
        "timestamp": "2022-09-04T03:12:37.044+0000",
        "status": 405,
        "error": "Method Not Allowed",
        "message": "Request method 'GET' not supported",
        "trace": "org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'GET' not supported\r\n\tat org.springframework
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    很明显,若之前的查看信息的访问健康检查接口:http://localhost:9100/actuator/health也操作post请求
    那么会有一样的类似错误,实际上是因为请求的规定
    比如@GetMapping,如果是post请求他,那么就会出现上面的错误,这里了解即可
    若是post请求(回答上面的如果不是post请求的后续),如果出现了如下:
    [
        "config.client.version",
        "mysal.user" 
    ]
    
    #上面的"config.client.version"若更新了,则会出现,如果什么都没更新,那么返回的就是"[]"
    #下面的是更新的数据属性,只有更新的数据才会显示出来
    #所以person.name不会显示,因为我们并没有更新他
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    代表已经更新了,这时,可以再次的访问,可以发现得到了更新的数据了
    但是这有个问题,总不能所有的对应微服务都进行刷新吧,在成百上千个微服务中,这样的刷新太麻烦
    当然,若写个脚本来进行刷新,也是可以的,但却并没有完全的解决
    因为如果添加几个微服务或者删除几个微服务,那么脚本的刷新也需要修改,所以手动的刷新,不可避免的有一点麻烦问题
    那么思考:可否使用广播机制,一次通知,处处生效,方便大范围配置自动刷新
    答:可以,看如下
    Config配置自动更新:
    实现一次通知,处处生效
    在微服务架构中,我们可以结合消息总线(Bus)实现分布式配置的自动更新(Spring Cloud Config + Spring Cloud Bus)
    消息总线Bus:
    所谓消息总线Bus,即我们经常会使用MQ消息代理构建一个共用的Topic
    通过这个Topic连接各个微服务实例,MQ广播的消息会被所有在注册中心的微服务实例监听和消费
    换言之就是通过一个主题连接各个微服务,打通脉络
    Spring Cloud Bus(基于MQ的,支持RabbitMq/Kafka) 是Spring Cloud中的消息总线方案
    Spring Cloud Config + Spring Cloud Bus 结合可以实现配置信息的自动更新

    在这里插入图片描述

    Spring Cloud Config + Spring Cloud Bus 实现自动刷新:
    MQ消息代理,我们还选择使用RabbitMQ,ConfigServer和ConfigClient都添加都消息总线的支持以及与RabbitMq的连接信息
    1:Config Server服务端和客户端都添加消息总线支持,这样他们就会操作同一个mq了
    <dependency>    
        <groupId>org.springframework.cloudgroupId> 
        <artifactId>spring-cloud-starter-bus-amqpartifactId>
    dependency>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    Config Server和客户端添加配置:
    spring:    
      rabbitmq:        
        host: 192.168.164.128       #RabbitMQ的服务器地址 
        port: 5672                  #通常都是5672
        username: laosun            #用户名
        password: 123123            #密码
        #用户名和密码记得对应,否则是访问不了的,81章博客中的就对应的
        #一般服务器地址,端口号,用户名,密码都有默认值
        #如下:
        #private String host = "localhost";
        #private int port = 5672;
        #private String username = "guest";
        #private String password = "guest";
        #如果你的配置也是他的值(对应),那么可以省略不写
    #之前学习的RabbitMQ里面的配置,用来进行连接的,如果不知道了,可以到81章博客进行学习
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    Config Server微服务暴露端口(他没有这个配置,所以需要加上):
    management:    
      endpoints:        
        web:            
          exposure:                
            include: bus-refresh     #只有暴露了端口,才能够操作对应的请求的     
            #否则一般只会显示404,找不到路径,前面的手动更新也是一样
            #除了那个特殊的健康显示不会外,只是没有对应的细节了,但还是可以访问
            #前面说过了具体显示,这里就不说明了,通常需要我们暴露
            #比如之前的actuator/refresh没暴露,那么无论什么请求的访问,都会出现如下错误显示:
       #     {
      #  "timestamp": "2022-09-04T07:07:02.256+0000",
      #  "status": 404,
       # "error": "Not Found",
       # "message": "No message available",
       # "path": "/actuator/refresh"
    #}
    
    #若这里的actuator/bus-refresh暴露了:
    #若请求不对会出现前面的显示(说过了)
    #操作post请求,若什么都没有返回,则代表操作成功,这时再次访问http://127.0.0.1:9100/config/query
    #可以发现,改变了信息
    
    #若这里的actuator/bus-refresh没暴露:
    #他并没有出现类似于出现上面的错误显示,而是操作了get请求
    #这时我们访问get出现如下:
    #{
    #    "name": "actuator",
    #    "profiles": [
    #        "bus-refresh"
    #    ],
    #   "label": null,
     #   "version": "a45eadf103487061dc4a38ed2988ad6ccfec8c0c",
     #   "state": null,
     #   "propertySources": []
    #}
    #并没有操作更新,即他的路径存在(而不是之前的没有暴露使得不存在了),只是请求方式变化了
    #那么post就是请求不对的错误信息了,前面的操作,即出现"message": "Request method 'POST' not supported",
    
    
            
    #建议暴露所有的端口,这样,我们就不用依次的添加了
    management:  
      endpoints:    
        web:      
          exposure:        
            include: "*"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    重启各个操作了上面的配置的服务,更改git的文件的配置之后,向配置中心服务端发送post请求
    访问http://127.0.0.1:9400/actuator/bus-refresh,各个客户端配置即可自动刷新
    至此,自动刷新操作完毕,实际上也算是手动的刷新,因为需要一次的手动的访问,而之所以说是自动
    因为该手动的访问,使得自动的读取而已
    那么如何操作只定向的更新,而不操作全部更新,访问如下:
    http://localhost:9400/actuator/bus-refresh/lagou-service-page:9100,即后面指定对应的ip和端口
    那么有个问题,后面的lagou-service-page:9100可以当成路径吗,答,可以,所以他能是过滤器,也可以是存在的路径
    只要不是"/"分开,基本都可以访问,那么由于指定了lagou-service-page:9100这个
    那么发送消息时,只会发送lagou-service-page:9100所对应的消息,而不会发现一系列的消息(名称加端口,自然是不同的信息的),从而使得,定向更新
    自己测试即可
    第二代 Spring Cloud 核心组件(Alibaba,SCA):
    SpringCloud 是若干个框架的集合,包括 spring-cloud-config、spring-cloud-bus 等近 20 个子项目
    提供了服务治理、服务网关、智能路由、负载均衡、断路器、监控跟踪、分布式消息队列、配置管理等领域的解决方案
    Spring Cloud 通过 Spring Boot 风格的封装,屏蔽掉了复杂的配置和实现原理
    最终给开发者留出了一套简单易懂、容易部署的分布式系统开发工具包
    一般来说,Spring Cloud 包含很多组件,主要以 Netflix 开源为主
    同 Spring Cloud 一样,Spring Cloud Alibaba 也是一套微服务解决方案(SCN虽然用的多)
    包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务
    依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里微服务解决方案
    通过阿里中间件来迅速搭建分布式应用系统

    在这里插入图片描述

    阿里开源组件:
    Nacos:一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台
    Sentinel:把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性
    RocketMQ:开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务
    Dubbo:这个就不用多说了,在国内应用非常广泛的一款高性能 Java RPC 框架
    Seata:阿里巴巴开源产品,一个易于使用的高性能微服务分布式事务解决方案
    Arthas:开源的Java动态追踪工具,基于字节码增强技术,功能非常强大
    我们再次看看前面的如下这个图(虽然并没有都说明出来):

    在这里插入图片描述

    阿里商业化组件:
    作为一家商业公司,阿里巴巴推出 Spring Cloud Alibaba,很大程度上市希望通过抢占开发者生态,来帮助推广自家的云产品
    所以在开源社区,夹带了不少私货,如前面图中的阿里云商业化组件(一般需要花钱才可使用)
    其中整体易用性和稳定性还是很高的,一般比其他免费的性能更强大(因为收费,自然需要更强大呀)
    对应的商业化组件如下:
    Alibaba Cloud ACM:一款在分布式架构环境中对应用配置进行集中管理和推送的应用配置中心产品
    Alibaba Cloud OSS:阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的云存储服务
    Alibaba Cloud SchedulerX:阿里中间件团队开发的一款分布式任务调度产品
    提供秒级、精准的定时(基于 Cron 表达式)任务调度服务
    集成 Spring Cloud 组件:
    Spring Cloud Alibaba 作为整套的微服务解决组件,只依靠目前阿里的开源组件是不够的
    更多的是集成当前的社区组件,所以 Spring Cloud Alibaba 可以集成 Zuul,GateWay等网关组件
    也可继承Ribbon、OpenFeign等组件
    Nacos 服务注册和配置中心:
    Nacos 介绍:
    Nacos (Dynamic Naming and Configuration Service)是阿里巴巴开源的一个针对微服务架构中服务发现、配置管理和服务管理平台
    Nacos就是注册中心+配置中心的组合(Nacos=Eureka + Config + Bus)
    官网:https://nacos.io
    下载地址:https://github.com/alibaba/Nacos
    Nacos功能特性:
    服务发现与健康检查
    动态配置管理
    动态DNS服务
    服务和元数据管理,管理平台的角度,nacos也有一个ui页面,可以看到注册的服务及其实例信息(元数据信息)等
    对动态的服务权重调整、动态服务优雅下线,都可以去做
    Nacos 单例服务部署:
    下载解压安装包,执行命令启动(这里使用最近比较稳定的版本 nacos-server-1.2.0.tar.gz):
    下载地址:
    链接:https://pan.baidu.com/s/1baEjvlXthlzDNLbZuDyl6w
    提取码:alsk
    对应的目录如下:

    在这里插入图片描述

    这里包括启动命令,一般不同的操作系统,执行不同的命令,如下:
    linux/mac:sh #执行后面的:
    startup.sh -m standalone #一般操作单机,集群可以去百度寻找如何操作
    windows:cmd #执行后面的:
    startup.cmd #我们考虑这个,在windows下执行
    
    • 1
    • 2
    • 3
    • 4
    我们双击startup.cmd运行(一般需要我们有JDK的环境变量),出现如下:

    在这里插入图片描述

    关闭窗口也就是关闭nacos,nacos也是一个项目(一般是java项目,即一般是Spring boot工程的项目,所以可以直接的访问)
    访问nacos控制台:
    http://127.0.0.1:8848/nacos/#/login 或者 http://127.0.0.1:8848/nacos/index.html 或者 http://127.0.0.1:8848/nacos
    默认端口8848,账号和密码 nacos/nacos
    我们可以发现,访问前面的一个地址,需要用户名和密码,后面的访问不需要,很明显
    用户名和密码实际上也是多次一举(可能在某些时候是有作用的)

    在这里插入图片描述

    微服务注册到Nacos:
    在父pom中引入SCA依赖:
              
    <dependencyManagement>
        <dependencies>
            <dependency>              
                <groupId>com.alibaba.cloudgroupId>       
                <artifactId>spring-cloud-alibaba-dependenciesartifactId>     
                <version>2.1.0.RELEASEversion>        
                <type>pomtype>             
                <scope>importscope> 
            dependency>
        dependencies>
    dependencyManagement>
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    由于他操作配置中心和服务注册,所以对应的注册中心和配置中心的服务器可以停掉了
    在商品微服务和静态化微服务中引入nacos客户端依赖,必须删除eureka-client依赖(否则可能因为冲突造成启动失败):
    <dependency>  
        
        <groupId>com.alibaba.cloudgroupId>  
        <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
    dependency>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    将原来的bootstrap.yml文件修改成application.yml文件
    并将对应的需要bootstrap.yml文件的配置和与他相关的配置(如rabbitmq)
    以及依赖和使用了依赖的类删除(当然可以直接注释也行)
    如spring-cloud-starter-bus-amqp依赖和他所对应的ConfigClientController类,需要删除
    实际上只需要删除ConfigClientController类即可,否则可能不会注册,访问失败的,因为没有对应的注入值
    然后将商品微服务和静态化微服务的eureka的配置也删除(当然可以直接注释也行)
    这样他们的原来的注册中心操作和配置中心操作就没有了
    那么可以不删除对应的配置中心吗,答,由于eureka-client依赖冲突,所以基本不能操作对应的配置,所以基本也就不可用
    再在商品微服务和静态化微服务的配置中添加如下信息:
    spring:
      cloud:    
        nacos:      
          discovery:        
            server-addr: 127.0.0.1:8848 #nacos server 地址
    #代表操作nacos的注册中心和配置中心
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    重新启动商品微服务和静态化微服务,就算先启动一般也会自动的连接上的nacos的
    这时可以停掉对应的配置中心服务器和注册中心服务器和网关的服务器了
    可能有对应的根据时间自动连接吧,虽然并没有日志出现
    观察nacos控制台:

    在这里插入图片描述

    具体介绍:

    在这里插入图片描述

    我们可以创建服务(虽然是空的),然后点击隐藏空服务,打开即可看到空的服务了,空服务一般是用来被真的服务顶替了
    即他也只是显示的作用
    具体服务详情:

    在这里插入图片描述

    编辑服务用来修改这些详情的(下面的集群显示操作他的编辑进行修改)
    权重一般操作负载均衡,权重大,一般访问他越多,但通常需要根据策略了
    比如操作轮询时,权重并没有任何关系,但可能因为权重的存在使得策略自动变化,比如nginx中,设置后,就操作权重的策略了
    一般默认的框架中,权重通常是1,且默认轮询的策略(虽然上面的ribbon不是)
    但通常设置权重或者权重不是1,则会操作权重的策略,当然也并不是所以的框架都有操作权重的,比如ribbon(可能有)
    保护阈值:可以设置为0-1之间的浮点数
    在创建服务时,虽然也能设置为其他的数,负数也可,是浮点数,若是字母会创建不了,有个提示出现)
    最好是0-1(不包括0和1),当然不能是-1-1,这样的计算表达式,也会创建不了,出现提示(一样的提示)
    但现在的创建空服务并没有什么实际意义,所以这里我们就不创建或者删除
    它其实是一个比例值(当前服务健康实例数/当前服务总实例数)
    注意:若删除有服务的(不是空),那么一般需要重启对应的服务,使得再次注册了
    上面的下线,并不会使得服务停止,只是不提供该服务的信息了,即操作对应的请求的,相当于他没有注册
    这时只能访问其他的没有下线的了,但若都下线了,也就相当于删除服务了,那么可能会访问不了,这时可能就会操作熔断了
    保护阈值的操作场景:
    一般流程下,nacos是服务注册中心,服务消费者要从nacos获取某一个服务的可用实例信息
    对于服务实例有健康/不健康状态(下线通常也可以看成不健康的状态)之分
    nacos在返回给消费者实例信息的时候,会返回健康实例
    这个时候在一些高并发、大流量场景下会存在一定的问题
    如果服务A有100个实例,98个实例都不健康了,只有2个实例是健康的,如果nacos只返回这两个健康实例的信息的话
    那么后续消费者的请求将全部被分配到这两个实例,流量洪峰到来
    2个健康的实例也扛不住了,整个服务A 就扛不住,上游的微服务也会导致崩溃,产生雪崩效应
    所以保护阈值的意义在于:
    当服务A健康实例数/总实例数 < 保护阈值 的时候(有些时候可能等于也算,可能可以通过设置或者版本的原因)
    说明健康实例真的不多了,这个时候保护阈值会被触发(状态true)
    nacos将会把该服务所有的实例信息(健康的+不健康的)全部提供给消费者
    下线的可能也会给(因为他通常也看成不健康的,从不提供变成了提供)
    消费者可能访问到不健康的实例,请求失败,但这样也比造成雪崩要好,牺牲了一些请求,保证了整个系统的一个可用
    总不能都没有可用吧,虽然不健康的可能更加不健康了,但总是可以有可用的,而不是基本没有
    基本上保存阈值调整保存后,基本是立即生效,而正是因为这样,所以nacos也是非常方便的
    注意:阿里内部在使用nacos的时候,也经常调整这个保护阈值参数
    负载均衡:
    Nacos客户端引入的时候,会关联引入Ribbon的依赖包,就如我们使用OpenFiegn的时候也会引入Ribbon的依赖,虽然熔断器也会
    但不管是什么,只要有即可
    所以Ribbon原来的配置可以不用变,而由于熔断器也在,所以包括Hystrix都按原来方式进行配置即可
    这时我们直接访问http://127.0.0.1:9100/page/loadProductServicePort,我们通过后台也可以看出,的确负载均衡了
    所以说,nacos基本只是替换了配置中心(后面有操作)和服务注册中心
    其他的基本该是什么就是什么,差不多都是一样的整合的,甚至对应的代码都不需要改变,这里要注意
    Nacos 数据模型(领域模型):
    Namespace命名空间、Group分组、集群这些都是为了进行归类管理,把服务和配置文件进行归类
    归类之后就可以实现一定的效果,比如隔离
    比如,对于服务来说,不同命名空间中的服务不能够互相访问调用

    在这里插入图片描述

    Namespace在外层的(互相隔离),是一个命名空间,在一个命名空间中,可以定义多个Group
    各个分组之间是相互隔离的,在一个group可以定义多个service/dataid(一般也是隔离的)
    Namespace:命名空间,对不同的环境进行隔离,比如隔离开发环境、测试环境和生产环境
    Group:分组,将若干个服务或者若干个配置集归为一组,通常习惯一个系统归为一个组(拉勾招聘、拉勾猎头、拉勾教育)
    Service:某一个服务,比如商品微服务
    DataId:配置集或者可以认为是一个配置文件(或者说配置文件的id)
    通常情况下,一个Service就是对应一个DataId(Id中的"I"的小写的是i)
    Namespace + Group + Service 如同 Maven 中的GAV坐标,GAV坐标是为了锁定Jar,而这里是为了锁定服务
    Namespace + Group + DataId 如同 Maven 中的GAV坐标,GAV坐标是为了锁定Jar,而这里是为了锁定配置文件
    最佳实践:
    Nacos抽象出了Namespace、Group、Service、DataId等概念,具体代表什么取决于怎么用(非常灵活),推荐用法如下

    在这里插入图片描述

    Nacos 配置中心:
    之前:Spring Cloud Config + Bus(配置的自动更新)
    1:Github 上添加配置文件
    2:创建Config Server 配置中心—>从Github上去下载配置信息
    3:具体的微服务(最终使用配置信息的)中配置Config Client—> ConfigServer获取配置信息
    有Nacos之后,分布式配置就简单很多
    Github不需要了(配置信息直接配置在Nacos server中),Bus也不需要了(依然可以完成动态刷 新)
    接下来:
    1:去Nacos server中添加配置信息
    2:改造具体的微服务,使其成为Nacos Config Client,能够从Nacos Server中获取到配置信息
    Nacos Server添加配置:
    首先点击命名空间,我们创建一个命名空间:

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    可以看到,出现了对应的命名空间,在public后面多出了三个

    在这里插入图片描述

    接下来我们回到配置列表,点击 " + " 加号(在public里面的,若要其他的命名空间的话,可以点击其他的命名空间然后创建即可

    在这里插入图片描述

    在这里插入图片描述

    这里与之前的配置文件不同的是,可以指定后缀,但要注意,这只是创建文件,而创建文件自然是可以随便创建的
    就如git的创建一样,只是对应的配置可能只会操作固定的后缀,就如前面的一样的示例(一般是yml和properties)
    后面会介绍这里所要的后缀的

    在这里插入图片描述

    其中lagou前面的"-"不用写,他只是一个标识,写完后,会加上的,接下来我们点击发布,在弹出的框框中,点击确定
    注意他并不会跳转,这是他的一个不足的点,我们返回即可
    这时,出现如下:

    在这里插入图片描述

    接下来我们在微服务中开启 Nacos 配置管理
    在静态化微服务里添加依赖:
    <dependency>
        <groupId>com.alibaba.cloudgroupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
    dependency>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    微服务中如何锁定 Nacos Server 中的配置文件(dataId)
    通过 Namespace + Group + dataId 来锁定配置文件,Namespace不指定就默认public,Group不 指定就默认 DEFAULT_GROUP
    dataId 的完整格式如下:
    ${prefix}-${spring.profile.active}.${file-extension}
    
    • 1
    prefix 默认为 spring.application.name 的值,也可以通过配置项 spring.cloud.nacos.config.prefix 来配置
    spring.profile.active 即为当前环境对应的 profile
    注意:当 spring.profile.active 为空时,对应的连接符 - 也将不存在,dataId 的拼接格式变成:
    ${prefix}.${file-extension}
    
    • 1
    file-exetension 为配置内容的数据格式,可以通过配置项
    spring.cloud.nacos.config.file-extension 来配置,目前只支持 properties 和 yaml 类 型
    所以就算前面的配置文件中创建的类型有多种,但是可能有些也操作不了
    spring:
      cloud:
        nacos:
          discovery:
            server-addr: 127.0.0.1:8848
          #需要bootstrap.yml文件才可操作,与前面一样
          config:
            server-addr: 127.0.0.1:8848
            #namespace: 26ae1708-28de-4f63-8005-480c48ed6510 #命名空间的ID,如果是public则不要指定
            group: DEFAULT_GROUP #如果使用的默认分组,可以不设置
            file-extension: yaml #一般也默认是properties,所以这里需要指定
    #那么他是如何指定lagou-service-page.yaml文件,首先,先找命名空间,因为默认是public
    #所以这里的命名空间就算public,分组是DEFAULT_GROUP,实际上默认也是这个,所以也可以不指定,后缀是yaml
    #所以现在我们只差对应的文件名称了,注意文件名称是默认项目名称的,即spring.application.name
    #所以这里是lagou-service-page
    #至此,他是去找public里面的DEFAULT_GROUP里面的lagou-service-page.yaml文件,自然是可以找到的,那么就会读取
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    接下来通过静态化微服务获取配置信息
    首先将application.yml文件修改成bootstrap.yml文件,使得操作对应的配置
    然后在controller包下,创建ConfigClientController类(之前没有删除的话,就需要创建):
    package com.lagou.page.controller;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.cloud.context.config.annotation.RefreshScope;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     *
     */
    @RestController
    @RequestMapping("/config")
    @RefreshScope //自动刷新,在之前的他是手动刷新,但nacos这里是自动刷新
    //那么之前的配置依赖没有删除,难道不会冲突吗,答:实际上是看对应的配置是否配置而操作的
    //所以与依赖无关,因为该注解所对应的包,基本上只要是依赖都会有他
    public class ConfigClientController {
    
        //既然已经读取了,那么自然可以注入进来
        @Value("${lagou.message}")
        private String message;
    
    
        @GetMapping("/query")
        public String getRemoteConfig(){
            return message;
    
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    至此,我们访问http://localhost:9100/config/query后,若出现数据,则操作成功
    接下来我们修改文件信息,然后发布,确认发布,继续访问,发现更新了,注意,他是直接的获取,所以没有缓存一说
    虽然他也是客户端,但跟前面的操作一样,那么后面的refresh: false可以使得会操作缓存吗
    答:还是不会,正如这里说的,是直接的获取,所以设置refresh: false与主获取没有关系,但与扩展有关
    #注意,主获取操作缓存和扩展操作的true,前提是需要有@RefreshScope注解,使得可以操作自动的更新
    #主获取可以看成自动的更新,需要@RefreshScope注解,与之前不同
    #所以他还是操作了@RefreshScope注解,只是不用我们操作而已,或者说,他是定时的访问使得更新
    #或者我们访问之前,会使得访问,即无论是主获取还是扩展的true获取,都需要这个@RefreshScope注解
    #这里针对于后面的扩展进行说明的
    #主获取:就是直接的获取,所以没有缓存一说的获取,即不是扩展的,操作拼接的这个
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    那么一个微服务希望从配置中心Nacos server中获取多个dataId的配置信息,这样可以操作吗
    答:可以的,虽然我们的主指定,只能是一个,但可以操作扩展配置,扩展多个dataId
    添加如下:

    在这里插入图片描述

    在这里插入图片描述

    回到ConfigClientController类进行修改:
    package com.lagou.page.controller;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.cloud.context.config.annotation.RefreshScope;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     *
     */
    @RestController
    @RequestMapping("/config")
    @RefreshScope //自动刷新,在之前的他是手动刷新,但nacos这里是自动刷新
    public class ConfigClientController {
    
        //既然已经读取了,那么自然可以注入进来
        @Value("${lagou.message}")
        private String message;
    
        @Value("${pagea}")
        private String pagea;
    
        @Value("${pageb}")
        private String pageb;
    
    
        @GetMapping("/query")
        public String getRemoteConfig(){
            return message+ "-" + pagea+"-"+pageb;
    
        }
    
    
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    对应的配置:
    # nacos配置
    spring:
      cloud:
        nacos:
          discovery:
            server-addr: 127.0.0.1:8848
          # nacos config 配置
          config:
            server-addr: 127.0.0.1:8848
            # 锁定server端的配置文件(读取它的配置项)
            #namespace: 07137f0a-bf66-424b-b910-20ece612395a  # 命名空间id
            #public不需要写,因为页面上没有这个id值,其他的创建的空间有id值,所以这就是public是默认的原因
            group: DEFAULT_GROUP  # 默认分组就是DEFAULT_GROUP,如果使用默认分组可以不配置
            file-extension: yaml   #默认properties
             # 上面根据规则拼接出来的dataId效果:lagou-service-page.yaml
             #下面直接不进行拼接了,因为是扩展的,只需要指定文即可,对应的命名空间就是上面的命名空间
             #即还是public,至于组,需要自己指定,虽然默认也是DEFAULT_GROUP
            ext-config[0]:
              data-id: pagea.yaml
              group: DEFAULT_GROUP
              refresh: true  #开启扩展dataId的动态刷新,一般操作自动更新
            ext-config[1]:
              data-id: pageb.yaml
              group: DEFAULT_GROUP
              refresh: true  #开启扩展dataId的动态刷新
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    这时我们重启,继续访问http://localhost:9100/config/query,发现有对应的数据了
    SCA Sentinel 分布式系统的流量防卫兵 :
    Sentinel 介绍:
    Sentinel是一个面向云原生微服务的流量控制、熔断降级组件
    替代Hystrix,针对问题:服务雪崩、服务降级、服务熔断、服务限流
    Hystrix:
    服务消费者(静态化微服务)—>调用服务提供者(商品微服务)
    在调用方引入Hystrix
    1:自己搭建监控平台 dashboard(虽然我们并没有搭建,但他也只是显示的作用,与程序无关,所以搭不搭建无所谓)
    好像前面也没有具体说明过
    2:没有提供UI界面进行服务熔断、服务降级等配置(使用的是@HystrixCommand参数进行设置)
    有一定的代码入侵,即代码太多了
    Sentinel:
    1:独立可部署Dashboard/控制台组件(其实就是一个jar文件,直接运行即可)
    一般是Spring boot工程的项目,所以可以直接的访问,一般可以直接访问的jar包,通常是Spring boot工程的项目
    2:减少代码开发,通过UI界面配置即可完成细粒度控制
    Sentinel 分为两个部分:
    核心库:(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境
    同时对 Dubbo / Spring Cloud 等框架也有较好的支持
    控制台:(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等 应用容器
    Sentinel 具有以下特征:
    丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景
    例如秒杀(即 突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用 应用等
    完备的实时监控:Sentinel 同时提供实时的监控功能
    您可以在控制台中看到接入应用的单台机器 秒级数据,甚至 500 台以下规模的集群的汇总运行情况
    广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo的整合
    您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel
    完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口
    您可以通过实现扩展接口来快 速地定制逻辑,例如定制规则管理、适配动态数据源等
    Sentinel 的主要特性:

    在这里插入图片描述

    Sentinel 的开源生态:

    在这里插入图片描述

    Sentinel 部署:
    下载地址:https://github.com/alibaba/Sentinel/releases,我们找到v1.7.1的版本,即1.7.1的版本,然后下载
    若不想下载,可以直接取出我下载好的,地址如下:
    链接:https://pan.baidu.com/s/18lfEh29-uaCCW_gwDwtAvg
    提取码:alsk
    我们可以到cmd命令里到该目录下访问,或者右键,点击独有的窗口(dos的加强版窗口,样式好看很多),然后访问
    启动:java -jar sentinel-dashboard-1.7.1.jar & ,注意,加上&代表是守护的进程
    即关闭窗口,他也不会关闭,但通常还是会关闭,可能现在不可用了,大概是为了防止出现多个守护进程造成资源持续占用吧
    用户名/密码:sentinel/sentinel
    访问http://localhost:8080/即可(注意:可能在一些浏览器里不会显示),一般使用谷歌浏览器可以显示
    访问后出现如下:

    在这里插入图片描述

    输入用户名/密码:sentinel/sentinel
    然后出现如下(点击首页):

    在这里插入图片描述

    服务改造:
    在我们已有的业务场景中,“静态化微服务"调用了"商品微服务”
    我们在静态化微服务进行的熔断降 级等控制,那么接下来我们改造静态化微服务,引入Sentinel核心包
    为了不污染之前的代码,复制一个页面静态化微服务 ,名称为lagou-service-page-9101(前面操作了复制,这里就不多说了):
    pom.xml引入依赖:
    
    <dependency>
     <groupId>com.alibaba.cloudgroupId>
     <artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    application.yml修改(配置sentinel dashboard,暴露断点依然要有,删除原有hystrix配置,删除 原有OpenFeign的降级配置)
    虽然该application.yml文件变成了bootstrap.yml(不用修改回来):
    server:
     port: 9101   # 后期该微服务多实例,端口从9100递增(10个以内)
    Spring: #大小或者小写,都没有关系SPring也可,一般忽略大小写,当操作赋值时
    #一般是以ASCII的大小来比对,大的优先,即只会操作大的
     #解决bean重复注册问题
     main:
       allow-bean-definition-overriding: true
     application:
       name: lagou-service-page
     datasource:
       driver-class-name: com.mysql.jdbc.Driver
       url: jdbc:mysql://localhost:3306/lagou?
    useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
       username: root
       password: 123456
     cloud:
       nacos:
         discovery:
           server-addr: 127.0.0.1:8848
         config:
           server-addr: 127.0.0.1:8848
           #namespace: 26ae1708-28de-4f63-8005-480c48ed6510 #命名空间的ID,public可以不写
           group: DEFAULT_GROUP #如果使用的默认分组,可以不设置
           file-extension: yaml
       #sentinel配置,这里是主要添加的
       sentinel:
         transport:
           dashboard: 127.0.0.1:8080 #指定sentinel控制台的地址
           port: 8719 #在微服务运行时会启动一个Http Server,该Server的作用就是与sentinel的dashboard进行交互
    
    # springboot中暴露健康检查等断点接口
    Management:
     endpoints: #只要有层级即可,所以一个空格便足以
       web:
         exposure:
           include: "*"
     # 暴露健康接口的细节
     endpoint:
       health:
         show-details: always
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    在这之前,我们要将RestTemplate类的实例注释掉,那么为什么要注释掉呢
    答:因为我们是复制过来的(不是复制过来的基本不会出现这样的情况),一般RestTemplate类并不是当前项目的注入,他的注入会根据项目的具体原名注入
    且会与maven打交道(因为已经打过一次,那么就以那一次为主,所以原项目不会出现这样的情况)
    所以,这时候,若我们复制过来的项目中有RestTemplate类的实例,那么会在启动时,出现已被注入的信息
    当然,在其他的组件或者实例中,可能也会出现这样的情况(基本对于复制的来说),所以这里提醒一下
    那么如何解决这样的情况呢:
    第一,就是注释掉对应的实例操作或者依赖操作
    第二,看如下:
    Spring:
     #解决bean重复注册问题
     main: #记得要完整,不能只有main:,否则一般报错
       allow-bean-definition-overriding: true #设置这个后,使得覆盖原来的实例,该覆盖并没有解决maven的信息
       #只是使用我们自己的,而不会出现使用其他的,即不会出现上面的已经注入的提示了
       #所以说,当你删除这个配置后,还是会出现错误的
     application:
       name: lagou-service-page
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    上述配置之后,启动静态化微服务,使用 Sentinel 监控静态化微服务
    此时我们发现控制台没有任何变化,因为懒加载
    懒加载:在满足一定条件时,才会加载资源,这里是发送一次请求,无论是否失败都可以
    即我们只需要发起一次请求触发即可,我们访问http://127.0.0.1:9101/page/loadProductServicePort

    在这里插入图片描述

    上面就是访问过后,出现的(也有对应的信息)
    只要现在只要不重启sentinel,那么他会一直存在,这时懒加载只针对于他的对应信息了
    重启sentinel后,对应的项目需要重启,使得连接,否则就算访问也不会出现
    因为不重启的话,以为原来的还在,实际上不在了,注意:懒加载可能会有延迟,多次访问即可,或者继续重启
    当然,有些配置(该配置了解即可)可能会使得不会加载,比如(可能会加载,但我测试时不会加载,参考即可):
    sentinel:
          filter:
            enabled: false 
    #一般用来操作链路的,可以却没有对应的信息(如果懒加载过,那么项目名还会在,因为懒加载不针对项目名,除了第一次)
            #可以理解为始终不操作懒加载,所以也操作不了,对于的链路的说明后面会介绍的,且默认是0
            #注释后,我们有时需要刷新,使得更新信息了(将0变成1),0变成1的过程,需要访问(即懒加载,这一次需要)
            #自己测试就知道了
            #否则不会,因为对应的项目名显示不操作懒加载的,刷新是更新他
            #每次的重启,都会变成0,又要懒加载了
            #即0代表没有信息,1代表有信息
            #该0和1通常需要手动刷新,才会刷新,当然可能也会有延迟
           
            
            
            #注意:上面的注释,并没有意义,可以直接不看
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    部分点击(实时监控和簇点链路):

    在这里插入图片描述

    一般需要访问才会出现,且过些时间可能会消失,这时需要继续访问
    刷新(一般是自动的刷新,如时刷,即一定时间内的刷新)可能有点延迟,这是自然的,总不能一瞬间吧
    注意:上面的QPS在这里代表用户发出请求到服务器做出响应成功的次数,通常是根据一秒种的时间来显示的次数
    所以我们也称QPS为:每秒通过的请求数(该秒只针对于对应的一秒钟,这里若明白,那么就知道为什么上面图中,只有1.0了)
    这里因为都有延迟,所以基本都是一次
    假设我们发送十个请求(等待请求完毕):

    在这里插入图片描述

    根据每秒通过的请求数,可以看到,后面的图中,有4个1.0,代表有4次成功,我们继续等待,一般会刷新的
    因为总不能全部将十次请求都显示完毕吧,因为窗口只有这么大
    一般若操作时间少,表格一般可以看到上次的请求结果,这一点在后面的测试显示的次数可以自己看出来
    为了测试是否是根据一秒种的时间来显示的次数,将原来的延迟代码注释,得到的结果如下:

    在这里插入图片描述

    发现的确是根据一秒种的时间来显示的次数的

    在这里插入图片描述

    不同的视图,上面是树状视图,下面是列表视图

    在这里插入图片描述

    Sentinel 关键概念 :

    在这里插入图片描述

    建议操作规则时,在簇点链路里点击,这样就会自动的传递资源名称了
    而不用手写的(虽然不对也没关系,因为是大多数是匹配的意思)
    Sentinel 流量规则(流控规则)模块:
    系统并发能力有限,比如系统A的QPS支持1个,如果太多请求过来,那么A就应该进行流量控制 了,比如其他请求直接拒绝
    现在开始,我们点击流控并打开高级选项:

    在这里插入图片描述

    出现如下:

    在这里插入图片描述

    上面的介绍:
    新增:点击后,退出框框,然后自动到流控规则界面
    新增并继续添加:点击后,退出框框,还是原来的页面,即只退出框框
    假设添加了,如我们将单机阈值设置为1,流控规则界面显示如下:

    在这里插入图片描述

    虽然我们可以点击这里的新增,但是资源名需要自己写,所以为了方便或者对应,一般在簇点链路里点击
    但无论是簇点链路里点击还是这里的点击,资源名都可以随便修改或者写上
    接下来,说明那些属性值的作用:
    资源名:默认请求路径
    针对来源:Sentinel可以针对调用者进行限流,如填写微服务名称,默认default(不区分来源)
    阈值类型/单机阈值:
    QPS:(每秒钟请求数量,一般是成功的数量)当调用该资源的QPS达到阈值时进行限流
    如单机阈值设置为100
    那么每秒中(注意:这是中,而不是钟,虽然意思是一样的,写成钟也可,这里写成中)最大只能通过100个请求,包括100
    即不能超过100个请求,如果多了,则操作流控模式
    若流控模式是直接,那么直接失败,代表自己,然后操作流控效果,若流控效果是快速失败
    则抛出异常,该异常使得准备要请求对应的方法时,直接被终止了,即没有我们设置的打印信息了
    且该异常,通常不会显示在服务器,一般会将对应的信息返回给页面
    实际上流控效果可能也会使得操作阈值,因为他们虽然是这样的顺序,但实际上也只是正常的顺序而已
    因为对应的异常自然是在后面
    即他们实际上只是设置关系,在初始化之前,会进行设置的,如后面的预热,所以他们实际上并没有顺序之分
    我们可以测试,测试上面的哪个单机阈值设置为1且其余不变的,因为添加了,所以我们可以直接测试了
    现在我们多次请求一下,争取在一秒中请求2次或者2次以上
    由于这里我们去除了延迟代码,所以只要我们点击够快即可
    如果出现如下:

    在这里插入图片描述

    就代表的确是出现了异常,即操作了限流,其中Blocked by Sentinel (flow limiting),可以理解为,让流量防卫兵的流控进行阻塞了
    至此QPS操作成功
    线程数:当调用该资源的线程数达到阈值的时候进行限流
    主要是为了防止线程处理请求的时候,如果说业务逻辑 执行时间很长,流量洪峰来临时,会耗费很多线程资源
    这些线程资源会堆积,最终可能造成服务不可用,进一步上游服务不可用,最终可能服务雪崩这样的问题
    现在进行测试,首先,我们编辑之前的流控规则,将QPS修改成线程数(一般线程数不操作流控效果),保存后
    我们将9101端口项目里面的loadProductServicePort路径方法里的调用方法之前的部分地方
    加上休眠,设置为5000 ms,即5秒钟的休眠
    现在重启项目,接下来在postman里面,打开两个可以请求的窗口
    其中一个执行时,另外一个立即执行,若出现Blocked by Sentinel (flow limiting),代表操作成功
    最后注意一点,一般若后台出现了什么问题
    sentinel除了实时监控外(他只是记录数据,不会与项目有关联,所以只会由他的自己不显示)
    其他的一般都不会显示,因为与项目有关,有非常小的延迟,相当于立即不显示
    没有这样说明的延时,通常会延时一段时间,而不是立即
    需要再次的访问才可显示,且对应的规则一般都会删除,因为并没有操作持久化
    其中显示一般只要访问了,就会显示(虽然有延迟),无论是否停了多长时间和是否访问失败
    但该次显示的访问不会计算到对应的流控规则(无论是什么规则)里面
    但也只有该次,其他没有显示的是不会的,即只有第一次的访问不会,所以如果设置了50秒的停顿
    那么访问显示后,第二次的访问不会出现异常抛出,虽然基本只有线程数可以测试到
    是否集群:是否集群限流
    流控模式:
    直接:资源调用达到限流条件时,直接限流
    关联:关联的资源调用达到阈值的时候限流自己的资源,而不是关联的资源
    比如说如下:

    在这里插入图片描述

    点击关联会出现关联资源的属性,具体的理解为,只要B这个资源,触发了这里的流控,即每秒中的访问达到2次或者2次以上
    那么就将A资源操作流控,即流控规则的操作,这里自然是快速失败,即抛出异常
    与直接不同的是,直接代表自己的资源,而关联代表关联的资源,对应的触发流控效果的对象不同
    所以流控模式也可以称为满足对应条件,而流控模式上面的属性配置就是条件的设置
    如"直接"是满足自己触发流控,然后操作效果
    而"关联"是关联触发流控,然后触发效果,但他们相同的是,效果的操作对象都是自己的资源,而不是关联的
    我们可以将关联简称为,你出问题,我背锅,具体测试,在后面中的"流控模式之关联限流"中进行
    链路:只记录指定链路上的流量,后面会说明他的测试的(虽然并没有实际操作说明),在"流控模式之链路限流"中进行
    流控效果:
    快速失败:直接失败,抛出异常,前面的多次测试,都是这个
    Warm Up:根据冷加载因子(默认为3)的值,从阈值/冷加载因子,经过预热时长,才达到设置的QPS阈值
    具体意思可以说明一下:
    当你操作单机阈值为10时,该值对应的预热时长是11(代表秒,即11秒),那么在11秒内,对应的单机阈值是10/3
    取整数,那么就是3,而10/3的这个3就是冷加载因子,所以在一开始的11秒内,我们的单机阈值是3,之后才是10
    他相当于规定一个空间,具体测试在后面的"流控效果之Warm up"
    主要针对于项目刚刚上线的时刻,因为一般情况下
    第一次是没有缓存的,即操作数据库,后续才会操作缓存,我们可以先操作少的请求,先缓存再说
    而不是然多的请求操作了数据库,这就是该预热的作用之一,当然,还有其他的作用
    排队等待:匀速排队,让请求匀速通过,阈值类型必须设置为QPS,否则无效
    因为线程数是不操作流控效果的,一般默认是快速失败,具体测试在"流控效果之排队等待"
    具体的解释是:假设对应的QPS设置为1,原来是代表每秒中最大只能是1个请求,否则操作流控模式,而当我们设置了这个时
    会根据时间来划分,即如果是1,那么一秒中最大执行一次,如果是2,那么一秒钟最大执行两次
    如果是0.5(这里与没有设置的不同),那么每两秒最大执行一次,其中如果超过了最大,那么多余的会排队
    而不是操作流控模式,且这些排队的,会在后面的秒中进行访问,如果设置的超时时间
    在排队访问时,他还没有在超时的时间内访问到,那么就抛出异常(显示给页面,即前端)
    流控模式之关联限流:
    关联的资源调用达到阈值时候限流自己,比如用户注册接口,需要调用身份证校验接口(往往身份证校验接口)
    如果身份证校验接口请求达到阈值,使用关联,可以对用户注册接口进行限流
    简单说,就是第三方接口出现大的延时时,将我们进行限流,通常操作第三方接口时使用这个模式
    现在我们进行测试,我们到9101端口项目里面的controller包下,创建UserController类:
    package com.lagou.page.controller;
    
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     *
     */
    @RestController
    @RequestMapping("/user")
    public class UserController {
        /**
         * 用户注册接口
         *
         * @return
         */
    
        @GetMapping("/register")
        public String register() {
            System.out.println("Register success!");
            return "Register success!";
        }
    
        /**
         * 验证注册身份证接口(需要调用公安户籍资源)
         *
         * @return
         */
    
        @GetMapping("/validateID")
        public String validateID() {
            System.out.println("validateID");
            return "ValidateID success!";
        }
    
        //上面都是假设的
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    现在我们创建对应的流控规则:

    在这里插入图片描述

    在这之前,我们需要访问一次,才能够新增,由此看来,第一次的访问,是为了初始化的,也顺便操作了显示
    接下来,我们操作访问http://127.0.0.1:9101/user/validateID设置访问100次,间隔100ms(前面有操作过多次的访问)
    然后在他访问时,我们访问http://127.0.0.1:9101/user/register,可以发现,出现了Blocked by Sentinel (flow limiting),即操作成功
    但是如果没有访问了,即100次访问完成,那么也就正常了
    所以我们可以发现,只要对方进行了操作限流,访问就会出现Blocked by Sentinel (flow limiting)
    那么对方的限流有效时间是多少,答,对应的1秒钟的时间如果出现限流,那么该一秒就是有效时间
    否则不可能你出现一次限流,那么后面的所有的http://127.0.0.1:9101/user/register都不能访问吧,所以有有效时间
    但实际上也可以说是满足一定条件,就会操作异常,就如前面说的,流控模式就是操作对应的满足条件,然后操作对应的流控效果
    但如果不满足,自然不会操作限流,就如前面的100次访问完毕
    但是这里需要提一点,该效果,只是准备出现(满足条件后,操作规则定义的而已)
    而让他出现,自然需要对应的操作的限流资源进行访问才可出现
    就如上面的访问http://127.0.0.1:9101/user/register,出现了Blocked by Sentinel (flow limiting)
    流控模式之链路限流:
    链路指的是请求链路(比如调用链:A–>B–>C,D–>E–>C),同一个资源被不同的调用链路调用,且并不是非要按照顺序
    如D–>E–>C,这是肯定的,因为调用本来就部分先后,也只是我们来设置逻辑顺序而已
    链路模式下会控制该资源所在的调用链路入口的流量,需要在规则中配置入口资源,即该调用链路 入口的上下文名称
    一棵典型的调用树如下图所示:

    在这里插入图片描述

    上图中来自入口 Entrance1 和 Entrance2 的请求都调用到了资源 NodeA ,Sentinel 允许只根据某个 调用入口的统计信息对资源限流
    比如链路模式下设置入口资源为 Entrance1 来表示只有从入口
    Entrance1 的调用才会记录到 NodeA 的限流统计当中,而不关心经 Entrance2 到来的调用

    在这里插入图片描述

    通常情况下,点击链路会出现入口资源的属性,且当我们访问入口资源的值并触发条件时
    就会报异常(如果没有,则去百度搜索解决方案即可)
    该异常由入口资源触发,而不是自己的资源,与前面的操作不同,这里操作的不是自己,而正是因为这样
    使得,不同的链路是互不影响的,因为我们只针对于入口操作限流
    虽然不同的链路,入口基本不同,但可能会有相同的入口
    如中间不同(比如根据静态变量的累加到一定程度,变成其他调用等等)
    但由于我们只看入口,所以这种情况虽然特殊,但却也是同一个入口资源,因为入口相同
    比如这里:https://blog.csdn.net/chyh741/article/details/123909064
    但是在高版本中,可能失效了,具体的说明这里非常难说
    我查看了一下对应的网上资料,这个资料的应该很详细:
    https://github.com/alibaba/Sentinel/issues/1024
    一般需要官方解决,所以就不说明了,可以使用低版本的
    流控效果之Warm up:
    当系统长期处于空闲的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮,比如电商网站的秒杀模块
    通过 Warm Up 模式(预热模式),让通过的流量缓慢增加,经过设置的预热时间以后,到达系统 处理请求速率的设定值
    Warm Up 模式默认会从设置的 QPS 阈值的 1/3 开始慢慢往上增加至 QPS 设置值

    在这里插入图片描述

    点击Warm Up,会出现预热时长的属性
    由于这里会改变阈值(流控效果是准备的),所以他也使得快速失败和设置阈值一起,虽然有设置阈值的能力
    这时,我们可以多次访问http://127.0.0.1:9101/user/register,一直访问,可以看到
    在前期,有时候可以看到异常信息,在之后就没有了,注意:该预热时长,一般从第一次的访问开始计时的
    预热时长过后,就会设置回来,即没有操作1/3的阈值了,也就回来了,
    流控效果之排队等待:
    排队等待模式下会严格控制请求通过的间隔时间,即请求会匀速通过,允许部分请求排队等待
    通常用于消息队列削峰填谷等场景,需设置具体的超时时间,当计算的等待时间超过超时时间时请求就会 被拒绝
    很多流量过来了,并不是直接拒绝请求,而是请求进行排队,一个一个匀速通过(处理)
    请求能 等就等着被处理,不能等(等待时间>超时时间)就会被拒绝
    例如,QPS 配置为 5,则代表请求每 200 ms 才能通过一个(设置这个就变成匀速了,否则我们想怎么访问就怎么访问)
    多出的请求将排队等待通过
    超时时间代表最大排队时间,超出最大排队时间的请求将会直接被拒绝
    排队等待模式下,QPS 设置值不要超过 1000(请求间隔 1 ms),即最大是1000,大概是对应的匀速最小是1ms
    如果超过了,一般会报错,那么为什么不能超过呢,主要是为了减少异步的处理
    但实际上会报错的,这里了解即可
    现在进行测试,回到9101项目的UserController类:
    修改如下:
    @GetMapping("/validateID")
        public String validateID() {
            String format = new SimpleDateFormat("yyyy/MM/dd mm:ss").format(new Date());
            System.out.println(format);
            System.out.println("validateID");
            return "ValidateID success!";
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    对应的流控如下:

    在这里插入图片描述

    点击排队等待会出现超时时间(毫秒单位,即上面的是1秒)
    接下来我们操作多次的访问,看看打印,发现,每秒只打印一次,至此操作大致成功
    可能该1秒不会出现异常出来(可能超时时间会延迟一点点)
    我们可以设置10毫秒,这时我们发现,多次访问时,一般会出现异常信息了
    注意:该异常是说明页面的打印异常,而不是服务器的
    注意:到这里,基本说明完了,但有个问题,如果单机阈值是小于1的,那么代表什么呢(前面的匀速说明过了,这里提一下):
    这里需要分情况考虑,如果没有操作匀速,那么默认是一直是限流的,无论是否访问,因为我们的访问
    最少也是1个请求,所以如果是小于1的,那么就相当于默认一直超过了,即操作限流了
    就算是没有访问也是如此(具体结论在于操作关联时,设置为0.5,然后我们不执行关联资源,也出现了限流)
    那么如果是匀速,那么对于匀速来说,该意思是变成的时间操作
    即每两秒最大执行一次,超过的排队,自己通过测试发现的确如此
    至此流控规则操作完毕
    Sentinel 降级规则模块:
    流控是对外部来的大流量进行控制访问,与熔断降级不同的是,他是直接操作外部问题的访问的
    而熔断降级的视角是对内部问题进行处理
    Sentinel 降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高)
    对这 个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误
    当资源被降级后, 在接下来的降级时间窗口之内,对该资源的调用都自动熔断,这里的降级其实是Hystrix中的熔断
    策略:
    Sentinel不会像Hystrix那样放过一个请求尝试自我修复,就是明明确确按照时间窗口来
    熔断触发 后,时间窗口内拒绝请求,时间窗口后就恢复

    在这里插入图片描述

    当然,还是建议在簇点链路里进行点击,方便一些

    在这里插入图片描述

    这里的时间的窗口与前面说过的熔断的自我修复机制不同,前面的是每隔5秒操作一次,只要有对于的正确访问,那么不操作熔断
    而这里只过5秒,即5秒后,就不操作熔断了,当然,这些都只是熔断的规则而已,规则有很多,自然不一定是相同的
    其中熔断的规则一般是降级的规则,因为熔断也只是断的意思,他们的确都断了,那么后续一般是降级的规则
    当然熔断一般也会加入该规则里面,比如说自我修复机制
    当然,你也可以称为熔断的规则
    RT(平均响应时间 ):
    只要有5个或者5个以上的请求,我们就会操作一次平均响应时间,一般只要5个即可
    操作之后,下次的访问操作熔断了,如果熔断了,那么下次的访问,也就是会出现异常信息,即Blocked by Sentinel (flow limiting)
    但在高并发下,可能会出现5个以上
    1s 内持续进入 5 个或者5 个以上的请求(注意:最少是5个,否则不会操作熔断)
    注意这里不是1秒中有五个或者5 个以上的请求,而是间隔
    那么为什么这样说呢:即我们在1秒的间隔内,连续的访问5次或者5次以上,即每次访问的之间的间隔是1秒内的间隔
    那么才会操作平均响应时间
    所以这里我们用持续表示,即"1s 内持续进入 5 个或者5 个以上的请求"
    若平均响应时间超过阈值(以 ms 为单位),阈值设置为200 ms
    即这5个请求或者5 个以上的请求响应给前端的时间加起来除以5或者5以上,若是>200
    那么在接下的时间 窗口(以 s 为单位)之内
    对这个方法的调用都会自动地熔断,即抛出 DegradeException,即前端显示Blocked by Sentinel (flow limiting)
    而不是有效时间的1秒,是时间窗口的有效时间,该有效时间过去,那么就不会操作熔断,自然也就没有异常了
    注意 Sentinel 默认统计的 RT 上限是 4900 ms,超出此阈值的设置都会算作 4900 ms
    若需要变更此上限可 以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx 来配置
    比如:java -jar -Dcsp.sentinel.statistic.max.rt=2000 sentinel-dashboard-1.7.1.jar这样来启动,设置上限为2000 ms(毫秒)

    在这里插入图片描述

    我们来测试一下,先操作降级规则

    在这里插入图片描述

    默认是点击RT,即有RT这个属性的设置(这里设置为200毫秒,即200 ms)
    回到UserController类,修改如下:
      @GetMapping("/register")
        public String register() throws InterruptedException {
            Thread.sleep(1000);
            System.out.println("Register success!");
            return "Register success!";
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    这里记得重启
    上面我们设置了延迟1秒钟,那么假设我们请求5次,每次的间隔不超过1秒,那么很明显
    由于每次的响应时间通常是1秒,即平均响应的时间也就是1秒,大于阈值
    那么在第5次或者5次以上的访问后,后面的访问即会请求失败
    返回Blocked by Sentinel (flow limiting),有效时间是对应的时间窗口
    这里只需要我们持续的点击访问即可,不需要操作循环多次的访问
    所以我们连续的点击访问localhost:9101/user/register,访问了5次或者5次以上之后
    后面的访问就会出现Blocked by Sentinel (flow limiting)
    由于这里并不是操作高并发,所以通常情况下,第5次访问后,再次的访问即第6次的访问就会出现Blocked by Sentinel (flow limiting)
    异常比例:
    当资源的每秒请求量 >= 5(注意:这里不是持续,而是真的一秒钟之内的访问次数大于等于5次)
    并且每秒异常总数占通过量的比值超过阈值之后,资源进入降级状态
    即在接下的时间窗口(以 s 为单位)之内,对这个方法的调用都会自动地返回对于的异常信息
    也就是Blocked by Sentinel (flow limiting),异常比率的阈值范围是 [0.0, 1.0] ,代表 0% - 100%

    在这里插入图片描述

    现在进行测试,我们回到UserController类,修改如下:
    @GetMapping("/register")
        public String register() throws InterruptedException {
            //Thread.sleep(1000);
            int i = 1/0;
            System.out.println("Register success!");
            return "Register success!";
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    对于的降级规则:

    在这里插入图片描述

    点击异常比例,那么就会出现异常比例的属性,这里设置为0.1
    由于代码里面设置了int i = 1/0;,那么这样必定百分百的报错
    那么我们只需要在一秒钟之内访问localhost:9101/user/register这个地址5次后,再次的访问
    就会出现异常,即Blocked by Sentinel (flow limiting)信息(有效时间也是时间窗口的时间)
    这里再次的说明一下,该熔断异常不会打印出来的,但会给前端异常信息
    即Blocked by Sentinel (flow limiting)信息
    至于多次的访问,看前面的操作即可,当然,记得重启
    异常数:
    当资源近 1 分钟内的异常数目超过阈值之后会进行熔断
    注意由于统计时间窗口是分钟级别的,若 timeWindow (时间窗口)小于 60s
    则结束熔断状态后仍可能再进入熔断状态,时间窗口 >= 60s
    现在进行测试,对应的降级规则:

    在这里插入图片描述

    点击异常数,会出现异常数的属性,这里设置为2,代表最大只能是2个异常
    对应的代码不用修改,其中2代表1分钟之内,最大只能是2个异常(该异常是程序异常,不是这里的熔断的)
    之后,若出现异常,则返回熔断的异常信息,66代表66秒,前面说过,要大于等于60秒,比如59秒
    但道理说,可能1分钟之内,若在第一秒钟直接熔断(返回信息),然后熔断后,可能在第60秒再次的熔断(返回信息)
    即多次的熔断,但实际上,我们只要在1分钟之内熔断了,那么时间窗口就是1分钟,那么时间窗口的作用是什么
    一般如果少于1分钟,那么默认1分钟,大于1分钟,则操作该时间窗口,而不是1分钟
    这时,我们试着访问localhost:9101/user/register两次,前两次是对应的程序的报错
    第3次,就是熔断的异常信息了,即Blocked by Sentinel (flow limiting)
    最后针对于异常比例和异常数,那么有个问题,熔断会使得打印报错信息吗,答:不会
    可以说在打印之前,就会取消的,或者说,被覆盖或者先操作熔断的异常了,即熔断的异常优先了
    所以这里以及前面的异常比例都是如此,不会打印异常信息
    SCA 小结:
    SCA实际上发展了三条线
    第一条线:开源出来一些组件
    第二条线:阿里内部维护了一个分支,自己业务线使用(这里并没有大致说明)
    第三条线:阿里云平台部署一套,付费使用 从战略上来说,SCA更是为了贴合阿里云
    目前来看,开源出来的这些组件(这里是SCA),推广及普及率不高,社区活跃度不高
    稳定性和体验度上仍需进 一步提升(如持久化好像并没有操作,但由于SCN好像并不怎么维护,可能在以后会广泛使用SCA的)
    但现在所以一般都是使用SCN(因为相当于全家桶的模式,即基本上对应的操作都有,且各个组件相对来说还是比较成熟稳定的)
    全家桶模式:包括各种的解决方案,基本能解决大多数问题,即比较全
    最后,根据实际使用来看SCA中的Sentinel的稳定性和体验度要好于Nacos
    最后说明:一般情况下,如果是操作地址的,那么如果地址不存在,一般是不会导致项目需要重新启动的
    只是使用不了对应的功能而已,比如nacos和sentinel等等,但如果对应的地址又回来了,一般会自动的连接
    但并不是全部,有些还是会导致重启的或者不连接的(非常少),但这里的Cloud基本都可以
    但这些只是提一下而已,了解即可
  • 相关阅读:
    自定义qtquick 插件模块,支持qmldesigner
    并列连词详解
    编译器-条件/循环代码生成
    MySQL数据库性能优化方法,一篇给你总结了
    docker学习-常用命令
    【Cucumber】关于BDD自然语言自动化测试的语法总结
    [附源码]计算机毕业设计基于springboot框架的食品安全监督平台的设计与实现
    The Hello World Module
    物联网主机:为智能交通赋能
    shiro授权
  • 原文地址:https://blog.csdn.net/qq_59609098/article/details/126748551