Spring Cloud 注册中心 ZooKeeper 入门

1. 概述

《ZooKeeper 极简入门》文章中,我们一起完成了 ZooKeeper 的学习,并完成了 ZooKeeper 服务器的搭建。

本文我们来学习 Spring Cloud ZooKeeper 提供的 spring-cloud-zookeeper-discovery 组件,基于 Spring Cloud 的编程模型,接入 ZooKeeper 作为注册中心,实现服务的注册与发现。

2. 注册中心原理

在开始搭建 ZooKeeper 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. 快速入门

示例代码对应仓库:

本小节,我们来搭建一个 ZooKeeper Discovery 组件的快速入门示例。步骤如下:

  • 首先,搭建一个服务提供者 demo-provider ,注册服务到 ZooKeeper 中。
  • 然后,搭建一个服务消费者 demo-consumer,从 ZooKeeper 获取到 demo-provider 服务的实例列表,选择其中一个示例,进行 HTTP 远程调用。

3.1 搭建服务提供者

创建 labx-25-sc-zookeeper-discovery-demo01-provider 项目,作为服务提供者 demo-provider。最终项目代码如下图所示:

项目

3.1.1 引入依赖

在 pom.xml 文件中,主要引入 Spring Cloud ZooKeeper 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-25</artifactId>
        <groupId>cn.iocoder.springboot.labs</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>labx-25-sc-zookeeper-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 ZooKeeper Discovery 相关依赖,将 ZooKeeper 作为注册中心,并实现对其的自动配置 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
        </dependency>
    </dependencies>

</project>

引入 spring-cloud-starter-zookeeper-discovery 依赖,将 ZooKeeper 作为注册中心,并实现对它的自动配置。

3.1.2 配置文件

创建 application.yaml 配置文件,添加 ZooKeeper Discovery 配置项。配置如下:

spring:
  application:
    name: demo-provider # Spring 应用名
  cloud:
    zookeeper:
      connect-string: 127.0.0.1:2181
      # ZooKeeper 作为注册中心的配置项,对应 ZooKeeperDiscoveryProperties 配置类
      discovery:
        root: /services # ZooKeeper 数据存储的根节点,默认为 /services

server:
  port: 18080 # 服务器端口。默认为 8080

① spring.cloud.zookeeper 配置项,设置 ZooKeeper 客户端的配置,对应 ZooKeeperProperties 配置类。

  • connect-string 配置项,设置 ZooKeeper 服务器的地址。

② spring.cloud.zookeeper.discovery 配置项,设置 ZooKeeper Discovery 配置项,对应 ZooKeeperDiscoveryProperties 配置类。

  • root 配置项,设置 ZooKeeper 数据存储的根节点,默认为 /services。稍后,我们来具体演示下哈~

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 ZooKeeper Discovery 提供了 ZooKeeperDiscoveryClient 实现。

如此,所有需要使用到的地方,只需要获取到 DiscoveryClient 客户端,而无需关注具体实现,保证其通用性。

③ TestController 类,提供了 /echo 接口,返回 provider:${name} 结果。

3.1.4 简单测试

通过 DemoProviderApplication 启动服务提供者。启动完成后,我们使用 ZooKeeper 自带的客户端,查看 ZooKeeper /services 目录下的数据。

# 进入 ZooKeeper 控制台
$ bin/zkCli.sh

# 查看 /services 目录
$ ls /services
[demo-provider]

# 查看 /services/demo-provider 目录
$ ls /services/demo-provider
[80b4d65d-a5d9-4df3-8a4b-6a3e8bca8fb3]

# 查看服务实例 80b4d65d-a5d9-4df3-8a4b-6a3e8bca8fb3 的数据
$ get /services/demo-provider/80b4d65d-a5d9-4df3-8a4b-6a3e8bca8fb3
{"name":"demo-provider","id":"80b4d65d-a5d9-4df3-8a4b-6a3e8bca8fb3","address":"10.101.16.12","port":18080,"sslPort":null,"payload":{"@class":"org.springframework.cloud.zookeeper.discovery.ZookeeperInstance","id":"application-1","name":"demo-provider","metadata":{}},"registrationTimeUTC":1591619511852,"serviceType":"DYNAMIC","uriSpec":{"parts":[{"value":"scheme","variable":true},{"value":"://","variable":false},{"value":"address","variable":true},{"value":":","variable":false},{"value":"port","variable":true}]}}

通过 address 和 port 属性,可以看到该服务实例的 IP 地址和 PORT 端口。

3.2 搭建服务消费者

创建 labx-25-sc-zookeeper-discovery-demo01-consumer 项目,作为服务提供者 demo-consumer。最终项目代码如下图所示:

项目

整个项目的代码,和服务提供者是基本一致的,毕竟是示例代码 😜

3.2.1 引入依赖

「3.1.1 引入依赖」一样,只是修改 Maven <artifactId /> 为 labx-25-sc-zookeeper-discovery-demo01-consumer,见 pom.xml 文件。

3.2.2 配置文件

创建 application.yaml 配置文件,添加相应配置项。配置如下:

spring:
  application:
    name: demo-consumer # Spring 应用名
  cloud:
    zookeeper:
      connect-string: 127.0.0.1:2181
      # Zookeeper 作为注册中心的配置项,对应 ZookeeperDiscoveryProperties 配置类
      discovery:
        root: /services # Zookeeper 数据存储的根节点,默认为 /services

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 启动服务消费者。启动完成后,我们使用 ZooKeeper 自带的客户端,查看 ZooKeeper /services 目录下的数据。

# 进入 ZooKeeper 控制台
$ bin/zkCli.sh

# 查看 /services 目录
$ ls /services
[demo-consumer, demo-provider]

注意,服务消费者和服务提供是一种角色的概念,本质都是一种服务,都是可以注册自己到注册中心的。

② 访问服务消费者的 http://127.0.0.1:28080/hello?name=yudaoyuanma 接口,返回结果为 "consumer:provider:yudaoyuanma"。说明,调用远程的服务提供者成功。

③ 关闭服务提供者后,再次访问 http://127.0.0.1:28080/hello?name=yudaoyuanma 接口,返回结果为报错提示 "获取不到实例",说明我们本地缓存的服务 demo-provider 的实例列表已刷新,没有任何实例。

😈 这里我们并没有演示启动多个服务提供者的测试,胖友可以自己尝试下哟。

4. 多环境配置

同一个服务,我们会部署到开发、测试、预发布、生产等环境中,那么我们需要在项目中,添加不同环境的 ZooKeeper 配置。

一般情况下,开发和测试使用同一个 ZooKeeper,预发布和生产使用另一个 ZooKeeper。那么针对相同的 ZooKeeper,我们怎么实现不同环境的隔离呢?

我们可以通过 spring.cloud.zookeeper.discovery.root 配置项,设置不同 Zookeeper 目录进行数据的存储。例如说,生产环境使用 services-prod 目录,预发布使用 services-pre 目录。

之后,在结合《Spring Boot 配置文件入门》「6. 多环境配置」小节,使用不同的 application-{env}.yaml 配置文件中,设置不同的 spring.cloud.zookeeper.discovery.root 配置项即可。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值