SpringCloud学习2:consul服务注册与发现、分布式配置、LoadBalance负载均衡

一:consul介绍:

Consul 是一套开源的分布式服务发现和配置管理系统,由 HashiCorp 公司用 Go 语言开发。

提供了微服务系统中的服务治理、配置中心、控制总线等功能。这些功能中的每一个都可以根据需要单独使用,也可以一起使用以构建全方位的服务网格,总之Consul提供了一种完整的服务网格解决方案。它具有很多优点。包括: 基于 raft 协议,比较简洁; 支持健康检查, 同时支持 HTTP 和 DNS 协议 支持跨数据中心的 WAN 集群 提供图形界面 跨平台,支持 Linux、Mac、Windows

简言之,Consul的作用是服务发现和配置管理(服务注册中心)

consul作用:

  • 服务发现:提供HTTP和DNS两种发现方式。
  • 健康检测:支持多种方式,HTTP、TCP、Docker、Shell脚本定制化监控
  • kv存储:Key、Value的存储方式
  • 多数据中心:Consul支持多数据中心
  • 可视化Web界面

服务注册与发现:

  • pom:pom文件中引入consul服务注册与发现的依赖

    <!--SpringCloud consul discovery -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-consul-discovery</artifactId>
            </dependency>
    
  • YML:写好关于consul的配置

    server:
      port: 8001 (支付服务提供者8001)
      
     spring:
      application:
        name: cloud-payment-service
      cloud:
        consul:
          host: localhost
          port: 8500
          discovery:
            service-name: ${spring.application.name} 
            //8001微服务将要以“cloud-payment-service”这个名称入驻8500consul注册中心
            
            
            
    server:
      port: 80 (服务消费者)
    
    spring:
      application:
        name: cloud-consumer-order
      ####Spring Cloud Consul for Service Discovery
      cloud:
        consul:
          host: localhost
          port: 8500
          discovery:
            prefer-ip-address: true #优先使用服务ip进行注册
            service-name: ${spring.application.name}    
            //80微服务将会以“cloud-consumer-order”微服务名称入住8500consul注册中心
    
    
  • 在启动类上加上@EnableDiscoveryClient

  • 在RestTemplateConfig配置类中,创建RestTemplate组件的方法上,加上负载均衡支持的注解:@LoadBalanced

  • 之后,在要引用其它微服务时:按照服务名称调用

    "80和8001就同时入驻进了consul即服务注册"
    private final String  PaymentSrv_URL ="http://cloud-payment-service";//服务注册中心上的微服务名称
     解决了硬编码的问题,就无需考虑"所要调用的服务"地址改变从而无法访问的问题,只需要使用"服务名称"即可
    

consul分布式配置:

微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能运行,所以一套集中式的、动态的配置管理设施是必不可少的。做到一处修改,处处生效。

eg:当下我们每一个微服务自己带着一个application.yml,上百个配置文件的管理…/(ㄒoㄒ)/~~

服务配置:

通用全局配置信息,直接注册进Consul服务器,从Consul获取:

  • 在pom中加入依赖;

    
    <!--SpringCloud consul config-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-consul-config</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bootstrap</artifactId>
    </dependency>
    
  • bootstrap.yaml文件:

    applicaiton.yml是"用户级"的资源配置项
    
    bootstrap.yml是"系统级的""优先级更加高"
    
    Spring Cloud会创建一个“Bootstrap Context”,作为Spring应用的`Application Context`的父上下文。初始化的时候,`Bootstrap Context`负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的`Environment`。
    
     
    `Bootstrap`属性有高优先级,默认情况下,它们'不会被本地配置覆盖'。 `Bootstrap context`和`Application Context`有着不同的约定,所以新增了一个`bootstrap.yml`文件,保证`Bootstrap Context`和`Application Context`配置的分离。
    
     application.yml文件改为bootstrap.yml,这是很关键的或者"两者共存"
    
    因为bootstrap.yml是比application.yml先加载的。bootstrap.yml优先级高于application.ym
    

    eg:

    spring:
      application:
        name: cloud-payment-service
        ####Spring Cloud Consul for Service Discovery
      cloud:
        consul:
          host: localhost
          port: 8500
          discovery:
            service-name: ${spring.application.name} #8001将要以“cloud-payment-service”这个名字入驻consul
          config:
            profile-separator: '-' # default value is ",",we update '-' 即使用“-”作为文件名分隔符号
            format: YAML
    
      # config/cloud-payment-service/data
      #       /cloud-payment-service-dev/data
      #       /cloud-payment-service-prod/data
    
    • consul服务器K/V填写:

    ​ eg: config/cloud/payment/service/data 创建文件夹: config/ :“文件夹以 / 结尾”

    ​ kv信息的配置格式为config/服务名-运行环境/data,最终的配置信息在data中配置,可以使用yaml的格式

    ​ 之后要读取配置信息,也是和读取普通配置文件中的信息一样,使用@Value注解获取。

