二、Spring Cloud Consul 实战


本文是 SpringCloud 实战教程第二篇, Spring Cloud 基于 Hoxton.SR8 版本,Spring Boot 基于2.3.5.RELEASE。使用Consul、OpenFeign组件。

一、组件选型

模块 组件 版本
服务注册与发现 Spring Cloud Consul 1.8.5
声明式服务调用(含客户端负载均衡) OpenFeign(Ribbon)
服务网关
负载均衡
熔断限流 Hystrix
配置中心 Spring Cloud Consul 1.8.5

二、服务注册和发现

1、安装并运行 Consul

获取 Docker 镜像。当前最新版本1.8.5。

$ docker pull consul:1.8.5

通过列出匹配的Docker镜像来检查映像是否已下载 consul

$ docker images -f 'reference=consul'
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
consul              1.8.5               4f7b214361a7        3 weeks ago         122MB

配置并运行 Consul 服务器:

docker run -d \
  --name ConsulDiscovery \
  -p 8500:8500 \
  -p 8600:8600/udp \
  consul:1.8.5 agent -server -ui -node=server-1 -bootstrap-expect=1 -client=0.0.0.0

由于以分离模式 -d 启动了容器,因此该进程将在后台运行。Consul Docker 镜像设置 /consul/config 为 Consul 的默认配置目录,代理将加载该目录中放置的所有配置文件。

在命令行查看版本号:

$ docker exec ConsulDiscovery consul -v
Consul v1.8.5
Revision 1e03567d3
Protocol 2 spoken by default, understands 2 to 3 (agent will automatically use protocol >2 when speaking to compatible agents)

服务已经启动,在浏览器中访问:http://localhost:8050,显示的界面如下:

Consul 服务首页

2、创建服务提供者注册到 Consul

首先创建一个主 Maven 工程,在其 pom 文件引入依赖,Spring Boot版本为 2.3.5.RELEASE,Spring Cloud版本为 Hoxton.SR8。这个pom文件作为父 pom 文件,起到依赖版本控制的作用,其他 module 工程继承该pom。

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

    <groupId>cn.lixl</groupId>
    <artifactId>SpringCloudDemo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>pom</packaging>

    <name>SpringCloudDemo</name>
    <description>Demo project for Spring Cloud</description>

    <parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.3.5.RELEASE</version>
        <relativePath/>
    </parent>

    <modules>
        <module>eureka-server</module>
        <module>eureka-client</module>
        <module>consul-provider</module>
    </modules>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR8</spring-cloud.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

创建 model 工程作为服务提供者,即: consul-provider。以 IDEA 工具为例:

创建 consul-provider 模块

编辑新创建工程的 pom.xml 文件,继承父 pom 文件,引入 spring-cloud-starter-netflix-eureka-server 的依赖:

  <parent>
        <groupId>cn.lixl</groupId>
        <artifactId>SpringCloudDemo</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <artifactId>consul-provider2</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>consul-provider2</name>
    <description>Demo project for Spring Cloud</description>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>

通过在 application 启动类上加注解 @EnableDiscoveryClient 表明自己是一个 Discovery client:

@SpringBootApplication
@EnableDiscoveryClient
@RestController
public class ConsulProviderApplication {

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

    @Value("${spring.application.name}")
    String appName;

    @RequestMapping("/")
    public String home(){
        return "Consul service name: " + appName +", port: " + port;
    }

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

在配置文件中注明服务注册中心地址,application.yml 配置文件如下:

server:
  port: 8763
spring:
  application:
    name: consul-provider
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: consul-provider #注册到 Consul 的服务名称,客户端根据这个名称来进行服务调用。

为了模拟注册负载均衡,需要启动 2 个 consul-producer 模块。在 IDEA 工具中,复制 consul-producer 启动配置,覆盖运行端口为: 8764。如图:

复制启动配置并覆盖端口

依次启动两个服务提供者项目,在浏览器上访问 http://localhost:8500,可以看到 consul-provider 服务已经注册到 Consul 上了。

Consul 服务首页

点进去后可以看到有 2 个服务提供实例。这样服务提供者就准备好了。

Consul 服务实例列表页

三、基于 OpenFeign 调用服务

1、OpenFeign 介绍

Spring Cloud OpenFeign 是一种声明式、模板化的服务调用组件。简化了 RestTemplate 代码,实现了 Ribbon 负载均衡,使代码变得更加简洁。

OpenFeign 的使用主要分为以下几个步骤:

