SolrCloud 分布式请求

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

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

查询容错

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

即使集群中的某些节点脱机或无法访问,只要 Solr 节点能够与每个分片的至少一个副本通信,或者如果用户通过 shards_route_ 参数限制了搜索,则可以与每个相关分片的至少一个副本通信,Solr 节点就能够正确响应搜索请求。每个分片的副本越多,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 参数

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

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

请注意,此优化仅适用于分布式搜索。诸如分面之类的某些功能可能会进行额外的网络请求以进行细化等操作。

路由查询

有几种方法可以控制查询的路由方式。

限制查询的分片

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

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

https://127.0.0.1:8983/solr/gettingstarted/select?q=*:*

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

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

https://127.0.0.1:8983/solr/gettingstarted/select?q=*:*&shards=shard1

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

https://127.0.0.1:8983/solr/gettingstarted/select?q=*:*&shards=shard1,shard2

在上面的两个示例中,虽然仅查询了特定的分片,但是该分片的任何随机副本都将收到该请求。

您可以改为指定要使用的副本列表,而不是使用分片 ID,方法是用逗号分隔副本 ID

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

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

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

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

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

https://127.0.0.1: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 实例在同一 Solr 实例上运行的任何本地副本。当查询请求返回每个文档的许多字段或大型字段时,这很有用,因为它避免了在本地可用时通过网络移动大量数据。此外,此功能对于最大限度地减少性能下降的有问题的副本的影响也很有用,因为它减少了其他健康副本命中性能下降的副本的可能性。

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

换句话说,此功能主要用于优化定向到具有少量分片和许多副本的集合的查询。

此外,仅当您跨所有托管要查询集合的副本的节点进行负载均衡请求时,才应使用此选项,因为 Solr 的 CloudSolrClient 将执行此操作。如果不进行负载均衡,此功能可能会在集群中引入热点,因为查询不会在集群中均匀分布。

replica.base

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

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

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

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 功能将在多个集合中起作用。

https://127.0.0.1:8983/solr/collection1/select?collection=collection1,collection2,collection3

_route_ 参数

_route_ 参数可用于指定用于确定相应分片的路由键。例如,如果您有一个唯一的键为“user1!123”的文档,则将路由键指定为“_route_=user1!”(请注意结尾的“!”字符)会将请求路由到托管该用户的分片。您可以指定多个用逗号分隔的路由键。当按用户分片数据时,可以使用此参数。有关更多信息,请参见文档路由

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

近实时 (NRT) 用例

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

文档的持久性和可搜索性由 commits 控制。“近实时”中的“近”是可配置的,以满足您的应用程序的需求。提交可以是“硬”提交或“软”提交,并且可以由客户端(例如 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"/>

distrib.statsCache 参数

查询参数 distrib.statsCache 默认为 true。如果设置为 false,则会为此查询关闭获取全局词项统计信息的分布式调用。这可以减少不使用分布式 IDF 进行评分计算的查询的开销。

https://127.0.0.1:8987/solr/collection1/select?q=*%3A*&wt=json&fq={!terms f=id}id1,id2&distrib.statsCache=false

避免分布式死锁

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

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

分布式跟踪和调试

可以使用值为 trackdebug 参数来跟踪请求,并查找分布式请求每个阶段的计时信息。