SpringCloud学习总结(一)——微服务基础、服务间通信及服务降级

本文详细介绍了SpringCloud的微服务基础,包括Eureka注册中心的搭建、服务间通信的组件如Zuul API网关、Ribbon的负载均衡以及OpenFeign的声明式调用。同时,文章深入探讨了服务降级和Hystrix的断路器模式,阐述了如何在服务出现故障时通过Hystrix实现服务容错和系统稳定性。
摘要由CSDN通过智能技术生成

一、初识微服务

1. 架构演进

单体架构–>SOA面向服务架构–>微服务架构

2. 单体架构应用

传统系统设计中,我们通常将系统分为三层,数据访问层、业务逻辑层、视图层,也就是我们常说的MVC模型。此架构没有对业务场景进行划分,所有业务模块的数据访问层、业务逻辑层、视图层都放在一个工程中,最终经过编译、打包,部署在一台服务器上。

优点:

  • 开销低:整个项目部署为一个独立的war包,在同一个服务器进程中,不会涉及到模块之间消息通信的额外开销,执行效率相对较高。
  • 易于测试:所有的应用全部打包在一个项目中,不依赖其他接口。
  • 易于部署:只需要将目录部署在运行环境中即可。

缺点

  • 系统的可维护行及可扩展性差,部署不够灵活;单体应用同意打包为一个war包,对其中任何节点的变更都会涉及到对整个应用进行全部重新部署。
  • 稳定性不高,程序中任何一个节点出现问题都可能会导致整个项目的整体失败。比如订单服务中发短信失败抛出Exception,就会导致后面的功能不会执行。
  • 扩展性不高,比如订单服务中要求对高并发处理的能力进行提高,我们要对订单服务扩展为多个副本进行分摊来自客户端的压力,单体应用中无法对单个服务进行拆分,只能将多个相同的war包分别部署在不同的服务器上,将所有的应用全部进行启动。

3. SOA架构

SOA即面向服务的架构,这种架构在九几年的时候就被提出了。SOA往往与企业服务总线联系在一起,通过业务将项目划分为不同的服务,部署在不同的服务器上,即我们以前常说的分布式即指SOA架构的应用。服务之间可以通过WebService等中间件进行通讯,像现在比较流行的Dubbo应用也属于SOA架构的应用。

通过ESB(企业服务总线)统一进行访问协议转换,应用统一通过ESB来访问后端服务,服务与服务之间也通过ESB来相互调用,以此降低系统的耦合程度。这种单体应用拆分为多个应用,公共服务单独抽取出来来管理,并使用企业消息总线来解除服务之间耦合问题的架构,就是所谓的SOA(面向服务)架构,这种架构与微服务架构容易混淆,因为表现形式十分相似。

优点

  • 将公共服务进行抽取,使用接口通信,降低模块之间的耦合度
  • 升级单个服务无需重写整个应用,可以灵活的进行分布式部署

缺点

  • SOA的核心是ESB,由于ESB内部包含通讯协议的解析,数据的序列化和反序列化,业务流程的编排和服务路由等,因此很笨重,继承方式复杂,不可以独立运行。

4. 微服务架构

所谓的微服务是指将应用按业务划分成一个个微小的服务,每个服务可以使用不同的开发语言和不同的存储技术,服务之间通过轻量级通信协议HTTP进行通信,每个服务都可以单独部署,运行在不同的进程中。微服务架构主要关键在于微小、独立、轻量级通信。微服务相比于SOA更加精细,微服务更多的以独立的进程的方式存在,相互之间并无影响;微服务提供的接口方式更加通用化,例如HTTP RESUlTFUL方式,各种终端都可以调用,无关语言、平台的限制;微服务更倾向于分布式去中心化的部署方式,在互联网业务场景下更适合。

1) 微服务的定义

  • 微服务架构风格是一种使用系列微小服务来开发单个应用的方式途径,每个服务运行在自己的进程中,称为独立的业务。
  • 每个服务基于业务能力构建,服务之间采用轻量级机制进行通信,通常是HTTP API,这些服务通过自动化部署机制进行独立部署。
  • 每个服务使用不同的编程语言实现,以及不同的数据存储技术,并保持分布式管理。

2) 微服务架构的优缺点

优点

  • 快速迭代,系统快速开发小功能模块,尽快上线进行验证,早日发现问题,大大降级系统交付的风险
  • 容错机制,建立在以用集群基础上,微服务架构下提供了负载均衡、重试、服务降级的机制
  • 独立按需扩展
  • 服务监控,在微服务架构下,提供相应的工具,从底层的执行的跟踪,到服务器运行状态的箭扣,所有的链条都可以看到执行的明细

缺点

  • 服务通信成本
  • 数据一致性
  • 系统集成测试
  • 多服务运维难度

5. SpringCloud简介

Spring Cloud是一个开发工具集,包含多个子项目;帮助开发者构建弹性、可靠、协调的应用程序。

  • Spring Cloud是在SpringBoot的基础上构建的,使开发者可以轻松入门并快速提高工作效率
  • 对Netflix开源组件的封装
  • 为微服务开发提供一站式解决方案,是一组独立组件的集合