及时动态刷新

在consul中配置信息修改之后,立即访问时,不会生效,会发现还是原来的内容,要等待一会才会刷新。(即服务器上已经变动,但本地却未立即生效)

步骤:

  • 在主启动类上加上主键:@RefreshScope

  • 在yml文件中修改watch的wait-time(不建议)

    spring:
      application:
        name: cloud-payment-service
        ####Spring Cloud Consul for Service Discovery
      cloud:
        consul:
          host: localhost
          port: 8500
          discovery:
            service-name: ${spring.application.name}
          config:
            profile-separator: '-' # default value is ",",we update '-'
            format: YAML
            watch:
              wait-time: 1
    
consul持久化
  • 背景:如果重启Consul,之前在consul的配置就会消失(建立的config、data、kv数据等)

  • 解决:Consul持久化配置并且注册未Windows服务,使Consul入驻windows后台服务(只要Windows一开机,后台就有一个consul进程),之后只要IDEA中任何一个服务启动成功之后,都可以入驻Consul

  • 启用consul持久化之后,关闭consul之后,上面的k-v等数据就不会消失,而是保存到指定的文件夹中。

二:负载均衡和服务调用

(1)LoadBalancer负载均衡

LoadBalance简介:
  1. LB负载均衡(LoadBalance)是什么:

    简单的说就是将用户的请求平摊到多个服务上面,从而达到系统的HA(高可用)

  2. spring-cloud-starter-loadbalancer组件是什么

    Spring Cloud LoadBalancer是由SpringCloud官方提供的一个开源的、简单易用的客户端负载均衡器,它包含在SpringCloud-commons中用它来替换了以前的Ribbon组件。相比较于Ribbon,SpringCloud LoadBalancer不仅能够支持RestTemplate,还支持WebClient(WeClient是Spring Web Flux中提供的功能,可以实现响应式异步请求)

  3. adbalancer本地负载均衡客户端 VS Nginx服务端负载均衡区别

    Nginx是服务器负载均衡,客户端所有请求都会交给nginx,然后由nginx实现转发请求,即负载均衡是由服务端实现的。

    loadbalancer本地负载均衡,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术。

LoadBalance服务调用负载均衡:

原理:

  1. LoadBalancer 在工作时分成两步:

    第一步,先选择ConsulServer从服务端查询并拉取服务列表,知道了它有多个服务(上图3个服务),这3个实现是完全一样的,

    默认轮询调用谁都可以正常执行。类似生活中求医挂号,某个科室今日出诊的全部医生,客户端你自己选一个。

    第二步,按照指定的负载均衡策略从server取到的服务注册列表中由客户端自己选择一个地址,所以LoadBalancer是一个客户端的负载均衡器。

    配置:

    1. 在客户端即消费者侧添加相关依赖:

      <!--loadbalancer-->
              <dependency>
                  <groupId>org.springframework.cloud</groupId>
                  <artifactId>spring-cloud-starter-loadbalancer</artifactId>
              </dependency>
      
    2. 加上@LoadBalanced注解赋予RestTemplate负载均衡的能力

    
    @Configuration
    public class RestTemplateConfig {
    
        @LoadBalanced //使用@LoadBalanced注解赋予RestTemplate负载均衡的能力,轮询算法就是通过它来实现!!!
        @Bean
        public RestTemplate restTemplate(){
    
            return new RestTemplate();
        }
    }
     
    
    1. 之后,消费者订单微服务:cloud-consumer-order 去调用 消费支付提供者微服务:cloud-provider-payment

      eg:

      //8080消费者订单微服务:
      @GetMapping(value = "consumer/pay/get/info")
          private String getInfoByConsul(){
              return restTemplate.getForObject(PaymentSrv_URL + "/pay/get/info",String.class);
          }
      
      
      //提供有8001和8002两个消费者支付模块
      
      //8001消费者支付提供模块
      @GetMapping("/pay/get/info") //调用地址
          public String getInfoByConsul(@Value("${atguigu.info}") String atguiguInfo){
              return "atguiguInfo"+ atguiguInfo +"\t" +"port" + port;
          }
      
      
      //8002消费者支付提供模块
      @GetMapping("/pay/get/info") //调用地址
          public String getInfoByConsul(@Value("${atguigu.info}") String atguiguInfo){
              return "atguiguInfo"+ atguiguInfo +"\t" +"port" + port;
          }
      
      
      在浏览器上访问:http://localhost/consumer/pay/get/info 之后(不指定端口号),会轮询访问微服务80018002,即一次访问8001,一次访问8002...
      

    ​ 默认是两种算法,除了轮询算法,还有一种是随机算法。

    ​ 一般推荐使用轮询算法

(2)OpenFeign服务接口调用(!!!)

