SolrCloud 分片和索引

当您的集合对于一个节点来说过大时,您可以通过创建多个分片来将其分解并分段存储。

分片是集合的逻辑分区,包含来自集合的文档子集,因此集合中的每个文档都恰好包含在一个分片中。集合中哪个分片包含每个文档取决于该集合的整体分片策略。

例如,您可能有一个集合,其中每个文档的“国家/地区”字段决定它属于哪个分片,因此来自同一国家/地区的文档位于同一位置。不同的集合可能只是使用每个文档的 uniqueKey 上的“哈希”来确定其分片。

在 SolrCloud 之前,Solr 支持分布式搜索,允许在一个查询中跨多个分片执行,因此该查询针对整个 Solr 索引执行,搜索结果中不会遗漏任何文档。因此,跨分片拆分索引并非 SolrCloud 独有的概念。然而,分布式方法存在一些问题,需要通过 SolrCloud 进行改进

  1. 将索引拆分为分片有些手动。

  2. 不支持分布式索引,这意味着您需要显式地将文档发送到特定的分片;Solr 无法自行确定要将文档发送到哪个分片。

  3. 没有负载均衡或故障转移,因此如果您收到大量查询,您需要确定将它们发送到哪里,如果一个分片死掉,它就直接消失了。

SolrCloud 解决了这些限制。它支持自动分配索引过程和查询,ZooKeeper 提供故障转移和负载均衡。此外,每个分片可以有多个副本,以提高稳健性。

领导者和副本

在 SolrCloud 中,没有领导者或追随者。相反,每个分片都至少包含一个物理副本,其中恰好有一个是领导者。领导者自动选举产生,最初是先到先得,然后基于 https://zookeeper.net.cn/doc/r3.9.2/recipes.html#sc_leaderElection 中描述的 ZooKeeper 进程。

如果领导者宕机,则会自动将其他副本之一选举为新的领导者。

当将文档发送到 Solr 节点进行索引时,系统首先确定该文档属于哪个分片,然后确定哪个节点当前托管该分片的领导者。然后将该文档转发给当前领导者进行索引,领导者将更新转发给所有其他副本。

副本类型

默认情况下,如果领导者宕机,所有副本都有资格成为领导者。然而,这是有代价的:如果所有副本随时都可能成为领导者,则每个副本都必须始终与其领导者同步。添加到领导者的新文档必须路由到副本,并且每个副本都必须执行提交。如果副本宕机或暂时不可用,然后重新加入集群,如果它错过了大量更新,则恢复可能会很慢。

这些问题对于大多数用户来说不是问题。然而,如果副本的行为更像以前的模型(要么不实时同步,要么根本没有资格成为领导者),某些用例会表现得更好。

Solr 通过允许您在创建新集合或添加副本时设置副本类型来实现这一点。可用的类型包括

  • NRT:这是默认值。NRT 副本(NRT = 近实时)维护事务日志并在本地将其新文档写入索引。任何此类类型的副本都有资格成为领导者。传统上,这是 Solr 支持的唯一类型。

  • TLOG:这种类型的副本维护事务日志,但不本地索引文档更改。这种类型有助于加快索引速度,因为副本中不需要发生提交。当这种类型的副本需要更新其索引时,它会通过从主节点复制索引来实现。这种类型的副本也有资格成为分片主节点;它会首先处理其事务日志。如果它确实成为主节点,它的行为将与 NRT 类型的副本相同。

  • PULL:这种类型的副本不维护事务日志,也不在本地索引文档更改。它只从分片主节点复制索引。它没有资格成为分片主节点,也不参与分片主节点的选举。

如果在创建副本时没有指定副本类型,则默认为 NRT 类型。

在集群中组合副本类型

推荐以下三种副本类型的组合

  • 全部使用 NRT 副本

  • 全部使用 TLOG 副本

  • TLOG 副本与 PULL 副本结合使用

全部使用 NRT 副本

适用于中小型集群,甚至更新(索引)吞吐量不太高的大型集群。NRT 是唯一支持软提交的副本类型,因此当需要近实时性时,也应使用此组合。

全部使用 TLOG 副本

如果不需要近实时性,并且每个分片的副本数量很高,但您仍然希望所有副本都能处理更新请求,则可以使用此组合。

TLOG 副本与 PULL 副本结合使用

如果不需要近实时性,每个分片的副本数量很高,并且您希望提高搜索查询的可用性,而不是文档更新的可用性(即使这意味着暂时提供过时的结果),则可以使用此组合。

其他副本类型的组合

不推荐其他副本类型的组合。如果分片中有一个以上的副本正在写入自己的索引,而不是从 NRT 副本复制,则主节点选举可能会导致分片的所有副本与主节点不同步,并且所有副本都必须复制完整索引。

PULL 副本的恢复

如果 PULL 副本宕机或离开集群,则需要考虑以下几种情况。

如果 PULL 副本无法与主节点同步,因为主节点已宕机,则不会发生复制。但是,它将继续为查询提供服务。一旦它可以再次连接到主节点,复制将恢复。

如果 PULL 副本无法连接到 ZooKeeper,它将从集群中删除,并且集群不会将查询路由到它。

如果 PULL 副本死机或因任何其他原因无法访问,则它将无法被查询。当它重新加入集群时,它将从主节点复制,并且在复制完成后,它将再次准备好为查询提供服务。

使用首选副本类型进行查询

默认情况下,所有副本都为查询提供服务。有关如何指示查询的首选副本类型的详细信息,请参见shards.preference 参数部分。

文档路由

Solr 允许在创建集合时指定 router.name 参数来指定集合使用的路由器实现。

