SolrCloud 分布式请求

当 Solr 节点收到搜索请求时,该请求会自动路由到正在搜索的集合中分片的一个副本。

选定的副本充当聚合器:它创建内部请求,随机选择集合中每个分片的副本,协调响应,根据需要发出任何后续内部请求(例如,优化分面值或请求其他存储的字段),并为客户端构建最终响应。

查询容错

在 SolrCloud 集群中,每个单独的节点在集合中的所有副本之间平衡读取请求。您可能仍然需要一个与集群通信的“外部”负载均衡器。或者,您需要一个客户端,该客户端了解如何读取和交互 Solr 在 ZooKeeper 中的元数据,并且仅请求 ZooKeeper 集的地址以发现哪些节点应该接收请求。Solr 提供了一个名为 CloudSolrClient 的智能 Java SolrJ 客户端,它能够做到这一点。

即使集群中的一些节点处于离线或不可达状态,只要 Solr 节点可以与每个分片的至少一个副本或每个相关分片的至少一个副本通信,它就能正确响应搜索请求(如果用户通过shards_route_参数限制了搜索)。每个分片的副本越多,Solr 集群在节点发生故障时处理搜索结果的可能性就越大。

zkConnected 参数

只要 Solr 节点可以与它知道的每个分片的至少一个副本通信,它就会返回搜索请求的结果,即使它在收到请求时无法与 ZooKeeper 通信。从容错角度来看,这通常是首选行为,但如果对集合结构进行了重大更改,而节点尚未通过 ZooKeeper 收到通知(即,可能已添加或删除分片,或将其拆分为子分片),则可能会导致结果陈旧或不正确。

每个搜索响应中都包含一个zkConnected头,指示处理请求的节点当时是否已连接到 ZooKeeper

带有 zkConnected 的 Solr 响应
{
  "responseHeader": {
    "status": 0,
    "zkConnected": true,
    "QTime": 20,
    "params": {
      "q": "*:*"
    }
  },
  "response": {
    "numFound": 107,
    "start": 0,
    "docs": [ "..." ]
  }
}

为了防止在请求服务节点无法与 ZooKeeper 通信时出现陈旧或不正确的结果,将 shards.tolerant 参数设置为 requireZkConnected。这将导致请求失败,而不是将 zkConnected 标头设置为 false

shards.tolerant 参数

如果一个或多个查询的分片不可用,则 Solr 的默认行为是让请求失败。但是,在很多用例中,部分结果是可以接受的,因此 Solr 提供了一个布尔值 shards.tolerant 参数(默认值为 false)。除了 truefalse 之外,shards.tolerant 还可以设置为 requireZkConnected - 请参见下文。

如果 shards.tolerant=true,则可能会返回部分结果。如果返回的响应不包含来自所有适当分片的结果,则响应标头将包含一个名为 partialResults 的特殊标志。

如果 shards.tolerant=requireZkConnected 并且为搜索请求提供服务的节点无法与 ZooKeeper 通信,则请求将失败,而不是返回可能陈旧或不正确的结果。这还将导致在查询的一个或多个分片完全不可用时请求失败,就像 shards.tolerant=false 时一样。

客户端可以指定 shards.info 以及 shards.tolerant 参数以检索更细粒度的详细信息。

带有将 partialResults 标志设置为 true 的示例响应

带有 partialResults 的 Solr 响应
{
  "responseHeader": {
    "status": 0,
    "zkConnected": true,
    "partialResults": true,
    "QTime": 20,
    "params": {
      "q": "*:*"
    }
  },
  "response": {
    "numFound": 77,
    "start": 0,
    "docs": [ "..." ]
  }
}

distrib.singlePass 参数

如果设置为 true,则 distrib.singlePass 参数将分布式搜索算法更改为在第一阶段本身从每个分片中获取所有请求的存储字段。这消除了进行第二次请求以获取存储字段的需要。

当请求包含少量小值字段时,这可能会更快。但是,如果请求了大字段或请求了很多字段,那么通过网络从所有分片中获取它们的开销可能会使请求比正常的分布式搜索路径慢。

请注意,此优化仅适用于分布式搜索。诸如分面之类的某些功能可能会针对优化等提出额外的网络请求。

路由查询

有几种方法可以控制如何路由查询。

限制要查询的分片

