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

分布式系统架构设计原理与实战:服务发现与动态配置 分布式系统架构设计原理与实战:服务发现与动态配置 作者:禅与计算机程序设计艺术 1. 背景介绍 1.1. 分布式系统架构设计的复杂性 随着互联网的发展,分布式系统的应用也越来越 widespread 阅读详情

前言


在分布式系统中的中心管理服务模式下,往往采用的模式是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

Golang服务发现:优化分布式系统的关键 随着微服务架构和云原生技术的普及,分布式系统的规模和复杂度呈指数级增长。服务发现作为连接服务生产者和消费者的桥梁,直接影响系统的可用性、可扩展性和性能。本文聚焦Golang语言生态,系统阐述服务发现的核心原理、主流实现方案及工程化最佳实践,涵盖从基础概念到复杂场景优化的全链路技术体系。本文采用"概念解析→原理分析→实战落地→场景扩展"的逻辑结构,依次讲解服务发现的核心概念、技术架构、算法实现、数学模型、项目实战及前沿趋势,结合Golang代码示例和架构图进行可视化说明。 阅读详情

相关推荐

Python金融数据获取工具库之baostock使用详解

在金融数据分析和量化交易中,获取准确及时的市场数据是非常重要的。baostock是一个专门为中国股市数据提供支持的 Python 库,它提供了免费的股票数据接口,用户可以方便地获取股票、指数、基金等各种金融数据。本文将详细介绍baostock库,包括其安装方法、主要特性、基本和高级功能,以及实际应用场景,帮助全面了解并掌握该库的使用。

Rocky006的博客 5248

分布式服务治理

一、服务协调 分布式协调技术主要用来解决分布式环境当中多个进程之间的同步控制,让他们有序的去访问某 种临界资源,防止造成"脏数据"的后果 分布式锁也就是我们分布式协调技术实现的核心内容。 分布式锁两种实现方式: 基于缓存(Redis等)实现分布式锁 获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自 动释放锁,锁的value值为一个随机生成的UUID...

吴大侠的博客 1271

LaTeX公式转Word竟这么简单?Python三行代码实现学术论文格式无忧

本文介绍如何利用Python的latex2word库,仅需三行代码即可将LaTeX数学公式精准转换为Word原生Office Math对象,解决学术写作中LaTeX与Word格式割裂的难题。该方法支持批量处理、格式保真,并能与Markdown、Jupyter Notebook等工具集成,构建自动化写作流程,实现学术论文格式无忧。

qsc90123456的博客 262

如何检测分布式系统中的故障节点

故障可能发生在网络连接级别(进程之间的消息丢失或传递缓慢),也可能发生在进程级别(进程崩溃或运行缓慢),并且延迟始终不能与故障区分开。这意味着在错误地将活动过程怀疑为已死(产生假阳性)与延迟将无响应过程标记为已死之间进行权衡,这给了它怀疑的好处并期望它最终出响应(产生假阴性)。故障检测器是一个本地子系统,负责识别失败或不可达的进程,以将其从集群中排除,并在保持安全性的同...

u012516914的专栏 2597

使用Spring Cloud实现分布式系统的服务治理

使用Spring Cloud实现分布式系统的服务治理 大家好,我是微赚淘客系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!今天,我们将探讨如何使用Spring Cloud来实现分布式系统的服务治理,涵盖服务注册与发现、负载均衡、断路器等关键技术。 一、服务注册与发现分布式系统中,服务的动态注册与发现是基础功能...

weixin_37074297的博客 176

# 微服务架构师必读必知的的服务治理一个不那都在这里(2)

分布式系统架构特别是进入微服务架构后,服务治理的重要性愈发变得不可缺少而且处于重要地位。缺乏服务治理的的分布式系统架构,很难正式投入生产。那么服务治理包括哪些方面呢?主要包括服务发现,负载均衡,限流,熔断,超时,重试,服务跟踪等。下面展开讲。 侵入式服务治理 1.服务发现 2.负载均衡 负载均衡是实现系统高可用,网络流量疏导和扩容的重要手段。通过合理的算法把请求分摊到后端的多个服务节点,关键在于均匀分发请求。 小规模的系统可以采用DNS来,通过为同一主机名配置多个IP地址,DNS应答时通过轮询的方式返回不

学习,输出==》再学习再输出 514

分布式系统架构2:服务发现

服务注册:服务启动时向注册中心注册自身的元数据。心跳检测:服务持续发送健康状态给注册中心,确保可用性。服务发现:消费者从注册中心获取服务实例信息。服务调用:消费者选择合适的实例进行调用(客户端负载均衡或服务端负载均衡)。服务注销:服务关闭时从注册中心注销自己的信息。

