本章内容
如何使用不同的评分公式及其特性。
如何使用不同的倒排表格式及其特性。
如何处理准实时搜索、实时读取,以及搜索器重新打开之后发生的动作。
深人理解多语言数据处理。
配置搜索事务日志以满足应用需求,并查看它对部署的影响。
段合并、各种索引合并策略和合并调度方式。
3.1 改变Apache Lucene的评分方式
3.1.1 可用的相似度模型
Lucene新增了以下三种相似度模型可供使用:
- Okapi BM25模型:这是一种基于概率模型的相似度模型,可用于估算文档与给 定查询匹配的概率。为了在ElasticSearch中使用它,你需要使用该模型的名字, BM25。一般来说,Okapi BM25模型在短文本文档上的效果最好,因为这种场景中重复词项对文档的总体得分损害较大。
- 随机偏离(Divergence from randomness)模型:这是一种基于同名概率模型的相似度模型。为了在ElasticSearch中使用它,你需要使用该模型的名字,DFR。一般来说,随机偏离模型在类似自然语言的文本上效果较好。
- 基于信息的(Information based )模型:这是最后一个新引人的相似度模型,与随机偏离模型类似。为了在ElasticSearch中使用它,你需要使用该模型的名字,IB。同样,IB模型也在类似自然语言的文本上拥有较好的效果。
3.1.2 为每字段配置相似度模型
我们希望的是,在name字段和contents字段中使用BM25相似度模型。为了实现这 个目的,我们需要扩展当前的字段定义,即添加similarity
字段,并将该字段的值设置为相应的相似度模型的名字:
{
"mappings": {
"post": {
"properties": {
"id": {
"type": "long",
"store": "yes",
"precision_step": "0"
},
"name": {
"type": "string",
"store": "yes",
"index": "analyzed",
"similarity": "BM25"
},
"contents": {
"type": "string",
"store": "no",
"index": "analyzed",
"similarity": "BM25"
}
}
}
}
}
3.2 相似度模型配置
以下代码定义了一个 名为mastering_similarity的新的相似度模型,它基于默认的TF/IDF相似度模型。接着将它的discount_overlaps属性值设置为false,指定该相似度模型用于name字段。
{
"settings": {
"index": {
"similarity": {
"mastering_similarity": {
"type": "default",
"discount_overlaps": false
}
}
}
},
"mappings": {
"post": {
"properties": {
"id": {
"type": "long",
"store": "yes",
"precision_step": "0"
},
"name": {
"type": "string",
"store": "yes",
"index": "analyzed",
"similarity": "mastering_similarity"
},
"contents": {
"type": "string",
"store": "no",
"index": "analyzed"
}
}
}
}
}
3.2.2 配置被选用的相似度模型
每个新增的相似度模型都可以根据用户需求进行配置,而ElasticSearch还允许用户不 加配置地直接使用default和BM25相似度模型。因为它们是预先配置好的,而DFR和IB模型则需要进一步配置才能使用。
具体相似度模型配置的参数略,包括:
配置TF/IDF相似度模型
配置Okapi BM25相似度模型
配置DFR相似度模型
配置IB相似度模型
3.3 使用编解码器
Lucene 4.0的另一个显著变化是允许用户改变索引文件编码方式。它提供了灵活的索引方式,允许用户改变倒排索引的写人方式。
3.3.1 简单使用范例
为什么用户需要改变 Lucene索引写人格式?
理由之一是性能。某些字段需要特殊处理,如主键字段。相较于包含多个重复值的标准数值类型或文本类型而言,主键字段中的数值并不重复,只要借助一些技术,主键值就能被快速搜索到。
3.3.2 工作原理解释
编解码器需要逐字段配置。为了配置某个字段使用特定的编解码器,需要在字段配置 文件中添加一个postings_format属性,并将具体的编解码器所对应的属性值赋给它,例如 将id字段使用pulsing编解码器,对应的mapping为:
{
"mappings": {
"post": {
"properties": {
"id": {
"type": "long",
"store": "yes",
"precision_step": "0",
"postings_format": "pulsing"
},
"name": {
"type": "string",
"store": "yes",
"index": "analyzed"
},
"contents": {
"type": "string",
"store": "no",
"index": "analyzed"
}
}
}
}
}
3.3.3 可用的倒排表格式
我们可以使用下面这些倒排表格式。
- default:当没有显式配置时,倒排表使用该格式。该格式提供了存储字段(stored field)和词项向量压缩功能。
- pulsing:该编解码器将高基(high cardinality)字段(即包含大量不同值的字段, 如 id )中的倒排表编码为词项数组,这会减少Lucene在搜索文档时的查找操作。使用该编解码器,可以提高在高基字段中的搜索速度。
- direct:该编解码器在读索引阶段将词项载人词典,且词项在内存中为未压缩状态。该编解码器能提升常用字段的查询性能,但也需要谨慎使用,由于词项和倒排表数组都需要存储在内存中,从而导致它非常消耗内存。
- memory:顾名思义,该编解码器将所有数据写人磁盘,而在读取时则使用FST (Finite State Transducers)结构直接将词项和倒排表载入内存。因为使用该编解码器时,数据都在内存中 因而它能加速常见词项的查询。
- bloom default:是default编解码器的一种扩展,即在default编解码器处理基础上 又加人了bloom filter的处理,且bloom filter相关数据会写人磁盘中。当读人索引时,bloom filter相关数据会被读人内存,用于快速判断某个特定值是否存在。该编解码器在处理主键之类的高基字段时非常有用。
- bloom-pulsing:它是pulsing编解码器的扩展,在pulsing编解码器处理基础上又加 入了bloom filter的处理。
3.3.4 配置编解码器
默认配置中提供的倒排索引格式已足以应付大多数应用了,但偶尔还是需要定制倒 排索引格式以满足具体的需求。这种情况下,ElasticSearch允许用户更改索引映射中的code。部分来配置编解码器。例如,我们想配置default编解码器,并且将其命名为custom_default,便可以通过定义下面这个映射,我们对索引的name字段 使用该倒排索引格式:
{
"settings": {
"index": {
"codec": {
"postings_format": {
"custom_default": {
"type": "default",
"min_block_size": "20",
"max_block_size": "60"
}
}
}
}
},
"mappings": {
"post": {
"properties": {
"id": {
"type": "long",
"store": "yes",
"precision_step": "0"
},
"name": {
"type": "string",
"store": "yes",
"index": "analyzed",
"postings_format": "custom_default"
},
"contents": {
"type": "string",
"store": "no",
"index": "analyzed"
}
}
}
}
}
各种编解码器属性设置略,包括:
default编解码器属性
direct编解码器属性
memory编解码器属性
pulsing编解码器属性
基于bloom filter的编解码器属性
3.4 准实时、提交、更新及事务日志
3.4.1 索引更新及更新提交
在索引期新文档会写人索引段,索引段是独立的 Lucene索引,这意味着查询是可以与索引并行的,只是不时会有新增的索引段被添加到可被搜索的索引段集合之中。Apache Lucene通过创建后续的〔基于索引只写一次的特性)segments_ N文件来实现此功能,且该文件列举了索引中的索引段。这个过程称为提交(committing), Lucene以一种安全的方式来执行该操作,能确保索引更改以原子操作方式写入索引,即便有错误发生,也能保证索引数据的一致性。
一次提交并不足以保证新索引的数 据能被搜索到,这是因为Lucene使用了一个叫作Searcher的抽象类来执行索引的读取。如果索引更新提交了,但Searcher实例并没有重新打开,那么它觉察不到新索引段的加入。 Searcher重新打开的过程叫作刷新( refresh )。出于性能考虑,Lucene推迟了耗时的刷新, 因此它不会在每次新增一个文档(或批量增加文档)的时候刷新,但Searcher会每秒刷新一次。这种刷新已经非常频繁了,然而有很多应用却需要更快的刷新频率。如果碰到这种状 况,要么使用其他技术,要么审视需求是否合理。ElasticSearch提供了强制刷新的API。例 如,在例子中可以使用下面的命令:
curl -XGET localhost:9200/test/_refresh
更改默认的刷新时间
Searcher自动刷新的时间间隔可以通过以下手段改变:更改ElasticSearch配置文件中 的index.refresh_interval参数值或者使用配置更新相关的API。例如:
curl -XPUT localhost:9200/test/_settings -d '{
"index":{
"refresh_interval": "5m"
}
}'
上面的命令将Searcher的自动刷新时间间隔更改为5分钟。请注意,上次刷新后的新 增数据并不会被搜索到。
优化:初始建立索引时关闭Searcher自动刷新
刷新操作是很耗资源的,因此刷新间隔时间越长,索引速度越快如果需要长时间 高速建索引,并且在建索引结束之前暂不执行查询,那么可以考虑将index.refresh_interval参数值设置为-1,然后在建索引结束以后再将该参数恢复为初始值。
3.4.2 事务日志
Apache Lucene能保证索引的一致性,这非常棒,但是这并不能保证当往索引中写数据 失败时不会损失数据(如磁盘空间不足、设备损坏,或没有足够的文件句柄供索引文件使用)。另外,频繁提交操作会导致严重的性能问题(因为i; j提交一次就会触发一个索引段的创建操作,同时也可能触发索引段的合并)。ElasticSearch通过使用事务日志(transaction log)来解决这些问题,它能保存所有的未提交的事务,而ElasticSearch会不时创建一个新的日志文件用于记录每个事务的后续操作。当有错误发生时,就会检查事务日志,必要时 会再次执行某些操作,以确保没有丢失任何更改信息。而且,事务日志的相关操作都是自动完成的,用户并不会意识到某个特定时刻触发的更新提交。事务日志中的信息与存储介质之间的同步(同时清空事务日志)称为事务日志刷新(flushing).
Searcher刷新(_refresh) VS 日志刷新(_flush)
请注意事务日志刷新与Searcher刷新的区别。大多数情况下,Searcher刷新是你所 期望的,即搜索到最新的文档。而事务日志刷新用来确保数据正确写入了索引并清空了事务日志。
除了自动的事务日志刷新以外,也可以使用对应的API。例如,可以使用下面的命令, 强制将事务日志中涉及的所有数据更改操作同步到索引中,并清空事务日志文件:
curl -XGET localhoat:9200/_flush
我们也可以使用flush命令对特定的索引进行事务日志刷新(如library索引):
curl -XGET localhoat:9200/library/_flush
curl -XGET localhoet:9200/library/_refresh
上面第二行命令中,我们紧接着在事务日志刷新之后,调用Searcher刷新操作,打开 一个新的Searcher实例。
事务日志相关配置
以下参数既可以通过修改clasticscarch.yml文件来配置,也可以通过索引配 置更新API来更改。
修改clasticscarch.yml
- index.translog.flush_threshold_period:该参数的默认值为30分钟,它控制了强制自动 事务日志刷新的时间间隔,即便是没有新数据写人。强制进行事务日志刷新通常会导致大量的I/O操作,因此当事务日志涉及少量数据时,才更适合进行这项操作。
- index.translog.flush_ threshold_ ops:该参数确定了一个最大操作数,即在上次事务 日志刷新以后,当索引更改操作次数超过该参数值时,强制进行事务日志刷新操作,默认值为5000.
- index.translog.flush_threshold_size:该参数确定了事务日志的最大容量,当容量大 于等于该参数值,就强制进行事务日志刷新操作,默认值为200MB.
- index.translog.disable_flush:禁用事务日志刷新。尽管默认情况下事务日志刷新是可 用的,但对它临时性地禁用能带来其他方面的便利。例如,向索引中导人大量文档的时候。
更新API
当然,除了通过修改elasticsearch.yml文件来配置上述参数,我们也可以使用设置更新 API来更改相关配置。例如:
curl -XPUT localhost:9200/test/_settings -d '{
"index":{
"translog.disable_flush":true "
}
}'
优化:初始建立索引时关闭日志自动刷新
前述命令会在向索引导人大量数据之前执行,大幅提高索引的速度。但是请记住,当 数据导人完毕之后,要重新设置事务日志刷新相关参数。
3.4.3准实时读取
事务日志给我们带来一个免费的特性:实时读取(real-time GET),该功能让返回文档 各种版本(包括未提交版本)成为可能。实时读取操作从索引中读取数据时,会先检查事务日志中是否有可用的新版本。如果近期索引没有与事务日志同步,那么索引中的数据将会被忽略,事务日志中最新版本的文档将会被返回。
Get 操作,没有使用Searcher刷新技巧就得到 了最新版本的文档。
3.5 深入理解数据处理
3.5.2 范例的使用
为了演示,我们先用下面的命令创建一个只包含单字段文档的索引:
curl -XPUT localhost:9200/test -d '{
"mappings":{
"test":{
"properties":{
"lang":{
"type":"string"
},
"title":{
"type":"multi_field",
"fields":{
"i18n":{
"type":"string",
"index":"analyzed",
"analyzer":"english"
},
"org":{
"type":"string",
"index":"analyzed",
"analyzer":"standard"
}
}
}
}
}
}
}'
尽管只有一个字段,但却使用了两个分析器进行文本分析处理,这是因为title字段是 multi_fteld类型的缘故,其中对title.org子字段使用了standard分析器,而对title.il8n子字段使用了english分析器(该分词器会将用户输人转换为词干形式)。
3.5.3 索引期更换分词器
另外一件值得一提的事情是,在处理多语言数据的时候,可能要在索引期中动态更换 分词器。比如说,我们修改前面的映射,添加_analyzer相关配置:
curl -XPUT localhost:9200/test -d '{
"mappings":{
"test":{
"_analyzer":{
"path":"lang"
},
"properties":{
"lang":{
"type":"string"
},
"title":{
"type":"multi_field",
"fields":{
"i18n":{
"type":"string"
},
"org":{
"type":"string",
"index":"analyzed",
"analyzer":"standard"
}
}
}
}
}
}
}'
我们仅做了少许修改,首先是允许ElasticSearch在处理文本时根据文本内容决定采 用何种分析器。其中,path参数为文档中的字段名,该字段中保存了分析器的名称。其次是移除了field.i18n字段所用分析器的定义。现在,我们可以用下面这条命令来创建索引:
curl -XPUT localhost:9200/test/test/1 -d'{
"title":"The quick brown fox jumps over the lazy dog.",
"lang":"engliah"
}'
上面的例子中,ElasticSearch从索引中提取lang字段的值,并将该值代表的分析器置 于当前文档的文本分析器处理。总之,当你想对不同文档采用不同分析器时,该设置非常有用(例如,在文档中移除或保留非重要词)。
引申
上述根据lang字段的值确定分词器,那么是否应该在预设所有可能的lang的类型,如:
{
"lang_en":{
"type":"string"
},
"lang_zh":{
"type":"ik_smart"
}
}
3.5.4 搜索时更换分析器
也可以在搜索时更换分词器,并通过配置analyzer属性来实现。例如,下面这个查询:
curl localhost:9200/test/_search?pretty -d'{
"query”:{
"multi_match":{
"query":"jumps",
"fields":["title.org^1000","title.i18n"],
"analyzer":"english"
}
}
}'
3.5.5 陷阱与默认分析
当没有定义分析器。针对这种情况,虽然ElasticSearch 会选用一个所谓的默认分析器,但这往往并不是我们想要的。这是因为默认分析器有时候会被文本分析插件模块重定义。此时,有必要指定ElasticSearch的默认分析器。为了实现该目的,我们还是像平常那样定义分析器,只是将自定义分析器的名称替换为default。
作为一种备选方案,你可以定义default_index分析器和default_search分析器,并将它们作为索引期和检索期的默认分析器.
下面是在elasticsearch.yml中配置默认analyzer的例子
index:
analysis:
analyzer:
default_index:
tokenizer: standard
filter: [standard, lowercase, my_synonym, my_snow]
default_search:
tokenizer: standard
filter: [standard, lowercase, stop]
3.6 控制索引合并
索引文件中绝大部分数据都是只写一次,读多次,而只有用于保存文档删除信息的文件才会被多次更改。在某些时刻,当某种条件满足时,多个索引段会被拷贝合并到一个更大的索引段,而那些旧的索引段会被抛弃并从磁盘中删除,这个操作称为段合并(segment merging).
为什么非要进行段合并?
这是因为:首先,索引段的个数越多,搜 索性能越低且耗费内存更多。另外,索引段是不可变的,因而物理上你并不能从中删除信息。也许你碰巧从索引中删除了大量文档,但这些文档只是做了删除标记,物理上并没有被删除。而当段合并发生时,这些标记为删除的文档并没有复制到新的索引段中。如此一来,这减少了最终索引段中的文档数。
从用户角度来看,段合并可快速概括为如下两个方面:
- 当多个索引段合并为一个的时候,会减少索引段的数量并提高搜索速度。
- 同时也会减少索引的容量(文档数),因为在段合并时会移除被标记为已删除的那些 文档。
尽管段合并有这些好处,但用户也应该了解到段合并的代价,即主要是I/O操作的代 价。在速度较慢的系统中,段合并会显著影响性能。基于这个原因,ElasticSearch允许用户选择段合并策略(merge policy)及存储级节流(store level throttling)。
本章后续部分将会讨论段合并策略,而存储级节流则安排在6.2节中讨论。
3.6.1 选择正确的合并策略
尽管段合并是Lucene的责任,ElasticSearch也允许用户配置想用的段合并策略。到目 前为止,有三种可用的合并策略:
- tiered(默认)
- log_byte_size
- log_doc
ElasticSearch 5.x版本只有tiered合并策略
参考Elasticsearch 5.x 源码分析(5)segments merge 流程分析
配置合并策略
修改elasticsearch.yml
配置文件的index.merge. policy.type字段配置成我们期望的段合并策略类型。例如下面这样:
index.merge.policy.type:tiered
Rest API
参考以前配置。
接下来我们来了解这些不同的段合并策略,以及它们提供的功能,并讨论这些段合并 策略的具体配置。
tiered合并策略
这是ElasticSearch的默认选项。它能合并大小相似的索引段,并考虑每层允许的索引 段的最大个数。读者需要清楚单次可合并的索引段的个数与每层允许的索引段数的区别。 在索引期,该合并策略会计算索引中允许出现的索引段个数,该数值称为闭值(budget ) 。
如果正在构建的索引中的段数超过了阂值,该策略将先对索引段按容量降序排序(这里考虑 了被标记为已删除的文档),然后再选择一个成本最低的合并。合并成本的计算方法倾向于回收更多删除文档和产生更小的索引段。
如果某次合并产生的索引段的大小大于index.merge.policy.max_merged_segment参数 值,则该合并策略会选择更少的索引段参与合并,使得生成的索引段的大小小于Ill值。这意味着,对于有多个分片的索引,默认的index.merge.policy.max_merged_segment则显得过小,会导致大量索引段的创建,从而降低查询速度。用户应该根据自己具体的数据量,观察索引段的状况,不断调整合并策略以满足应用需求。
log byte size合并策略
该策略会不断地以字节数的对数为计算单位,选择多个索引来合并创建新索引。
log doc合并策略
该策略与log_byte_size合并策略类似,不同的是前者基于索引的字节数计算,而后者 基于索引段的文档数计算。
3.6.2 合并策略配置
大多数情况下默认选项是 够用的,除非有特殊的需求才需要修改。
配置tiered合并策略
当使用tiered合并策略时,可配置以下这些选项:
- index.merge.policy.expunge_deletes_allowed:默认值为10,该值用于确定被删除文 档的百分比,当执行expungeDeletes时,该参数值用于确定索引段是否被合并。
- index.merge.policy.floor_segment:该参数用于阻止频繁刷新微小索引段。小于该参 数值的索引段由索引合并机制处理,并将这些索引段的大小作为该参数值。默认值 为2MB 。
- index.merge.policy.max_merge_at_once:该参数确定了索引期单次合并涉及的索引 段数量的上限,默认为10。该参数值较大时,也就能允许更多的索引段参与单次合并,只是会消耗更多的I/O资源。
- index.merge.policy.max_merge_at_once_explicit:该参数确定了索引优化(optimize ) 操作和expungeDeletes操作能参与的索引段数量的上限,默认值为30}〕但该值对索引期参与合并的索引段数量的上限没有影响。
- index.merge.policy.max_ merged_ segment:该参数默认值为SGB,它确定了索引期 单次合并中产生的索引段大小的上限。这是一个近似值,因为合并后产生的索引段的大小是通过累加参与合并的索引段的大小并减去被删除文档的大小而得来的。
- index.merge.policy.segments-per tier:该参数确定了每层允许出现的索引段数量的 上限。越小的参数值会导致更少的索引段数量,这也意味着更多的合并操作以及更 低的索引性能。默认值为10,建议设置为大于等于index.merge.policy.max_merge_at_once,否则你将遇到很多与索引合并以及性能相关的问题。
- index.reclaim_ deletes_ weight:该参数值默认为2.0,它确定了索引合并操作中清除 被删除文档这个因素的权重。如果该参数设置为0.0,则清除被删除文档对索引合并没有影响。该值越高,则清除较多被删除文档的合并会更受合并策略青睐。
- index.compund_format:该参数类型为布尔型,它确定了索引是否存储为复合文件 格式(compound format),默认值为false。如果设置为true,则Lucene会将所有文件存储在一个文件中。这样设置有时能解决操作系统打开文件处理器过多的问题, 但是也会降低索引和搜索的性能。
- index.merge.async:该参数类型为布尔型,用来确定索引合并是否异步进行。默认 为true.
- index.merge.async_interval:当index.merge.async设置为true(因此合并是异步进行 的),该参数值确定了两次合并的时间间隔,默认值为is。请记住,为了触发真正的索引合并以及索引段数量缩减操作,该参数值应该保持为一个较小值。
log byte size合并策略 与 log doc合并策略 的配置项略。
3.6.3 调度
除了可以影响索引合并策略的行为之外,ElasticSearch还允许我们定制合并策略的执行 方式。索引合并调度器(scheduler)分为两种,默认的是并发合并调度器ConcurrentMergeScheduler。
并发合并调度器
该调度器使用多线程执行索引合并操作 。为了控制最大线程数,可以通过修改index.merge.scheduler.max thread_count
属性来实 现。一般来说,可以按如下公式来计算允许的最大线程数:
maximum_value(1, minimum_value(4,available_processors/2)
如果我们的系统是8核的,那么调度器允许的最大线程数可以设置为4.
参考[Elasticsearch Merge合并操作与配置](
https://blog.csdn.net/likui1314159/article/details/53224132)
设置合并调度
为了设置特定的索引合并调度器,用户可将index.merge.scheduler.type
的属性值设置为 concurrent或serial。例如,为了使用并发合并调度器,用户应该如此设置:
index.merge.scheduler.type:concurrent
如果想使用顺序合并调度器,用户则应该像下面这样设置:
index.merge.scheduler.type:serial
参考资料
Elasticsearch 5.x 源码分析(5)segments merge 流程分析
Elasticsearch Merge合并操作与配置