在微服务世界中,通常在PaaS环境中分布多个服务。 不变的基础架构,例如由容器或不变的VM映像提供的基础架构。 服务可以基于某些预定义的指标来扩大和缩小。 在部署服务并准备使用该服务之前,可能无法知道该服务的确切地址。

服务端点地址的这种动态性质由服务注册和发现处理。 这样,每个服务都会向代理注册,并提供有关其自身的更多详细信息,例如端点地址。 然后其他消费者服务查询代理以找出服务的位置并调用它。 有几种注册和查询服务的方法,例如ZooKeeper,etcd,领事,Kubernetes,Netflix Eureka等。

从单片到微服务的重构展示了如何将现有的单片重构为基于微服务的应用程序。 用户,目录和订单服务URI是静态定义的。 该博客将展示如何使用ZooKeeper注册和发现微服务。

非常感谢Ioannis Canellos( @iocanel所做的所有ZooKeeper骇客活动!

什么是ZooKeeper?

ZooKeeper是一个Apache项目,并提供一个分布式的,最终一致的分层配置存储。

zookeeper实现服务注册和服务发现 zookeeper 服务发现_数据库 ZooKeeper是用于维护配置信息,命名,提供分布式同步和提供组服务的集中式服务。 所有这些类型的服务都以某种形式被分布式应用程序使用。

阿帕奇ZooKeeper

因此,服务可以使用逻辑名在ZooKeeper中注册,并且配置信息可以包含URI端点。 它也可以包含其他详细信息,例如QoS。

zookeeper实现服务注册和服务发现 zookeeper 服务发现_python_02 ZooKeeper具有陡峭的学习曲线,如Apache的Curator简化的ZooKeeper所述 。 因此,此博客将直接使用Apache Curator ,而不是直接使用ZooKeeper。

策展人n kyoorcollectionātər:博物馆或其他藏品的饲养员或保管人– ZooKeeper饲养员。

阿帕奇策展人

Apache Curator具有多个组件,该博客将使用Framework

Curator框架是一个高级API,可以大大简化ZooKeeper的使用。 它添加了许多基于ZooKeeper构建的功能,并处理了管理与ZooKeeper集群的连接以及重试操作的复杂性。

Apache Curator框架

ZooKeeper概念

ZooKeeper概述对主要概念进行了很好的概述。 以下是一些相关的内容:

  • Znodes :ZooKeeper将数据存储在共享的分层命名空间中,该命名空间的组织方式类似于标准文件系统。 名称空间由数据寄存器(在ZooKeeper中称为znodes)组成 ,它们类似于文件和目录。
  • 节点名称 :ZooKeeper名称空间中的每个节点均由路径标识。 节点的确切名称是由斜杠(/)分隔的一系列路径元素。
  • 客户端/服务器 :客户端连接到单个ZooKeeper服务器。 客户端维护一个TCP连接,通过它发送请求,获取响应,获取监视事件并发送心跳。 如果与服务器的TCP连接断开,则客户端将连接到其他服务器。
  • 配置数据 :ZooKeeper命名空间中的每个节点都可以具有与其关联的数据以及子节点。 ZooKeeper最初旨在存储协调数据,因此存储在每个节点上的数据通常很小,小于KB范围。
  • 合奏 :ZooKeeper本身旨在在称为集合的一组主机上进行复制。 组成ZooKeeper服务的服务器都必须彼此了解。
    zookeeper实现服务注册和服务发现 zookeeper 服务发现_linux_03
  • 手表 :ZooKeeper支持手表的概念。 客户端可以在znode上设置手表。 znode更改时,将触发并删除监视。

ZooKeeper是关于CAP定理的CP系统。 这意味着,如果分区出现故障,它将保持一致但不可用。 这可能会导致Eureka中解释的问题 为什么不应该使用ZooKeeper进行服务发现

尽管如此,ZooKeeper是微服务领域中最流行的服务发现机制之一。

让我们开始吧!

启动ZooKeeper

docker run -d -p 2181:2181 fabric8/zookeeper
  1. 通过以下方式使用telnet验证ZooKeeper实例:
telnet $(docker-machine ip dockerhost)

键入命令“ ruok”以验证服务器是否在非错误状态下运行。如果服务器正在运行,则服务器将以“ imok”作为响应:

Trying 192.168.99.103...
Connected to dockerhost.
Escape character is '^]'.
ruok
imokConnection closed by foreign host.

否则,它将完全不响应。 ZooKeeper具有其他类似的四个字母的命令。

服务注册和发现

在我们的案例中,每个服务,用户,目录和订单都有一个热切初始化的bean,它作为生命周期初始化方法的一部分注册和注销该服务。 这是CatalogService的代码:

@Inject @ZooKeeperRegistry ServiceRegistry services;

private static final String endpointURI = "http://localhost:8080/catalog/resources/catalog";
private static final String serviceName = "catalog";

@PostConstruct
public void registerService() {
    services.registerService(serviceName, endpointURI);
}

@PreDestroy
public void unregisterService() {
    services.unregisterService(serviceName, endpointURI);
}

该代码非常简单,它使用@ZooKeeperRegistry限定符注入ServiceRegistry类。 然后将其用于注册和注销服务。 可以使用相同的逻辑名称注册多个URI,每个URI用于一个无状态服务。

此时,限定符来自另一个Maven模块。 较干净的Java EE方法是将@ZooKeeperRegistry限定符移动到CDI扩展名( #20 )。 并且,当在任何REST端点上指定此限定符时,都将向ZooKeeper( #22 )注册该服务。 目前,服务端点URI也已进行硬编码( #24 )。

ZooKeeper类是什么样的?

  1. ZooKeeper类使用构造函数注入并对IP地址和端口( #23 )进行硬编码:
@ApplicationScoped
public class ZooKeeper implements ServiceRegistry {

    private final CuratorFramework curatorFramework;
    private final ConcurrentHashMap<String, String> uriToZnodePath;
    
    @Inject
    public ZooKeeper() {
        try {
            Properties props = new Properties();
            props.load(this.getClass().getResourceAsStream("/zookeeper.properties"));
            
            curatorFramework = CuratorFrameworkFactory
                    .newClient(props.getProperty("host") 
                            + ":" 
                            + props.getProperty("port"), new RetryNTimes(5, 1000));
            curatorFramework.start();
            uriToZnodePath = new ConcurrentHashMap<>();
        } catch (IOException ex) {
            throw new RuntimeException(ex.getLocalizedMessage());
        }
    }

它执行以下任务:

  1. 从属性文件加载ZooKeeper的主机/端口
  2. 初始化Curator框架并启动它
  3. 初始化哈希图,以将URI名称存储到zNode映射。 稍后将删除此节点以取消注册服务。
  1. 服务注册使用registerService方法完成,如下所示:
String znode = "/services/" + name;

if (curatorFramework.checkExists().forPath(znode) == null) {
    curatorFramework.create().creatingParentsIfNeeded().forPath(znode);
}

String znodePath = curatorFramework
        .create()
        .withMode(CreateMode.EPHEMERAL_SEQUENTIAL)
        .forPath(znode+"/_", uri.getBytes());

uriToZnodePath.put(uri, znodePath);

代码很简单:

  1. 根据需要创建父级zNode
  2. 创建一个临时节点和顺序节点
  3. 将包含URI的元数据添加到此节点
  1. 使用discover方法完成服务发现:
String znode = "/services/" + name;

List<String> uris = curatorFramework.getChildren().forPath(znode);
return new String(curatorFramework.getData().forPath(ZKPaths.makePath(znode, uris.get(0))));

再次,简单的代码:

  1. 查找所有子项以注册该服务
  2. 获取与此节点关联的元数据,本例中的URI并返回。在这种情况下,将返回第一个此类节点。 可以将不同的QoS参数附加到配置数据。 这将允许返回适当的服务端点。

阅读有关API的ZooKeeper Javadocs

可以设置ZooKeeper手表来通知客户端服务的生命周期( #27 )。 ZooKeeper路径缓存可以提供子节点的优化实现( #28 )。

多种服务发现实施

我们的购物车应用程序具有两个服务发现实现ServiceDisccoveryStaticServiceDiscoveryZooKeeper 。 第一个具有静态定义的所有服务URI,另一个从ZooKeeper检索它们。

通过在services模块中创建新程序包并实现ServiceRegistry接口,可以轻松添加其他注册和发现方式。 例如, SnoopetcdConsulKubernetes 。 随意发送任何一个PR。

运行应用程序

  1. 确保ZooKeeper映像正在如前所述运行。
  2. 下载并运行WildFly:
./bin/standalone.sh
cd microservice
mvn install
  1. 通过localhost:8080 / everest-web /访问该应用程序。 在Java EE应用程序的单片到微服务重构博客中了解有关应用程序和不同组件的更多信息。

请享用!

翻译自: https://www.javacodegeeks.com/2015/06/zookeeper-for-microservice-registration-and-discovery.html