SolrJ

SolrJ 是一个 API,它使用 Java(或基于 JVM 的任何语言)编写的应用程序可以轻松地与 Solr 通信。SolrJ 隐藏了连接到 Solr 的许多详细信息,并允许您的应用程序使用简单的高级方法与 Solr 交互。SolrJ 支持大多数 Solr API,并且高度可配置。

构建和运行 SolrJ 应用程序

SolrJ API 随 Solr 一起提供,因此您无需下载或安装任何其他内容。但您需要配置您的构建以包含 SolrJ 及其依赖项。

常见构建系统

大多数主流构建系统极大地简化了依赖项管理,使您可以轻松地将 SolrJ 添加到您的项目中。

对于使用 Ant(使用 Ivy)构建的项目,请将以下内容放入您的 ivy.xml

<dependency org="org.apache.solr" name="solr-solrj" rev="9.5.0"/>

对于使用 Maven 构建的项目,请将以下内容放入您的 pom.xml

<dependency>
  <groupId>org.apache.solr</groupId>
  <artifactId>solr-solrj</artifactId>
  <version>9.5.0</version>
</dependency>

对于使用 Gradle 构建的项目,请将以下内容放入您的 build.gradle

compile group: 'org.apache.solr', name: 'solr-solrj', version: '9.5.0'

如果您想使用 CloudSolrClient 并且 让它直接与 ZooKeeper 通信,您需要添加对 solr-solrj-zookeeper 工件的依赖项。

如果您没有在 Java 代码中使用 流表达式 类,则可以排除 solr-solrj-streaming 依赖项。

手动将 SolrJ 添加到类路径

如果您没有使用上述构建系统之一,仍然可以轻松地将 SolrJ 添加到您的构建中。

在构建时,只需要 SolrJ jar 本身:solr-solrj-9.5.0.jar。要手动编译使用 SolrJ 的代码,请使用类似于以下内容的 javac 命令

javac -cp .:$SOLR_TIP/server/solr-webapp/webapp/WEB-INF/lib/solr-solrj-9.5.0.jar ...

在运行时,除了 SolrJ 本身,您还需要 SolrJ 的一些依赖项。在 Solr 发行版中,这些依赖项未与 Solr 的依赖项分开,因此您必须全部包含或手动选择所需的确切集合。请参阅 maven 发布 以了解您的版本所需的准确依赖项。使用类似于以下内容的类路径运行您的项目

