凌月风的个人博客

记录精彩人生

Open Source, Open Mind,
Open Sight, Open Future!
  menu

Java笔记系列——10-微服务

0 浏览

微服务

架构演变

  • 单体架构
    • 我们最先接触的单体架构,整个系统就只有一个工程,打包往往是打成了war包,然后部署到单一tomcat上面。
      • 假如系统按照功能划分了商品模块,购物车模块,订单模块,物流模块等等模块。那么所有模块的代码都会在一个工程里面,这就是单体架构。 image-20220711164939843
    • 该架构模式没有对我们业务逻辑代码实现拆分,所有的代码都写入到同一个工程中里面。结构简单部署简单,所需所需的硬件资源少,能节省成本。适合小公司开发团队或者个人开发。
    • 单体架构的缺点
      • 🏷版本迭代慢,往往改动一个代码会影响全局
      • 🏷并发量不高
      • 🏷代码维护困难,所有代码在一个工程里面,存在被其他人修改的风险
      • 🏷如果该系统一个模块出现不可用、会导致整个系统无法使用。

  • 水平扩展架构
    • 随着业务的拓展,并发量提高,单体架构往往不能满足我们的需求,我们需要对架构进行变动。
      • 我们能够想到的最简单的办法就是加机器,每个机器部署相同的war包,对应用横向扩展,这就是水平扩展架构 image-20220711165321018
    • 水平扩展架构解决了单体应用的并发量限制,能让系统支撑更大的访问量
    • 水平扩展架构缺点
      • 存在资源浪费:随着业务量的增长,真正需要扩容的可能只是某个模块,但是水平扩展的架构是整个工程的扩展,存在资源浪费
      • 没有解决单体架构的版本迭代慢,代码维护困难的问题。

  • 垂直应用架构
    • 水平扩展架构存在资源浪费的问题,那将系统里面的模块拆分开来,这样对于需要水平扩展的节点,是比较友好的。
      • 一个单体应用的war,按照模块分为多个war。这样对于单个war的水平扩展就比较友好 image-20220711165707111
    • 在垂直应用架构下,通过系统拆分实现了流量分担,解决了并发问题,而且可以针对不同模块进行优化和水平扩展,解决了水平扩展的资源浪费问题。同时各模块系统之间不会相互影响,一定程度上解决了单体架构的缺点。
    • 垂直应用架构缺点
      • 系统之间相互独立, 无法进行相互调用
      • 系统之间相互独立, 会有重复的开发任务

  • 分布式架构
    • 分布式架构是基于垂直应用架构演变而来,属于垂直应用架构的优化
      • 当垂直应用越来越多,重复的业务代码就会越来越多。将重复的代码抽取出来,做成统一的业务层作为独立的服务,然后由前端控制层调用不同的业务层服务,这就是分布式系统架构。
    • 分布式架构将工程拆分成表现层和服务层两个部分。
      • 表现层只需要处理和页面的交互,业务逻辑都是调用服务层的服务来实现,解决了垂直应用架构中个模块无法相互调用的问题。
      • 服务层中包含业务逻辑,属于公共逻辑的抽线,提高了代码复用性image-20220711170448687
    • 分布式架构的缺点
      • 系统间耦合度变高,调用关系错综复杂,难以维护

  • 面向服务的架构
    • 面向服务的架构属于对于分布式架构的优化,主要解决分布式架构的调用关系负责难以维护的问题
      • 在分布式架构下,当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心对集群进行实时管理
      • 此时,用于资源调度和治理中心(SOA Service OrientedArchitecture,面向服务的架构)是关键。 image-20220711171355524
    • 基于注册中心的SOA框架,扩展是非常方便的,因为不需要维护分流工具,但我们启动应用的时候就会把服务注册到注册中心。在SOA框架中一般会有三种角色:
      • 注册中心:在注册中心维护了服务列表
      • 服务提供方 :服务提供方启动的时候会把自己注册到注册中心
      • 服务消费方:服务消费方启动的时候,把获取注册中心的服务列表,然后调用的时候从这个服务列表中选择某一个去调用。