6. Sprig Cloud子项目简介

1) Spring Cloud Config

  • 集中配置管理工具,分布式系统中统一的外部配置管理,默认使用Git来存储胚子,可以支持客户端配置的刷新及加密、解密操作。

2) Spring Cloud Netflix

Netflix OSS开源组件集成,包括Eureka、Hystrix、Ribbon、Feign、Zuul等核心组件。

  • Eureka:服务治理组件,包括服务端的注册中心和客户端的服务发现机制;
  • Ribbon:负载均衡的组件调用组件,具有多种负载均衡调用策略;
  • Hystrix:服务容错组件,实现了断路器模式,为依赖服务的出错和延迟提供了容错能力;
  • Feign:基于Ribbon和Hystrix的声明式服务调用组件;
  • Zuul:API网关组件,对请求提供路由及过滤功能。

3) Spring Cloud Bus

用于传播集群状态变化的消息总线,使用轻量级消息代理链接分布式系统中的节点,可以用来动态刷新集群中的服务配置。

4) Spring Cloud Consul

基于Hashicorp Consul的服务治理组件。

5) Spring Cloud Security

安全工具包,对Zuul代理中的负载均衡OAuth2客户端及登录认证进行支持。

6) Spring Cloud Sleuth

SpringCloud应用程序的分布式请求链路跟踪,支持使用Zipkin、HTrace和基于日志(例如ELK)的跟踪。

7) Spring Cloud Gateway

API网关组件,对请求提供路由及过滤功能。

8) Spring Cloud Open Feign

基于Ribbon和Hystrix的声明式服务调用组件,可以动态创建基于Spring MVC注解的接口实现用于服务调用。


二、Eureka注册中心

服务注册:服务进程在注册中i性能注册自己的元数据信息。通常包括主机和端口号,有时还有身份验证信息、协议、版本号以及运行环境的信息

服务发现:客户端服务进程向注册中心发起查询,来获取服务的信息。服务发现的一个重要作用就是提供给客户端一个可用的服务列表。

在这里插入图片描述

1. Spring Cloud Eureka:服务注册与发现

Spring Cloud Eureka是Spring Cloud Netflix子项目的核心组件之一,主要用于微服务架构中的服务治理。本文将对搭建Eureka注册中心、搭建Eureka客户端、搭建Eureka集群、给Eureka注册中心添加登录认证进行介绍。

2. Eureka简介

Eureka注册中心是SpringCloud微服务架构核心组件,每个微服务都会想注册中心去注册自己的地址及端口信息,注册中心负责维护服务名臣与服务实例的对应关系。每个微服务都会定时从注册中心获取服务列表,同时汇报自己的运行情况,这样当有的服务需要调用其他服务时,就可以从自己获取到的服务列表中获取实例地址进行调用,Eureka实现了这套服务注册与发现机制。

3. 搭建Eureka注册中心

创建并运行Eureka注册中心-创建一个eureka-server模块

  • 添加依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
    
  • 在启动类上添加@EnableEurekaServer注解来启用Eureka注册中心功能

    @SpringBootApplication
    @EnableEurekaServer
    public class RegistryCenterApplication {
        public static void main(String[] args) {
            SpringApplication.run(RegistryCenterApplication.class, args);
        }
    }
    
  • 在配置文件application.yml中添加Eureka注册中心的配置

    server:
      port: 8761   # 指定运行的端口号
    spring:
      application:
      	# 指定应用名称(推荐),也可以使用erueka.instance.appname指定(优先级低不推荐)
        name: registry-center   
    eureka:
      client:
        fetch-registry: false   # 指定是否要从注册中心获取服务(如果是非高可用不需要开启)
        register-with-eureka: false  # 指定是否需要注册到注册中心(非高可用可不开启)
      server:
        enable-self-preservation: false  # 关闭保护模式
    

4. 运行SpringCloud应用

此时服务已经创建完成,点击启动类的main方法就可以运行了

访问http://localhost:8761就可以看到Eureka的注册中心的界面

5. 搭建Eureka客户端

  • 新建一个eureka-client模块,并在pom.xml中添加依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    
  • 在启动类上添加@EnableDiscoveryClient注解表明是一个Eureka客户端

    @SpringBootApplication
    @EnableDiscoveryClient  //或者@EnableEurekaClient
    public class EurekaClientApplication {
        public static void main(String[] args) {
            SpringApplication.run(EurekaClientApplication.class, args);
        }
    }
    
  • 在配置文件中添加Eureka客户端的配置

    server:
      port: 8888  #运行端口号
    spring:
      application:
        name: lkj-eureka-client  #服务名称
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8761/eureka/ #配置中心地址
        register-with-eureka: true  #注册到Eureka的注册中心
        fetch-registry: true  #获取注册实例列表
    
  • 运行eureka-client,查看注册中心http://localhost:8761 发现Eureka客户端已经成功注册

