• 分布式系统中如何较好地做服务发现


    前言


    在分布式系统中的中心管理服务模式下,往往采用的模式是1个manager服务节点,多个worker节点,然后由manager来管控这些worker节点。但是本篇文章不是来讲manager如何管理的问题,而是woker识别发现manager服务的问题。目前一种比较简单的做法,通过worker节点本地配置的方式,来指定manager服务地址。这种方式实现较为容易,但是可维护性并不高。比如一个简单的场景,如果manager节点地址发生改变,其下worker节点内所标明的manager地址就得被动地一个个更新了。我们可以用一个专业的术语表示这个现象:Service Discovery(服务发现)。

    服务发现的基本原则


    服务发现说到底就是让客户端如何快速,高效地“找到”服务端。前面提到的通过本地配置直接指明地址的方式是一种,但是确切地来说,它并不高效。

    一种更为高效的方式应该是下面这种:

    客户端始终联系(通信)的是一个固定(共享)的地址,而不是实际的地址,通过这个共享的地址,我们能够找到实际的地址。

    可能有人会说了,这不就是代理地址的意思嘛。但其实这并不完全等同于代理地址的意思。在后面的篇幅内,后具体介绍这里面的差异。

    服务发现的现有解决方案


    针对上节提到的大原则的前提下,我们有哪些可行的方案呢?从最近Hadoop社区讨论中,笔者归纳出了以下几种:

    • 第一种,通过本地xml文件的方式,就是和现有HDFS指定hdfs-site.xml的配置方式类型。但是这个解析得到配置结果的途径改为统一走底层通用框架的模式,而不是原有直接在执行代码中解析配置。只是说,我们保留了一种与原先本地配置化读取一样效果的方式。
    • 第二种,通过外部存储的方式。具体地来说,是引入一个外部Store来存储实际的地址,而客户端只需要看到的地址是这个Store地址。也就是说,客户端需要从这个Store中先查询实际的地址。这个Store可以是我们常见的比如ZK,HDFS或HBase等等。至于在查询过程潜在的性能问题,可以通过引入缓存机制来优化这个问题。
    • 第三种,通过Router的方式。Router与上面提到的方式的一个不同点在于,Router帮我们免去了实际查询的动作,也就是说,客户端只需要知道Router地址,这就足够了。这种方式就可以理解为是完全一个代理地址的概念了。现有Router模式的例子,大家可以学习HDFS RBF特性。
    • 第四种,DNS解析的方式。这是指我们引入一个共享地址host,这个host表明的是我们具体服务的地址。由于DNS服务器来做这个地址的解析。这种方案主要考虑的问题在于服务host地址的及时更新问题。

    依赖外部存储Store的服务发现解决方案实现


    下面笔者给出社区上被提出过的第二种方案的具体代码实现,大家可以仔细理解其中的解决过程(这里依赖的外部存储是ZK,分布式系统服务为HDFS)。

    /**
     * 服务发现抽象类.
     */
    public abstract class NameserviceDiscovery implements Configurable, Closeable {
    
      private static final Logger LOG =
          LoggerFactory.getLogger(NameserviceDiscovery.class);
    
    
      /** 针对每个服务发现实例,进行缓存构造. */
      protected static final LoadingCache<Id, NameserviceDiscovery> CACHE =
          CacheBuilder.newBuilder()
              .expireAfterAccess(1, TimeUnit.MINUTES)
              .removalListener(getRemover())
              .build(getLoader());
    
      /** Local configuration. */
      private Configuration conf;
    
    
      static class Id {
        // 服务发现类
        Class<? extends NameserviceDiscovery> clazz;
        // 节点当前配置信息
        Configuration conf;
    
        Id(
            Class<? extends NameserviceDiscovery> className,
            Configuration config) {
          this.clazz = className;
          this.conf = config;
        }
    
      }
    
      /**
       * 从缓存中获得服务发现实例
       */
      public static NameserviceDiscovery get(Configuration conf) {
        Class<? extends NameserviceDiscovery> clazz = conf.getClass(
            DFS_DISCOVERY_CLASS_KEY,
            DFS_DISCOVERY_CLASS_DEFAULT,
            NameserviceDiscovery.class);
        try {
          Id key = new Id(clazz, conf);
          return CACHE.get(key);
        } catch (ExecutionException e) {
          LOG.error("Cannot get a nameservice discovery from the cache", e);
        }
        return ReflectionUtils.newInstance(clazz, conf);
      }
    
      private static CacheLoader<Id, NameserviceDiscovery> getLoader() {
        return new CacheLoader<Id, NameserviceDiscovery>() {
          @Override
          public NameserviceDiscovery load(Id id) throws Exception {
            return ReflectionUtils.newInstance(id.clazz, id.conf);
          }
        };
      }
    
      private static RemovalListener<Id, NameserviceDiscovery> getRemover() {
        return new RemovalListener<Id, NameserviceDiscovery>() {
          @Override
          public void onRemoval(
              RemovalNotification<Id, NameserviceDiscovery> notification) {
            NameserviceDiscovery discovery = notification.getValue();
            try {
              discovery.close();
            } catch (IOException e) {
              LOG.error("Cannot close nameservice discovery");
            }
          }
        };
      }
    
      @Override
      public void setConf(Configuration config) {
        this.conf = config;
      }
    
      @Override
      public Configuration getConf() {
        return this.conf;
      }
    
    
      /**
       * 获取服务地址方法
       */
      public abstract Collection<String> getNameServiceIds();
      public abstract Map<String, Map<String, InetSocketAddress>>
      public abstract Map<String, Map<String, InetSocketAddress>> getHttpAddresses();
      public abstract Map<String, Map<String, InetSocketAddress>> getHttpsAddresses();
    }
    

    下面是基于ZK的服务发现实现类,

    /**
     * 基于ZK的服务发现实现类.
     */
    public class ZookeeperBasedNameserviceDiscovery extends NameserviceDiscovery
        implements DynamicNameserviceDiscovery {
    
      private static final Logger LOG =
          LoggerFactory.getLogger(ZookeeperBasedNameserviceDiscovery.class);
    
      /** ZK管理器接口. */
      private ZKCuratorManager zkManager;
    
      /** 实际地址信息的ZK存储目录. */
      private String baseZNode;
    
    
      /**
       * 初始化ZK连接操作
       */
      public void init() {
        if (zkManager == null) {
          Configuration conf = getConf();
          baseZNode = conf.get(
              DFS_DISCOVERY_ZK_PARENT_PATH_KEY,
              DFS_DISCOVERY_ZK_PARENT_PATH_DEFAULT);
          try {
            zkManager = new ZKCuratorManager(conf);
            zkManager.start();
          } catch (IOException e) {
            LOG.error("Cannot initialize the ZK connection", e);
          }
        }
      }
    
      /**
       * 关闭ZK连接
       */
      public void close() throws IOException {
        if (zkManager != null) {
          zkManager.close();
          zkManager = null;
        }
      }
    
      /**
       * 从ZK中获取地址的操作方法
       */
      Map<String, Map<String, InetSocketAddress>> getAddresses(
          final String attr) throws IOException {
        Map<String, Map<String, InetSocketAddress>> ret = Maps.newLinkedHashMap();
        try {
          List<String> nsIds = zkManager.getChildren(baseZNode);
          for (String nsId : nsIds) {
            Map<String, InetSocketAddress> nsMap = Maps.newLinkedHashMap();
            String pathNs = baseZNode + "/" + nsId;
            List<String> nnIds = zkManager.getChildren(pathNs);
            for (String nnId : nnIds) {
              String pathNn = pathNs + "/" + nnId;
              String pathAddress = pathNn + "/" + attr;
              String addr = zkManager.getStringData(pathAddress);
              InetSocketAddress sockAddr = NetUtils.createSocketAddr(addr);
              nsMap.put(nnId, sockAddr);
            }
            ret.put(nsId, nsMap);
          }
        } catch (Exception e) {
          LOG.error("Cannot get the addresses", e);
          throw new IOException(e.getMessage());
        }
        return ret;
      }
    
      /**
       * 其它类型方法
       */
      @Override
      public Collection<String> getNameServiceIds() {
        init();
        try {
          return getAddresses("rpcAddress").keySet();
        } catch (IOException e) {
          // Fallback to the configuration based
          return getConf().getTrimmedStringCollection(DFS_NAMESERVICES);
        }
      }
    
      @Override
      public Map<String, Map<String, InetSocketAddress>> getRpcAddresses() {
        init();
        try {
          return getAddresses("rpcAddress");
        } catch (IOException e) {
          // Fallback to the configuration based
          Configuration conf = getConf();
          return DFSUtilClient.getAddresses(conf, null,
            HdfsClientConfigKeys.DFS_NAMENODE_RPC_ADDRESS_KEY);
        }
      }
    
      @Override
      public Map<String, Map<String, InetSocketAddress>> getHttpAddresses() {
        init();
        try {
          return getAddresses("httpAddress");
        } catch (IOException e) {
          // Fallback to the configuration based
          Configuration conf = getConf();
          return DFSUtilClient.getAddresses(conf, null,
              HdfsClientConfigKeys.DFS_NAMENODE_HTTP_ADDRESS_KEY);
        }
      }
    
      @Override
      public Map<String, Map<String, InetSocketAddress>> getHttpsAddresses() {
        init();
        try {
          return getAddresses("httpsAddress");
        } catch (IOException e) {
          // Fallback to the configuration based
          Configuration conf = getConf();
          return DFSUtilClient.getAddresses(conf, null,
              HdfsClientConfigKeys.DFS_NAMENODE_HTTPS_ADDRESS_KEY);
        }
      }
    }
    

    使用的方式很简单,调用底层NameserviceDiscovery的接口即可。在系统中将配置解析操作方法替换为上述接口方式的话,服务发现的方式就优化成了第二种方案了,可维护性也增强了许多。以上就是一个简单的依赖外部Store的服务发现的实现方案。

    引用


    [1].https://issues.apache.org/jira/browse/HADOOP-15774. Discovery of HA servers
    [2].https://issues.apache.org/jira/browse/HDFS-13312. NameNode High Availability ZooKeeper based discovery rather than explicit nn1,nn2 configs

  • 相关阅读:
    Linux-配置共享目录
    Linux-expect脚本-编写一个expect脚本
    MySQL-Linux下安装
    ETL-拉链算法-1
    ETL-拉链算法-带删除的拉链算法
    postgresql-基础-1
    Oracle-sql*plus
    【剑指offer11二进制中1的个数】
    【剑指offer10 矩形覆盖】
    【剑指offer080 09 跳台阶、变态跳台阶】
  • 原文地址:https://www.cnblogs.com/bianqi/p/12183594.html
Copyright © 2020-2023  润新知