kfashfasf的博客 1317

分布式系统架构设计原理与实战:理解分布式系统服务发现

1.背景介绍 在当今的互联网时代,分布式系统已经成为了支撑大规模应用的基础设施。随着微服务架构的流行,服务的数量呈指数级增长,如何有效地管理和发现这些服务成为了一个重要的问题。本文将深入探讨分布式系统中的服务发现机制,包括其设计原理、核心算法以及实践应用。 2.核心概念与联系 2.1 服务发现 服务发现分布式系统中的一个关键问题,它的主要任务是在一个动态变化的系统环境中,使得服务消费者能...

AI天才研究院 1133

Spring Boot如何实现分布式系统中的服务发现和注册?

在传统的单体应用中,我们可以很容易地将所有的组件都部署在同一台服务器上。但是在分布式系统中,我们需要将应用程序的不同部分分散在多个服务器上。这些服务器可以位于不同的地理位置,甚至由不同的团队管理。在这种情况下,服务发现和注册是必不可少的。服务发现是指在分布式系统中,服务能够自动地发现其他服务的位置和状态。例如,当一个服务需要调用另一个服务时,它需要知道该服务的IP地址和端口号。如果这些信息是硬编码在服务中的,那么当服务的位置或状态发生变化时,我们就需要手动更改代码。

徐师兄的博客 2125

简单介绍—服务发现

一、何为服务发现服务发现是指使用一个注册中心来记录分布式系统中的全部服务的信息,以便其他服务能够快速的找到这些已注册的服务。 意思就是,所有服务器(无论是同一种APP的多个服务器,还是不同APP的多个服务器)在启动时,都需要在“注册中心”进行注册;客户端发送“请求”的时候,需要从“注册中心”获取它所属APP的服务器(有可能是多个服务器,即获取到某个服务的服务器列表)的地址信息。 从客户端角度来看,注册中心起到的作用包含两个: 1、可以知道是否存在某个APP的服务器; 2、在客户端发送请求时,需要从注册中

Ctrl_viviya的博客 4971

什么是服务发现

服务发现是一种自动识别和访问网络上的设备和服务的方法。这是分布式系统和微服务架构中遵循的常见模式。因为这种环境中的服务器可能是短暂的,并且依赖静态IP地址与这些系统连接是不可靠的,并且可能导致系统故障和错误。

weixin_44867889的博客 867

Api网关Kong集成Consul服务发现及在Asp.Net Core中的使用

目录 写在前面 本文目的 运行环境 kong kong的简介 kong的安装 consul consul简介 consul的安装 kong集成consul服务发现 在Asp.net Core中的使用 Asp.net Core 服务自动注册到Consul 源码解析 Asp.net core WebApi 自动注册路由规则到kong 通过Consul 不通过Consul,直接配置路由到kong 源码解析 总结 [参考] 写在前面   Api网关我们之前是用 ...

寒冰屋的专栏 3369

Prometheus有哪几种服务发现

Prometheus 支持多种服务发现 (Service Discovery) 机制,用于自动发现需要监控的目标。这些服务发现机制主要分为以下几类:1.静态配置 (Static Configuration)手动定义静态目标列表。适用于小规模的、固定的目标环境,通过在配置文件中直接指定目标的地址和端口。2.基于 DNS 的服务发现 (DNS Service Discovery)通过 DNS SRV 记录自动发现目标。这种方式适用于使用 DNS 进行服务注册和发现的环境。

运维老生常谈 917

为什么不应该使用ZooKeeper服务发现

原文标题:Eureka! Why You Shouldn’t Use ZooKeeper for Service Discovery原文链接:https://medium.com/kner...

kevin_tech的博客,微信搜「网管叨bi叨」 268

consul php,用 Consul 来服务注册与服务发现

服务注册与服务发现是在分布式服务架构中常常会涉及到的东西,业界常用的服务注册与服务发现工具有 ZooKeeper、etcd、Consul 和 Eureka。Consul 的主要功能有服务发现、健康检查、KV存储、安全服务沟通和多数据中心。Consul 与其他几个工具的区别可以在这里查看 Consul vs. Other Software。为什么需要有服务注册与服务发现?假设在分布式系统中有两个服务...

weixin_36363813的博客 499
上一篇: YARN Container的NUMA感知支持
下一篇: HDFS支持外部存储
Android路上的人
Android路上的人 领域专家: 大数据技术领域 领域专家: 大数据技术领域
博客等级 码龄14年 3398粉丝 451原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值