6. 注册中心高可用

由于所有服务都会注册到注册中心去,服务之间的调用都是通过从注册中心获取的服务列表来调用,注册中心一旦宕机,所有服务都会出现问题。所以我们需要多个注册中心组成集群来提供服务。

  • 给eureka-server添加配置文件application-replica1.yml配置第一个注册中心

    server:
      port: 8761   # 指定运行的端口号
    spring:
      application:
        name: registry-center1   # 指定应用名称
    eureka:
      client:
        fetch-registry: true   # 指定是否要从注册中心获取服务(如果是非高可用不需要开启)
        register-with-eureka: true  # 指定是否需要注册到注册中心(非高可用可不开启)
      	serviceUrl:
      	  defaultZone: http://localhost:8762/eureka/ #注册到另一个Eureka注册中心
    
  • 给eureka-server添加配置文件application-replica2.yml配置第二个注册中心

    server:
      port: 8762   # 指定运行的端口号
    spring:
      application:
        name: registry-center2   # 指定应用名称
    eureka:
      client:
        fetch-registry: true   # 指定是否要从注册中心获取服务(如果是非高可用不需要开启)
        register-with-eureka: true  # 指定是否需要注册到注册中心(非高可用可不开启)
      	serviceUrl:
      	  defaultZone: http://localhost:8761/eureka/ #注册到第一个Eureka注册中心
    

7. 运行Eureka注册中心集群

  • 启动两个eureka-server,访问其中一个注册中心发现另一个已经称为其备份

  • 修改Eureka-client,让其连接到集群,添加eureka-client配置文件application.yml,让其同时注册到两个注册中心

  • 以该配置文件启动客户端后访问任意一个注册中心节点都可以看到eureka-client

    server:
      port: 8888  #运行端口号
    spring:
      application:
        name: lkj-eureka-client  #服务名称
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/
        register-with-eureka: true  
        fetch-registry: true   
    
    

8. Eureka注册认证

Eureka Server的安全认证:SpringCloud默认使用Spring Security进行身份认证,建立安全连接。

  • 创建一个eureka-security-server服务,在pom.xml中添加以下依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    
    
  • application.yml配置文件,配置了登录注册中心的用户名和密码

    server:
      port: 8761
    # 应用名称
    spring:
      security:  #配置SpringSecurity登录用户名和密码
        user:
          name: lkj
          password: 123
      application:
        name: registry-center
    eureka:
      client:
        fetch-registry: false
        register-with-eureka: false
    
    
  • 添加Java配置WebSecurityConfig

    默认情况下添加SpringSecurity依赖的应用每个请求都需要添加CSRF token才能访问,Eureka客户端注册时并不会添加,所以需要配置/eureka/**路径不需要CSRF token。

    /**
     * 配置过滤CSRF token的Spring Security配置类
     */
    @Configuration
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            //访问注册中心的服务忽略SCRF token的认证
            http.csrf().ignoringAntMatchers("/eureka/**"); 
            super.configure(http);
        }
    }
    
    
  • 运行eureka-security-server,访问http://localhost:8761自动跳转到登陆认证界面

  • eureka-client注册到由登陆认证的注册中心时,需要将配置文件中的defaultZone中对应的注册中心地址前加上user:pasword@

    http://${username}:${password}@{hostname}:${port}/eureka/
    
    

9. 术语解释

  • 服务续约

    在注册中心完成服务以后,服务提供者会维持一个心跳(定时向Eureka发起Rest请求),告诉EurekaServer “我还活着”;我们称之为服务的续约(renew);有两个重要的参数可以修改服务续约的行为

    eureka:
      instance:
        lease-renewal-interval-in-seconds: 30 # 服务续约的时间间隔,默认为30秒
        lease-expiration-duration-in-seconds: 90 # 服务失效时间,默认为90秒
    
    

    在默认情况下,没过30秒服务会向注册中心发送一次心跳包,证明自己还活着,如果超过90秒没有发送心跳,EurekaServer就会认为服务宕机,会从服务列表中移除。

  • 自我保护机制:

    某时刻某一个微服务不可用,eureka不会立即清理,依旧会对该微服务的信息进行保存。

    • 默认情况下,当eureka server在一定时间内没有收到实例的心跳,便会把该实例从注册表中删除(默认是90秒),但是如果短时间内丢失大量的实例心跳,便会触发eureka server的自我保护机制,比如在开发测试时,需要频发的重启微服务实例,但是我们很少会把eureka server一起重启(以为在开发过程中不会修改eureka注册中心),**当一分钟内收到的心跳数大量减少时,会触发该保护机制。**可以在eureka管理界面看到Renews thresholdRenews(last min),当后者(最后一分钟收到的心跳数)小于前者(心跳阈值)的时候,触发保护机制,会出现红色的警告

      EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.
      
      

      从警告中可以看到,eureka认为虽然收不到实例的心跳,但他认为实例还是健康的,eureka会保护这些实例,不会把他们从注册表中删掉。

    • 该保护机制的目的是避免网络连接故障,在发生网络故障是,微服务和注册中心之间无法正常通信,但服务本身是健康的,不应该注销该服务,【如果eureka因为网络故障而把微服务误删了,那即使网络恢复了,该微服务也不会重新注册到eurekaserver了】,只有在微服务启动的时候才会发起注册请求,后面只会发送心跳和服务列表请求,这样的化该实例虽然是运行着,但永远不被其他服务所感知。所以,eureka server在短时间内丢失过多的客户端心跳是,会进入自我保护模式,该模式下,eureka会保护注册表中的信息,不再注销任何微服务,当网络故障恢复后,eureka会自动给退出保护模式。自我保护模式可以让集群更加健壮。

    • 在开发测试阶段,我们可以把自我保护模式关闭

    eureka:
        server:
            enable-self-preservation: false
    

