请求路由

请求路由

现在我们已经将数据集分割到多个机器上运行的多个节点上。但是仍然存在一个悬而未决的问题:当客户想要发出请求时,如何知道要连接哪个节点?随着分区重新平衡,分区对节点的分配也发生变化。为了回答这个问题,需要有人知晓这些变化:如果我想读或写键“foo”,需要连接哪个 IP 地址和端口号?

这个问题可以概括为 服务发现(service discovery),它不仅限于数据库。任何可通过网络访问的软件都有这个问题,特别是如果它的目标是高可用性(在多台机器上运行冗余配置)。概括来说,这个问题有几种不同的方案:

  • 允许客户联系任何节点(例如,通过循环策略的负载均衡(Round-Robin Load Balancer))。如果该节点恰巧拥有请求的分区,则它可以直接处理该请求;否则,它将请求转发到适当的节点,接收回复并传递给客户端。

  • 首先将所有来自客户端的请求发送到路由层,它决定了应该处理请求的节点,并相应地转发。此路由层本身不处理任何请求;它仅负责分区的负载均衡。

  • 要求客户端知道分区和节点的分配。在这种情况下,客户端可以直接连接到适当的节点,而不需要任何中介。

以上所有情况中的关键问题是:作出路由决策的组件(可能是节点之一,还是路由层或客户端)如何了解分区-节点之间的分配关系变化?

将请求路由到正确节点的三种不同方式。

许多分布式数据系统都依赖于一个独立的协调服务,比如 ZooKeeper 来跟踪集群元数据。每个节点在 ZooKeeper 中注册自己,ZooKeeper 维护分区到节点的可靠映射其他参与者(如路由层或分区感知客户端)可以在 ZooKeeper 中订阅此信息只要分区分配发生的改变,或者集群中添加或删除了一个节点,ZooKeeper 就会通知路由层使路由信息保持最新状态。

使用ZooKeeper跟踪分区分配给节点。

例如,LinkedIn 的 Espresso 使用 Helix 进行集群管理(依靠 ZooKeeper),实现了如图 6-8 所示的路由层 HBase,SolrCloud 和 Kafka 也使用 ZooKeeper 来跟踪分区分配 MongoDB 具有类似的体系结构,但它依赖于自己的配置服务器(config server)实现和 mongos 守护进程作为路由层。Cassandra 和 Riak 采取不同的方法:他们在节点之间使用流言协议(gossip protocol)来传播群集状态的变化。请求可以发送到任意节点,该节点会转发到包含所请求的分区的适当节点。这个模型在数据库节点中增加了更多的复杂性,但是避免了对像 ZooKeeper 这样的外部协调服务的依赖。Couchbase 不会自动重新平衡,这简化了设计。通常情况下,它配置了一个名为 moxi 的路由层,它会从集群节点了解路由变化。

当使用路由层或向随机节点发送请求时,客户端仍然需要找到要连接的 IP 地址。这些地址并不像分区的节点分布变化的那么快,所以使用 DNS 通常就足够了。

执行并行查询

到目前为止,我们只关注读取或写入单个键的非常简单的查询(对于文档分区的二级索引,另外还有分散/聚集查询)。这与大多数 NoSQL 分布式数据存储所支持的访问级别有关。然而,通常用于分析的大规模并行处理(MPP, Massively parallel processing)关系型数据库产品在其支持的查询类型方面要复杂得多。一个典型的数据仓库查询包含多个连接,过滤,分组和聚合操作 MPP 查询优化器将这个复杂的查询分解成许多执行阶段和分区,其中许多可以在数据库集群的不同节点上并行执行。涉及扫描大规模数据集的查询特别受益于这种并行执行。