elasticsearch - 分词,优化,别名替换

filter,仅仅只是按照搜索条件过滤出需要的数据而已,不计算任何相关度分数,对相关度没有任何影响,不需要按照相关度分数进行排序,同时还有内置的自动cache最常使用filter的数据
query,会去计算每个document相对于搜索条件的相关度,并按照相关度进行排序,而且无法cache结果

 

使用validate来对语句进行验证

 

 

如果对一个string field进行排序,结果往往不准确,因为分词后是多个单词,再排序就不是我们想要的结果了

通常解决方案是,将一个string field建立两次索引,一个分词,用来进行搜索;一个不分词,用来进行排序

 

 

relevance score算法,简单来说,就是计算出,一个索引中的文本,与搜索文本,他们之间的关联匹配程度

Elasticsearch使用的是 term frequency/inverse document frequency算法,简称为TF/IDF算法

Term frequency:搜索文本中的各个词条在field文本中出现了多少次,出现次数越多,就越相关

Inverse document frequency:搜索文本中的各个词条在整个索引的所有文档中出现了多少次,出现的次数越多,就越不相关

Field-length norm:field长度,field越长,相关度越弱

 

 

搜索的时候,要依靠倒排索引;排序的时候,需要依靠正排索引,看到每个document的每个field,然后进行排序,所谓的正排索引,其实就是doc values

在建立索引的时候,一方面会建立倒排索引,以供搜索用;一方面会建立正排索引,也就是doc values,以供排序,聚合,过滤等操作使用

doc values是被保存在磁盘上的,此时如果内存足够,os会自动将其缓存在内存中,性能还是会很高;如果内存不足够,os会将其写入磁盘上

 

query phase:

搜索请求发送到某一个coordinate node,会构建一个priority queue,长度以paging操作from和size为准,如果不加from和size,就默认搜索前10条,按照_score排序,coordinate node将请求转发到所有shard,每个shard本地搜索,并构建一个本地的priority queue,各个shard将自己的priority queue返回给coordinate node,并构建一个全局的priority queue。如果from + size 的值太大,就会需要过多的资源。

fetch phase:

query phase结束后会返回给coordinate node一些doc id等信息,之后coordinate node再查询需要的数据,将其再发往对应的shard上,将获取的结果在返回给客户端。

 

 

preference:

决定了哪些shard会被用来执行搜索操作

_primary, _primary_first, _local, _only_node:xyz, _prefer_node:xyz, _shards:2,3

两个document排序,field值相同;不同的shard上,可能排序不同;每次请求轮询打到不同的replica shard上;每次页面上看到的搜索结果的排序都不一样。这就是bouncing result,也就是跳跃的结果。搜索的时候,是轮询将搜索请求发送到每一个replica shard(primary shard),但是在不同的shard上,可能document的排序不同。解决方案就是将preference设置为一个字符串,比如说user_id,让每个user每次搜索的时候,都使用同一个 shard去执行,就不会看到bouncing results了

timeout:限定在一定时间内,将部分获取到的数据直接返回,避免查询耗时过长

routing:document文档路由,_id路由,routing=user_id,这样的话可以让同一个user对应的数据到一个shard上去

search_type:

default:query_then_fetch
dfs_query_then_fetch,可以提升revelance sort精准度

 

scroll搜索:

如果一次性要查出来比如10万条数据,那么性能会很差,此时一般会采取用scoll滚动查询,一批一批的查,直到所有数据都查询完处理完,使用scoll滚动搜索,可以先搜索一批数据,然后下次再搜索一批数据,以此类推,直到搜索出全部的数据来
scoll搜索会在第一次搜索的时候,保存一个当时的视图快照,之后只会基于该旧的视图快照提供数据搜索,如果这个期间数据变更,是不会让用户看到的,采用基于_doc进行排序的方式,性能较高,每次发送scroll请求,我们还需要指定一个scoll参数,指定一个时间窗口,每次搜索请求只要在这个时间窗口内能完成就可以了

获得的结果会有一个scroll_id,下一次再发送scoll请求的时候,必须带上这个scoll_id

scroll,看起来挺像分页的,但是其实使用场景不一样。分页主要是用来一页一页搜索,给用户看的;scoll主要是用来一批一批检索数据,让系统进行处理的

 