微服务架构

  • 微服务架构是微粒度的服务,在SOA架构上进行的进一步的演进优化,相较于SOA架构强调的是解耦,模块划分更加细致。
    • 微服务是在服务治理和快速交付的链路上不断优化完善所延申出来的概念
    • 微服务工程的特点:
      1. 扩展灵活
      2. 每个应用都规模不大
      3. 服务边界清晰,各司其职
      4. 打包应用变多,往往需要借助CI持续集成工具

  • 在微服务架构中需要解决的问题

    • 服务注册与发现
      • 服务实例将自身服务信息对外公开。这部分服务信息包括服务所在主机IP和提供服务的Port,以及暴露服务自身状态以及访问协议等信息。
      • 服务实例能够发现已经公开的所有服务实例。

    • 远程服务调用
      • 服务消费者称为客户端,服务提供者称为服务端,两者通常位于网络上两个不同的地址,要完成一次 RPC 调用,就必须先建立网络连接。
      • 建立连接后,双方还必须按照某种约定的协议进行网络通信,这个协议就是通信协议。
      • 通信过程为了减少传输的数据大小,还要对数据进行压缩,也就是对数据进行序列化。

    • 负载均衡
      • 负载均衡建立在现有网络结构之上,它提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽、增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性。
      • 负载均衡(Load Balance)其意思就是分摊到多个操作单元上进行执行,例如Web服务器、FTP服务器、企业关键应用服务器和其它关键任务服务器等,从而共同完成工作任务。
      • 负载均衡问题:公开的服务实例提供相同的服务时,要访问哪一个具体实例

    • 熔断降级
      • 在分布式架构中,各个服务节点一定需要满足高可用,所以对于服务本身来说,一方面是在有准备的前提下做好充足的扩容。另一方面,服务需要有熔断、限流、降级的能力。
      • 当一个服务调用另外一个服务,可能因为网络原因、或者连接池满等问题导致频繁出现错误,需要有一种熔断机制,来防止因为请求堆积导致整个应用雪崩。
      • 当发现整个系统的确负载过高的时候,可以选择降级某些功能或某些调用,保证最重要的交易流程的通过,以及最重要的资源全部用于保证最核心的流程。
      • 在设置了熔断以及降级策略后,还有一种手段来保护系统,就是限流算法。我们能够通过全链路压测了解到整个系统的吞吐量,但实际上的流量可能会超过我们预期的值,比如存在恶意攻击、或者突然的高峰流量。在这种情况下可以通过限流来保护系统不崩溃,但是对于部分用户来说,会出现被限流导致体验不好的情况。

    • 配置中心
      • 服务拆分以后,服务的数量非常多,如果所有的配置都以配置文件的方式放在应用本地的话,非常难以管理,可以想象当有几百上千个进程中有一个配置出现了问题,是很难将它找出来的,因而需要有统一的配置中心,来管理所有的配置,进行统一的配置下发。
      • 在微服务中,配置往往分为几类,一类是几乎不变的配置,这种配置可以直接打在容器镜像里面,第二类是启动时就会确定的配置,这种配置往往通过环境变量,在容器启动的时候传进去,第三类就是统一的配置,需要通过配置中心进行下发,例如在大促的情况下,有些功能需要降级,哪些功能可以降级,哪些功能不能降级,都可以在配置文件中统一配置。

  • 服务网格 ServiceMesh
    • **在介绍Service Mesh概念之前,我们先来了解一下Sidecar。Sidecar是以第一次世界大战时活跃在战场上的军用边斗车命名(也是我们在抗日神剧中最常见的道具之一)。Sidecar是Service Mesh中的重要组成部分,Sidecar在软件系统架构中特指边斗车模式,这个模式的精髓在于实现了数据面(业务逻辑)和控制面的解耦。 **
    • 在Service Mesh架构中,给每一个微服务实例部署一个Sidecar Proxy。该Sidecar Proxy负责接管对应服务的入流量和出流量,并将微服务架构中的服务订阅、服务发现、熔断、限流、降级、分布式跟踪等功能从服务中抽离到该Proxy中。
    • Sidecar以一个独立的进程启动,可以每台宿主机共用同一个Sidecar进程,也可以每个应用独占一个Sidecar进程。
    • 所有的服务治理功能,都由Sidecar接管,应用的对外访问仅需要访问Sidecar即可。当该Sidecar在微服务中大量部署时,这些Sidecar节点自然就形成了一个服务网格。image-20220711173848076
    • Service Mesh为业务开发团队降低了门槛,提供了稳定的基础设施,最重要的是,让业务开发团队从微服务实现的具体技术细节中解放出来回归到业务。

  • 通过领域驱动模型拆分现有架构项目,向微服务扩展

