Solr搜索引擎

202 阅读5分钟

先学概念🙌:Solr是一个开源的、基于Lucene的全文搜索服务器,它提供了一个强大的搜索平台,支持分布式索引、复制和负载均衡,以及强大的全文搜索功能。

Solr使用Java语言开发,它扩展了Lucene的功能,提供了更丰富的查询语言,并且对查询性能进行了优化。Solr还提供了一个基于Web的管理界面,使得管理和监控搜索服务变得简单。

说人话😭:就是一个用Java开发的搜索服务器

基本知识

Core

Solr中的Core是索引和搜索的容器,Solr中的Core相当于数据库中的表。可以通过Solr管理界面或命令行创建Core。

每个Core都有自己的配置文件,包括schema.xmlsolrconfig.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中,索引是存储和组织文档数据的核心结构,它使得快速搜索成为可能。

索引是怎么创建的呢?

包含两个阶段:

  1. 字段分析:在文档被添加到索引之前,Solr会对文档中的文本字段进行分析。分析过程包括分词、去除停用词、词干提取等,以便于搜索时能够匹配到正确的文档。
  2. 倒排索引:分析后的文本会被构建成倒排索引。这是一种数据结构,它记录了每个词(或分词后的结果)在哪些文档中出现。倒排索引使得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()即是JsonQueryRequestExecuteServicesuccess

游标查询

首先游标初始化:

 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;
 }