  • 服务消费者添加 Feign 依赖;
  • 创建业务层接口,添加 @FeignClient 注解声明需要调用的服务;
  • 业务层抽象方法使用 SpringMVC 注解配置服务地址及参数;
  • 启动类添加 @EnableFeignClients 注解激活 Feign 组件。

2、声明式服务调用

与创建服务提供者方法一致,创建服务消费者 model 工程,即: consul-consumer。编辑 pom.xml 文件,引入 spring-cloud-starter-openfeign 依赖:

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

application.yml 配置文件如下:

server:
  port: 8765
spring:
  application:
    name: consul-consumer
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: consul-consumer
        register: true       # 是否将自己注册到注册中心,默认为 true

定义业务层接口,并通过注解 @FeignClient(value = "服务名") 绑定到服务提供方,在抽象方法上使用 SpringMVC 注解配置服务地址及参数:

@FeignClient(value = "consul-provider")  
public interface ConsumerService {

    @RequestMapping(value = "/",method = RequestMethod.GET)
    String callConsulProvider();
}

在 application 启动类上加注解 @EnableFeignClients ,启用 Feign 进行远程调用:

@SpringBootApplication
@EnableDiscoveryClient
@RestController
@EnableFeignClients
public class ConsulConsumerApplication {

    @Autowired
    ConsumerService consumerService;

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

    @Value("${spring.application.name}")
    String appName;

    @RequestMapping("/")
    public String home(){
        return "Consul service name: " + appName +", port: " + port;
    }

    @RequestMapping("/call")
    public String cllConsulProvider(){
        return "Call consul provider response: "+ consumerService.cllConsulProvider();
    }

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

}

启动程序,然后多次访问: http://localhost:8765/call,可以看到消费者在交替请求 2 个服务提供者:

Call consul provider response: Consul service name: consul-provider, port: 8763
Call consul provider response: Consul service name: consul-provider, port: 8764

到此,Feign 消费者验证成功。

3、负载均衡配置

由于 Spring Cloud Feign 的客户端负载均衡是通过 Spring Cloud Ribbon 实现的,所以我们可以直接通过配置 Ribbon 客户端的方式来定义各个服务客户端调用的参数。

ribbon:
  ConnectTimeout: 600   # 连接超时时间
  ReadTimeout: 5000     # 调用超时时间

我们需要让 Hystrix 的超时时间大于 Ribbon 的超时时间,否则 Hystrix 命令超时后,该命令直接熔断,重试机制就没有任何意义了。

4、服务断路保护

Feign 中的服务降级使用起来非常方便,只需要为 Feign 客户端定义的接口添加一个服务降级处理的实现类即可。下面我们为 ConsumerService 接口添加一个服务降级实现类,并对接口中的每个方法定义服务降级处理逻辑。

@Component
public class ConsumerServiceFallback implements ConsumerService{

    @Override
    public String callConsulProvider() {
        return "调用失败,服务被降级。";
    }
}

修改 ConsumerService 接口,设置服务降级处理类为 ConsumerServiceFallback.class

@FeignClient(value = "consul-provider", fallback = ConsumerServiceFallback.class)  // 声明需要调用的服务
public interface ConsumerService {

修改 application.yml,开启 Hystrix 功能 :

feign:
  hystrix:
    enabled: true  # 开启 Hystrix 服务降级功能

关闭 2 个 consul-provider 服务,重启 consul-consumer 服务,访问 http://localhost:8765/call,可以看到页面显示服务降级信息。

服务降级示例

5、调整日志级别

Spring Cloud Feign 在构建被 @FeignClient 注解修饰的服务客户端时,会为每一个客户端都创建一个 Logger.Level 实例,我们可以利用该日志对象的 DEBUG 模式来帮助分析 Feign 的请求细节。日志级别:

  • NONE:默认的,不显示任何日志;
  • BASIC:仅记录请求方法、URL、响应状态码及执行时间;
  • HEADERS:除了 BASIC 中定义的信息之外,还有请求和响应的头信息;
  • FULL:除了 HEADERS 中定义的信息之外,还有请求和响应的正文及元数据。

通过 Java 配置类将 Feign 日志设定为 FULL

@Configuration
public class FeignConfig {

