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.7.0"/>
对于使用 Maven 构建的项目,请将以下内容放入您的 pom.xml
中
<dependency>
<groupId>org.apache.solr</groupId>
<artifactId>solr-solrj</artifactId>
<version>9.7.0</version>
</dependency>
对于使用 Gradle 构建的项目,请将以下内容放入您的 build.gradle
中
compile group: 'org.apache.solr', name: 'solr-solrj', version: '9.7.0'
如果您想使用 CloudSolrClient
*并且* 希望它直接与 ZooKeeper 通信,则需要添加对 solr-solrj-zookeeper
工件的依赖。
如果您在 Java 代码中不使用 流式表达式 类,则可以排除 solr-solrj-streaming
依赖项。
手动将 SolrJ 添加到类路径
如果您不使用上述任何一种构建系统,仍然可以很容易地将 SolrJ 添加到您的构建中。
在构建时,只需要 SolrJ jar 本身:solr-solrj-9.7.0.jar
。要手动编译使用 SolrJ 的代码,请使用类似于以下的 javac
命令
javac -cp .:$SOLR_TIP/server/solr-webapp/webapp/WEB-INF/lib/solr-solrj-9.7.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 通信的工作,并且是用户配置的主要场所。
请求以 SolrRequests
的形式发送,并以 SolrResponses
的形式返回。
SolrClient 的类型
SolrClient
有几个具体的实现,每个都针对不同的使用模式或弹性模型。
-
HttpSolrClient
- 适用于以查询为中心的工作负载,但也适用于通用客户端。直接与单个 Solr 节点通信。 -
Http2SolrClient
- 异步、非阻塞和通用的客户端,使用 Jetty Http 库利用 HTTP/2。 -
HttpJdkSolrClient
- 使用 JDK 内置 Http 客户端的通用客户端。支持 Http/2 和 Http/1.1。支持异步。目标是那些希望最小化应用程序依赖项的用户。 -
LBHttpSolrClient
- 在 Solr 节点列表中平衡请求负载。根据节点健康状况调整“服务中”节点列表。 -
LBHttp2SolrClient
- 与LBHttpSolrClient
类似,但使用Http2SolrClient
代替,并使用 Jetty Http 库。 -
CloudSolrClient
- 适用于与 SolrCloud 部署通信。使用已记录的 ZooKeeper 状态来发现并将请求路由到健康的 Solr 节点。 -
ConcurrentUpdateSolrClient
- 适用于以索引为中心的工作负载。在将较大的批次发送到 Solr 之前,在内部缓冲文档。 -
ConcurrentUpdateHttp2SolrClient
- 与ConcurrentUpdateSolrClient
类似,但使用Http2SolrClient
代替,并使用 Jetty Http 库。
常用配置选项
大多数 SolrJ 配置发生在 SolrClient
级别。下面讨论其中最常见/最重要的配置。有关如何调整 SolrClient
的全面信息,请参阅所涉及客户端及其相应的构建器对象的 Javadoc。
基本 URL
大多数 SolrClient
实现(CloudSolrClient
和 Http2SolrClient
除外)要求用户指定一个或多个 Solr 基本 URL,客户端随后使用这些 URL 将 HTTP 请求发送到 Solr。用户在提供的基本 URL 中包含的路径会对从那时起创建的客户端的行为产生影响。
-
路径指向特定核心或集合的 URL(例如,
http://hostname:8983/solr/core1
)。当在基本 URL 中指定核心或集合时,使用该客户端发出的后续请求无需在每个请求中重新指定受影响的集合。但是,使用这种形式 URL 的客户端只能用于向 URL 中包含的核心或集合发送请求。“管理”(即非核心)请求,或使用客户端向其他索引发出的请求将失败。由于这些限制,不建议使用这种类型的路径,并且将在 Solr 10 中删除。鼓励用户在创建客户端时提供“根”基本 URL(请参阅下文),并使用相关SolrClient
构建器对象上可用的withDefaultCollection(String)
方法指定核心。 -
指向根 Solr 路径的 URL(例如,
http://hostname:8983/solr
)。希望指定默认集合的用户可以使用相关SolrClient
构建器对象上可用的withDefaultCollection(String)
方法来指定。
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 工作最可靠和性能最高的方式。另一方面,这意味着将 ZooKeeper 更广泛地暴露给 Solr 节点,这是一种安全风险。它还会添加更多的 JAR 依赖项。
超时
所有 SolrClient
实现都允许用户指定与 Solr 通信的连接和读取超时。这些在客户端创建时提供,如下例所示
final String solrUrl = "https://127.0.0.1:8983/solr";
return new HttpSolrClient.Builder(solrUrl)
.withConnectionTimeout(10000, TimeUnit.MILLISECONDS)
.withSocketTimeout(60000, TimeUnit.MILLISECONDS)
.build();
当未显式提供这些值时,SolrJ 会回退到使用操作系统/环境运行时的默认值。
ConcurrentUpdateSolrClient
及其对应项 ConcurrentUpdateHttp2SolrClient
也实现了一个暂停防止超时,该超时允许无响应节点的请求比等待套接字超时更快地失败。此超时的默认值设置为 15000 毫秒,并且可以通过系统属性 solr.cloud.client.stallTime
进行调整。此值应小于 solr.jetty.http.idleTimeout
(默认为 120000 毫秒)并且大于最大更新请求的处理时间。
云请求路由
SolrJ CloudSolrClient
实现 (CloudSolrClient
和 CloudHttp2SolrClient
) 遵循 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 提供的 UpdateResponse
和 QueryResponse
接口很有用,但通常更方便使用应用程序更容易理解的特定于域的对象。值得庆幸的是,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 非常简单,只需找到适当的请求对象,提供任何必要的参数,并将其传递给 SolrClient
的 request()
方法。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");