关历史文章(阅读本文前,您可能需要先看下之前的系列👇

国内最全的Spring Boot系列之四

享元模式:共享女友 - 第355篇

什么是 ZooKeeper - 第347篇

ZooKeeper安装 - 第348篇

ZooKeeper数据结构和实操  - 第349篇

ZooKeeper的watch机制 - 第350篇

ZooKeeper的acl权限控制  - 第351篇

ZooKeeper内存数据和持久化  - 第352篇

ZooKeeper集群搭建 - 第354篇

ZooKeeper Java客户端的基本使用 - 第356篇

ZooKeeper客户端Curator - 第358篇

ZooKeeper客户端Curator的进阶使用 - 第359篇

ZooKeeper客户端Curator实现Watch事件监听 - 第361篇

Spring Boot 使用 Curator 操作 ZooKeeper - 第363篇

Spring Boot使用Apache Curator实现服务的注册和发现 - 第364篇
Spring Boot使用Apache Curator实现分布式锁(可重入排它锁) - 第365篇

Spring Boot使用Apache Curator实现leader选举 - 第366篇

Spring Boot使用Apache Curator实现分布式计数器 - 367篇

ZooKeeper Session 基本原理 - 第369篇

ZooKeeper分桶策略实现高性能的会话管理 - 第371篇

ZooKeeper集群架构以及读写原理 - 第372篇

ZooKeeper Leader选举原理也不过如此,看完这篇你不再懵逼了 - 第374篇

Zookeeper 集群节点为什么要部署成奇数呢?- 第376篇

ZooKeeper集群脑裂问题 - 第379篇

分布式一致性算法Paxos,ZooKeeper的ZAB协议 - 第381篇

 

在前面我们将Curator集成到了Spring Boot项目中,这一节我们看看Curator支持的应用场景之一:注册中心之服务的注册和发现

一、基本概念和思路

1.1 注册中心/服务注册/服务发现概念

1.1.1 注册中心

注册中心可以说是微服务架构中的”通讯录“,它记录了服务和服务地址的映射关系。在分布式架构中,服务会注册到这里,当服务需要调用其它服务时,就到这里找到服务的地址,进行调用。

       在我们这里注册中心是不要我们进行编码的,ZooKeeper就是一个注册中心,我把服务的信息存放在注册中心上。

1.1.2 服务注册

       就是将提供某个服务的模块信息(通常是这个服务的ip和端口)注册到1个公共的组件上去(比如: zookeeper/consul)。

1.1.3 服务发现

服务发现,简单的说,就是消费者去注册中心上查询注册了哪些服务,服务有多少个实例,哪些是健康的,哪些是不可用的。

1.2 Curator Service Discovery

Curator Service Discovery就是为了解决这个问题而生的,它对此抽象出了ServiceInstance、ServiceProvider、ServiceDiscovery三个接口,通过它我们可以很轻易的实现Service Discovery。

1.3 思路

       对于Curator Service Discovery的使用大体的思路就是:

(1)引入依赖包:curator-x-discovery-server

(2)使用ServiceDiscovery进行服务的发现和注册。

(3)服务实体的类是ServiceInstance,构造ServiceInstance的类是ServiceInstanceBuilder。

二、Curator的服务注册和服务发现实战

       说的再多,不如实际来操作下。

2.1 环境说明

(1)基于上一节《Spring Boot 使用 Curator 操作 ZooKeeper》进行往下编码。

(2)ZooKeeper版本:3.6.2

(3)Curator版本:5.1.0

(4)对于在普通的java项目中,服务发现和注册的代码是一样的,只要能获取到CuratorFramework就能搞定。

2.2 添加依赖

       在pom.xml文件中,新增依赖curator-x-discovery,加上原先的依赖curator-recipes,那么现在核心的依赖就是:

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>5.1.0</version>
</dependency>

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-x-discovery</artifactId>
    <version>5.1.0</version>
</dependency>

2.3 服务详情信息

       在构造服务实例ServiceInstance的时候,允许我们自己自定义一些其他的服务的信息,这里我们取名为ServiceDetail:

package com.kfit.springbootcuratordemo.register;

/**
 * 服务详情信息
 *
 * @author 悟纤「公众号SpringBoot」
 * @date 2021-03-25
 * @slogan 大道至简 悟在天成
 */
public class ServiceDetail {
    //服务注册的根路径
    public static final String REGISTER_ROOT_PATH = "/apps";

    private String desc;
    private int weight;

    @Override
    public String toString() {
        return "ServiceDetail{" +
                "desc='" + desc + '\'' +
                ", weight=" + weight +
                '}';
    }

    public ServiceDetail() {
    }

    public ServiceDetail(String desc, int weight) {
        this.desc = desc;
        this.weight = weight;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    public int getWeight() {
        return weight;
    }

    public void setWeight(int weight) {
        this.weight = weight;
    }
}

2.4 服务注册和服务发现

       这里对于服务注册和服务发现,我们放在一个类中进行管理,具体的代码如下:

package com.kfit.springbootcuratordemo.register;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.x.discovery.ServiceDiscovery;
import org.apache.curator.x.discovery.ServiceDiscoveryBuilder;
import org.apache.curator.x.discovery.ServiceInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;

import java.net.InetAddress;
import java.util.Collection;

/**
 * TODO
 *
 * @author 悟纤「公众号SpringBoot」
 * @date 2021-03-25
 * @slogan 大道至简 悟在天成
 */
@Service
public class RegisterService {

    //环境信息类
    @Autowired
    private Environment environment;

    //Curator client
    @Autowired
    private CuratorFramework curatorFramework;

    /**
     * 服务注册:将当前启动的服务信息(ip+port)进行注册到zk中。
     */
    public void register(){
        try {
            //address.getHostAddress(): 192.168.0.106
            InetAddress address = InetAddress.getLocalHost();
            ServiceInstance<ServiceDetail> instance = ServiceInstance.<ServiceDetail>builder()
                    .address(address.getHostAddress())//ip地址:192.168.0.106
                    .port(Integer.parseInt(environment.getProperty("local.server.port")))//port:8080
                    .name("userService") //服务的名称
                    .payload(new ServiceDetail("用户服务", 1))
                    .build();

            ServiceDiscovery<ServiceDetail> serviceDiscovery = ServiceDiscoveryBuilder.builder(ServiceDetail.class)
                    .client(curatorFramework)
                    //.serializer() //序列化方式
                    .basePath(ServiceDetail.REGISTER_ROOT_PATH)
                    .build();

            //服务注册
            serviceDiscovery.registerService(instance);
            serviceDiscovery.start();

        } catch (Exception e) {
            e.printStackTrace();
        }

    }


    /**
     * 服务发现:通过serviceDiscovery查询到服务
     */
    public void discovery(){
        try {
            ServiceDiscovery<ServiceDetail> serviceDiscovery = ServiceDiscoveryBuilder.builder(ServiceDetail.class)
                    .client(curatorFramework)
                    .basePath(ServiceDetail.REGISTER_ROOT_PATH)
                    .build();
            serviceDiscovery.start();

            //根据名称获取服务
            Collection<ServiceInstance<ServiceDetail>> services = serviceDiscovery.queryForInstances("userService");
            for(ServiceInstance<ServiceDetail> service : services) {
                System.out.print(service.getPayload()+" -- ");
                System.out.println(service.getAddress() + ":" + service.getPort());
            }
            System.out.println();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }


}

2.5 启动注册到zk上

       在应用启动成功之后,注册到zk上:

package com.kfit.springbootcuratordemo;

import com.kfit.springbootcuratordemo.register.RegisterService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class SpringbootCuratorDemoApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext ctx = SpringApplication.run(SpringbootCuratorDemoApplication.class, args);

        //将服务进行注册
        RegisterService registerService = ctx.getBean(RegisterService.class);
        registerService.register();

        //测试:服务发现
        while (true){
            registerService.discovery();
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }



}

2.6 启动测试

       首先启动zk server服务,然后启动spring boot应用。

       修改application.properties的属性server.port=8081,再次启动应用。

       观察控制台的打印信息:

 

       先启动的那个应用,一开始只能找到自己本身发布的那个服务,当另外一个应用启动的时候就能够监听到另外的服务信息了。

       这时候我们看下zk server的节点信息:

       可以看到userService/下有两个节点,我们来看下数据:

get /curator/apps/userService/260508a9-83c8-4d95-bdf6-8879888a6600

        返回的数据是:

{"name":"userService","id":"260508a9-83c8-4d95-bdf6-8879888a6600","address":"192.168.0.106","port":8080,"sslPort":null,"payload":{"@class":"com.kfit.springbootcuratordemo.register.ServiceDetail","desc":"用户服务","weight":1},"registrationTimeUTC":1616657422516,"serviceType":"DYNAMIC","uriSpec":null}

       当我们关闭一个应用的时候,瞬间就只能发现一个服务了,并且zk server上的节点就少了。

三、Curator的服务注册和服务发现一探究竟

3.1 服务注册使用的是什么类型的节点

 

       从这里我们对于每个服务的节点是临时节点,那么对于临时节点有什么特性呢?也就是当我们的session和服务端断开的时候,该节点会被删除。

3.2 服务注册源码分析

       我们跟进方法serviceDiscovery.registerService(instance),可以找到

org.apache.curator.x.discovery.details.ServiceDiscoveryImpl#internalRegisterService里面有这么一段代码:

       这里创建节点核心的代码就是下面这句:

client.create().creatingParentContainersIfNeeded().withMode(mode).forPath(path, bytes);

       父节点是容器节点,大家还记得容器节点的特性吗?这里在帮大家温习一下:

container 节点用来存放子节点,如果container节点中的子节点为0 ,则container节点在未来会被服务器删除,定时任务默认60秒执行一次。

       我们可以把所有的应用删除看看,父类节点是不是自动被删除了。

       等待60秒看一下,确实父类节点被删除了。

       在过一会apps也被删除掉了,在过一段时间/curator也会被删除掉。

       withMode是传入了参数mode,对于mode是在switch case进行判断的,我们并没有指定这个ServiceType,那么默认值是什么呢,可以跟进去看下,在ServiceInstance的构造方法设置了默认值:ServiceType.DYNAMIC。

       所以对于每个服务的节点是临时节点。

四、小结

       最后我们小结本文的内容:

(1)使用curator实现服务的注册和发现需要添加依赖curator-x-discovery,核心的类是ServiceDiscovery。

(2)创建的父类节点是容器节点,在没有子节点的时候60秒后会被删除;创建的服务实例的节点是临时节点,在session断开的时候,会立即被删除。/容器节点/临时节点

 

我就是我,是颜色不一样的烟火。
我就是我,是与众不同的小苹果。

à悟空学院:悟空学院

学院中有Spring Boot相关的课程!!

SpringBoot视频:从零开始学Spring Boot Plus - 网易云课堂

SpringBoot交流平台:https://t.cn/R3QDhU0

SpringSecurity5.0视频:权限管理spring security - 网易云课堂

ShardingJDBC分库分表:分库分表Sharding-JDBC实战 - 网易云课堂

分布式事务解决方案:分布式事务解决方案「手写代码」 - 网易云课堂

JVM内存模型调优实战:深入理解JVM内存模型/调优实战 - 网易云课堂

Spring入门到精通:Spring零基础从入门到精通 - 网易云课堂

大话设计模式之爱你:大话设计模式之爱你一万年 - 网易云课堂

 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