    @Bean
    public Logger.Level feignLoggerLevel(){
        return Logger.Level.FULL;
    }
}

在 application.yml 中配置日志级别为:debug :

logging:
  level:
    cn.lixl.consulconsumer: debug

访问 http://localhost:8765/call ,可以看到控制台显示出详细的日志:

2020-11-21 16:36:10.240 DEBUG 52581 --- [nio-8765-exec-2] cn.lixl.consulconsumer.ConsumerService   : [ConsumerService#callConsulProvider] ---> GET http://consul-provider/ HTTP/1.1
2020-11-21 16:36:10.240 DEBUG 52581 --- [nio-8765-exec-2] cn.lixl.consulconsumer.ConsumerService   : [ConsumerService#callConsulProvider] ---> END HTTP (0-byte body)
2020-11-21 16:36:10.252 DEBUG 52581 --- [nio-8765-exec-2] cn.lixl.consulconsumer.ConsumerService   : [ConsumerService#callConsulProvider] <--- HTTP/1.1 200 (12ms)
2020-11-21 16:36:10.253 DEBUG 52581 --- [nio-8765-exec-2] cn.lixl.consulconsumer.ConsumerService   : [ConsumerService#callConsulProvider] connection: keep-alive
2020-11-21 16:36:10.253 DEBUG 52581 --- [nio-8765-exec-2] cn.lixl.consulconsumer.ConsumerService   : [ConsumerService#callConsulProvider] content-length: 48
2020-11-21 16:36:10.253 DEBUG 52581 --- [nio-8765-exec-2] cn.lixl.consulconsumer.ConsumerService   : [ConsumerService#callConsulProvider] content-type: text/plain;charset=UTF-8
2020-11-21 16:36:10.253 DEBUG 52581 --- [nio-8765-exec-2] cn.lixl.consulconsumer.ConsumerService   : [ConsumerService#callConsulProvider] date: Sat, 21 Nov 2020 08:36:10 GMT
2020-11-21 16:36:10.253 DEBUG 52581 --- [nio-8765-exec-2] cn.lixl.consulconsumer.ConsumerService   : [ConsumerService#callConsulProvider] keep-alive: timeout=4
2020-11-21 16:36:10.253 DEBUG 52581 --- [nio-8765-exec-2] cn.lixl.consulconsumer.ConsumerService   : [ConsumerService#callConsulProvider] proxy-connection: keep-alive
2020-11-21 16:36:10.253 DEBUG 52581 --- [nio-8765-exec-2] cn.lixl.consulconsumer.ConsumerService   : [ConsumerService#callConsulProvider] 
2020-11-21 16:36:10.255 DEBUG 52581 --- [nio-8765-exec-2] cn.lixl.consulconsumer.ConsumerService   : [ConsumerService#callConsulProvider] Consul service name: consul-provider, port: 8764
2020-11-21 16:36:10.255 DEBUG 52581 --- [nio-8765-exec-2] cn.lixl.consulconsumer.ConsumerService   : [ConsumerService#callConsulProvider] <--- END HTTP (48-byte body)

四、使用 Consul 作为配置中心

Consul 支持 Key/Value 键值对的存储,可以用来做配置中心。Spring Cloud 提供 Spring Cloud Consul Config 依赖和 Consul 集成。

参照 consul-provider 创建配置中心模块:consul-config-client,在工程的 pom 文件中添加相关依赖:

  <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-config</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

新建配置文件 bootstrap.yml,对 Consul 进行配置:

spring:
  cloud:
    consul:
      config:
        enabled: true   # 是否启用配置中心功能
        format: yaml # 配置值格式
        prefix: SpringCloudDemo # 配置数据所在目录
        data-key: consul-config-data  # 配置key的名字

application.yml 配置文件如下:

server:
  port: 8766
spring:
  application:
    name: consul-config-client
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: consul-config-client
        register: false

创建 ConfigClientController,从 Consul 配置中心中获取配置信息:

@RestController
@RefreshScope
public class ConfigClientController {

    @Value("${config.project.name}")
    private String projectName;

    @GetMapping("/project-name")
    public String getProjectName() {
        return projectName;
    }
}

在 Consul 中添加如下配置:

在 Consul 中添加配置

启动 consul-config-client,调用接口 http://localhost:8766 查看配置信息,页面会显示配置值:SpringCloudDemo。只要修改下 Consul 中的配置信息,再次调用此接口,就会发现配置已经刷新。

五、服务网关 Spring Cloud GateWay

1、简介

Spring Cloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Netflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。

Spring Cloud Gateway 具有如下特性:

  • 基于Spring Framework 5, Project Reactor 和 Spring Boot 2.0 进行构建;
  • 动态路由:能够匹配任何请求属性;
  • 可以对路由指定 Predicate(断言)和 Filter(过滤器);
  • 集成 Hystrix 的断路器功能;
  • 集成 Spring Cloud 服务发现功能;
  • 易于编写的 Predicate(断言)和 Filter(过滤器);
  • 请求限流功能;
  • 支持路径重写。

相关概念

  • Route(路由):路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为 true 则匹配该路由;
  • Predicate(断言):路由组成的一部分,主要负责路由的匹配,来决定此次请求是否匹配路由,我们可以使用它匹配来自 HTTP 请求的任何内容,比如路径、参数或者 header 信息等。
  • Filter(过滤器):指的是 Spring 框架中 GatewayFilter 的实例,请求经过 Predicate 匹配路由之后执行 Filter,我们可以使用它修改请求和响应。

Spring Cloud Gateway 运行架构

客户端向 Spring Cloud Gateway 发出请求。如果 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。前置过滤器在代理请求之前运行,后置过滤器收到代理响应后开始起作用。筛选器提供了在两者之间修改流程的机制。

参考


文章作者: 李小龙
版权声明: 本博客文章除特別声明外,均采用 CC BY-NC-ND 4.0 许可协议,转载请注明来源 悟尘记 - 李小龙的博客网站 !
评论
  目录