前言1. Zookeeper 基础知识
1.1 Zookeeper 是什么1.2 Zookeeper 的数据结构1.3 Watcher 机制1.4 常见应用场景分析1.5 Zookeeper 的版本冲突问题1.6 Zookeeper 注册中心的实现原理1.7 下面示例的相关说明 2. 安装并运行 Zookeeper 服务器
2.1 下载 Zookeeper2.2 修改配置2.3 运行 Zookeeper 服务器 3. 使用 Zookeeper 管理服务提供者
3.1 引入 pom.xml 依赖3.2 修改 bootstrap.yml 配置文件3.3 在主启动类上添加注解3.4 编写业务类,并在 controller 层开放接口 4. 使用 Zookeeper 管理服务消费者
4.1 引入 pom.xml 依赖4.2 修改 bootstrap.yml 配置文件4.3 主启动类上不需要添加额外注解4.4 编写业务类,调用提供者接口 5. Dubbo 使用 Zookeeper 作为注册中心最后
前言
参考资料:
《Spring Microservices in Action》
《Spring Cloud Alibaba 微服务原理与实战》
《B站 尚硅谷 SpringCloud 框架开发教程 周阳》
《Zookeeper 教程》
《Zookeeper 官网》
ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务,是 Google 的 Chubby 一个开源的实现,是 Hadoop 和 Hbase 的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等;
1. Zookeeper 基础知识 1.1 Zookeeper 是什么
Zookeeper 是一个分布式协调工具,可以实现注册中心功能;是 Apache Hadoop 的一个子项目,用来解决分布式应用中经常遇到的一些数据管理问题。包括:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等;简单来说:zookeeper=文件系统+监听通知机制;ZooKeeper 的架构通过冗余服务实现高可用性;【注意】ZooKeeper 本身并不是注册中心,只是基于 ZooKeeper 本身的特性可以实现注册中心这个场景; 1.2 Zookeeper 的数据结构
系统概述:
ZooKeeper 的数据模型和分布式文件系统类似,是一种层次化的属性结构;和文件系统不同的是,ZooKeeper 的数据是结构化存储的,并没有在物理上体现出文件和目录;ZooKeeper 上的每个节点的数据都是允许读和写,且必须要按照层级创建;
Znode 节点:
Zookeeper 中的所有存储的数据是由 Znode 组成,并以 key/value 形式存储数据;ZooKeeper 只是管理和协调有关的数据,所以 value 的数据大小不建议设置得非常大,较大的数据会带来更大的网络开销;Znode 维护一个 stat 状态信息,其中包含数据变化的时间和版本等;
stat 状态信息:
| 属性 | 说明 |
|---|---|
| cZxid | 创建节点时的事务 ID |
| ctime | 创建节点时的时间 |
| mZxid | 最后修改节点时的事务 ID |
| mtime | 最后修改节点时的时间 |
| pZxid | 表示该节点的子节点列表最后一次修改的事务 ID(包括增删子节点,不包括改子节点内容) |
| cversion | 子节点版本号,子节点每次修改版本号加 1 |
| dataversion | 数据版本号,数据每次修改该版本号加 1 |
| aclversion | 权限版本号,权限每次修改该版本号加 1 |
| ephemeralOwner | 创建该临时节点的会话的 sessionID(持久节点,该属性值为 0) |
| dataLength | 该节点的数据长度 |
| numChildren | 该节点拥有直接子节点的数量 |
Znode 的节点类型:
持久化节点:节点的数据会持久化到磁盘;临时节点:节点的生命周期和创建该节点的客户端的生命周期保持一致。客户端会话结束,则该临时节点删除;有序节点:在创建的节点后增加一个递增的序列,该序列在同一级父节点之下是唯一的;容器节点:容器节点下最后一个子节点被删除时,容器节点自动删除(3.5.3版本后);TTL 节点:设置一个存活时间,如果在存活时间之内该节点没有任何修改并且没有子节点,则自动删除(3.5.3版本后);
一个示例图:
一种针对 Znode 的订阅/通知机制;当 Znode 节点状态发生变化时或者 ZooKeeper 客户端连接状态发生变化时,会触发事件通知;该机制在服务注册与发现中,用于服务调用者及时感知到服务提供者的变化;ZooKeeper 提供了三种 JavaAPI 机制进行对 Znode 进行注册监听:
getData():用于获取指定节点的 value 信息,并且可以注册监听,当监听的节点进行创建、修改、删除操作时,会触发相应的事件通知;getChildren():用于获取指定节点的所有子节点,并且允许注册监听,当监听节点的子节点进行创建、修改、删除操作时,触发相应的事件通知;exists():用于判断指定节点是否存在,同样可以注册针对指定节点的监听,监听的时间类型和 getData() 相同 Watcher 事件的触发都是一次性的,客户端在发现节点修改后需要在事件回调中再次注册事件; 1.4 常见应用场景分析
分布式锁:
排他性:避免在同一时刻多个进程同时访问某一个共享资源;基于节点的性质:临时节点、同级节点的唯一性;获得锁的过程:在获得排他锁时,所有客户端可以去 ZooKeeper 服务器上 /Exclusive_Locks 节点下创建一个临时节点 /lock。只有一个客户端能创建成功;释放锁的过程:有两种情况:
获得锁的客户端因为异常断开了和服务端的连接,基于临时节点的特性,/lock 节点会被自动删除;获得锁的客户端执行完业务逻辑之后,主动删除了创建的 /lock 节点; Master 选举:
当集群机器中有一个机器宕机后,其他机器会接替故障机器继续工作;基于节点的性质有两种实现方式:
唯一性:类似分布式锁。在 ZooKeeper 服务器上创建一个临时节点 /master-election,只有成功创建的机器工作。其他机器针对该节点注册 Watcher 事件;临时有序节点:所有参与选举的客户端在 ZooKeeper 服务器的 /master 节点下创建一个临时有序节点,编号最小的节点表示 Master,后续的节点可以监听前一个节点的删除事件,用于触发重新选举。如下图所示:
1.5 Zookeeper 的版本冲突问题Zookeeper 的依赖如下:
org.springframework.cloud spring-cloud-starter-zookeeper-discovery
如果我们直接添加这个依赖可能会出现以下问题,出现以下日志信息是说明出现 Zookeeper 版本冲突问题:
原因是:上述依赖自带 3.5.3beta 的版本,与本地的下载的 zookeeper 版本对应不上;可以通过修改 pom.xml 文件解决;修改后的示例请见本篇《2.1 引入 pom.xml 依赖》; 1.6 Zookeeper 注册中心的实现原理
本篇或《12.1 使用 Apache Dubbo 实现远程通信》里的《5. Dubbo 使用 Zookeeper 作为注册中心》构建了如下示例;
当 Dubbo 服务启动时:
在 Zookeeper 服务器上的 /dubbo/com.dlhjw.dubbo.service.impl.TestServiceImpl/providers 目录下创建当前服务的 URL;com.dlhjw.dubbo.service.impl.TestServiceImpl 表示发布服务的接口全路径名称;providers 表示服务提供者的类型;dubbo://ip:port 表示该服务发布的协议类型及访问地址;URL 是临时节点,其他皆为持久化节点; 当 Dubbo 服务消费者启动时:
对 /dubbo/com.dlhjw.dubbo.service.impl.TestServiceImpl/providers 目录下子节点注册 Watcher 监听;服务消费者会在 /dubbo/com.dlhjw.dubbo.service.impl.TestServiceImpl/consumers 目录下写入自己的 URL;服务消费者如果需要调用 TestServiceImpl 服务,它会先去 /dubbo/com.dlhjw.dubbo.service.impl.TestServiceImpl/providers 路径下获得所有该服务的提供方 URL 列表,然后通过负载均衡算法计算出一个地址进远程访问; 1.7 下面示例的相关说明
客户端(在这里体现不出来)调用服务消费者的 http://consumer/zk 接口,请求消费者的资源;接着服务消费者通过负载均衡算法调用 http://zkcloud-provider/provider/zk 接口,请求服务提供者的资源;
2. 安装并运行 Zookeeper 服务器
2.1 下载 Zookeeper基于 Win10 下的 Zookeeper 服务器安装;
访问连接:https://zookeeper.apache.org/releases.html;
下载解压后:
到 apache-zookeeper-3.7.0-binapache-zookeeper-3.7.0-binconf 目录下,备份一份 zoo_sample.cfg 配置文件;
tickTime:Zookeeper 服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每隔 tickTime 时间就会发送一个心跳;dataDir:Zookeeper 保存数据的目录,默认情况下,Zookeeper 将写数据的日志文件也保存在这个目录里;dataLogDir:Zookeeper 保存日志文件的目录;clientPort:客户端连接 Zookeeper 服务器的端口,Zookeeper 会监听这个端口,接受客户端的访问请求;注意:查看 bin 目录下的 zkEvn.cmd 里的 JAVA_HOME 名字对不对; 2.3 运行 Zookeeper 服务器
到 bin 目录下:
双击 zkServer.cmd,会启动一个 java 进程,即服务端;双击 zkCli.cmd,会启动一个客户端 ;
3. 使用 Zookeeper 管理服务提供者
3.1 引入 pom.xml 依赖使用 Zookeeper 构建服务提供者大致与 Nacos 和 Consul 相同; Nacos 与 Consul 的构建方式详情请见《3.2 Alibaba Nacos 注册中心》与《3.4 HashiCorp Consul 注册中心》;
org.springframework.cloud spring-cloud-starter-zookeeper-discovery org.apache.zookeeper zookeeper org.apache.zookeeper zookeeper 3.4.9
Spring 管理的依赖自带 3.5.3beta 版本,与本地的下载的 zookeeper 版本对应不上,可以通过修改 pom 文件解决; 3.2 修改 bootstrap.yml 配置文件
#8004表示注册到zookeeper服务器的支付服务提供者端口号
server:
port: 8004
#服务别名----注册zookeeper到注册中心名称
spring:
application:
name: zkcloud-provider
cloud:
zookeeper:
connect-string: 192.168.111.144:2181
3.3 在主启动类上添加注解
@EnableDiscoveryClient:使用其他组件(Nacos、zookeeper、Consul)作为注册中心; 3.4 编写业务类,并在 controller 层开放接口
这里编写一个简单接口仅作为示例;
@RestController
public class providerController{
@Value("${server.port}")
private String serverPort;
@RequestMapping(value = "/provider/zk")
public String providerzk(){
return "springcloud with zookeeper: "+serverPort+"t"+ UUID.randomUUID().toString();
}
}
4. 使用 Zookeeper 管理服务消费者
4.1 引入 pom.xml 依赖使用 Zookeeper 构建服务消费者大致与 Nacos 和 Consul 相同; Nacos 与 Consul 的构建方式详情请见《3.2 Alibaba Nacos 注册中心》与《3.4 HashiCorp Consul 注册中心》;
同服务提供者端的依赖;
4.2 修改 bootstrap.yml 配置文件org.springframework.cloud spring-cloud-starter-zookeeper-discovery org.apache.zookeeper zookeeper org.apache.zookeeper zookeeper 3.4.9
server:
port: 80
spring:
application:
name: zkcloud-consumer
cloud:
#注册到zookeeper地址
zookeeper:
connect-string: 192.168.111.144:2181
4.3 主启动类上不需要添加额外注解
4.4 编写业务类,调用提供者接口
由于我们使用 Ribbon + RestTemplate 的负载均衡策略,因此需要在 IoC 容器中添加一个 RestTemplate JavaBean;详情请见《4.1 基于 Ribbon 的负载均衡详解》;该 Bean 可以在主启动类中添加;也可以在主启动类所在包或子包的 config 包中添加,如下:
@Configuration
public class ApplicationContextBean{
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
我们在 controller 层开放接口给客户端,并在该接口里调用提供者的 API;
@RestController
public class ComsumerZKController{
public static final String INVOKE_URL = "http://zkcloud-provider";
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value = "/consumer/zk")
public String paymentInfo(){
String result = restTemplate.getForObject(INVOKE_URL+"/provider/zk", String.class);
System.out.println("消费者调用提供者获取服务--->result:" + result);
return result;
}
}
5. Dubbo 使用 Zookeeper 作为注册中心
详情请见:本系列另外一篇文章《12.1 使用 Apache Dubbo 实现远程通信》里的《5. Dubbo 使用 Zookeeper 作为注册中心》;
最后