使用 SolrCloud 的一个优点是能够查询分布在各个分片上的非常大的集合,但在某些情况下,您可能已使用特定的 文档路由 配置了 Solr。您可以选择搜索所有数据或仅搜索其中的一部分。

由于 SolrCloud 会自动对查询进行负载平衡,因此针对集合的所有分片的查询只是一个未定义 shards 参数的查询

http://localhost:8983/solr/gettingstarted/select?q=*:*

这与用户管理的集群形成对比,在用户管理的集群中,需要使用 shards 参数来分发查询。

要将查询限制为仅一个分片,请使用 shards 参数按其逻辑 ID 指定分片,如下所示

http://localhost:8983/solr/gettingstarted/select?q=*:*&shards=shard1

如果您想搜索一组分片,您可以在一个请求中用逗号分隔指定每个分片

http://localhost:8983/solr/gettingstarted/select?q=*:*&shards=shard1,shard2

在以上两个示例中,虽然只查询了特定分片,但分片的任何随机副本都将获得请求。

您还可以通过用逗号分隔副本 ID 来指定您希望用来代替分片 ID 的副本列表

http://localhost:8983/solr/gettingstarted/select?q=*:*&shards=localhost:7574/solr/gettingstarted,localhost:8983/solr/gettingstarted

或者,您可以通过在不同的副本 ID 之间使用管道符号 (|) 来指定要从单个分片中选择的副本列表(用于负载平衡目的)

http://localhost:8983/solr/gettingstarted/select?q=*:*&shards=localhost:7574/solr/gettingstarted|localhost:7500/solr/gettingstarted

最后,您可以指定一个分片列表(用逗号分隔),每个分片由一个副本列表(用管道分隔)定义。

在以下示例中,查询了 2 个分片,第一个是来自 shard1 的随机副本,第二个是来自显式管道分隔列表的随机副本

http://localhost:8983/solr/gettingstarted/select?q=*:*&shards=shard1,localhost:7574/solr/gettingstarted|localhost:7500/solr/gettingstarted

shards.preference 参数

Solr 允许您传递一个名为 shards.preference 的可选字符串参数,以指示分布式查询应按每个分片内给定的优先顺序对可用副本进行排序。

语法为:shards.preference=property:value。属性和值的顺序很重要:第一个是主排序,第二个是次排序,依此类推。

仅当使用 SolrJ 客户端时,才支持单分片场景的 shards.preference。不使用 SolrJ 客户端的查询不能在单分片集合中使用 shards.preference

可以指定的属性如下

replica.type

一个或多个首选的副本类型。允许 PULLTLOGNRT 的任意组合。

replica.location

一个或多个首选的副本位置。

位置以 http://hostname:port 开头。匹配是针对给定字符串作为前缀进行的,因此可以省略端口等。

可以使用特殊值local来表示在与处理查询相同的 Solr 实例上运行的任何本地副本。当查询请求每个文档返回许多字段或大字段时,这非常有用,因为它避免了在本地可用时通过网络移动大量数据。此外,此功能还可以帮助最大程度地减少性能下降的故障副本的影响,因为它降低了其他正常副本受到故障副本影响的可能性。

随着集合中分片(没有本地可用副本)的数量增加,replica.location:local的值会减小,因为查询控制器必须将查询定向到大多数分片的非本地副本。

换句话说,此功能主要用于优化针对分片数量少、副本数量多的集合的查询。

此外,只有在您对为正在查询的集合托管副本的所有节点进行负载均衡请求时才应使用此选项,就像 Solr 的CloudSolrClient所做的那样。如果没有负载均衡,此功能可能会在群集中引入热点,因为查询不会在群集中均匀分布。

replica.base

在按固有副本属性排序后应用,此属性定义了首选项相等的副本集之间的后备排序;如果指定,只能为此属性指定一个值,并且必须最后指定。

默认值random为每个请求随机打乱副本。这会均匀地分配请求,但可能会导致复制因子 > 1 的分片的缓存使用率低于最佳值。

stable:dividend:_paramName_从与给定参数名称关联的值中解析一个整数;此整数用作除数(mod 等效副本计数)以确定等效副本之间的首选项顺序(通过列表旋转)。

stable[:hash[:_paramName_]]与给定参数名称关联的字符串值被哈希为一个除数,该除数用于确定副本首选项顺序(类似于上面明确的dividend属性);如果未指定,paramName默认为q,提供以“主查询”的字符串值为键的稳定路由。请注意,这可能不适用于某些用例(例如,利用参数替换的静态主查询)

