先学概念🙌:Solr是一个开源的、基于Lucene的全文搜索服务器,它提供了一个强大的搜索平台,支持分布式索引、复制和负载均衡,以及强大的全文搜索功能。
Solr使用Java语言开发,它扩展了Lucene的功能,提供了更丰富的查询语言,并且对查询性能进行了优化。Solr还提供了一个基于Web的管理界面,使得管理和监控搜索服务变得简单。
说人话😭:就是一个用Java开发的搜索服务器
基本知识
Core
Solr中的Core是索引和搜索的容器,Solr中的Core相当于数据库中的表。可以通过Solr管理界面或命令行创建Core。
每个Core都有自己的配置文件,包括schema.xml和solrconfig.xml。这些文件定义了索引的结构和搜索行为。
文档
在Solr中,文档(Document)是索引的基本单位,它代表了要被搜索和检索的数据。每个文档由多个字段(Fields)组成,每个字段可以包含一个或多个值。
举个例子😜:
{
"id": "123456",
"title": "Solr入门教程",
"content": "Solr是一个强大的搜索引擎,它基于Lucene构建,提供了丰富的查询功能和高效的索引性能。",
"author": "张三",
"publish_date": "2023-04-01",
"category": ["技术", "搜索"],
"views": 100,
"tags": ["Solr", "Lucene", "搜索技术"]
}
在Solr中,每个字段都需要在schema.xml文件中定义,包括字段的名称、类型、是否可索引、是否可存储等属性。例如,title 字段可能被定义为:
<field name="title" type="text_general" indexed="true" stored="true"/>
什么意思呢😭😭😭?
看字段,猜功能😜:这表示title字段是一个文本类型,可以被索引和存储。在实际应用中,文档的内容会根据业务需求和搜索需求来设计,字段的类型和属性也会相应调整以优化搜索性能和用户体验。
索引
在Solr中,索引是存储和组织文档数据的核心结构,它使得快速搜索成为可能。
索引是怎么创建的呢?
包含两个阶段:
- 字段分析:在文档被添加到索引之前,Solr会对文档中的文本字段进行分析。分析过程包括分词、去除停用词、词干提取等,以便于搜索时能够匹配到正确的文档。
- 倒排索引:分析后的文本会被构建成倒排索引。这是一种数据结构,它记录了每个词(或分词后的结果)在哪些文档中出现。倒排索引使得Solr能够快速定位到包含特定搜索词的文档。
倒排索引(Inverted Index)之所以被称为“倒排”,是因为它的结构与传统的正排索引(Forward Index)相反。在正排索引中,数据是按照文档的顺序组织起来的,每个文档都有一个或多个索引项,指向文档中包含的特定内容。例如,在一本书的索引中,每个条目都会指向书中包含该条目内容的页面。
原始Solr查询
写接口:
List<String> searchWordAssociate(WordAssociateSearchParam param);
实现接口:
对入参封装成JsonQueryRequest,然后获取SolrClient实例,执行查询获取Solr文档
QueryResponse queryResponse = request.process(solrClient);
SolrDocumentList list = queryResponse.getResults();
对中标单位聚合:
request.withFacet("categories", map);
map中设置了分面的明细:
// 创建一个分面,按中标单位分组
map.put("field", BusinessLibrarySolrConstants.BIDDER);
// 设置分面的其他参数
map.put("limit", 3000);
map.put("numBuckets", true);
// 创建一个facetMap,用于指定分面的排序方式
HashMap<String, Object> facetMap = new HashMap<>();
facetMap.put("updateTimeSort", "max(" + BusinessLibrarySolrConstants.UPDATE_TIME + ")");
// 将facetMap添加到map中,作为分面的配置
map.put("facet", facetMap);
// 将配置好的分面添加到请求中
request.withFacet("categories", map);
获取分面结果进行解析处理:
NestableJsonFacet facet = businessLibraryUtil.sendRequest(request, 16);
List<BucketJsonFacet> buckets = facet.getBucketBasedFacets(CommonConstants.CATEGORIES).getBuckets();
BucketJsonFacet
BucketJsonFacet 是在Solr的JSON分面查询结果中使用的一个结构,它代表了分面查询的一个桶(bucket)。
例子:包含子聚合分面的
{
"label": "分类A",
"count": 100,
"value": "分类A",
"subFacets": [
{
"label": "子分类A1",
"count": 50
},
{
"label": "子分类A2",
"count": 50
}
]
}
进阶Solr查询:构造者模式
使用构造者模式简化创建 JsonQueryRequest 对象的过程
新建JsonQueryRequestBuilder对象,继承AbstractRequestBuilder
三个泛型参数:
JsonQueryRequestBuilder:这是构建器类本身JsonQueryRequest:这是构建器将要构建的对象的类型JsonQueryRequestExecuteService:这是执行服务的类型。执行服务的类型是负责实际处理和发送由JsonQueryRequestBuilder构建的请求的组件JsonQueryRequest
public class JsonQueryRequestBuilder extends AbstractRequestBuilder<JsonQueryRequestBuilder, JsonQueryRequest, JsonQueryRequestExecuteService> {
怎么使用?一个例子入门:
public class SearchService {
public SearchResultDto<BusinessLibraryProjectDto> simpleSearchExample() {
// 使用链式调用构建查询请求
return JsonQueryRequestBuilder.builder()
// 设置查询字符串
.q("your search query here") // 替换为你的查询字符串
// 添加项目类型过滤器
.withFilterField(BusinessLibrarySolrConstants.PROJECT_TYPE, 6, Arrays.asList(1, 2))
// 添加其他必要的过滤器或参数
// ...
// 执行查询
.execute(16) // 假设solrType为16
.success();
}
}
分析:先使用JsonQueryRequestBuilder的静态方法builder()来构建JsonQueryRequestBuilder的实例,然后链式调用即可
.execute(16)后会默认执行JsonQueryRequestExecuteService的执行方法,并且返回JsonQueryRequestExecuteService,接着执行.success()即是JsonQueryRequestExecuteService的success
游标查询
首先游标初始化:
String cursorMark = CursorMarkParams.CURSOR_MARK_START;//游标初始化
写一个标志变量用来控制循环的执行:
boolean done = false;
进入循环,开始查询:
boolean done = false;
while (!done) {
solrQuery.set(CursorMarkParams.CURSOR_MARK_PARAM, cursorMark); // 变化游标条件
try {
QueryResponse rsp = solrClient.query(solrQuery, SolrRequest.METHOD.POST); // 执行多次查询读取
} catch (Exception e) {
log.error("游标查询solr项目信息异常", e);
throw new ContactsSearchException(ContactsSearchExceptionCode.SEARCH_CURSOR_MARK_ERROR);
}
String nextCursorMark = rsp.getNextCursorMark(); // 获取下次游标
// 做一些业务处理代码
// 如果两次游标一样,说明数据拉取完毕,可以结束循环了
if (cursorMark.equals(nextCursorMark)) {
done = true;
}
cursorMark = nextCursorMark;
}