10. Eureka常用配置

  • 配置文件详情

    eureka:
      client: # eureka客户端配置
        service-url:
          defaultZone: http://lkj:123@localhost:8761/eureka/ #配置中心地址
        register-with-eureka: true  #时候将自己注册到Eureka的注册中心
        fetch-registry: true  #是否获取eureka服务端上注册的服务列表
        enabled: true #启用eureka客户端,默认为true
        registry-fetch-interval-seconds: 30 # 定义去eureka服务端获取服务列表的时间间隔
      instance:  #eureka客户端实例配置
        lease-renewal-interval-in-seconds: 10 # 服务续约的时间间隔,默认为30秒
        lease-expiration-duration-in-seconds: 90 # 服务失效时间,默认为90秒
        hostname: localhost #服务主机名称
        prefer-ip-address: false #是否优先使用ip来作为主机名,默认为false
        
      server: #eureka服务端配置
      	enable-self-preservation: false  #关闭eureka服务端的自我保护机制
    

三、Spring Cloud Zuul:API网关服务

API网关为微服务架构中的服务提供了统一的访问入口,客户端通过API网关访问相关服务。API网关的定义类似于设计模式中的门面模式,他相当于整个微服务架构中的门面,所有客户端的访问都通过它来进行路由及过滤。它实现了请求路由、负载均衡、校验过滤、服务容错、服务聚合等功能。

创建网关服务

  • 导入依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId><!--一个网关也是一个微服务的客户端-->
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    
  • 在启动类上添加@EnableZuulProxy注解来启用Zuul的API网关功能

    @SpringBootApplication
    @EnableZuulProxy
    @EnableDiscoveryClient
    public class NetflixZuulApplication {
        public static void main(String[] args) {
            SpringApplication.run(NetflixZuulApplication.class, args);
        }
    }
    
  • 查看eureka-server,出现注册的网关的服务名,可以直接通过访问网关 主机名:port/微服务名 访问注册中心中注册的其他微服务

    http://${zullhostname}:${zullport}/${otherservicename}/${path}
    

四、Spring Cloud Ribbon:微服务通信

Spring Cloud Ribbon是Spring Cloud Netflix子项目的核心组件之一,主要给服务间调用及API网关转发提供负载均衡的功能。

1. Ribbon简介

在Spring Cloud中,微服务之间消息调用有两种方式:RestTemplate+@LoadBalance显示调用,另一种方式可以通过OpenFeign隐藏服务间通信细节。在使用RestTemplate来调用其他服务时,Ribbon可以很方便的实现负载均衡功能,负载均衡可以增加系统的可用性。

在这里插入图片描述

2. RestTemplate的使用

RestTemplate是一个HTTP客户端,是SpringCloud访问Restful API的请求对象:使用它我们可以方便的调用HTTP接口,支持GET、POST、DELETE等方法。

1) GET请求

  • getForObject():返回对象为响应体中数据转化成的对象。
  • getForEntity():返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等。
	<T> T getForObject(String var1, Class<T> var2, Object... var3) throws RestClientException;

    <T> T getForObject(String var1, Class<T> var2, Map<String, ?> var3) throws RestClientException;

    <T> T getForObject(URI var1, Class<T> var2) throws RestClientException;

    <T> ResponseEntity<T> getForEntity(String var1, Class<T> var2, Object... var3) throws RestClientException;

    <T> ResponseEntity<T> getForEntity(String var1, Class<T> var2, Map<String, ?> var3) throws RestClientException;

    <T> ResponseEntity<T> getForEntity(URI var1, Class<T> var2) throws RestClientException;

2)POST请求

<T> T postForObject(String var1, @Nullable Object var2, Class<T> var3, Object... var4) throws RestClientException;

    <T> T postForObject(String var1, @Nullable Object var2, Class<T> var3, Map<String, ?> var4) throws RestClientException;

    <T> T postForObject(URI var1, @Nullable Object var2, Class<T> var3) throws RestClientException;

    <T> ResponseEntity<T> postForEntity(String var1, @Nullable Object var2, Class<T> var3, Object... var4) throws RestClientException;

    <T> ResponseEntity<T> postForEntity(String var1, @Nullable Object var2, Class<T> var3, Map<String, ?> var4) throws RestClientException;

    <T> ResponseEntity<T> postForEntity(URI var1, @Nullable Object var2, Class<T> var3) throws RestClientException;

3. 创建服务提供者

创建一个服务提供者服务,用于给Ribbon提供服务调用

  • 添加依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    
  • 配置文件

    server:
      port: 8888  #运行端口号
    spring:
      application:
        name: service-provider  #服务名称
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8761/eureka/ #配置中心地址
        register-with-eureka: true  #注册到Eureka的注册中心
        fetch-registry: true  #获取注册实例列表
    
  • 添加controller用于提供调用接口

    @RestController
    @RequestMapping("/provider")
    public class ServiceProviderController {
    
        @PostMapping("/create")
        public ResultDto<?> cereate(@RequestBody User user){
            System.out.println(user);
            if(user.getName()!=null){
                return ResultDto.builder().msg("success").build();
            }
            return ResultDto.builder().msg("failed").build();
        }
    
        @GetMapping("/get")
        public ResultDto<?> getUser(@RequestParam("id") Integer id){
            System.out.println("id="+id);
            if(id != null){
                return ResultDto.builder().msg("success").data(new User("kk",24)).build();
            }
            return ResultDto.builder().msg("failed").build();
        }
    
        @GetMapping("/getByCondition")
        public ResultDto<?> getByCondition(@RequestParam Map<String, Object> params){
            System.out.println("params="+params);
            ArrayList<User> list = new ArrayList<>();
            if(params != null){
                for(int i=0;i<5;i++){
                    list.add(new User("ll"+i,i));
                }
            }
            return ResultDto.builder().msg("success").data(list).build();
        }
    
    }
    

4. 创建Ribbon-service服务

创建ribbon-service模块调用service-provider模块演示负载均衡的服务调用

  • 在pom.xml中添加依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    </dependency>
    
  • 配置application.yml

    server:
      port: 8888  #运行端口号
    spring:
      application:
        name: ribbon-service  #服务名称
    eureka:
      client: # eureka客户端配置
        service-url:
          defaultZone: http://lkj:123@localhost:8761/eureka/ #配置中心地址
        register-with-eureka: true  #时候将自己注册到Eureka的注册中心
        fetch-registry: true  #是否获取eureka服务端上注册的服务列表
    
  • 创建RestTemplate

    // 方式一:通过使用@LoadBalance注解实现RestTemplate负载均衡,再注入到controller中
    
    @Configuration
    public class RibbonConfig {
        @Bean
        @LoadBalanced
        public RestTemplate restTemplate(){  // 注入bean
            return new RestTemplate();
        }
    }
    
    // 方式二:在controller中通过手工创建RestTemplate
    
    @RestController
    public class RestTemplatController { 
        @Autowired
        private LoadBalancerClient loadBalancerClient;
    
        @GetMapping("/get")
        public ResultDto<User> getUser(@RequestParam Integer id){
            RestTemplate template = new RestTemplate();
            ServiceInstance instace = loadBalancerClient.choose("service-provider");
            String host = instace.getHost();
            int port = instace.getPort();
            return template.getForObject("http://"+host+":"+port+"/service-provider/get?id="+id,ResultDto.class);
        }
    }
    
  • 创建客户端访问的Controller

    @RestController
    public class HelloController {  //对应的controller
        
        @Autowired
        private RestTemplate template;
    
        @PostMapping("/create")
        public ResultDto<?> create(@RequestBody User u){
            return template.postForObject("http://service-provider/create",u,ResultDto.class);
        }
    
        @GetMapping("/get")
        public ResultDto<User> getUser(@RequestParam Integer id){
            return template.getForObject("http://service-provider/get?id="+id,ResultDto.class);
        }
    
        @GetMapping("/getByCondition")
        public ResultDto<List<User>> getByCondition(@RequestParam Map<String, Object>params){
            System.out.println(params);
            return template.getForObject("http://service-provider/create",ResultDto.class,params);
        }
    }
    

5. Ribbon的负载均衡策略

所谓的负载均衡策略,就是当A服务调用B服务时,此时B服务有多个实例,这是A服务一何种方式来选择调用B的实例,ribbon可以选择以下几种负载均衡策略。

  • com.netflix.loadbalancer.RandomRule:从提供服务的实例中一随机的方式进行轮询;

  • com.netflix.loadbalancer.RoundRobinRule:以线性轮询的方式,维护一个计数器,从提供服务的实例中按顺序选取,第一次选第一个,第二次选第二个,到最后一个再从头来过;

  • com.netflix.loadbalancer.RetryRule: 在RoundRobinRule的基础上添加重试机制,即在指定的时间内,反复使用线性轮询策略来选择可用实例;

  • com.netflix.loadbalancer.WeightedResponseTimeRule:对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择;

  • com.netflix.loadbalancer.BestAvailableRule:选择并发较小的实例;

  • com.netflix.loadbalancer.AvaliabiliFilteringRule:先过滤掉故障实例,再优先选择并发较小的实例;

  • com.netflix.loadbalancer.ZoneAwareLoadBalancer:采用双重过滤,同时过滤不是同一区域的实例和故障实例,选择并发较小的实例。

    #针对具体服务的局部配置
    service-provider:   #服务提供者的名字
      ribbon:
      	NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
    
    //全局配置,自定义配置类,注入IRule
    @Bean
    public IRule ribbonRule() {
        return new RoundRobinRule();
    }
    