replica.leader

根据副本的领导者状态(设置为truefalse)来优先考虑副本。

考虑一个具有两个 TLOG 副本和四个 PULL 副本的分片(总共六个副本,其中一个为主副本)。使用 shards.preference=replica.leader:false,6 个副本中的 5 个将被优先考虑。将其与 shards.preference=replica.type:PULL 进行对比,其中只有 6 个副本中的 4 个将被优先考虑。

请注意,从搜索角度来看,非主副本 TLOG 副本的行为类似于 PULL 副本;它从主副本拉取索引更新,就像 PULL 副本一样,并且不执行软提交。不同之处在于,非主副本 TLOG 副本还会在其 TLOG 中捕获更新,以便在当前主副本丢失时成为替换主副本的候选者。

node.sysprop

查询将被路由到与当前节点定义的系统属性相同的节点。例如,如果您在不同的机架上启动 Solr 节点,您将希望通过 系统属性(例如,-Drack=rack1)来标识这些节点。然后,查询可以包含 shards.preference=node.sysprop:sysprop.rack,以确保您始终命中具有相同 rack 值的分片。

示例:

  • 在其他同等副本中优先考虑稳定路由(以客户端“sessionId”参数为键)

    shards.preference=replica.base:stable:hash:sessionId&sessionId=abc123
  • 优先考虑 PULL 副本

    shards.preference=replica.type:PULL
  • 优先考虑 PULL 副本,或者在没有 PULL 副本时优先考虑 TLOG 副本

    shards.preference=replica.type:PULL,replica.type:TLOG
  • 优先考虑任何本地副本

    shards.preference=replica.location:local
  • 优先考虑主机名为“server1”的任何副本,并以“server2”作为备选方案

    shards.preference=replica.location:http://server1,replica.location:http://server2
  • 如果可用,优先考虑 PULL 副本,否则优先考虑 TLOG 副本,并在其中优先考虑本地副本

    shards.preference=replica.type:PULL,replica.type:TLOG,replica.location:local
  • 优先考虑本地副本,并在其中优先考虑可用的 PULL 副本,否则优先考虑 TLOG 副本

    shards.preference=replica.location:local,replica.type:PULL,replica.type:TLOG
  • 优先考虑任何非主副本

    `shards.preference=replica.leader:false`

请注意,如果您在查询字符串中提供这些参数,则需要对它们进行正确的 URL 编码。

collection 参数

collection 参数允许您指定应在其中执行查询的集合或多个集合。这允许您一次查询多个集合,并且以分布式方式工作的 Solr 的功能将在集合中发挥作用。

http://localhost:8983/solr/collection1/select?collection=collection1,collection2,collection3

_route_ 参数

_route_ 参数可用于指定路由键,该键用于找出相应的碎片。例如,如果您有一个具有唯一键“user1!123”的文档,那么将路由键指定为“route=user1!”(注意尾随的“!”字符)将把请求路由到托管该用户的碎片。您可以指定多个用逗号分隔的路由键。当我们按用户分片数据时,可以使用此参数。有关更多信息,请参阅文档路由

http://localhost:8983/solr/collection1/select?q=*:*&_route_=user1!
http://localhost:8983/solr/collection1/select?q=*:*&_route_=user1!,user2!

近实时 (NRT) 使用案例

近实时 (NRT) 搜索意味着文档在被索引后不久即可用于搜索。NRT 搜索是 SolrCloud 的主要功能之一,并且很少在用户管理的集群或单节点安装中尝试。

文档的持久性和可搜索性由提交控制。“近实时”中的“近”是可配置的,以满足您的应用程序的需求。提交可以是“硬”或“软”,并且可以通过客户端(例如 SolrJ)、REST 调用发出,或配置为在solrconfig.xml中自动发生。通常给出的建议是在solrconfig.xml中配置您的提交策略(见下文),并避免外部发出提交。

通常在 NRT 应用程序中,硬提交配置为openSearcher=false,而软提交配置为使文档对搜索可见。

当提交发生时,将启动各种后台任务,例如段合并。这些后台任务不会阻止对索引的附加更新,也不会延迟文档的搜索可用性。

在为 NRT 配置时,请特别注意缓存和自动预热设置,因为它们会对 NRT 性能产生重大影响。对于极短的 autoCommit 间隔,请考虑完全禁用缓存和自动预热。