OpenFeign介绍:
  • openfeign是一个声明式的Web服务客户端,只需要创建Rest接口并在该接口上添加注解@FeignClient即可

  • OpenFeign基本上就是当前服务之间调用的事实标准(原来传统是使用RestTemplate进行调用

  • 它也包含且支持Loadbalance

  • Feign是在消费端使用

     ```java
     
     由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每一个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以OpenFeign在此基础上做了进一步封装,帮助我们定义和实现依赖服务接口的定义。
     
     可以在使用OpenFeign式提供http客户端的负载均衡,也可以集成阿里巴巴的Sentinel来提供熔断降级等功能。通过OpenFeign只需要服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用
     
     ```
    
OpenFeign通用步骤:

两个(多个)微服务之间如何通过OpenFeign进行基础的调用:

微服务Api接口(用于写服务提供者有哪些对外暴露的服务清单)+接口上使用@FeignClient注解标签

  • 服务消费者服务提供者之间增加了服务接口(与服务提供者对应,服务提供者想要对外暴露哪些服务供其它服务调用,就以Feign接口的形式写道公共API模块),之后,服务消费者需要通过调用含有@FeignClient注解的Api服务接口,来间接调用服务提供者,而不是直接访问服务提供者。
  • 书写在公共API模块的服务接口的方法,和在服务提供者上面的方法,方法签名一致、注解路径一致,只是添加了@FeignClient注解来对外暴露,使消费者模块通过@FeignClient来找到服务提供者模块对应的服务

--------》即想要访问某个服务就要先访问它的OpenFeign接口

  1. 建立好客户端模块之后,在其pom文件下导入相关依赖:

     <!--openfeign-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
            </dependency>
    
  2. 写YML:

    server:
      port: 80
    
    spring:
      application:
        name: cloud-consumer-openfeign-order
      ####Spring Cloud Consul for Service Discovery
      cloud:
        consul:
          host: localhost
          port: 8500
          discovery:
            prefer-ip-address: true #优先使用服务ip进行注册
            service-name: ${spring.application.name}
    
  3. 主启动

    主启动类上加上consul的支持注册服务的注解和启用feign客户端的注解

    @SpringBootApplication
    @EnableDiscoveryClient //该注解用于向使用consul为注册中心时注册服务
    @EnableFeignClients//主启动类上配置**@EnableFeignClients**注解表示开启OpenFeign功能并激活
    public class MainOpenFeign80 {
        public static void main(String[] args) {
            SpringApplication.run(MainOpenFeign80.class,args);
        }
    }
    
  4. 因为服务之间都要通过Feign接口来实现调用,所以要把Feign接口定义到通用模块commons下面

    新建服务接口PayFeignApi,在接口上配置@FeignClient注解,并定义好抽象方法,抽象方法的定义要与对应的服务的controller类中的controller方法对应。相应的mapping注解也要对应好。

    eg:在cloud-common-api通用模块下定义Feign接口:
    
    @FeignClient(value ="cloud-payment-service")//(1)PayFeignApi就相当于"支付微服务"的Feign接口(服务提供者!)(标明是哪个微服务的Feign接口即哪个微服务向外暴露,eg这里就是8001支付服务的Feign接口)
    public interface PayFeignApi {
    
        /**
         * eg:(2)微服务提供者8001对外暴露3个方法,其它微服务eg:消费者80通过访问在这个OpenFeign接口中方法来实现访问8001中对应的服务
         */
    
        //消费者80直接通过这个地址访问服务提供者8001微服务的对应地址
    
        //新增一条支付相关流水记录
        @PostMapping(value = "/pay/add")
        public ResultData<PayDto>addPay(@RequestBody PayDto payDto);
    
        //按照主键记录查询支付流水信息
        @GetMapping(value = "pay/get/{id}")
        public ResultData getPayInfo(@PathVariable("id") Integer id);
    
        //openfeign天然支持负载均衡演示
       @GetMapping(value = "/pay/get/info")
        public String mylb();
    }
    
    eg:"之后消费者端80就可以在controller中通过调用Feign接口来实现访问8001支付服务提供服务"
    
  5. 在服务客户端的controller通过调用对应服务的Feign接口(PayFeignApi)来访问对应的服务

    
    /**
     * 微服务消费者 就不用使用orderController来调用OrderService来实现服务了,作为服务端消费者80直接就只通过  支付微服务提供的OpenFeign接口来调用 支付微服务8001即可
     *
     * 我们这里使用OpenFeign接口的方式,不再使用RestTemplate
     * 通过下面三个方法来演示
     */
    @RestController
    public class OrderController {
    
    
        @Resource
        private PayFeignApi payFeignApi;
    
        //新增
        @PostMapping(value = "/feign/pay/add")
        public ResultData addOrder(@RequestBody PayDto payDto){
            System.out.println("第一步:模拟本地addOrder新增订单成功(省略sql操作),第二步:再开启addPay支付微服务远程调用");
            ResultData<PayDto> payDtoResultData = payFeignApi.addPay(payDto);
            return payDtoResultData;
        }
        
        //之后如果用户“新增订单”即访问这个"/feign/pay/add"地址,会先去到OpenFeign接口中对应的方法,然后再去支付微服务中找到对应的"新增流水方法"
    
        //根据主键id进行查询
        @GetMapping(value = "/feign/pay/get/{id}")
        public ResultData getPayInfo(@PathVariable ("id") Integer id){
            System.out.println("-------支付微服务远程调用,按照id查询订单支付流水信息");
            ResultData payInfo = payFeignApi.getPayInfo(id);
            return payInfo;
        }
    
        //演示OpenFeign支持负载均衡
        @GetMapping(value = "/feign/pay/get/info")
        public String mylb(){
            return payFeignApi.mylb();
        }
    
    }
    
OpenFeign高级特性:
超时控制:

SpringCloud微服务架构中,大部分公司都是利用OpenFeign进行服务间的调用,如果服务要进行比较繁杂的业务计算,那后台很有可能会出现Read Timeout这个异常,因此定制化配置超时时间就非常有必要了。

微服务提供者可能因为网络阻塞、网络抖动、或者被其它服务调用时等原因造成超时的问题。openFeign客户端默认等待六十秒,如果服务处理器超过规定时间会导致Feign客户端返回超时报错。

关于超时控制的配置项:

  • connectTimeOut:连接超时时间
  • readTimeOut:请求处理超时时间

OpenFeign客户端超时控制配置:

  1. 全局配置:

    • 在YML中进行全局配置

      server:
        port: 80
       
      spring:
        application:
          name: cloud-consumer-openfeign-order
        ####Spring Cloud Consul for Service Discovery
        cloud:
          consul:
            host: localhost
            port: 8500
            discovery:
              prefer-ip-address: true #优先使用服务ip进行注册
              service-name: ${spring.application.name}
          openfeign:
            client:
              config:
                default:  //设置全局超时时间
                  #连接超时时间
                  connectTimeout: 3000 //3s
                  #读取超时时间
                  readTimeout: 3000
      
  2. 指定配置:

    • 在Feign客户端的YML中进行全局配置

      server:
        port: 80
       
      spring:
        application:
          name: cloud-consumer-openfeign-order
        ####Spring Cloud Consul for Service Discovery
        cloud:
          consul:
            host: localhost
            port: 8500
            discovery:
              prefer-ip-address: true #优先使用服务ip进行注册
              service-name: ${spring.application.name}
          openfeign:
            client:
              config:
                default:
                cloud-provider-payment: //指定服务名:为这个服务单独配置超时时间,单个配置的超时时间会覆盖全局配置!!!
                  #连接超时时间
                  connectTimeout: 5000
                  #读取超时时间
                  readTimeout: 5000
      
重试机制:

重试:即一次请求失败之后,再次发起请求

默认重试机制是关闭的:即OpenFeign一次调用要么成功,要么失败

开启重试机制需要在 配置类FeignConfig中配置


@Configuration
public class FeignConfig {

    @Bean
    public Retryer myRetryer()
    {
        //return Retryer.NEVER_RETRY; //Feign默认配置是不走重试策略的
        //最大请求次数为3(1:开始的那次+2),初始间隔时间为100ms,重试间最大间隔时间为1s
        return new Retryer.Default(100,1,3);
        
        //eg:如果配置的超时时间是4s,那么重试2次就要等待12s
    }
}

性能优化HttpClient5

OpenFeign中的Http client 如果不做特殊配置,OpenFeign默认使用JDK自带的HttpURLConnection发送Http请求,由于默认的HttpURLConnection没有连接池、性能和效率比较低。

–>推荐使用阿帕奇的HttpClient5

*配置步骤:

  • 在Feign客户端pom文件中添加依赖:

    <!-- httpclient5-->
    <dependency>
        <groupId>org.apache.httpcomponents.client5</groupId>
        <artifactId>httpclient5</artifactId>
        <version>5.3</version>
    </dependency>
    <!-- feign-hc5-->
    <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-hc5</artifactId>
        <version>13.1</version>
    </dependency>
    
  • 在yaml文件:开启httpclient5的配置

    server:
      port: 80
    
    spring:
      application:
        name: cloud-consumer-openfeign-order
      ####Spring Cloud Consul for Service Discovery
      cloud:
        consul:
          host: localhost
          port: 8500
          discovery:
            prefer-ip-address: true #优先使用服务ip进行注册
            service-name: ${spring.application.name}
        openfeign:
          client:
            config:
              default:
                #连接超时时间
                connectTimeout: 3000 #设置设置全局超时时间为3s
                #读取超时时间
                readTimeout: 3000
              cloud-payment-service: # //指定服务名:为这个服务单独配置超时时间,单个配置的超时时间会覆盖全局配置!!!
                #连接超时时间
                connect-timeout: 40000
                #读取超时时间
                read-timeout: 40000
          httpclient:
            hc5:
              enabled: true
    
请求回应压缩:

Spring Cloud OpenFeign支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。

通过在YML文件中进行配置,可以开启请求与响应的压缩功能,还可以对压缩做一些更细致化的设置,比如配置内容指定压缩的请求数据类型并设置了请求压缩的大小下限,只有超过这个大小的请求才会进行压缩。

server:
  port: 80

spring:
  application:
    name: cloud-consumer-openfeign-order
  ####Spring Cloud Consul for Service Discovery
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        prefer-ip-address: true #优先使用服务ip进行注册
        service-name: ${spring.application.name}
    openfeign:
      client:
        config:
          default:
            #连接超时时间
            connectTimeout: 3000 #设置设置全局超时时间为3s
            #读取超时时间
            readTimeout: 3000
          cloud-payment-service: # //指定服务名:为这个服务单独配置超时时间,单个配置的超时时间会覆盖全局配置!!!
            #连接超时时间
            connect-timeout: 40000
            #读取超时时间
            read-timeout: 40000
      httpclient:
        hc5:
          enabled: true
      compression:
        request:
          enabled: true  #请求开启压缩功能
          min-request-size: 2048 #最小触发压缩的大小
          mime-types: text/xml,application/xml,application/json #触发压缩数据类型
        response:    #响应也开启压缩功能
          enabled: true
OpenFeign日志打印功能:

Feign提供了日志打印功能,可以通过配置来调整日志级别,从而了解feign中http请求的细节,也就是对Feign接口的调用情况进行监控和输出。

日志级别

NONE:默认的,不显示任何日志;

BASIC:仅记录请求方法、URL、响应状态码及执行时间;

HEADERS:除了 BASIC 中定义的信息之外,还有请求和响应的头信息;

FULL:除了 HEADERS 中定义的信息之外,还有请求和响应的正文及元数据。

配置方式:

  • 在FeignConfig中,配置:

    
    @Configuration
    public class FeignConfig {
    
        @Bean
        public Retryer myRetryer() {
            //return Retryer.NEVER_RETRY; //Feign默认配置是不走重试策略的
    
            //最大请求次数为3(1+2),初始间隔时间为100ms,重试间最大间隔时间为1s
            return new Retryer.Default(100,1,3);
        }
    
        @Bean
        Logger.Level feignLoggerLevel() {
            return Logger.Level.FULL; //设置日志级别:FULL,来代替默认的NONE:不打印日志信息
        }
    }
    
    
  • 在yml文件配置:对哪个Feign接口进行日志检查

    配置公式是:logging.level + 含有@FeignClient注解的完整带包名的接口名+debug,例如:

    server:
      port: 80
    
    spring:
      application:
        name: cloud-consumer-openfeign-order
      ####Spring Cloud Consul for Service Discovery
      cloud:
        consul:
          host: localhost
          port: 8500
          discovery:
            prefer-ip-address: true #优先使用服务ip进行注册
            service-name: ${spring.application.name}  //80服务以cloud-consumer-openfeign-order名称入驻进consul注册中心
        openfeign:  //设置连接和读取的超时时间
          client:
            config:
              default:
                #连接超时时间
                connectTimeout: 3000 #设置设置全局超时时间为3s
                #读取超时时间
                readTimeout: 3000
              cloud-payment-service: # 指定服务名:为这个服务单独配置超时时间,"单个配置的超时时间会覆盖全局配置!!!"
                #连接超时时间
                connect-timeout: 2000
                #读取超时时间
                read-timeout: 2000
          httpclient://开启阿帕奇httpclient5对原生的httpclient的替换
            hc5:
              enabled: true
          compression://开启请求和响应的压缩功能
            request:
              enabled: true  #请求开启压缩功能
              min-request-size: 2048 #最小触发压缩的大小
              mime-types: text/xml,application/xml,application/json #触发压缩数据类型
            response:    #响应也开启压缩功能
              enabled: true
    
    
    #feign日志以什么级别监控哪个接口://即对哪个Feign接口进行日志检查
    logging:
      level:
        com:
          atguigu:
            cloud:
              apis:
                PayFeignApi: debug  #之后比如我们设置了重试机制,会将每次的具体访问信息打印出来
    
Logo

更多推荐