五、Spring Cloud OpenFeign:基于Ribbon和Hystrix的声明式服务调用

Spring Cloud OpenFiegn 是声明式的夫服务调用工具,它整合了Ribbon和Hystrix,拥有负载均衡和服务容错功能。

1. Feign 简介

Feign是生命是的服务调用工具,我们只需创建一个接口并用注解的方式来配置它,就可以实现对某个服务接口的调用。简化了直接使用RestTemplate来调用服务接口的开发量。

Feign具备可插拔的注解支持,同时支持Feign注解、JAX-RS注解及SpringMVC注解。当使用Feign时,Spring Cloud 集成了Ribbon以提供负载均衡的服务调用,及基于Hystrix的服务容错保护功能。

2. 创建feign-service服务

创建一个具有feign接口调用功能的服务。

  • 导入相关依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <!--  OpenFeign声明式服务调用需要的依赖  -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    
  • 修改配置文件application.yml

    server:
      port: 8888  #运行端口号
    spring:
      application:
        name: feign-service  #服务名称
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8761/eureka/ #配置中心地址
        register-with-eureka: true  #注册到Eureka的注册中心
        fetch-registry: true  #获取注册实例列表
    
  • 在启动类上添加@EnableFeignClient 注解来启用Feign的客户端功能

    @SpringBootApplication
    @EnableEurekaClient
    @EnableFeignClients // 使用OpenFeign声明式调用时需要开启
    public class EurekaClientApplication {
        public static void main(String[] args) {
            SpringApplication.run(EurekaClientApplication.class, args);
        }
    }
    
  • 新建一个client接口,并完成对service-provider服务的接口的绑定

    通过@FeignClient 注解声明一个Feign客户端接口,其中的value为被调用服务方的名字,并与被调用的服务进行绑定。接口中声明的方法分别对应被调用的服务中相应的访问路径。

    @FeignClient("service-provider")
    public interface FeignExecutor {
        @PostMapping("/provider/create")
        public ResultDto<?> create(@RequestBody User u);
    
        @GetMapping("/provider/get")
        public ResultDto<User> getUser(@RequestParam Integer id);
    
        @GetMapping("/provider/getByCondition")
        public ResultDto<List<User>> getByCondition(@RequestParam Map<String, Object> params);
    }
    
  • 创建一个FeignController,注入FeignExecutor并实现服务调用

    @RestController
    @RequestMapping("/f")
    public class FeignController {
    
        @Autowired
        private FeignExecutor feign;
    
        @PostMapping("/create")
        public ResultDto<?> create(@RequestBody User u){
            return feign.create(u);  //调用时直接调用方法即可
        }
        @GetMapping("/get")
        public ResultDto<User> getUser(@RequestParam Integer id){
            return feign.getUser(id);
        }
        @GetMapping("/getByCondition")
        public ResultDto<List<User>> getByCondition(@RequestParam Map<String, Object> params){
            return feign.getByCondition(params);
        }
    }
    

3. OpenFeign 负载均衡配置

  • 全局配置(与Ribbon一样,OpenFeign底层还是用Ribbon实现的)

    //全局配置,自定义配置类,注入IRule
    @Bean
    public IRule ribbonRule() {
        return new RoundRobinRule();
    }
    
  • 局部配置

    #针对具体服务的局部配置
    service-provider:   #服务提供者的名字
      ribbon:
      	NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
    

4. OpenFeign 通信日志

  • 基于SpringBoot的log back输出,默认debug级别

    # default为全局配置 feign.client.config.服务ID.loggerLevel=HEADERS
    feign:
      client:
        config:
          default:
            loggerLevel: HEADERS
    
  • 日志格式

    • NONE:不输出任何日志
    • BASIC:包含URL、请求方法、状态码执行时间
    • HEADERS:在BASIC基础上,包含请求头、响应头
    • FULL:完整信息
    logging:
      level: 
        ROOT: info
          com.lkj.eurekaclient.*: debug
    feign:
      client:
        config:
          default:
            loggerLevel: HEADERS
    
    

六、Spring Cloud Hystrix:服务容错降级

1. 服务降级

开发高并发系统的三把利器:缓存、降级和限流,用来保护系统。

为什么需要降级:当访问量剧增、服务出现问题(如响应实践慢或不响应)或非核心服务影响到核心流程性能时,仍然需要保证服务还是可用的。

降级的最终目的:保证核心服务可用。

  • 降级按照是否自动化可分为:自动开关降级和人工开关降级,按照功能可分为:读服务降级、写服务降级,按照处于的系统层次可分为:多级降级
    • 页面降级:在大促或者某些特殊情况下,某些页面占用了一些稀缺服务器资源,在紧急情况下可以对其整个降级,以达到丢卒保帅;
    • **页面片段降级:**比如渲染商品详情页时需要调用一些不太重要的服务:相关分类、热销榜等,而这些服务在异常情况下直接不获取,此时需要对其进行降级;
    • 超时降级:当访问的 数据库/http服务/远程调用 响应慢或者长时间响应慢,且该服务不是核心服务的话可以在超时后自动降级;比如商品详情页上有推荐内容/评价,但是推荐内容/评价暂时不展示,对用户购物流程不会产生很大的影响;对于这种服务是可以超时降级的。定义一个服务响应最大时间,如果超时了则自动降级。
    • 读降级:比如多级缓存模式,如果后端服务有问题,可以降级为只读缓存,这中方式适用于对读一致性要求不高的场景;
    • 写降级:比如秒杀抢购,我们可以只进行cache的更新,然后异步同步扣减库存到DB,保证最终一致性即可,此时可以将DB降级为Cache。
    • 自动开关降级:自动降级是根据系统负载、资源使用情况、SLA等指标进行降级。
    • 故障降级:比如要调用的远程服务挂掉了,(网络故障、DNS故障、http服务返回错误的状态码、rpc服务抛出异常),则可以直接降级。降级后的处理方案有:默认值(比如库存服务挂了,返回默认现货兜底数据(比如广告挂了,返回提前准备好的一些静态页面)、缓存(之前暂存的一些缓存数据)
    • 人工开关降级:在大促期间通过监控系统发现线上的一些服务存在问题,这个时候需要暂时将这些服务摘掉;还有有时候通过任务系统调用一些服务,但是服务依赖的数据库可能存在:网卡被打满了、挂掉了或者很多慢查询,此时需要暂停下任务系统让服务方进行处理。此时就可以使用开关来完成降级。

2. Hystrix 简介

Hystrix 实现了断路器模式,当某个服务发生故障时,通过断路器的监控,给调用发返回一个错误响应,而不是长时间等待,这样就不会使得调用发由于长时间得不到响应而占用线程,从而防止故障的蔓延。Hystrix具备服务降级、服务熔断、线程隔离、请求缓存、合并请求及服务监控等强大功能。

Spring Cloud Hystrix 是Spring Cloud Netflix 子项目的核心组件之一,具有服务容错及线程隔离等一系列服务保护功能,用于为微服务提供熔断机制预防雪崩,保护整体微服务架构的健康。

3. Hystrix 服务降级的实现

基于RestTemplate实现服务降级

  • 创建hystrix-service服务,添加依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
    
  • 配置application.yml,配置端口、注册中心地址

    server:
      port: 10000
    
    spring:
      application:
        name: hystrix-service
    eureka:
      client:
        service-url:
          defaultZone: http://lkj:123@localhost:8761/eureka/
    
  • 在启动类上添加注解开启Hystrix的断路器功能

    @SpringBootApplication
    @EnableDiscoveryClient
    @EnableHystrix  // 与@EnableCircuitBreaker同样的作用,用于开启Hystrix的断路器的功能。
    public class HystrixServiceApplication {
        public static void main(String[] args) {
            SpringApplication.run(HystrixServiceApplication.class,args);
        }
    }
    
  • 在使用RestTemplate的类中,添加调用方法与服务降级方法,在需要进行服务降级的方法上需要添加@HystrixCommand注解,fallbackMthod关键字指明降级时使用的方法。

    @PostMapping("/create")
        @HystrixCommand(fallbackMethod = "createFallback") // 指明需要降级时要使用的方法
        public ResultDto<?> create(@RequestBody User u){
            return template.postForObject("http://service-provider/provider/create",u,ResultDto.class);
        }
    	// 声明降级所使用的方法
        public ResultDto<?> createFallback(@RequestBody User u){
            return ResultDto.builder().msg("方法执行失败").data("进入降级方法"+"createFallback").build();
        }
    

OpenFeign 中使用Hystrix

Feign中的服务降级使用只需要为Feign客户端定义的接口添加一个服务降级处理的实现类即可。

  • OpenFeing中内置了Hystrix,通过fiegn.hystrix.enable配置开启即可
  • 在@FeignClient增加fallback属性指定Fallback的实现类
  • Fallback类要实现相同的接口,重写服务降级业务逻辑

配置OpenFeign的依赖(已经内置了Hystrix,不用再依赖Hystrix)

<!--  OpenFeign声明式服务调用需要的依赖,以及OpenFeign使用Hystrix降级时也需要加入此依赖  -->
<!--  OpenFeign中内置了Hystrix依赖  -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

yml配置文件开启Hystrix配置

feign:
  hystrix:
  	enable: true

修改使用FeignClient注解的接口,设置服务降级处理类

@FeignClient(value = "service-provider",fallback = FeingExecutorFallback.class)
public interface FeignExecutor {
}

添加FeignClient的实现接口,当调用的方法符合降级条件时会自动执行实现的方法进行降级处理

public class FeingExecutorFallback implements FeignExecutor {
    @Override
    public ResultDto<?> create(User u) {
        return new ResultDto<>(500,"服务调用失败,执行"+this.getClass().getName()+"的降级方法","failed");
    }

    @Override
    public ResultDto<User> getUser(Integer id) {
        return new ResultDto<>(500,"服务调用失败,执行"+this.getClass().getName()+"的降级方法",new User("default",0));
    }

    @Override
    public ResultDto<List<User>> getByCondition(Map<String, Object> params) {
        return new ResultDto<>(500,"服务调用失败,执行"+this.getClass().getName()+"的降级方法",null);
    }
}

4. Hystrix超时设置

  • Ribbon的连接超时时间,默认1秒

    feign:
      client:
        config:
          default: # 全局设置,也可以写[微服务id]
            connetcTimeout: 1000  #Ribbon的连接超时时间,默认一秒
    
  • Ribbon的读取超时时间,默认一秒

    feign:
      client:
        config:
          default: # 全局设置,也可以写[微服务id]
            readTimeout: 2000   #Ribbon的读取超时时间,默认一秒
    
  • 熔断的超时时间>=connectionTimeout+readTimeout

    feign:
      client:
        config:
          default: # 全局设置
            connetcTimeout: 1000  #Ribbon的连接超时时间,默认一秒
            readTimeout: 2000   #Ribbon的读取超时时间,默认一秒
      hystrix:  # 在使用FeignClient时,启用 Feign 内置的 hystrix 进行降级
        enabled: true
    hystrix:
      commamd:
        default:
          execution:
            isolation:
              thread:
                timeoutInMilliseconds: 3000
    

5. Hystrix Dashboard 监控

Hystrix Dashboard 是Spring Cloud中查看Hystrix实例执行情况的一种仪表盘组件,支持查看单个实例和查看集群实例;

Hystrix Dashboard 可以实时监控Hystrix Command 方法的执行情况。Hystrix Dashboard可以有效的反映出每个Hystrix实例的运行情况,帮助我们快速发现系统中的问题,从而采取对应措施

6. Hystrix Dashboard 使用步骤

  • Hystrix Client(服务调用者)配置依赖:hystrix-metrics-event-stream

    <dependency>
        <groupId>com.netflix.hystrix</groupId>
        <artifactId>hystrix-metrics-event-stream</artifactId>
    </dependency>
    
  • Hystrix Client注册HystrixMetricsStreamServlet:对外暴露当前客户端产生的Hystrix调用信息(数据的输出者)

    @Bean
    public ServletRegistrationBean registrationBean(){
        HystrixMetricsStreamServlet serv = new HystrixMetricsStreamServlet();
        ServletRegistrationBean regist = new ServletRegistrationBean(serv);
        regist.addUrlMappings("/hystrix.stream");
        regist.setName("HystrixMetricsStreamServlet");
        regist.setLoadOnStartup(1);
        return regist;
    }
    
  • 监控微服务配置依赖:spring-cloud-starter-netflix-hystrix-dashboard

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
    </dependency>
    
  • 监控微服务添加@EnableHystrixDashboard开启仪表盘

    @SpringBootApplication
    @EnableDiscoveryClient  //@EnableEurekaClient
    @EnableHystrixDashboard
    public class HystrixServiceApplication {
        public static void main(String[] args) {
            SpringApplication.run(HystrixServiceApplication.class,args);
        }
    }
    
  • 监控仪表盘默认访问地址:ip:port/hystrix,弹出配置界面。将被监控服务的地址:http://ip:port/hystrix.stream 填写到被检测的位置即可。

7. Hystrix熔断设置

  • 最近调用次数,默认20

    hystrix:
      commamd:
        default:    #全局配置
          circuitBreaker:
            requestVolumeThreshold: 20   #最近调用次数
    
  • 请求错误率,默认50

    hystrix:
      commamd:
        default:    #全局配置
          circuitBreaker:
            errorThresholdPercentage: 50  #请求错误率
    
  • 熔断持续时间

    hystrix:
      commamd:
        default:    #全局配置
          circuitBreaker:
            sleepWindowInMilliseconds: 5000   #熔断持续时间
    
  • 滑动窗口期,默认10000

    hystrix:
      commamd:
        default:    #全局配置
          metrics:
            rollingStats:
              timeMilliseconds:1000  #滑动窗口期
    
  • 配置文件实例如下

    hystrix:
      commamd:
        default:    #全局配置
          execution:
            isolation:
              thread:
                timeoutInMilliseconds: 3000
          circuitBreaker:
            requestVolumeThreshold: 20   #最近调用次数
            sleepWindowInMilliseconds: 5000   #熔断持续时间
            errorThresholdPercentage: 50  #请求错误率
          metrics:
            rollingStats:
              timeMilliseconds:1000  #滑动窗口期
    

Hystrix断路机制

Hystrix 有三种状态

  • open:开启状态

  • closed:关闭状态

  • half-open:半开状态

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值