前言
Solr 是一个基于 Lucene 的搜索平台,而 Lucene 是一个强大的全文搜索引擎库。Solr 使用 Lucene 的倒排索引来实现高效的搜索功能。在 Solr 中,倒排索引由索引和查询两个主要部分组成。
索引
Solr 的索引是倒排索引的实际存储。它包含了文档的词语信息、位置信息和其他相关的统计信息。索引的构建是一个离线的过程,当新文档加入时,Solr 会更新索引以保持数据的一致性。
查询
查询是通过 Solr API 提交的搜索请求。Solr 将查询解析为对倒排索引的检索操作,并返回匹配的文档及其相关信息。查询可以包括搜索关键词、过滤条件、排序规则等。
本次只研究 solr 的倒排索引部分。
Solr 倒排索引的基本组成
Solr 中的倒排索引由以下三部分组成:
- 词典 (Dictionary)
- 倒排列表 (Postings)
- 频率 / 位置信息 (Frequencies/Positions)
词典 (Dictionary)
词典存储着所有文档中出现过的唯一词语,每个词语映射到一个数字化的词 ID。
例如,假设存在以下 3 个文档:
Doc1: solr是一个流行的搜索引擎
Doc2: lucene为solr和elasticsearch提供支撑
Doc3: solr基于Lucene开发
上面例子中的唯一词语有:
solr、 一个、流行、的、搜索、引擎、lucene、提供、支撑、为、和、elasticsearch、基于、开发、是
那么词典中会有:
solr -> 1
一个 -> 2
流行 -> 3
的 -> 4
搜索 -> 5
引擎 -> 6
lucene -> 7
为 -> 8
和 -> 9
elasticsearch -> 10
提供 -> 11
支撑 -> 12
基于 -> 13
开发 -> 14
是 -> 15
词典为去重和数据压缩提供了支持。
倒排列表 (Postings)
倒排列表列出每个词对应的文档集合以及在这些文档中词语的频率。
对于上面的例子,倒排列表如下:
solr -> {Doc1:1, Doc2:1, Doc3:1}
一个 -> {Doc1:1}
流行 -> {Doc1:1}
的 -> {Doc1:1}
搜索 -> {Doc1:1}
引擎 -> {Doc1:1}
lucene -> {Doc2:1, Doc3:1}
为 -> {Doc2:1}
和 -> {Doc2:1}
elasticsearch -> {Doc2:1}
提供 -> {Doc2:1}
支撑 -> {Doc2:1}
基于 -> {Doc3:1}
开发 -> {Doc3:1}
是 -> {Doc1:1}
从上面可以看出,倒排列表告诉我们 solr
这个词出现在 Doc1 和 Doc3 文档中,lucene
出现在 Doc2 和 Doc3 中, 等等。
这种结构非常适合支持文本搜索。例如, 搜索 “solr AND lucene” 可以很容易地从倒排列表中得到匹配文档 Doc2 和 Doc3。
频率和位置信息 (Frequencies/Positions)
Solr 还在倒排列表中存储了词语在文档中出现的频率 (Frequencies),以及出现的位置 (Positions)。
这可以支持语义相似度排名、短语查询等功能。
以 “solr” 为例,其倒排列表如下:
solr -> {
Doc1: {frequency:1, positions:[0]},
Doc2: {frequency:1, positions:[2]},
Doc3: {frequency:1, positions:[0]}
}
表示 “solr” 在 Doc1 中出现了 1 次,位置在第 1 个词语;在 Doc2 中出现 1 次,位置在第 3 个词语;在 Doc3 中出现 1 次,位置在第 1 个词语。
位置信息对支持短语查询非常重要。
Solr 倒排索引结构
Solr 倒排索引的逻辑结构包括索引定义、字段、文档、词条、位置信息以及统计信息。下面详细介绍 Solr 倒排索引每个组成部分。
索引定义 (Index Definition)
索引定义指定了 Solr 索引的基础信息,主要包括:
- 字段名称 (Field Names):要索引的文档字段。
- 字段类型 (Field Types):Solr 支持多种字段类型, 如文本、数字、日期等。
- 字段属性:存储、索引、词条分析器等。汇总了字段应该如何处理。
下面是一个索引定义示例:
<fields>
<field name="id" type="string" indexed="true" stored="true" required="true" />
<field name="product_name" type="text_general" indexed="true" stored="true"/>
<field name="content" type="text_general" indexed="true" stored="false"/>
<field name="price" type="float" indexed="true" stored="true"/>
<field name="publish_date" type="date" indexed="true" stored="true"/>
</fields>
它定义了文档中很多常见的字段。
字段 (Fields)
字段是文档的逻辑单元。不同字段会有不同的处理、存储方式。
例如 product_name 字段表示产品名称,price 表示价格。
字段由字段类型 (field type) 定义其属性。
这部分之前学习过,所以不再做深入了解。
文档 (Documents)
文档是 Solr 要索引的基本数据单元。每个文档会根据字段定义携带不同字段的值。
例如下面是一个文档示例:
{
"id": "1",
"product_name": "Apache Solr 搜索引擎",
"content": "Lucene是Apache下的开源搜索引擎项目"
"price": 99.99,
"publish_date": "2023-11-15"
}
2.4 词条 (Terms)
词条由字段经过分析器处理后得到。一个词条通常代表字段中一个单词或词组。
例如 product_name 字段的值 “Apache Solr” 经过分词可能会得到两个词条 “apache”,“solr”。
词条顺序存储在 倒排列表 的位置信息中。
2.5 位置信息 (Positions)
位置信息表示词条在字段值中出现的位置。
取上面的例子, 如果以空格分词的话,倒排列表信息会记录 “apache” 的位置是 0,“solr” 的位置是 1。
这是支持短语或相邻词语查询的基础。
统计信息 (Statistics)
统计信息存储对索引整体或字段的元数据。例如文档数量、词条数量等整体统计。
这可以让查询快速定位或预估结果大小、调整查询策略。
下面是 Solr Admin UI 显示统计信息的示例:
主要数据包括:
- numDocs - 文档总数
- maxDoc - 最大文档 ID
- deletedDocs - 删除的文档数
- indexHeapUsageBytes - 倒排索引占用的内存字节数
- version - schema 定义的版本
Solr 倒排索引的构建流程
当新增或更新文档到 Solr 时,它们如何转换为倒排索引的?
文档解析
Solr 会先解析文档的格式,支持 XML、JSON 等格式文档。
这一步会验证文档字段是否符合预定义的 schema,提取出索引字段。
下面是一个例子:
{
"id": "1",
"content": "Lucene是Apache下的开源搜索引擎项目"
}
解析验证并提取 content 字段的值进行后续分析。
字段分析
在解析字段值后,会根据字段类型的分析器对其分词、变形,例如文本类字段会分词切分,时序字段转化为日期格式等。
以上面的 content 字段值为例:
-
使用 IKAnalyzer 进行中文分词:
Lucene、是、Apache、下、的、开源、搜索、引擎、项目
-
英文词条化 并 转小写
EnglishAnalyzer 将 “Lucene” 作为一个词条。LowerCaseFilter 将 “Lucene” 转为 “lucene”,“Apache” 转为 “apache”。
-
去停用词
“是”、“下”、“的” 为无意义词条,被移除。
-
词性过滤
保留名词、动词。
-
最终词条列表:
lucene、apache、开源、搜索、引擎、项目
建立词条关联
上一步的字段值变成了词条的流或集合后,Solr 会在词典中查找每个词条,获得其编号词 ID,同时记录下词条对应的字段、文档编号、频率和位置,最后写入到倒排索引相关的倒排列表中。
以“lucene”词条为例,在中文词典中其 ID 为 1,记录其关联信息为:
lucene -> {
Doc1: {frequency:1, positions:[0]}
}
表示 Lucene 在 Doc1 中出现一次,位置在第一个词条。
到这里就建立起了一个正向 (文档 ->词条) 和反向 (词条 ->文档) 的关联。
写入提交
构建倒排索引后要写入文件提交,才能让索引对搜索可见。
提交方式有软提交 (内存中可见) 和硬提交(持久化文件)。
Solr 倒排索引的维护
当文档发生新增、删除、修改时,Solr 倒排索引也需要同步变更。常见维护方式有:
重建索引
重新为所有文档构建整个索引,适合批量变更。但是比较耗时。
curl http://localhost:8983/solr/{collection_name}/dataimport?command=full-import
这将触发 Solr 数据导入进程,重建整个索引。
4.2 增量更新
单文档级别增量更新索引。增加或重建该文档相关所有词条的索引。
curl http://localhost:8983/solr/{collection_name}/update?commit=true -d '
[
{ "id": "1", "content": "Lucene是Apache下的开源搜索引擎项目222" }
]'
通过指定文档 ID 和要更新的字段值,可以进行增量更新。这里使用 commit=true
立即提交更改(硬提交)。
软提交 / 硬提交
软提交可以加快文档可搜索,但仍在内存中,并且频繁软提交会增大内存。定期执行硬提交可以持久化到文件。
软提交:
curl http://localhost:8983/solr/{collection_name}/update?softCommit=true -d '
[
{ "id": "1", "content": "Lucene是Apache下的开源搜索引擎项目222" }
]'
硬提交:
curl http://localhost:8983/solr/{collection_name}/update?commit=true -d '
[
{ "id": "1", "content": "Lucene是Apache下的开源搜索引擎项目222" }
]'
主从同步
SolrCloud 集群会有 1 个 Master 主节点提供索引修改服务,以及多个 Slave 从节点提供文档搜索服务。
主节点构建完新的索引后可以异步复制到所有从节点。
索引段合并 (Segment Merging)
搜索优化器可以在后台自动或定期将小的索引段合并成更大的段。这可以提高搜索效率。
也可以手动触发:
curl http://localhost:8983/solr/{collection_name}/update?optimize=true
Solr 倒排索引的优化
词典优化
词典存储结构对内存占用影响大,Solr 有针对性的优化措施:
- 词典数值编解码:将字符串转为更小的整型 ID。
- 固定长度编码:null 字符补全控制词典 entries 大小。
- 分段存储:分表减少单机内存压力。
词典数值编解码
curl http://localhost:8983/solr/{collection_name}/schema -d '
{
"add-field-type" : {
"name":"text_encoded",
"class":"solr.TextField",
"positionIncrementGap":"100",
"analyzer" : {
"tokenizer":{
"class":"solr.WhitespaceTokenizerFactory"
},
"filters":[{
"class":"solr.NumericTokenFilterFactory",
"minDigits":1,
"maxDigits":10
}]
}
},
"add-field":{
"name":"example_field",
"type":"text_encoded",
"stored":true
}
}'
这个例子中,定义了一个新的字段类型 text_encoded
,使用了 NumericTokenFilterFactory
对字段进行数值编解码。
固定长度编码
在 Solr 中,可以通过配置字段的 docValuesFormat
属性来进行固定长度编码。具体配置参考 Solr 文档。
分段存储
Solr 在内部自动管理索引的分段存储。可以通过触发索引优化操作,例如前面提到的 optimize
命令,来合并小的索引段。
索引压缩
对索引和频率 / 位置的编码存储可以大幅减少磁盘占用。每种数据结构选择不同的编码算法。
分段合并
将小的索引分片合并成大分片, 可以减少字典重复, 也提高查询效率。
缓存
Solr 提供文档、过滤器、查询结果的缓存,可以避免大量的查询和磁盘 IO。开启适当缓存可以大幅提升 Solr 检索速度。
常见缓存类型有:
- 文档缓存(Document Cache):
作用: 存储检索到的文档,以避免在执行相同查询时重新检索相同的文档。
使用场景: 适用于经常被查询的文档,可以有效减少对底层存储的频繁访问。 - 过滤器缓存(Filter Cache):
作用: 存储已计算过的过滤器,以便在多次查询中重复使用,提高过滤操作的性能。
使用场景: 适用于频繁使用相同过滤器的查询,例如,相同的筛选条件被多次使用。 - 查询结果缓存(Query Result Cache):
作用: 存储查询的结果集,以避免在多次执行相同查询时重新计算结果。
使用场景: 适用于经常执行相同查询的情况,可以显著减少相同查询的响应时间。 - 分段缓存(Segment Cache):
作用: 存储分段信息,以避免在查询时重新加载相同的分段。
使用场景: 适用于索引分段比较大,而且相同分段被多次查询的情况。分段缓存可以帮助提高查询性能。
Solr 缓存的配置可以在 solrconfig.xml
文件中进行,例如开启文档缓存:
<!-- solrconfig.xml -->
<config>
<!-- 配置查询结果缓存 -->
<queryResultCache class="solr.LRUCache" size="1024" initialSize="512" autowarmCount="256"/>
<!-- 配置文档缓存 -->
<documentCache class="solr.LRUCache" size="1024" initialSize="512" autowarmCount="256"/>
<!-- 配置过滤器缓存 -->
<filterCache class="solr.LRUCache" size="1024" initialSize="512" autowarmCount="256"/>
<!-- 配置分段缓存 -->
<queryResultMaxDocsCached>500</queryResultMaxDocsCached>
<queryResultWindowSize>100</queryResultWindowSize>
<queryResultMaxRamMB>50</queryResultMaxRamMB>
</config>
并行 / 分布式搜索
并行搜索和分布式搜索也是重要的优化手段。
例如,将大索引切分为独立分片,分别存储和搜索。或者跨服务器并行搜索聚合结果。
Solr 倒排索引的高级特性
Solr 倒排索引有一些高级特性,可以建立更加智能和复杂的索引。先简单了解一下。
动态字段
动态字段根据文档值的不同特性, 将其自动映射到预定义的字段类型上。这可以大幅简化 schema 定义,降低字段爆炸问题。
字段爆炸(Field Explosion) 是指索引中的一个字段包含了大量的重复或冗余信息,导致索引文件变得非常庞大,可能影响检索性能和占用大量存储空间。字段爆炸通常发生在将一个文本域的内容进行复制到多个其他字段时,或者当某个字段中包含大量重复的文本内容时。
动态字段的使用可以降低字段爆炸问题,主要有以下几个原因:
- 动态映射: 动态字段允许 Solr 自动映射文档中未事先定义的字段。这意味着,如果文档中的字段名满足动态字段的匹配规则,那么 Solr 将自动为这些字段选择合适的字段类型。这种灵活性减少了在 schema 中显式定义每个可能出现的字段的需要,避免了一开始就预定义所有可能的字段。
- 减少手动维护: 在传统的 schema 中,如果有大量字段需要处理,就需要手动定义每个字段及其类型。这容易导致“字段爆炸”问题,即 schema 变得庞大、不易维护。动态字段的使用使得可以通过模式中的一些通用规则自动处理新字段的映射,减轻了手动维护的负担。
- 降低存储开销: 动态字段的灵活性意味着不必为每个可能的字段定义类型。当文档中包含大量字段时,如果采用动态字段,可以避免显式定义所有这些字段,从而降低了存储开销。
总体而言,动态字段的使用降低了在 schema 中维护大量字段的复杂性,使得 Solr 在面对动态数据结构时更为灵活,减轻了对 schema 设计的负担,同时有助于避免因字段爆炸而导致的存储和管理问题。
例子:
<!-- schema.xml -->
<schema name="example" version="1.5">
<!-- 定义动态字段 -->
<dynamicField name="*_i" type="int" indexed="true" stored="true"/>
<dynamicField name="*_f" type="float" indexed="true" stored="true"/>
</schema>
在上述示例中,*_i
动态字段会被映射为整型,而 *_f
动态字段会被映射为浮点数。
复制字段
同一个文档字段可以映射到多个字段类型。这称为复制字段。这样一个字段可以同时获得多个特性。
例子:
<!-- schema.xml -->
<schema name="example" version="1.5">
<!-- 定义复制字段 -->
<field name="source_field" type="text_general" indexed="true" stored="true"/>
<field name="destination_field" type="text_general" indexed="true" stored="true"/>
<!-- 将 source_field 复制到 destination_field -->
<copyField source="source_field" dest="destination_field"/>
</schema>
在上述示例中,<copyField>
元素指定了将 source_field
的内容复制到 destination_field
中。这样,destination_field
就包含了与 source_field
相同的内容。
复制字段的主要好处在于:
- 多字段搜索: 复制字段允许你在同一文档中使用多个字段,每个字段可以使用不同的分析器或字段类型。这有助于在搜索时更灵活地匹配不同的条件。
- 存储不同版本的字段: 你可以使用复制字段来存储同一文本的不同版本,例如,一个经过全文搜索分析的版本和一个存储原始内容的版本。
- 提高检索性能: 将同一信息复制到不同字段中,可以在不同的场景下提高检索性能。例如,你可能希望在一个字段上使用较为精确的匹配,而在另一个字段上使用模糊搜索。
- 避免字段爆炸: 在某些情况下,文档中可能包含大量的信息,而复制字段可以帮助减轻字段爆炸问题,使得每个字段的内容更为精炼。
语言检测
Solr 有内置自动语言检测功能, 可以自动识别各种语言文本。
这可以根据语言选择不同的分词器、停用词、同义词等特性。
单词拆分
文本字段可以支持把单词拆分为单字。这对处理复合词和中文很有帮助。
- 处理复合词: 在中文中,很多词是由多个字符组成的合成词,而不像英文一样使用空格分隔。例如,“搜索引擎”是一个中文合成词。如果不进行拆分,整个合成词可能无法被正确地搜索和匹配。
- 支持中文搜索: 中文是一个没有空格分隔单词的语言,因此在搜索和分析中文文本时,拆分单字可以帮助构建更精确的索引。这使得在中文文本中能够准确匹配单个字符的查询,从而提高搜索的准确性。
- 灵活匹配和拼写检查:
灵活匹配: 拆分为单字后,搜索引擎可以更灵活地匹配部分单词,使得搜索结果更加全面。
拼写检查: 单字拆分可以作为拼写检查的基础,支持用户输入的错别字或拼写错误的情况,提供更好的搜索体验。
需要配置 WordBreakSolrFilterFactory 的 splitOnCaseChange 参数。
<!-- schema.xml -->
<schema name="example" version="1.5">
<!-- 配置 IK 分词器的字段类型 -->
<fieldType name="text_ik" class="solr.TextField">
<analyzer>
<!-- 使用 IK 分词器 -->
<tokenizer class="org.wltea.analyzer.solr.IKTokenizerFactory"/>
</analyzer>
</fieldType>
<!-- 配置拆分单字的字段类型 -->
<fieldType name="text_chinese" class="solr.TextField">
<analyzer>
<tokenizer class="solr.WhitespaceTokenizerFactory"/>
<filter class="solr.WordBreakSolrFilterFactory" splitOnCaseChange="1"/>
</analyzer>
</fieldType>
</schema>
NGram 索引
Solr 有内置的 NGram 过滤器, 可以把单词拆分为所有可能的 N 字词组。
NGram 索引是一种在索引中存储单词的连续子字符串的技术。具体而言,NGram 是由原始单词中连续出现的 N 个字符组成的子字符串。例如,在单词 “Solr” 中,2-Gram 就是 [“So”, “ol”, “lr”],3-Gram 就是 [“Sol”, “olr”]。
NGram 索引的主要好处在于:
- 支持词组搜索: NGram 索引可以支持更灵活的词组搜索。例如,在一个 NGram 索引中,单词 “search” 可能被索引为 [“se”, “ea”, “ar”, “rc”, “ch”]。这样,当用户搜索 “arch” 时,也可以匹配到 “search”。
- 支持模糊搜索: NGram 索引可以用于实现模糊搜索,即在用户拼写错误或输入不完整的情况下,仍然能够找到相关的结果。通过匹配 NGram,搜索引擎可以找到相似的单词。
- 支持拼写纠正: NGram 索引是构建拼写纠正系统的基础。通过比较输入单词的 NGram 与索引中存储的 NGram,可以找到最相似的单词,从而进行拼写纠正。
- 提高灵活性: NGram 索引可以提高搜索的灵活性,特别是在处理不同语言、用户输入错误等情况时。它有助于更全面地匹配用户的搜索意图。
- 解决中文语言中的分词问题: 对于中文语言,NGram 索引可以作为一种方式来解决分词问题。通过将中文文本转换成 NGram,可以支持更灵活的检索。
需要注意的是,NGram 索引可能会增加索引的大小,并且在某些情况下可能导致更多的存储开销。因此,使用 NGram 索引时需要谨慎权衡存储需求和搜索性能的平衡。
相关度排名
相关度排名(Relevance Ranking)是搜索引擎在执行用户查询时,根据检索到的文档与查询的相关性对搜索结果进行排序的过程。相关度排名的目标是将最相关的文档排在搜索结果的前面,以提供用户最相关的信息。常见的 TF-IDF 算法用于根据索引统计信息计算查询与结果匹配的相关度分值。
相关度排名的作用和好处:
-
提高搜索结果的质量: 相关性排名旨在将最相关的文档放在搜索结果的前面,从而提高搜索结果的质量。用户更有可能在前面的搜索结果中找到满足其需求的信息。
-
优化用户体验: 通过将最相关的结果排在前面,搜索引擎提供了更直观、更易于浏览的搜索结果列表,从而优化了用户的搜索体验。
-
增加搜索效率: 相关性排名有助于用户更快速地找到他们感兴趣的内容,减少了浏览搜索结果的时间,提高了搜索的效率。
-
个性化搜索结果: 一些搜索引擎采用个性化排名策略,根据用户的搜索历史、行为和兴趣,调整搜索结果的相关性排名,以更好地满足用户的个性化需求。
-
支持复杂的查询: 相关性排名不仅仅根据关键词匹配,还考虑了文档中词语的重要性、查询中词语的权重等因素,从而支持更复杂、更准确的查询。
SolrCloud 分布式倒排索引
SolrCloud 支持在集群环境下建立分布式索引和搜索。
分片 (Shard)
SolrCloud 可以将大索引切分为多个逻辑分片,每个分片是一个独立的 Lucene 索引。这样可以将索引数据分布到不同的节点上,提高搜索和索引的并行性。
好处:
- 提高搜索和索引的并行性,加速查询和更新操作。
- 分片可以水平扩展,使得系统能够处理更大规模的数据。
副本 (Replica)
分片可以配置多个副本,存储到不同节点上,实现数据的冗余备份。 Leader 和 Replica 通过 Zookeeper 协调更新。
好处:
- 提高系统的可用性和容错性,防止单点故障。
- 加速搜索操作,允许并行处理查询请求。
文档路由
SolrCloud 根据路由算法将文档分配到不同分片,以保证负载均衡。路由算法可以是 Hash 取模、CompositeKey 等。
好处:
- 避免单个分片负载过重,提高系统整体性能。
- 通过合理的文档路由,支持更灵活的数据分布。
分布式搜索
搜索请求可以并行发到多个分片,然后在端节点收集、合并结果。分布式搜索提高了搜索操作的并发性和响应速度。
好处:
- 提高搜索性能,缩短搜索响应时间。
- 通过并行搜索,支持处理大规模索引。
示例:
发送查询请求时,SolrCloud 会将请求分发到多个分片上执行搜索操作,然后汇总结果返回给客户端。
主从切换
当主分片失效时,SolrCloud 会自动在副本中选择新的 Leader 分片。这确保了系统在面临硬件故障或其他问题时的可用性。
好处:
- 提高系统的容错性,防止单点故障。
- 在节点宕机或主分片不可用时,系统可以继续提供服务。
SolrCloud 倒排索引更新
分布式环境下倒排索引的更新也是增量式完成的。
主分片建立索引
- 当有新文档需要加入索引时,更新请求首先发送到主分片的 Leader 节点。
- Leader 负责在本地建立新文档的索引。
异步复制到副本
- Leader 通过 Zookeeper 向所有的 Follower 副本发送新索引的复制请求。
- Follower 副本接收到请求后,通过网络异步复制 Leader 的索引文件,以保持副本与 Leader 的一致性。
提交生效
- Leader 向 Zookeeper 发送总的提交请求。
- Zookeeper 确认所有副本都已成功复制并准备好生效后,通知 Leader 进行提交。
- Leader 和 Follower 副本同时提交,使新索引生效。
客户端请求重试
- 如果客户端的请求在某个节点失败,客户端可以自动重试请求到其他可用的副本节点。
- SolrCloud 通过 Zookeeper 确保客户端请求能够路由到可用的副本,实现高可用性。
总体流程:
- 更新请求: 客户端发送索引更新请求到主分片的 Leader 节点。
- 主分片建立索引: Leader 在主分片的本地 Lucene 索引中建立新文档的倒排索引。
- 异步复制到副本: Leader 将更新后的索引通过 Zookeeper 异步复制到所有的副本。
- 提交生效: 当所有副本都确认接收并准备好时,Leader 向 Zookeeper 发送总的提交请求,Leader 和 Follower 副本同时提交,使新索引生效。
- 客户端请求重试: 如果客户端的请求在某个节点失败,客户端可以自动重试请求到其他可用的副本节点,保证高可用性。
Solr 倒排索引调优
在大数据场景下, 合理配置 Solr 倒排索引可以获得更好的写入和查询性能。
写入优化
- 批量写入: 尽量将写入操作组织成批量写入,减少网络开销和提交次数,提高写入性能。
- 控制软提交频率: 适当控制软提交的频率,过于频繁的软提交会增加内存开销。可以根据实际需求合理设置软提交的时间间隔。
- 合理设置副本数: 需要权衡副本数和同步开销。增加副本数可以提高系统的可用性,但同时也会增加数据同步的开销。根据业务需求和性能要求选择合适的副本数。
- 选择基数较高的字段作为文档路由键: 可以减少热点分片,确保数据均匀分布,提高写入性能。
查询优化
- 为搜索频繁的字段设置 docValues 格式: 使用 docValues 加速对字段的访问,特别是在排序和分组等操作中。
docValues
是 Solr 中的一种存储结构,它使得在不加载完整文档的情况下,能够更高效地执行排序、分组等操作。 - 设置合理的分片大小: 分片太小会降低搜索效率,而分片太大会增加索引时间。选择适当的分片大小以平衡索引构建和搜索性能。
- 缓存热点查询、过滤器、分段: 通过适当配置缓存,可以显著降低 Solr 检索时间,提高查询性能。
- 开启分布式搜索: 在不需要完全精度的情况下,可以开启分布式搜索,通过并行搜索提高查询速度。
- 对数值类型字段设置索引精度(step): 针对数值类型字段,设置索引精度可以减少索引的大小,提高查询性能。
硬件优化
- 使用 SSD 替换机械硬盘: SSD 具有更高的 IO 效率,可以加速索引和查询的读写操作。
- 加大内存: 增加节点的内存,可以缓存更多的索引和查询结果,提高检索性能。
- 分配更多 CPU: Solr 倒排索引构建可以并行计算,通过增加 CPU 的数量,可以提高索引构建的速度。
- 升级到 10Gbps 以上网卡: 提高节点间通信的带宽,加速数据同步和分布式搜索的效率。
结果 relevance 优化
结果的 relevance 优化指的是通过调整搜索引擎的相关度算法和参数,以提高检索结果的相关性,使得搜索结果更符合用户的期望。在搜索引擎中,相关度是衡量搜索结果与用户查询意图匹配程度的度量。
相关度的重要性:
- 用户体验: 更相关的搜索结果能够更快速地满足用户的需求,提高用户的满意度和搜索体验。
- 搜索效率: 通过调整相关度参数,可以优化搜索引擎的性能,减少不必要的计算和检索开销。
相关度的优化方法:
- 查询解析器的选择: 选择合适的查询解析器,根据查询的特点和需求选择适当的解析器,以确保用户输入的查询能够正确解析并产生相关度高的搜索结果。
- 权重调整: 调整字段的权重,赋予某些字段更高的权重值,以便更注重这些字段的匹配程度。
- 相似度算法: 选择或定制合适的相似度算法,例如 TF-IDF、BM25 等,以更准确地计算文档与查询之间的相似性。
- 停用词和同义词处理: 处理停用词,过滤掉那些在搜索中无意义的常见词汇,同时处理同义词,使得搜索引擎能够理解用户查询中的近义词,提高匹配度。
- 字段 Boosting: 通过字段 boosting,提高某些字段在计算相关度时的权重,使得这些字段更具影响力。
- 分析用户行为: 分析用户的搜索历史、点击行为等,根据用户的偏好调整相关度参数,实现个性化搜索结果。
- 实时反馈: 结合用户反馈信息,例如点击率、满意度等,实时调整相关度参数,使得搜索引擎能够不断学习和优化。
拓展
为什么 docValues 格式能够更高效地执行排序、分组等操作。
docValues
是 Solr 中一种存储字段值的数据结构,它在执行排序、分组等操作时能够更高效,主要是因为 docValues
的特性和存储方式。
以下是一些 docValues
能够提高排序、分组等操作效率的原因:
- 离线排序:
docValues
存储了每个文档的字段值,并且对这些值进行了离线排序。这意味着在排序时,不再需要实时计算和排序字段值,而是可以直接使用预先排序好的值,提高排序的速度。 - 列存储结构:
docValues
以一种列存储的方式存储数据,而非传统的行存储。对于排序、分组等操作,列存储结构能够更快地定位和读取所需的字段值,减少了不必要的 IO 操作。 - 跳表(Skip List)索引:
docValues
在存储时使用了跳表索引结构,这种结构可以快速定位某个值在数组中的位置,提高了检索效率。这对于排序和分组等操作尤为重要。 - 不加载完整文档:
docValues
可以在不加载完整文档的情况下,仅加载排序、分组等操作所需的字段值,减少了内存开销和计算成本。 - 支持多值字段:
docValues
对多值字段的支持也比较好,可以有效地处理文档中包含多个字段值的情况,对于排序和分组等操作提供了更灵活的处理方式。
评论区