配置 ShardHandlerFactory

对于更精细的控制,您可以直接配置和调整 Solr 中分布式搜索中使用的并发性和线程池的各个方面。默认配置优先考虑吞吐量而不是延迟。

这是通过在搜索处理程序的配置中定义shardHandlerFactory来完成的。

要将shardHandlerFactory添加到标准搜索处理程序,请在solrconfig.xml中提供配置,如下例所示

<requestHandler name="/select" class="solr.SearchHandler">
  <!-- other params go here -->
  <shardHandlerFactory class="HttpShardHandlerFactory">
    <int name="socketTimeout">1000</int>
    <int name="connTimeout">5000</int>
  </shardHandlerFactory>
</requestHandler>

HttpShardHandlerFactory是 Solr 中开箱即用的唯一ShardHandlerFactory实现。

注意

shardHandlerFactory 依赖于 solr.xml 中配置的 allowUrls 参数,该参数控制哪些节点可以相互通信。这意味着主机配置是全局性的,而不是针对每个内核或每个集合。有关详细信息,请参阅 allowUrls 部分。

HttpShardHandlerFactory 接受以下参数

socketTimeout

可选

默认值:0

允许套接字等待的毫秒数。默认值为 0,其中将使用操作系统的默认值。

connTimeout

可选

默认值:0

绑定/连接套接字所接受的毫秒数。默认值为 0,其中将使用操作系统的默认值。

maxConnectionsPerHost

可选

默认值:100000

在分布式搜索中与每个单独分片建立的最大并发连接数。

corePoolSize

可选

默认值:0

用于协调分布式搜索的线程数的保留最低限制。

maximumPoolSize

可选

默认值:Integer.MAX_VALUE

用于协调分布式搜索的最大线程数。

maxThreadIdleTime

可选

默认值:5

在缩减线程以响应负载减少之前等待的秒数。

sizeOfQueue

可选

默认值:-1

如果指定,线程池将使用后备队列而不是直接传递缓冲区。高吞吐量系统将希望将其配置为直接传递(使用 -1)。希望获得更好延迟的系统将希望配置一个合理的队列大小来处理请求的变化。

fairnessPolicy

可选

默认值:false

选择处理公平策略排队的 JVM 规范。如果启用,分布式搜索将以先入先出的方式处理,代价是吞吐量。如果禁用,将优先考虑吞吐量而不是延迟。

分布式逆文档频率 (IDF)

需要文档和术语统计信息来计算相关性。在分布式系统中,这些统计信息可能因节点而异,从而在评分计算中引入偏差或不准确性。

Solr 将文档和术语统计信息存储在称为 statsCache 的缓存中。在文档统计信息计算方面,开箱即用有四种实现

  • LocalStatsCache:仅使用本地术语和文档统计信息来计算相关性。在分片中术语分布均匀的情况下,此方法效果相当好。如果没有配置 <statsCache>,则此选项为默认选项。

  • ExactStatsCache:此实现使用文档频率的全局值(跨集合)。如果您实施时需要跨节点进行精确评分,建议选择此选项。

  • ExactSharedStatsCache:其功能类似于 ExactStatsCache,但全局统计信息会重复用于具有相同术语的后续请求。

  • LRUStatsCache:此实现使用最近最少使用缓存来保存全局统计信息,这些统计信息在请求之间共享。

可以通过在 solrconfig.xml 中设置 <statsCache> 来选择实现。例如,以下行使 Solr 使用 ExactStatsCache 实现

<statsCache class="org.apache.solr.search.stats.ExactStatsCache"/>

避免分布式死锁

每个分片都会处理顶级查询请求,然后向所有其他分片发出子请求。应注意确保为 HTTP 请求服务的最大线程数大于来自顶级客户端和其他分片的可请求数。如果不是这种情况,则配置可能会导致分布式死锁。

例如,在两个分片的情况下,每个分片只有一个线程来处理 HTTP 请求,可能会发生死锁。这两个线程可以同时收到一个顶级请求,并互相发出子请求。由于没有更多剩余的线程来处理请求,因此传入请求将被阻塞,直到其他待处理请求完成,但它们不会完成,因为它们正在等待子请求。通过确保 Solr 配置为处理足够数量的线程,您可以避免此类死锁情况。

分布式跟踪和调试

值为 trackdebug 参数可用于跟踪请求,以及查找分布式请求每个阶段的时间信息。