手动创建索引:

PUT /my_index
{
    "settings": { ... any settings ... },
    "mappings": {
        "type_one": { ... any mappings ... },
        "type_two": { ... any mappings ... },
        ...
    }
}

修改索引

PUT /my_index/_settings
{
    "number_of_replicas": 1
}

删除索引

DELETE /my_index
DELETE /index_one,index_two
DELETE /index_*
DELETE /_all

elasticsearch.yml
action.destructive_requires_name: true

 

分词器:

默认的分词器  : standard

standard tokenizer:以单词边界进行切分
standard token filter:什么都不做
lowercase token filter:将所有字母转换为小写
stop token filer(默认被禁用):移除停用词,比如a the it等等

修改分词器的设置:

启用english停用词token filter

定制化自己的分词器

手动创建的索引下建立type,使用自己定义的分词器:

 

 

type,是一个index中用来区分类似的数据的,类似的数据,但是可能有不同的fields,而且有不同的属性来控制索引建立、分词器
field的value,在底层的lucene中建立索引的时候,全部是opaque bytes类型,不区分类型的
lucene是没有type的概念的,在document中,实际上将type作为一个document的field来存储,即_type,es通过_type来进行type的过滤和筛选
一个index中的多个type,实际上是放在一起存储的,因此一个index下,不能有多个type重名,而类型或者其他设置不同的,因为那样是无法处理的

 

root object:

就是某个type对应的mapping json,包括了properties,metadata(_id,_source,_type),settings(analyzer),其他settings(比如include_in_all)

properties: type,index,analyzer

_source:

(1)查询的时候,直接可以拿到完整的document,不需要先拿document id,再发送一次请求拿document
(2)partial update基于_source实现
(3)reindex时,直接基于_source实现,不需要从数据库(或者其他外部存储)查询数据再修改
(4)可以基于_source定制返回field
(5)debug query更容易,因为可以直接看到_source

如果不需要上述好处,可以禁用_source

_all

将所有field打包在一起,作为一个_all field,建立索引。没指定任何field进行搜索时,就是使用_all field在搜索。

也可以在field级别设置include_in_all field,设置是否要将field的值包含在_all field中

标识性metadata:_index,_type,_id

 

 

定制dynamic策略

true:遇到陌生字段,就进行dynamic mapping
false:遇到陌生字段,就忽略
strict:遇到陌生字段,就报错

定制dynamic mapping策略

默认会按照一定格式识别date,比如yyyy-MM-dd。但是如果某个field先过来一个2017-01-01的值,就会被自动dynamic mapping成date,后面如果再来一个"hello world"之类的值,就会报错。可以手动关闭某个type的date_detection,如果有需要,自己手动指定某个field为date类型。

定制自己的dynamic mapping template(type level)

title没有匹配到任何的dynamic模板,默认就是standard分词器,不会过滤停用词,is会进入倒排索引,用is来搜索是可以搜索到的
title_en匹配到了dynamic模板,就是english分词器,会过滤停用词,is这种停用词就会被过滤掉,用is来搜索就搜索不到了

定制自己的default mapping template(index level):

 

重建索引

一个field的设置是不能被修改的,如果要修改一个Field,那么应该重新按照新的mapping,建立一个index,然后将数据批量查询出来,重新用bulk api写入index中,批量查询的时候,建议采用scroll api,并且采用多线程并发的方式来reindex数据,每次scoll就查询指定日期的一段数据,交给一个线程即可

有些数据是2017-10-10这种日期格式的,所以title这种field被自动映射为了date类型,实际上它应该是string类型的

之后插入数据格式不正确会报错

如果此时想修改title的类型,是不可能的,唯一的办法,就是进行reindex,也就是说,重新建立一个索引,将旧索引的数据查询出来,再导入新索引,如果说旧索引的名字,是old_index,新索引的名字是new_index,终端java应用,已经在使用old_index在操作了,这个时候给java应用一个别名,这个别名是指向旧索引的,java应用先使用。先用goods_index alias来操作,此时实际指向的是旧的my_index

新建一个index,调整其title的类型为string

再使用scroll api将数据批量查询出来

采用bulk api将scoll查出来的一批数据,批量写入新索引

