在微服务世界中,多个服务通常分布在 PaaS 环境中。不可变的基础设施由容器或不可变的 VM 映像提供。服务可以根据某些预定义的指标扩大和缩小。在部署服务并准备好使用之前,可能不知道服务的确切地址。
服务端点地址的动态特性由服务注册和发现处理。在此,每个服务都向代理注册并提供有关自身的更多详细信息,例如端点地址。然后其他消费者服务查询代理以找出服务的位置并调用它。注册和查询服务有多种方式,例如 ZooKeeper、etcd、consul、Kubernetes、Netflix Eureka 等。
从单体到微服务重构 展示了如何将现有单体重构为基于微服务的应用程序。用户、目录和订单服务 URI 是静态定义的。此博客将展示如何使用 ZooKeeper 注册和发现微服务。
非常感谢 Ioannis Canellos ( @iocanel ) 对 ZooKeeper 的所有破解!
什么是动物园管理员?
ZooKeeper 是一个 Apache 项目,提供分布式、最终一致的分层配置存储。
ZooKeeper 是一种集中式服务,用于维护配置信息、命名、提供分布式同步和提供组服务。所有这些类型的服务都以某种形式被分布式应用程序使用。
因此服务可以使用逻辑名称向 ZooKeeper 注册,配置信息可以包含 URI 端点。它也可以包含其他细节,例如 QoS。
ZooKeeper 有一个陡峭的学习曲线,如 Apache ZooKeeper Made Simpler with Curator 中所述。因此,本博客将不直接使用 ZooKeeper,而是使用 Apache Curator 。
Curator n ˈkyoor͝ˌātər:博物馆或其他藏品的饲养员或保管人 – 动物园管理员。
Apache Curator 有几个组件,本博客将使用 框架 :
Curator Framework 是一个高级 API,它大大简化了 ZooKeeper 的使用。它添加了许多基于 ZooKeeper 构建的功能,并处理管理与 ZooKeeper 集群的连接和重试操作的复杂性。
ZooKeeper 概念
ZooKeeper 概述 提供了对主要概念的很好的概述。以下是一些相关的:
- Znodes :ZooKeeper 将数据存储在一个共享的分层命名空间中,该命名空间的组织方式类似于标准文件系统。名称空间由数据寄存器组成——用 ZooKeeper 的说法称为 znodes—— 它们类似于文件和目录。
- 节点名称 :ZooKeeper 名称空间中的每个节点都由路径标识。节点的确切名称是由斜杠 (/) 分隔的一系列路径元素。
- 客户端/服务器 :客户端连接到单个 ZooKeeper 服务器。客户端维护一个 TCP 连接,通过它发送请求、获取响应、获取监视事件和发送心跳。如果与服务器的 TCP 连接中断,客户端将连接到另一台服务器。
- 配置数据 :ZooKeeper 命名空间中的每个节点都可以有与其关联的数据以及子节点。 ZooKeeper 最初设计用于存储协调数据,因此每个节点存储的数据通常很小,在 KB 范围内)。
- 集成 :ZooKeeper 本身旨在通过一组称为集成的主机进行复制。构成 ZooKeeper 服务的服务器必须相互了解。
- 手表 :ZooKeeper 支持 手表 的概念。客户端可以在 znode 上设置监视。当 znode 更改时,将触发并删除 watch。
ZooKeeper 是一个关于 CAP 定理的 CP 系统。这意味着如果出现分区故障,它将保持一致但不可用。这可能会导致 Eureka! 中解释的问题。 为什么不应该使用 ZooKeeper 进行服务发现 。
然而,ZooKeeper 是微服务世界中使用的最流行的服务发现机制之一。
让我们开始吧!
启动动物园管理员
在 Docker 容器中启动 ZooKeeper 实例:
docker run -d -p 2181:2181 fabric8/zookeeper
使用
telnet
验证 ZooKeeper 实例:
docker run -d -p 2181:2181 fabric8/zookeeper
键入命令“ruok”以验证服务器是否在非错误状态下运行。如果服务器正在运行,它将以“imok”响应:
docker run -d -p 2181:2181 fabric8/zookeeper
否则它根本不会响应。 ZooKeeper 还有其他类似的 四字母命令。
服务注册和发现
在我们的例子中,每个服务、用户、目录和订单都有一个预先初始化的 bean,它注册和注销服务作为生命周期初始化方法的一部分。这是来自
CatalogService
的代码:
docker run -d -p 2181:2181 fabric8/zookeeper
代码非常简单,它注入
ServiceRegistry
类,带有
@ZooKeeperRegistry
限定符。然后用于注册和注销服务。可以在相同的逻辑名称下注册多个 URI,每个 URI 对应一个无状态服务。
这个时候限定符来自另一个maven模块。一种更简洁的 Java EE 方法是将
@ZooKeeperRegistry
限定符移动到 CDI 扩展(
#20
)。当在任何 REST 端点上指定此限定符时,将向 ZooKeeper 注册服务(
#22
)。目前,服务端点 URI 也是硬编码的 (
#24
)。
ZooKeeper
类是什么样的?
-
ZooKeeper
类使用构造函数注入和硬编码 IP 地址和端口( #23 ): -
docker run -d -p 2181:2181 fabric8/zookeeper
它执行以下任务:
- 从属性文件加载 ZooKeeper 的主机/端口
- 初始化 Curator 框架并启动它
- 初始化一个 hashmap 来存储 URI 名称到 zNode 的映射。稍后删除此节点以注销服务。
-
服务注册是使用
registerService
方法完成的: -
docker run -d -p 2181:2181 fabric8/zookeeper
代码非常简单:
- 如果需要,创建父 zNode
- 创建一个临时的和顺序的节点
- 向该节点添加元数据,包括 URI
-
服务发现是使用
discover
方法完成的:
docker run -d -p 2181:2181 fabric8/zookeeper
再次,简单的代码:
- 查找为服务注册的路径的所有孩子
- 获取与此节点关联的元数据,在我们的例子中为 URI,然后返回。在本例中返回第一个这样的节点。可以将不同的 QoS 参数附加到配置数据。这将允许返回适当的服务端点。
阅读 ZooKeeper Javadocs 的 API。
ZooKeeper watches 可以设置为通知客户端服务的生命周期( #27 )。 ZooKeeper 路径缓存可以提供子节点的优化实现( #28 )。
多个服务发现实现
我们的购物车应用程序有两个
服务发现实现
ServiceDisccoveryStatic
和
ServiceDiscoveryZooKeeper
。第一个具有静态定义的所有服务 URI,另一个从 ZooKeeper 检索它们。
通过在
services
模块中创建新包并实现
ServiceRegistry
接口,可以轻松添加其他注册和发现方式。例如,
Snoop
、
etcd
、
Consul
和
Kubernetes
。随时为其中任何一个发送 PR。
运行应用程序
- 确保 ZooKeeper 映像正在如前所述运行。
- 下载 并运行 WildFly:
-
docker run -d -p 2181:2181 fabric8/zookeeper
- 部署应用程序:
-
docker run -d -p 2181:2181 fabric8/zookeeper
- 在 localhost:8080/everest-web/ 访问应用程序。在 Java EE 应用程序的单体到微服务重构 博客中了解有关应用程序和不同组件的更多信息。