Spring Cloud

  • SpringCloud是分布式微服务架构下的一站式解决方案,是各个微服务架构落地技术的集合体,俗称微服务全家桶。
    • SpringCloud是基于SpringBoot的一整套实现微服务的框架。提供了微服务开发所需的配置管理、服务发现、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等组件。
    • 最重要的是,跟springboot框架一起使用的话,会让你开发微服务架构的云服务非常好的方便。 SpringBoot旨在简化创建产品级的 Spring应用和服务,简化了配置文件,使用嵌入式web服务器,含有诸多开箱即用微服务功能。

  • SpringCloud版本说明
    • SNAPSHOT: 快照版,可以稳定使用,且仍在继续改进版本。
    • PRE: preview edition, 预览版,内部测试版. 主要是给开发人员和测试人员测试和找BUG用的,不建议使用;
    • RC :Release Candidate,发行候选版本,基本不再加入新的功能,主要修复bug。是最终发布成正式版的前一个版本,将bug修改完就可以发布成正式版了。
    • SR :Service Release,表示bug修复 修正版或更新版,修正了正式版推出后发现的Bug。
    • GA :General Availability,正式发布的版本,官方开始推荐广泛使用,国外有的用GA来表示release版本。


Spring Cloud 组件

  • Spring Cloud框架既然是一个微服务的框架,那么针对于微服务架构需要解决的问题,Spring Cloud必然提供了对应的解决方案。在Spring Cloud提供了各种不同的组件来解决对应的问题。

  • Spring Cloud Ribbon

    • Spring Cloud Ribbon 是一套基于 Netflix Ribbon 实现的****客户端 负载均衡服务调用工具。
    • Ribbon使用RestTemplate实现通信

    • Ribbon使用示例
      1. 导入依赖
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>
        
      2. 为RestTemplate设置@LoadBalanced注解
        @Configuration
        public class AppConfig {
            @Bean
            @LoadBalanced  //负载均衡  ribbon
            public RestTemplate restTemplate(){
                return new RestTemplate();//httpClient
            }
        }
        
      3. 使用RestTemplate 通信
        @Service
        public class TestService {
            @Autowired
            private RestTemplate restTemplate;
            public  String getMessage(){
                String message = restTemplate.getForObject("http://service-message/message", String.class);
                return message;
            }
        }
        

    • 在RestTemplate中加入拦截器,来实现负载均衡,Ribbon支持7种负载均衡策略。默认使用的是轮询策略 image-20220726185648015

    • 指定使用的轮询策略有两种方式
      • 指定全局负载均衡策略:编写配置类,然后注册到容器即可
        @Configuration
        public class RibbonConfiguration {
            @Bean
            public IRule defaultLBStrategy() {
                return new RandomRule();
            }
        }
        
      • 通过配置文件指定
        spring:
          application:
            name: my-test-application
        my-test-application:
          ribbon:
            NFLoadBalancerRuleClassName: com.cc.order.load.myLoadRule
        

    • 自定义负载均衡策略
      1. 编写策略类
        public class myLoadRule extends ClientConfigEnabledRoundRobinRule {
            @Override
            public Server choose(Object key) {
                ILoadBalancer lb = getLoadBalancer();
                Server server = getLoadBalancer().getAllServers().get(0);
                return server;
            }
        }
        
      2. 配置
        spring:
          application:
            name: my-test-application
        my-test-application:
          ribbon:
            NFLoadBalancerRuleClassName: com.cc.order.load.myLoadRule
        

    • Ribbon配置文件
      spring.application.name: my-test-application
      ######################不适用注册中心的配置##################
      # 禁用 Eureka
      ribbon.eureka.enabled=false
      # 禁用 Eureka 后手动配置服务地址
      my-test-application.ribbon.listOfServers=localhost:8081,localhost:8083
      ######################不适用注册中心的配置##################
      # 最大连接数
      ribbon.MaxTotalConnections=500
      # 每个host最大连接数
      ribbon.MaxConnectionsPerHost=500
      # 请求连接的超时时间
      ribbon.ConnectTimeout=2000
      # 请求处理的超时时间
      ribbon.ReadTimeout=5000
      
      #也可以为每个Ribbon客户端设置不同的超时时间, 通过服务名称进行指定:
      my-test-application.ribbon.ConnectTimeout=2000
      my-test-application.ribbon.ReadTimeout=5000
      
      #不指定Ribbon默认使用轮询进行重试
      my-test-application.ribbon.NFLoadBalancerRuleClassName
      =com.netflix.loadbalancer.RetryRule
      
      # 在所有HTTP Method进行重试
      my-test-application.ribbon.OkToRetryOnAllOperations=true
      
      # 每台机器最大重试次数
      my-test-application.ribbon.MaxAutoRetries=2
      
      # 可以再重试几台机器
      my-test-application.ribbon.MaxAutoRetriesNextServer=2
      

  • Spring Cloud Eureka

    • Spring Cloud Eureka是一套服务注册与服务发现功能的组件
    • Spring Cloud 使用 Spring Boot 思想为 Eureka 增加了自动化配置,开发人员只需要引入相关依赖和注解,就能将 Spring Boot 构建的微服务轻松地与 Eureka 进行整合。

    • 搭建服务注册中心,使用示例
      1. 导入依赖
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
        
      2. 开启注册中心功能
        @SpringBootApplication
        @EnableEurekaServer //开启 Eureka server,接受其他微服务的注册
        public class EurekaServerApplication {
            public static void main(String[] args) {
                SpringApplication.run(EurekaServerApplication.class, args);
            }
        }
        
      3. 编写配置文件
        spring:
          application:
            name: eureka-server
        server:
          port: 8761
        
        eureka:
          instance:
            hostname: localhost #eureka服务端的实例名称,影响的是查看服务状态的地址
          client:
            #false表示不向注册中心注册自己。
            register-with-eureka: false
            #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
            fetch-registry: false 
            # 指向服务注册中心的地址,设置集群中的其他注册中心的地址   
            server-url:
              defaultZone: http://localhost:8761/eureka  
        

    • 服务提供者,使用实例
      1. 导入依赖
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        
      2. 使用注解,注册服务
        @SpringBootApplication
        @EnableEurekaClient // Spring cloud Eureka 客户端,自动将本服务注册到 Eureka Server 注册中心中
        public class ClientApplication {
            public static void main(String[] args) {
                SpringApplication.run(ClientApplication.class, args);
            }
        }
        
      3. 配置文件
        server:
          port: 8001 #服务端口号
        spring:
          application:
            name: eureka-client  #微服务名称,对外暴漏的微服务名称,十分重要
        eureka:
          client: #将客户端注册到 eureka 服务列表内
            service-url:
              defaultZone: http://localhost:8761/eureka  #注册中心的发布地址
          instance:
            instance-id: spring-cloud-provider-8001 #自定义服务名称信息
            prefer-ip-address: true  #显示访问路径的 ip 地址
        

    • 心跳机制
      1. 在默认的参数情况下,服务提供者每30秒向注册中心发送一次心跳请求
      2. 注册中心每隔60秒检查一次所有实例的心跳时间间隔
      3. 如果上一次的心跳时间距离检查时的时间大于90秒,那么认为这个服务提供者过期
      4. 如果没有开启自我保护机制,那么将这个服务提供者剔除 image-20220727130139436

    • 自我保护机制
      • 🏷心跳检测机制有一定的不确定性。由于网络通信的问题,导致在90s内没有收到心跳请求,那将会导致健康的服务被误杀。
      • 🏷为了避免这种问题,Eureka提供了一种自我保护机制
    • 自我保护机制触发条件
      1. 打开自我保护开关(默认打开状态,建议生产环境打开此配)

        **通过配置 **eureka.server.enable-self-preservation=true来打开自我保护机制

      2. 当自我保护机制开启之后,如果上一次的所有心跳总数没有达到自我保护的阈值,那么开启自我保护

        🏷自我保护阈值 = 服务提供者总量 * 每个服务提供者每分钟的心跳次数 * renewal-percent-threshold** 🏷 **renewal-percent-threshold是配置的百分比,默认0.85。通过eureka.server.renewal-percent-threshold=0.85配置

      3. 开启自我保护机制后,如果发现心跳超时,此时的服务提供者不会被剔除

    • 自我保护机制下的整体流程
      1. 注册中心会每隔15分钟,或者有服务提供者上线或者下线时,更新自我保护的阈值
      2. 注册 中心每隔60秒钟检查所有的实例过期时间(即上次心跳距离检查时的时间间隔)
      3. 此时会进行自我保护开启判定
      4. 如果发现60秒内收到的心跳总数小于自我保护的阈值,则开启自我保护,否则不开启
      5. 然后进行服务提供者的心跳续约检查
      6. 如果有超过90秒钟没有发送过心跳的服务提供者,且没有触发自我保护机制则将该服务提供者剔除
      7. 如果出发了自我保护机制,心跳超时的服务提供者不会被剔除 image-20220711162611154

    • 存储结构
      1. eureka 的核心存储结构是两层ConcurrentHashMap,ConcurrentHashMap<String,Map<String,Lease<InstanceINfo>>>
      2. 在外层的Map中,key代表的是配置文件中的spring.application.name
      3. 内层的Map中,key代表的是instanceId,代表的是配置文件中的eureka.instance.instance-id
      4. 内层Map中,value代表的是实例的详细信息、服务注册的时间戳等信息 image-20220711180940075

    • 多级缓存设计

      • 为了减少ConcurrentHashMap的资源竞争,采用了多级缓存设计。只读缓存和读写缓存
      • 所有的查询请求都会从只读缓存readOnlyCacheMap中获取
      • 当收到服务注册请求于下线请求时,会更新主要的数据缓存,然后将数据同步到读写缓存,然后每隔30秒将数据同步到只读缓存

      image-20220711181223510


  • Spring Cloud OpenFeign

    • Spring Cloud Feign 是一种声明式服务调用组件,它在 RestTemplate 的基础上做了进一步的封装屏蔽了网络通信的细节,直接面向接口的方式开发,让开发者感知不到网络通信细节。所有远程调用,都像调用本地方法一样完成!
    • OpenFeign使用
      1. 导入依赖
        <!--添加 OpenFeign 依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        
      2. 创建服务调用接口
        //添加为容器内的一个组件
        @Component
        // 绑定服务提供者,value为服务提供者的名称,即 application.name
        @FeignClient(value = "feign-service-provider")
        public interface FeignClient {
            //对应服务提供者的Controller 中定义的方法
            @RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
            public User get(@PathVariable("id") int id);
        }
        
      3. 启动类配置注解
        @SpringBootApplication
        @EnableFeignClients //开启 OpenFeign 功能
        public class FeignClientApplication {
            public static void main(String[] args) {
                SpringApplication.run(FeignClientApplication.class, args);
            }
        }
        

    • OpenFeign Gzip压缩配置
      • OpenFeign 使用 Gzip 方式压缩数据,对于大文本通常压缩后尺寸只相当于原始数据的 10%~30%,这会极大提高带宽利用率。
      • 如果应用属于计算密集型,CPU 负载长期超过 70%,开启数据压缩功能反而会给 CPU 增加额外负担,导致系统性能降低,这是不可取的
        feign:
          compression:
            request:
              # 开启请求数据的压缩功能
              enabled: true
              # 压缩支持的MIME类型
              mime-types: text/xml,application/xml, application/json
              # 数据压缩下限 1024表示传输数据大于1024 才会进行数据压缩(最小压缩值标准)
              min-request-size: 1024
            # 开启响应数据的压缩功能
            response:
              enabled: true
        

    • OpenFeign 超时控制
      • OpenFeign 客户端的默认超时时间为 1 秒钟,如果服务端处理请求的时间超过 1 秒就会报错。
      • 为了避免这样的情况,我们需要对 OpenFeign 客户端的超时时间进行控制。
        #由于 OpenFeign 集成了 Ribbon ,其服务调用以及负载均衡在底层都是依靠 Ribbon 实现的,因此 OpenFeign 超时控制也是通过 Ribbon 来实现的。
        ribbon:
          ReadTimeout: 6000 #建立连接所用的时间,适用于网络状况正常的情况下,两端两端连接所用的时间
          ConnectionTimeout: 6000 #建立连接后,服务器读取到可用资源的时间
        

    • OpenFeign 日志增强
      • Feign 为每一个 FeignClient 都提供了一个 feign.Logger 实例,通过它可以对 OpenFeign 服务绑定接口的调用情况进行监控。使用步骤如下
        1. 配置文件
          logging:
            level:
              #指定openfeign日志以什么级别监控哪个接口(可多个)
              com.cn.example.FeignClient: debug
          
        2. 编写配置类
          /**
              配置 OpenFeign 记录哪些内容
              Logger.Level 的具体级别如下:
              NONE:不记录任何信息。
              BASIC:仅记录请求方法、URL 以及响应状态码和执行时间。
              HEADERS:除了记录 BASIC 级别的信息外,还会记录请求和响应的头信息。
              FULL:记录所有请求与响应的明细,包括头信息、请求体、元数据等等。
          */
          @Configuration
          public class OpenFeignConfig {
              @Bean
              Logger.Level feignLogLevel(){
                  return Logger.Level.FULL;
              }
          }
          

    • 替换通信组件
      • OpenFeign 默认使用 Java 自带的 URLConnection 对象创建 HTTP 请求,
      • 接入生产时,如果能将底层通信组件更换为Apache HttpClientOKHttp 这样的专用通信组件
      • 基于这些组件自带的连接池,可以更好地对 HTTP 连接对象进行重用与管理。
      • 作为 OpenFeign 目前默认支持 Apache HttpClient 与 OKHttp 两款产品。这里以OKHttp配置方式为例。
      • 使用步骤
        1. 引入 feign-okhttp 依赖包
          <dependency>
              <groupId>io.github.openfeign</groupId>
              <artifactId>feign-okhttp</artifactId>
              <version>11.0</version>
          </dependency>
          
        2. 利用 Java Config 形式初始化 OkHttpClient 对象。
          @Configuration
          public class fenginConfig{
              @Bean
              public okhttp3.OkHttpClient okHttpClient(){
                  return new okhttp3.OkHttpClient.Builder()
                      //读取超时时间
                      .readTimeout(10, TimeUnit.SECONDS)
                      //连接超时时间
                      .connectTimeout(10, TimeUnit.SECONDS)
                      //写超时时间
                      .writeTimeout(10, TimeUnit.SECONDS)
                      //设置连接池
                      .connectionPool(new ConnectionPool())
                      .build();
              }
          }
          

    • 使用动态代理,底层封装http通信 image-20220711191035634

  • Nacos:配置中心

  • Spring Cloud Hyxtrix

    • Spring Cloud Hystrix 是一款优秀的服务容错与保护组件
    • 在微服务架构中,一个请求可能会经过多个服务进行处理,导致整个处 理链路会比较长。而在整条调用链路中,可能会因为某个节点因为网络故障导致响应时间比较 长,而这个节点的阻塞将会影响整条链路的结果返回。
      • 在访问量比较高的请求下,一个后端依赖节点的延迟响应可能导致所有服务器上的所有资源在 数秒内饱和。
      • 一旦出现这个问题,会导致系统资源被快速消耗,从而导致服务宕机等问题,最 坏的情况会导致服务雪崩。
      • 所以在软件系统中,为了防止这种问题的产生,也引入了熔断的概念

    • 熔断: 如果某个目标服务调用比较慢或者大量的超时,这个时候如果触发熔断机制,则可以保证后续 的请求不会继续发送到目标服务上,而是直接返回降级的逻辑并且快速释放资源。如果目标服 务的情况恢复了,那么熔断机制又会动态进行关闭。
    • 降级:如果主方案行不通,改用备用方案。
      • 举个例子,如果在分布式架 构中,A服务调用B服务的时候,由于B服务宕机了,导致请求失败,那这个时候我们有几种方 式来处理,
      • **第一种就是返回一个 “系统繁忙” 的信息给到用户,这种就是属于降级; **
      • 另一种就 是发起重试,这种就是容错。
    • 由于服务之间的依赖性,故障会传播,造成连锁反应,会对整个微服务系统造成灾难性 的严重后果,这就是服务故障的雪崩效应
    • Spring Cloud Hyxtrix是在客户端使用保护服务端,一般来说只在一些访问量大的关键的方法上使用,而不是所有方法都使用

    • 使用
      1. 导入依赖
        <!--hystrix 依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        
      2. 开启熔断
        @SpringBootApplication
        @EnableEurekaClient //开启 Eureka 客户端功能
        @EnableCircuitBreaker //激活熔断器功能
        public class HystrixExampleApplication {
            public static void main(String[] args) {
                SpringApplication.run(HystrixExampleApplication.class, args);
            }
        }
        
      3. 在方法上添加注解
        @Service
        public class HystrixExampleImpl{
           
            //一旦该方法失败并抛出了异常信息后,会自动调用  @HystrixCommand 注解标注的 fallbackMethod 指定的方法
            @HystrixCommand(fallbackMethod = "fallback",
                    commandProperties =
                            //规定 5 秒钟以内就不报错,正常运行,超过 5 秒就报错,调用指定的方法
                            {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000")},
                             // 统计时间窗口定义,配合最小请求数,如果8秒内出现2此请求,那么开始熔断判断
                            @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds",value = "8000"),
                            // 统计时间窗口内的最小请求数
                            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "2"),
                            // 统计时间窗口内的错误数量百分比阈值,如果开启判断之后,请求失败的百分比达到了50%,则开启
                            @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "50"),
                            // 自我修复时的活动窗口长度
                            @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "3000")                 
                           )
            @Override
            public String test(Integer id) {
                int outTime = 6;
                try {
                    TimeUnit.SECONDS.sleep(outTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return "线程池:" + Thread.currentThread().getName() + "  deptInfo_Timeout,id:   " + id + "  耗时: " + outTime;
            }
            // 当服务出现故障后,调用该方法给出友好提示
            public String fallback() {
               return  "服务降级";
            }
        }
        

    • 配合OpenFeign使用
      1. 导入依赖
        <!--hystrix 依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        
      2. 开启熔断
        @SpringBootApplication
        @EnableEurekaClient //开启 Eureka 客户端功能
        @EnableCircuitBreaker //激活熔断器功能
        @EnableFeignClients //开启 OpenFeign 功能
        public class HystrixExampleApplication {
            public static void main(String[] args) {
                SpringApplication.run(HystrixExampleApplication.class, args);
            }
        }
        
      3. 通过配置开启OpenFeigh的Hystrix支持
        #feign客户端配置
        feign:
          hystrix:
            #设置feign开启hystrix(服务保护)
            enabled: true
        #hystrix配置
        hystrix:
          command:
            default:
              execution:
                isolation:
                  thread:
                    #feign整合hystrix 光设置Hystrix 超时没用的要配合ribbon超时
                    timeoutInMilliseconds: 3000
              circuitBreaker:
                #默认20 ,熔断的阈值,如何user服务报错满足3次,熔断器就会打开,就算order之后请求正确的数据也不行。
                requestVolumeThreshold: 3
                #默认5S , 等5S之后熔断器会处于半开状态,然后下一次请求的正确和错误讲决定熔断器是否真的关闭和是否继续打开
                sleepWindowInMilliseconds: 8000
        
      4. 在@FeignClient 注解上加上属性fallback,并编写fallback处理方法。该处理方法要实现FeignClient对应的接口
        //添加为容器内的一个组件
        @Component
        // 绑定服务提供者,value为服务提供者的名称,即 application.name
        @FeignClient(value = "feign-service-provider", fallback =ClientFallback.class)
        public interface FeignClient {
            //对应服务提供者的Controller 中定义的方法
            @RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
            public User get(@PathVariable("id") int id);
        }
        
        @Component //注意一定要把处理类放入容器中
        public class ClientFallback implements FeignClient {
            @Override
            public User get(int id){
                log.error("服务降级方法被调用");
                return null;
            }
        }
        

    • 触发熔断机制原理分析
      1. 当在10s内收到20次请求之后,hystrix开始检查整体故障的百分比,如果整体访问失败的百分比超过阈值,默认是50%,hystrix将会触发熔断
        ##############################################################
        # 统计时间窗口定义,配合最小请求数默认10s
        metrics.rollingStats.timeInMilliseconds=10000
        #统计时间窗口内的最小请求数 默认20次
        circuitBreaker.requestVolumeThreshold=20
        #统计时间窗口内的错误数量百分比阈值
        circuitBreaker.errorThresholdPercentage=50
        ##############################################################
        #以上配置代表在10秒内收到20次请求之后,开始判断如果这20此请求有超过50%失败,则开启熔断
        
      2. Hystrix熔断之后,在5s内,所有请求都不会发送到远程服务器上,而是直接返回降级方法的结果,防止服务雪崩
        #熔断开启后的睡眠时间,此时间内所有请求会直接访问降级方法
        circuitBreaker.sleepWindowInMilliseconds=5  
        
      3. 同时,Hystrix每隔5s会发送一个测试的请求,如果测试请求返回成功,则Hystrix就会 尝试关闭断路器,使得后续请求继续正常访问;如果失败则hystrix在下一个5s再尝试关闭断路器
      4. 底层通过滑动窗口来实现统计窗口的逻辑 image-20220730164402465

    • 服务隔离
      • 线程池隔离:Hystrix默认情况下使用一个线程池 。如果要使用隔离机制,需要进行配置不同的请求配置不同的线程池,由此来达成服务隔离的目的
      • 服务隔离配置去网上查找

  • Spring Cloud Gateway
    • Spring Cloud Gateway 是 Spring Cloud 团队基于 Spring 5.0、Spring Boot 2.0 和 Project Reactor 等技术开发的高性能 API 网关组件。
    • 核心概念 image-20220730170115507
    • Spring Cloud Gateway 通过 Predicate 断言来实现 Route 路由的匹配规则。简单点说,Predicate 是路由转发的判断条件,请求只有满足了 Predicate 的条件,才会被转发到指定的服务上进行处理。
    • 使用 Predicate 断言需要注意以下 3 点:
      • Route 路由与 Predicate 断言的对应关系为“一对多”,一个路由可以包含多个不同断言。
      • 一个请求想要转发到指定的路由上,就必须同时匹配路由上的所有断言。
      • 当一个请求同时满足多个路由的断言条件时,请求只会被首个成功匹配的路由转发。
    • 工作流程
      1. 客户端将请求发送到 Spring Cloud Gateway 上。
      2. Spring Cloud Gateway 通过 Gateway Handler Mapping 找到与请求相匹配的路由,将其发送给 Gateway Web Handler。
      3. Gateway Web Handler 通过指定的过滤器链(Filter Chain),将请求转发到实际的服务节点中,执行业务逻辑返回响应结果。
      4. 过滤器之间用虚线分开是因为过滤器可能会在转发请求之前(pre)或之后(post)执行业务逻辑。
      5. 过滤器(Filter)可以在请求被转发到服务端前,对请求进行拦截和修改,例如参数校验、权限校验、流量监控、日志输出以及协议转换等。
      6. 过滤器可以在响应返回客户端之前,对响应进行拦截和再处理,例如修改响应内容或响应头、日志输出、流量监控等。
      7. 响应原路返回给客户端。

心中无我,眼中无钱,念中无他,朝中无人,学无止境

纸上得来终觉浅,绝知此事要躬行

image/svg+xml