重复这两个操作。。。

将tmp_index alias切换到my_index_new上去,java应用会直接通过index别名使用新的索引中的数据,java应用程序不需要停机,零提交,高可用

再通过别名来先查询也是可以的。

 

 

倒排索引,是适合用于进行搜索的

(1)包含这个关键词的document list
(2)包含这个关键词的所有document的数量:IDF(inverse document frequency)
(3)这个关键词在每个document中出现的次数:TF(term frequency)
(4)这个关键词在这个document中的次序
(5)每个document的长度:length norm
(6)包含这个关键词的所有document的平均长度

倒排索引不可变的好处

(1)不需要锁,提升并发能力,避免锁的问题
(2)数据不变,一直保存在os cache中,只要cache内存足够
(3)filter cache一直驻留在内存,因为数据不变
(4)可以压缩,节省cpu和io开销

倒排索引不可变的坏处:每次都要重新构建整个索引

 

document的写入原理:

es底层是Lucene,会将一个index分成多个segment,每个segment都会存放数据。

数据首先会被写入buffer,之后commit point阶段,是将buffer中的数据写入到index segment中,每次commit point时,如果出现删除操作,会有一个.del文件,标记了哪些segment中的哪些document被标记为deleted了,搜索的时候,会依次查询所有的segment,从旧的到新的,比如被修改过的document,在旧的segment中,会标记为deleted,在新的segment中会有其新的数据,等待在os cache中的index segment被fsync强制刷到磁盘上,供搜索使用,同时buffer也会被清空。
如果是更新操作,会将现有的document标记为deleted状态,然后把新来的document插入index segment中,

 

 

NRT:

数据写入buffer,每隔一定时间,buffer中的数据被写入segment文件,但是先写入os cache,只要segment写入os cache,那就直接打开供search使用,不立即执行commit,数据写入os cache,并被打开供搜索的过程,叫做refresh,默认是每隔1秒refresh一次。也就是说,每隔一秒就会将buffer中的数据写入一个新的index segment file,先写入os cache中。所以,es是近实时的,数据写入到可以被搜索,默认是1秒。

POST /my_index/_refresh,可以手动refresh,一般不需要手动执行

如果时效性要求,比较低,只要求一条数据写入es,一分钟以后才让我们搜索到就可以了,那么就可以调整refresh interval

PUT /my_index
{
  "settings": {
    "refresh_interval": "30s" 
  }
}


数据写入buffer缓冲同时也会写入translog日志文件,每隔一秒钟,buffer中的数据被写入新的segment file,并进入os cache,此时segment被打开并供search使用,buffer被清空,新的segment不断添加,buffer不断被清空,而translog中的数据不断累加,当translog长度达到一定程度的时候,commit操作发生:  buffer中的所有数据写入一个新的segment,并写入os cache,打开供使用,buffer被清空,一个commit ponit被写入磁盘,标明了所有的index segment,filesystem cache中的所有index segment file缓存数据,被fsync强行刷到磁盘上,现有的translog被清空,创建一个新的translog

fsync+清空translog,就是flush,默认每隔30分钟flush一次,或者当translog过大的时候,也会flush

POST /my_index/_flush,一般不手动flush

translog:数据每隔5秒被fsync一次到磁盘上。在一次增删改操作之后,当fsync在primary shard和replica shard都成功之后,那次增删改操作才会成功

但是这种在一次增删改时强行fsync translog可能会导致部分操作比较耗时,也可以允许部分数据丢失,设置异步fsync translog

PUT /my_index/_settings
{
    "index.translog.durability": "async",
    "index.translog.sync_interval": "5s"
}

 

每秒一个segment file,文件过多,而且每次search都要搜索所有的segment,很耗时,默认会在后台执行segment merge操作,在merge的时候,被标记为deleted的document也会被彻底物理删除

每次merge操作的执行流程

(1)选择一些有相似大小的segment,merge成一个大的segment
(2)将新的segment flush到磁盘上去
(3)写一个新的commit point,包括了新的segment,并且排除旧的那些segment
(4)将新的segment打开供搜索
(5)将旧的segment删除

POST /my_index/_optimize?max_num_segments=1,尽量不要手动执行

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值