如果您使用 compositeId 路由器(默认值),则可以在文档 ID 中发送带有前缀的文档,该前缀将用于计算 Solr 用于确定将文档发送到哪个分片进行索引的哈希值。前缀可以是您想要的任何内容(例如,它不必是分片名称),但它必须是一致的,以便 Solr 的行为保持一致。

例如,如果您想为某个客户共同放置文档,则可以使用客户名称或 ID 作为前缀。例如,如果您的客户是 "IBM",文档的 ID 为 "12345",则需要将前缀插入文档 ID 字段中:"IBM!12345"。这里的感叹号 ('!') 非常重要,因为它区分了用于确定将文档定向到哪个分片的前缀。

然后在查询时,您可以使用 _route_ 参数将前缀包含在查询中 (即,q=solr&_route_=IBM!) 以将查询定向到特定的分片。在某些情况下,这可以提高查询性能,因为它克服了查询所有分片时的网络延迟。

compositeId 路由器支持包含最多 2 个级别路由的前缀。例如:一个首先按地区,然后按客户进行路由的前缀:"USA!IBM!12345"

另一个用例可能是如果客户 "IBM" 有大量文档,并且您希望将其分散到多个分片上。此用例的语法为:shard_key/num!document_id,其中 /num 是要用于复合哈希的分片键的位数。

因此,IBM/3!12345 将从分片键中获取 3 位,从唯一文档 ID 中获取 29 位,从而将租户分散到集合中 1/8 的分片上。同样,如果 num 值为 2,它会将文档分散到 1/4 的分片上。在查询时,您可以使用 _route_ 参数将前缀以及位数包含在查询中 (即,q=solr&_route_=IBM/3!) 以将查询定向到特定的分片。

如果您不想影响文档的存储方式,则无需在文档 ID 中指定前缀。

如果您在创建集合时定义了 “implicit” 路由器,则还可以定义 router.field 参数,以使用每个文档中的字段来标识文档所属的分片。但是,如果文档中缺少指定的字段,则该文档将被拒绝。您还可以使用 _route_ 参数来命名特定的分片。

分片拆分

在 SolrCloud 中创建集合时,您需要决定要使用的初始分片数量。但是,提前知道您需要的分片数量可能很困难,尤其是在组织需求随时可能变化时,并且稍后发现您选择错误所付出的代价可能很高,这涉及到创建新的核心和重新索引所有数据。

在 Collections API 中具有拆分分片的功能。它目前允许将分片拆分为两部分。现有的分片保持不变,因此拆分操作实际上是将数据复制为两个新分片。您可以在以后准备好时删除旧的分片。

有关如何使用分片拆分的更多详细信息,请参见 Collection API 的SPLITSHARD 命令部分。

在 SolrCloud 中忽略来自客户端应用程序的提交

在大多数情况下,当在 SolrCloud 模式下运行时,索引客户端应用程序不应发送显式提交请求。相反,您应该配置 openSearcher=false 的自动提交和 autoSoftCommit,以便在搜索请求中看到最近的更新。这确保了自动提交在集群中按计划定期发生。

使用 autoSoftCommitcommitWithin 要求客户端应用程序接受 “最终一致性” 的现实。Solr 将使文档在集合的副本之间大致同时可搜索,但没有硬性保证。因此,在极少数情况下,一个文档可能在一个搜索中出现,但在紧随第一个搜索之后发生的第二个搜索中,当第二个搜索路由到不同的副本时,该文档可能不会出现。此外,当存在分片时,按特定顺序(即使在同一批次中)添加的文档可能会在提交顺序之外变得可搜索。在下一个 autoCommitcommitWithin 时间间隔到期后,文档将在分片的所有副本上变得可见。

为了强制执行客户端应用程序不应发送显式提交的策略,您应该更新所有将数据索引到 SolrCloud 中的客户端应用程序。但是,这并非总是可行的,因此 Solr 提供了 IgnoreCommitOptimizeUpdateProcessorFactory,它允许您忽略来自客户端应用程序的显式提交和/或优化请求,而无需重构客户端应用程序代码。

要激活此请求处理器,您需要在 solrconfig.xml 中添加以下内容

<updateRequestProcessorChain name="ignore-commit-from-client" default="true">
  <processor class="solr.IgnoreCommitOptimizeUpdateProcessorFactory">
    <int name="statusCode">200</int>
  </processor>
  <processor class="solr.LogUpdateProcessorFactory" />
  <processor class="solr.DistributedUpdateProcessorFactory" />
  <processor class="solr.RunUpdateProcessorFactory" />
</updateRequestProcessorChain>

如上例所示,处理器将向客户端返回 200,但会忽略提交或优化请求。请注意,您还需要连接 SolrCloud 所需的隐式处理器,因为此自定义链正在取代默认链。

在以下示例中,处理器将引发异常,并带有 403 代码和自定义错误消息

<updateRequestProcessorChain name="ignore-commit-from-client" default="true">
  <processor class="solr.IgnoreCommitOptimizeUpdateProcessorFactory">
    <int name="statusCode">403</int>
    <str name="responseMessage">Thou shall not issue a commit!</str>
  </processor>
  <processor class="solr.LogUpdateProcessorFactory" />
  <processor class="solr.DistributedUpdateProcessorFactory" />
  <processor class="solr.RunUpdateProcessorFactory" />
</updateRequestProcessorChain>

最后,您还可以将其配置为仅忽略优化并让提交通过,方法是执行

<updateRequestProcessorChain name="ignore-optimize-only-from-client-403">
  <processor class="solr.IgnoreCommitOptimizeUpdateProcessorFactory">
    <str name="responseMessage">Thou shall not issue an optimize, but commits are OK!</str>
    <bool name="ignoreOptimizeOnly">true</bool>
  </processor>
  <processor class="solr.RunUpdateProcessorFactory" />
</updateRequestProcessorChain>