java -cp .:$SOLR_TIP/server/lib/ext:$SOLR_TIP/server/solr-webapp/webapp/WEB-INF/lib/* ...

如果您担心 SolrJ 库会扩展客户端应用程序的大小,您可以使用 ProGuard 等代码混淆器来删除您不使用的 API。

SolrJ 概述

SolrJ 围绕几个简单的接口构建,以实现其灵活性。

所有对 Solr 的请求都由 SolrClient 发送。SolrClient 是 SolrJ 核心中的主要工作引擎。它们处理连接到 Solr 并与 Solr 通信的工作,并且大多数用户配置都在这里发生。

请求以 SolrRequests 的形式发送,并作为 SolrResponses 返回。

SolrClients 的类型

SolrClient 有几个具体实现,每个实现都针对不同的使用模式或弹性模型

  • HttpSolrClient - 针对以查询为中心的负载,但也适用于通用客户端。直接与单个 Solr 节点通信。

  • Http2SolrClient - 异步、非阻塞且通用的客户端,利用 HTTP/2。此类为实验性质,因此其 API 可能会在 SolrJ 的次要版本中更改或被移除。

  • LBHttpSolrClient - 在 Solr 节点列表中平衡请求负载。根据节点运行状况调整“正在运行”的节点列表。

  • LBHttp2SolrClient - 与 LBHttpSolrClient 相同,但使用 Http2SolrClient。此类为实验性质,因此其 API 可能会在 SolrJ 的次要版本中更改或被移除。

  • CloudSolrClient - 旨在与 SolrCloud 部署进行通信。使用已记录的 ZooKeeper 状态来发现和将请求路由到运行状况良好的 Solr 节点。

  • ConcurrentUpdateSolrClient - 旨在面向以索引为中心的负载。在将较大的批处理发送到 Solr 之前,在内部缓冲文档。

  • ConcurrentUpdateHttp2SolrClient - 与 ConcurrentUpdateSolrClient 相同,但使用 Http2SolrClient。此类为实验性质,因此其 API 可能会在 SolrJ 的次要版本中更改或被移除。

常见配置选项

大多数 SolrJ 配置在 SolrClient 级别进行。下面讨论了其中最常见/最重要的配置。有关如何调整 SolrClient 的全面信息,请参阅所涉及客户端及其相应构建器对象的 Javadoc。

基本 URL

大多数 SolrClient 实现(CloudSolrClientHttp2SolrClient 除外)要求用户指定一个或多个 Solr 基本 URL,然后客户端使用这些 URL 向 Solr 发送 HTTP 请求。用户在提供的基本 URL 上包含的路径会影响从那时起创建的客户端的行为。

  1. 指向特定核心或集合的路径的 URL(例如,http://hostname:8983/solr/core1)。当在基本 URL 中指定核心或集合时,使用该客户端发出的后续请求不需要重新指定受影响的集合。但是,客户端只能向该核心/集合发送请求,而不能向任何其他核心/集合发送请求。

  2. 指向 Solr 根路径的 URL(例如,http://hostname:8983/solr)。当基本 URL 中未指定核心或集合时,可以向任何核心/集合发出请求,但受影响的核心/集合必须在所有请求中指定。

一般来说,如果你的 SolrClient 仅用于单个核心/集合,那么在路径中包含该实体是最方便的。在需要更多灵活性时,应排除集合/核心。

Http2SolrClient 的基本 URL

Http2SolrClient 有效地管理与不同节点的连接。Http2SolrClient 不需要 baseUrl。如果未提供 baseUrl,则必须设置 SolrRequest.basePath,以便 Http2SolrClient 知道向哪些节点发送请求。如果没有,将抛出 IllegalArgumentException

CloudSolrClient 的基本 URL

也可以为 CloudSolrClient 指定基本 URL,但 URL 预计指向 Solr 根路径(例如,http://hostname:8983/solr)。它们不应包含任何集合、核心或其他路径组件。

final List<String> solrUrls = new ArrayList<>();
solrUrls.add("http://solr1:8983/solr");
solrUrls.add("http://solr2:8983/solr");
return new CloudSolrClient.Builder(solrUrls).build();

如果未提供 baseUrl,则必须提供 ZooKeeper 主机列表(带端口)和 ZooKeeper 根。如果未使用 ZooKeeper 根,则必须提供 java.util.Optional.empty() 作为该方法的一部分。

final List<String> zkServers = new ArrayList<>();
zkServers.add("zookeeper1:2181");
zkServers.add("zookeeper2:2181");
zkServers.add("zookeeper3:2181");
return new CloudSolrClient.Builder(zkServers, Optional.empty()).build();
final List<String> zkServers = new ArrayList<>();
zkServers.add("zookeeper1:2181");
zkServers.add("zookeeper2:2181");
zkServers.add("zookeeper3:2181");
return new CloudSolrClient.Builder(zkServers, Optional.of("/solr")).build();

此外,你需要依赖 solr-solrj-zookeeper 工件,否则会收到 ClassNotFoundException

基于 ZooKeeper 的连接是 CloudSolrClient 工作的最可靠、性能最好的方式。另一方面,这意味着比 Solr 节点更广泛地公开 ZooKeeper,这是一个安全风险。它还增加了更多的 JAR 依赖项。

超时

所有 SolrClient 实现允许用户指定与 Solr 通信的连接和读取超时。这些在客户端创建时提供,如下例所示

final String solrUrl = "http://localhost:8983/solr";
return new HttpSolrClient.Builder(solrUrl)
    .withConnectionTimeout(10000, TimeUnit.MILLISECONDS)
    .withSocketTimeout(60000, TimeUnit.MILLISECONDS)
    .build();

当未明确提供这些值时,SolrJ 会回退到使用 OS/环境正在运行的默认值。

ConcurrentUpdateSolrClient 及其对应项 ConcurrentUpdateHttp2SolrClient 还实现了一个停滞预防超时,该超时允许对无响应节点的请求比等待套接字超时更快地失败。此超时的默认值设置为 15000 毫秒,可以通过系统属性 solr.cloud.client.stallTime 进行调整。此值应小于 solr.jetty.http.idleTimeout(默认值为 120000 毫秒),并大于最大更新请求的处理时间。

云请求路由

SolrJ CloudSolrClient 实现(CloudSolrClientCloudHttp2SolrClient)遵循 shards.preference 参数。因此,使用上述任何客户端发送到单分片集合的请求将以分布式请求路由到各个分片的方式路由请求。如果未提供 shards.preference 参数,则客户端将默认随机对副本进行排序。

对于更新请求,虽然副本按照请求定义的顺序进行排序,但领导者副本始终会首先进行排序。

在 SolrJ 中查询

SolrClient 有许多 query() 方法,用于从 Solr 中获取结果。这些方法中的每一个都采用一个 SolrParams,一个封装任意查询参数的对象。每个方法都输出一个 QueryResponse,一个可用于访问结果文档和其他相关元数据的包装器。

以下代码段使用 SolrClient 查询 Solr 的“techproducts”示例集合,并遍历结果。

final SolrClient client = getSolrClient();

final Map<String, String> queryParamMap = new HashMap<>();
queryParamMap.put("q", "*:*");
queryParamMap.put("fl", "id, name");
queryParamMap.put("sort", "id asc");
MapSolrParams queryParams = new MapSolrParams(queryParamMap);

final QueryResponse response = client.query("techproducts", queryParams);
final SolrDocumentList documents = response.getResults();

print("Found " + documents.getNumFound() + " documents");
for (SolrDocument document : documents) {
  final String id = (String) document.getFirstValue("id");
  final String name = (String) document.getFirstValue("name");

  print("id: " + id + "; name: " + name);
}

SolrParams 有一个 SolrQuery 子类,它提供了一些极大地简化查询创建的便捷方法。以下代码段展示了如何使用 SolrQuery 中的一些便捷方法构建前一个示例中的查询

final SolrQuery query = new SolrQuery("*:*");
query.addField("id");
query.addField("name");
query.setSort("id", ORDER.asc);
query.setRows(numResultsToReturn);

在 SolrJ 中索引

使用 SolrJ 进行索引也很简单。用户将他们想要索引的文档构建为 SolrInputDocument 的实例,并将它们作为 SolrClient 上的 add() 方法之一的参数提供。

以下示例展示了如何使用 SolrJ 将文档添加到 Solr 的“techproducts”示例集合

final SolrClient client = getSolrClient();

final SolrInputDocument doc = new SolrInputDocument();
doc.addField("id", UUID.randomUUID().toString());
doc.addField("name", "Amazon Kindle Paperwhite");

final UpdateResponse updateResponse = client.add("techproducts", doc);
// Indexed documents must be committed
client.commit("techproducts");
上述索引示例旨在展示语法。为简洁起见,它们打破了 Solr 的多项最佳索引实践。在正常情况下,应批量索引文档,而不是一次索引一个文档。还建议 Solr 管理员使用 Solr 的自动提交设置提交文档,而不是使用显式 commit() 调用。

Java 对象绑定

虽然 SolrJ 提供的 UpdateResponseQueryResponse 接口非常有用,但通常使用应用程序更易于理解的特定于域的对象会更方便。值得庆幸的是,SolrJ 通过将文档隐式转换为任何已使用 Field 注释进行特别标记的类,并从这些类中转换文档来支持此功能。

Java 对象中的每个实例变量都可以使用 Field 注释映射到相应的 Solr 字段。默认情况下,Solr 字段与带注释变量的名称相同,但是,可以通过为注释提供显式字段名称来覆盖此名称。

以下示例代码段显示了带注释的 TechProduct 类,该类可用于表示 Solr 的“techproducts”示例集合中的结果。

public static class TechProduct {
  @Field public String id;
  @Field public String name;

  public TechProduct(String id, String name) {
    this.id = id;
    this.name = name;
  }

  public TechProduct() {}
}

具有对上述带注释的 TechProduct 类访问权限的应用程序代码可以直接索引 TechProduct 对象,无需任何转换,如下面的示例代码段所示

final SolrClient client = getSolrClient();

final TechProduct kindle = new TechProduct("kindle-id-4", "Amazon Kindle Paperwhite");
final UpdateResponse response = client.addBean("techproducts", kindle);

client.commit("techproducts");

类似地,可以使用 QueryResponse 上的 getBeans() 方法将搜索结果直接转换为 Bean 对象

final SolrClient client = getSolrClient();

final SolrQuery query = new SolrQuery("*:*");
query.addField("id");
query.addField("name");
query.setSort("id", ORDER.asc);

final QueryResponse response = client.query("techproducts", query);
final List<TechProduct> products = response.getBeans(TechProduct.class);

其他 API

SolrJ 不仅允许查询和索引。它支持 Solr 的所有 API。访问 Solr 的其他 API 就像查找适当的请求对象、提供任何必要的参数并将其传递给 SolrClientrequest() 方法一样简单。request() 将返回一个 NamedList:一个反映其请求返回的 JSON 或 XML 的层次结构的通用对象。

以下示例展示了 SolrJ 用户如何调用 SolrCloud 部署的 CLUSTERSTATUS API,并操作返回的 NamedList

final SolrClient client = getSolrClient();

@SuppressWarnings({"rawtypes"})
final SolrRequest request = new CollectionAdminRequest.ClusterStatus();

final NamedList<Object> response = client.request(request);
@SuppressWarnings({"unchecked"})
final NamedList<Object> cluster = (NamedList<Object>) response.get("cluster");
@SuppressWarnings({"unchecked"})
final List<String> liveNodes = (List<String>) cluster.get("live_nodes");

print("Found " + liveNodes.size() + " live nodes");