1. 概述
在《Consul 极简入门》文章中,我们一起完成了 Consul 的学习,并完成了 Consul 服务器的搭建。
本文我们来学习 Spring Cloud Consul 提供的 spring-cloud-consul-discovery
组件,基于 Spring Cloud 的编程模型,接入 Consul 作为注册中心,实现服务的注册与发现。
2. 注册中心原理
在开始搭建 Consul Discovery 的示例之前,我们先来简单了解下注册中心的原理。
在使用注册中心时,一共有三种角色:服务提供者(Service Provider)、服务消费者(Service Consumer)、注册中心(Registry)。
在一些文章中,服务提供者被称为 Server,服务消费者被称为 Client。胖友们知道即可。
三个角色交互如下图所示:
① Provider:
- 启动时,向 Registry 注册自己为一个服务(Service)的实例(Instance)。
- 同时,定期向 Registry 发送心跳,告诉自己还存活。
- 关闭时,向 Registry 取消注册。
② Consumer:
- 启动时,向 Registry 订阅使用到的服务,并缓存服务的实例列表在内存中。
- 后续,Consumer 向对应服务的 Provider 发起调用时,从内存中的该服务的实例列表选择一个,进行远程调用。
- 关闭时,向 Registry 取消订阅。
③ Registry:
- Provider 超过一定时间未心跳时,从服务的实例列表移除。
- 服务的实例列表发生变化(新增或者移除)时,通知订阅该服务的 Consumer,从而让 Consumer 能够刷新本地缓存。
当然,不同的注册中心可能在实现原理上会略有差异。例如说,Eureka 注册中心,并不提供通知功能,而是 Eureka Client 自己定期轮询,实现本地缓存的更新。
另外,Provider 和 Consumer 是角色上的定义,一个服务同时即可以是 Provider 也可以作为 Consumer。例如说,优惠劵服务可以给订单服务提供接口,同时又调用用户服务提供的接口。
3. 快速入门
示例代码对应仓库:
本小节,我们来搭建一个 Consul Discovery 组件的快速入门示例。步骤如下:
- 首先,搭建一个服务提供者
demo-provider
,注册服务到 ZooKeeper 中。 - 然后,搭建一个服务消费者
demo-consumer
,从 ZooKeeper 获取到demo-provider
服务的实例列表,选择其中一个示例,进行 HTTP 远程调用。
3.1 搭建服务提供者
创建 labx-27-sc-consul-discovery-demo01-provider
项目,作为服务提供者 demo-provider
。最终项目代码如下图所示:
3.1.1 引入依赖
在 pom.xml
文件中,主要引入 Spring Cloud Consul Discovery 相关依赖。代码如下:
<?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">
<parent>
<artifactId>labx-27</artifactId>
<groupId>cn.iocoder.springboot.labs</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>labx-27-sc-consul-discovery-demo01-provider</artifactId>
<properties>
<spring.boot.version>2.2.4.RELEASE</spring.boot.version>
<spring.cloud.version>Hoxton.SR1</spring.cloud.version>
</properties>
<!--
引入 Spring Boot、Spring Cloud、Spring Cloud Alibaba 三者 BOM 文件,进行依赖版本的管理,防止不兼容。
在 https://dwz.cn/mcLIfNKt 文章中,Spring Cloud Alibaba 开发团队推荐了三者的依赖关系
-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<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>
<dependencies>
<!-- 引入 SpringMVC 相关依赖,并实现对其的自动配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入 Spring Cloud Consul Discovery 相关依赖,将 Consul 作为注册中心,并实现对其的自动配置 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!-- 引入 Spring Boot Actuator 组件,因为需要通过它提供健康检查的接口给 Consul -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
</project>
① 引入 spring-cloud-starter-consul-discovery
依赖,将 Consul 作为注册中心,并实现对它的自动配置。
② 引入 spring-boot-starter-actuator
依赖,添加 Spring Boot Actuator 组件,因为需要通过它提供健康检查的接口给 Consul。
3.1.2 配置文件
创建 application.yaml
配置文件,添加 Consul Discovery 配置项。配置如下:
spring:
application:
name: demo-provider # Spring 应用名
cloud:
# Spring Cloud Consul 通用配置项,对应 ConsulProperties 类
consul:
host: 127.0.0.1 # Consul 主机
port: 8500 # Consul 端口
# Spring Cloud Consul Discovery 配置项,对应 ConsulDiscoveryProperties 类
discovery:
service-name: ${spring.application.name} # 注册到 Consul 的服务名,默认为 `spring.application.name` 配置项
health-check-path: /actuator/health # 健康检查的接口,默认为 /actuator/health,由 Spring Boot Actuator 提供
server:
port: 18080 # 服务器端口。默认为 8080
① spring.cloud.consul
配置项,设置 Consul 客户端的配置,对应 ConsulProperties 配置类。
通过 host
和 port
配置项,设置 Consul 服务器的地址。
② spring.cloud.consul.discovery
配置项,设置 ZooKeeper Discovery 配置项,对应 ConsulDiscoveryProperties 配置类。
service-name
配置项,设置注册到 Consul 的服务名,默认为spring.application.name
配置项。health-check-path
配置项,设置 Consul 健康检查的接口,默认为/actuator/health
,由 Spring Boot Actuator 提供。
3.1.3 DemoProviderApplication
创建 DemoProviderApplication 类,创建应用启动类,并提供 HTTP 接口。代码如下:
@SpringBootApplication
@EnableDiscoveryClient
public class DemoProviderApplication {
public static void main(String[] args) {
SpringApplication.run(DemoProviderApplication.class, args);
}
@RestController
static class TestController {
@GetMapping("/echo")
public String echo(String name) {
return "provider:" + name;
}
}
}
① @SpringBootApplication
注解,被添加在类上,声明这是一个 Spring Boot 应用。Spring Cloud 是构建在 Spring Boot 之上的,所以需要添加。
② @EnableDiscoveryClient
注解,开启 Spring Cloud 的注册发现功能。不过从 Spring Cloud Edgware 版本开始,实际上已经不需要添加 @EnableDiscoveryClient
注解,只需要引入 Spring Cloud 注册发现组件,就会自动开启注册发现的功能。例如说,我们这里已经引入了 spring-cloud-starter-zookeeper-discovery
依赖,就不用再添加 @EnableDiscoveryClient
注解了。
拓展小知识:在 Spring Cloud Common 项目中,定义了 DiscoveryClient 接口,作为通用的发现客户端,提供读取服务和读取服务列表的 API 方法。而想要集成到 Spring Cloud 体系的注册中心的组件,需要提供对应的 DiscoveryClient 实现类。
例如说,Spring Cloud Alibaba Nacos Discovery 提供了 NacosDiscoveryClient 实现,Spring Cloud Consul Discovery 提供了 ConsulDiscoveryClient 实现。
如此,所有需要使用到的地方,只需要获取到 DiscoveryClient 客户端,而无需关注具体实现,保证其通用性。
③ TestController 类,提供了 /echo
接口,返回 provider:${name}
结果。
3.1.4 简单测试
通过 DemoProviderApplication 启动服务提供者。启动完成后,我们可以在 Consul 运维界面,看到该服务提供者。如下图所示:
3.2 搭建服务消费者
创建 labx-27-sc-consul-discovery-demo01-consumer
项目,作为服务提供者 demo-consumer
。最终项目代码如下图所示:
整个项目的代码,和服务提供者是基本一致的,毕竟是示例代码 😜
3.2.1 引入依赖
和「3.1.1 引入依赖」一样,只是修改 Maven <artifactId />
为 labx-27-sc-consul-discovery-demo01-consumer
,见 pom.xml
文件。
3.2.2 配置文件
创建 application.yaml
配置文件,添加相应配置项。配置如下:
spring:
application:
name: demo-consumer # Spring 应用名
cloud:
# Spring Cloud Consul 通用配置项,对应 ConsulProperties 类
consul:
host: 127.0.0.1 # Consul 主机
port: 8500 # Consul 端口
# Spring Cloud Consul Discovery 配置项,对应 ConsulDiscoveryProperties 类
discovery:
service-name: ${spring.application.name} # 注册到 Consul 的服务名,默认为 `spring.application.name` 配置项
health-check-path: /actuator/health # 健康检查的接口,默认为 /actuator/health,由 Spring Boot Actuator 提供
server:
port: 28080 # 服务器端口。默认为 8080
和「3.1.2 配置文件」基本一致,主要是将配置项目 spring.application.name
修改为 demo-consumer
。
3.2.3 DemoConsumerApplication
创建 DemoConsumerApplication 类,创建应用启动类,并提供一个调用服务提供者的 HTTP 接口。代码如下:
@SpringBootApplication
// @EnableDiscoveryClient
public class DemoConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(DemoConsumerApplication.class, args);
}
@Configuration
public class RestTemplateConfiguration {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
@RestController
static class TestController {
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private RestTemplate restTemplate;
@Autowired
private LoadBalancerClient loadBalancerClient;
@GetMapping("/hello")
public String hello(String name) {
// <1> 获得服务 `demo-provider` 的一个实例
ServiceInstance instance;
if (true) {
// 获取服务 `demo-provider` 对应的实例列表
List<ServiceInstance> instances = discoveryClient.getInstances("demo-provider");
// 选择第一个
instance = instances.size() > 0 ? instances.get(0) : null;
} else {
instance = loadBalancerClient.choose("demo-provider");
}
// <2> 发起调用
if (instance == null) {
throw new IllegalStateException("获取不到实例");
}
String targetUrl = instance.getUri() + "/echo?name=" + name;
String response = restTemplate.getForObject(targetUrl, String.class);
// 返回结果
return "consumer:" + response;
}
}
}
① @EnableDiscoveryClient
注解,因为已经无需添加,所以我们进行了注释,原因在上面已经解释过。
② RestTemplateConfiguration 配置类,创建 RestTemplate Bean。RestTemplate 是 Spring 提供的 HTTP 调用模板工具类,可以方便我们稍后调用服务提供者的 HTTP API。
③ TestController 提供了 /hello
接口,用于调用服务提供者的 /demo
接口。代码略微有几行,我们来稍微解释下哈。
discoveryClient
属性,DiscoveryClient 对象,服务发现客户端,上文我们已经介绍过。这里我们注入的不是 Nacos Discovery 提供的 NacosDiscoveryClient,保证通用性。未来如果我们不使用 Nacos 作为注册中心,而是使用 Eureka 或则 ZooKeeper 时,则无需改动这里的代码。
loadBalancerClient
属性,LoadBalancerClient 对象,负载均衡客户端。稍后我们会使用它,从 Nacos 获取的服务 demo-provider
的实例列表中,选择一个进行 HTTP 调用。
拓展小知识:在 Spring Cloud Common 项目中,定义了LoadBalancerClient 接口,作为通用的负载均衡客户端,提供从指定服务中选择一个实例、对指定服务发起请求等 API 方法。而想要集成到 Spring Cloud 体系的负载均衡的组件,需要提供对应的 LoadBalancerClient 实现类。
例如说,Spring Cloud Netflix Ribbon 提供了 RibbonLoadBalancerClient 实现。
如此,所有需要使用到的地方,只需要获取到 DiscoveryClient 客户端,而无需关注具体实现,保证其通用性。😈 不过貌似 Spring Cloud 体系中,暂时只有 Ribbon 一个负载均衡组件。
当然,LoadBalancerClient 的服务的实例列表,是来自 DiscoveryClient 提供的。
/hello
接口,示例接口,对服务提供者发起一次 HTTP 调用。
<1>
处,获得服务demo-provider
的一个实例。这里我们提供了两种方式的代码,分别基于 DiscoveryClient 和 LoadBalancerClient。<2>
处,通过获取到的服务实例 ServiceInstance 对象,拼接请求的目标 URL,之后使用 RestTemplate 发起 HTTP 调用。
3.2.4 简单测试
① 通过 DemoConsumerApplication 启动服务消费者。启动完成后,我们可以在 Consul 运维界面,看到该服务消费者。如下图所示:
注意,服务消费者和服务提供是一种角色的概念,本质都是一种服务,都是可以注册自己到注册中心的。
② 访问服务消费者的 http://127.0.0.1:28080/hello?name=yudaoyuanma 接口,返回结果为 "consumer:provider:yudaoyuanma"
。说明,调用远程的服务提供者成功。
③ 关闭服务提供者后,再次访问 http://127.0.0.1:28080/hello?name=yudaoyuanma 接口,返回结果为报错提示 "获取不到实例"
,说明我们本地缓存的服务 demo-provider
的实例列表已刷新,没有任何实例。
至此,我们已经完成 Spring Cloud Consul 作为注册中心的学习。如下是 Consul Discovery 相关的官方文档: