使用ZooKeeper实现服务发现

1,357 阅读3分钟
服务发现

服务发现主要应用于微服务架构和分布式架构场景下。在这些场景下,一个服务通常需要松耦合的多 个组件的协同才能完成。服务发现就是让组件发现相关的组件。服务发现要提供的功能有以下3点:

  • 服务注册。
  • 服务实例的获取。
  • 服务变化的通知机制。 Curator 有一个扩展叫作 curator-x-discovery。curator-x-discovery 基于 ZooKeeper 实现了服务发现。
curator-x-discovery设计

使用一个base path作为整个服务发现的根目录。在这个根目录下是各个服务的的目录。服务目录下面是服务实例。实例是服务实例的JSON序列化数据。服务实例对应的znode节点可以根据需要设置成持久性、临时性和顺序性。

image.png

下图列出了服务发现用户代码使用的curator-x-discovery接口。最主要的有以下三个接口:

  • ServiceProvider:在服务cache之上支持服务发现操作,封装了一些服务发现策略。
  • ServiceDiscovery:服务注册,也支持直接访问ZooKeeper的服务发现操作。
  • ServiceCache:服务cache。

image.png

curator-x-discovery主要类
  • ServiceInstance 用来表示服务实例的POJO,除了包含一些服务实例常用的成员之外,还提供一个payload成员让用 户存自定义的信息。
private final String        name;
private final String        id;
private final String        address;
private final Integer       port;
private final Integer       sslPort;
private final T             payload;
private final long          registrationTimeUTC;
private final ServiceType   serviceType;
private final UriSpec       uriSpec;
private final boolean       enabled;
  • ServiceDiscovery 从一个ServiceDiscovery,可以创建多个ServiceProvider和多个ServiceCache。

image.png

image.png

  • ServiceProvider ServiceProvider 提供服务发现 high-level API。ServiceProvider 是封装ProviderStraegy 和InstanceProvider的facade。InstanceProvider的数据来自一个服务 Cache。 服务cache是ZooKeeper数据的一个本地cache,服务cache里面的数据可能会比ZooKeeper里面的数据旧一些。ProviderStraegy提供了三种策略:轮询,随机和sticky。

image.png ServiceProvider除了提供服务发现的方法(getlnstance和getAllnstances )以外,还通过noteError提供了一个让服务使用者把服务使用情况反馈给ServiceProvider的机制。

  • ServiceCache

public interface ServiceCache<T> extends Closeable, Listenable<ServiceCacheListener>, InstanceProvider<T>
{
    /**
     * Return the current list of instances. NOTE: there is no guarantee of freshness. This is
     * merely the last known list of instances. However, the list is updated via a ZooKeeper watcher
     * so it should be fresh within a window of a second or two.
     *
     * @return the list
     */
    public List<ServiceInstance<T>> getInstances();

    /**
     * The cache must be started before use
     *
     * @throws Exception errors
     */
    public void start() throws Exception;

    CountDownLatch startImmediate() throws Exception;
}

与ZooKeeper交互
ServiceDiscovery提供的服务注册方法是对znode的更新操作,服务发现方法是znode的读取操作。 同时它也是最核心的类,所有的服务发现操作都要从这个类开始。 另外服务Cache会接受来自ZooKeeper的更新通知,读取服务信息(也就是读取znode信息)。

ServiceDiscovery ServiceCache Serviceprovider 说明

  • 都有一个对应的builder。这些builder提供一个创建这三个类的fluent API。
  • 在使用之前都要调用start方法。
  • 在使用之后都要调用close方法。close方法只会释放自己创建的资源,不会释放上游关联的资源。例如 ServiceDiscovery 的 close 方法不会去调用 CuratorFramework 的 close 方法。 示例代码
/** Shows the basic usage for curator-x-discovery. */
  @Test
  public void testBasics() throws Exception {
    CuratorFramework client = null;
    ServiceDiscovery<String> discovery = null;
    ServiceProvider<String> provider = null;
    String serviceName = "test";
    String basePath = "/services";

    try {
      client = CuratorFrameworkFactory.newClient(connectString, new RetryOneTime(1));
      client.start();

      ServiceInstance<String> instance1 =
          ServiceInstance.<String>builder().payload("plant").name(serviceName).port(10064).build();
      ServiceInstance<String> instance2 =
          ServiceInstance.<String>builder().payload("animal").name(serviceName).port(10065).build();

      System.out.printf("instance1 id: %s\n", instance1.getId());
      System.out.printf("instance2 id: %s\n", instance2.getId());

      discovery =
          ServiceDiscoveryBuilder.builder(String.class)
              .basePath(basePath)
              .client(client)
              .thisInstance(instance1)
              .build();
      discovery.start();
      discovery.registerService(instance2);

      provider = discovery.serviceProviderBuilder().serviceName(serviceName).build();
      provider.start();

      assertThat(provider.getInstance().getId()).isNotEmpty();
      assertThat(provider.getAllInstances()).containsExactly(instance1, instance2);

      client.delete().deletingChildrenIfNeeded().forPath(basePath);
    } finally {
      CloseableUtils.closeQuietly(provider);
      CloseableUtils.closeQuietly(discovery);
      CloseableUtils.closeQuietly(client);
    }
  }