您的当前位置:首页正文

Elasticsearch知识点整理一

来源:个人技术集锦
Elasticsearch知识点整理⼀

摘⾃:

极⼒推荐: 官⽹地址: https://www.elastic.co/guide/en/elasticsearch/reference/6.0

肺腑之⾔,学ES先学原⽣的语法,SpringData封装的是太好⽤了,但是没玩过原⽣的语法,可能不知道Spring提供的API在⼲什么

核⼼概念:

Near Realtime (NRT)

在ES中进⾏搜索是近实时的,意思是数据从写⼊ES到可以被searchable仅仅需要1秒钟,因此说基于ES执⾏的搜索和分析可以达到秒级

Cluster

集群 , 集群是⼀个或多个node的集合,他们⼀起保存你存放进去的数据,⽤户可以在所有的node之间进⾏检索,⼀般的每个集群都会有⼀个唯⼀的名称标识,默认的名称标识为 elasticsearch , 这个名字很重要,因为node想加⼊cluster时,需要这个名称信息

确保别在不同的环境中使⽤相同的集群名称,进⽽避免node加错集群的情况,⼀颗考虑下⾯的集群命名风格logging-stage和logging-dev和logging-pro

Node

单台server就是⼀个node,他和 cluster⼀样,也存在⼀个默认的名称,但是它的名称是通过UUID⽣成的随机串,当然⽤户也可以定制不同的名称,但是这个名字最好别重复,这个名称对于管理来说很在乎要,因为需要确定,当前⽹络中的哪台服务器,对应这个集群中的哪个节点

node存在⼀个默认的设置,默认的,当每⼀个node在启动时都会⾃动的去加⼊⼀个叫elasticsearch的节点,这就意味着,如果⽤户在⽹络中启动了多个node,他们会彼此发现,然后组成集群

在单个的cluster中,你可以拥有任意多的node,假如说你的⽹络上没有有其他正在运⾏的节点,然后你启动⼀个新的节点,这个新的节点⾃⼰会组件⼀个集群

Index

Index是⼀类拥有相似属性的document的集合,⽐如你可以为消费者的数据创建⼀个index,为产品创建⼀个index,为订单创建⼀个indexindex名称(必须是⼩写的字符), 当需要对index中的⽂档执⾏索引,搜索,更新,删除,等操作时,都需要⽤到这个index⼀个集群中理论上你可以创建任意数量的index

Type

Type可以作为index中的逻辑类别,为了更细的划分,⽐如⽤户数据type,评论数据type,博客数据type在设计时,尽最⼤努⼒让拥有更多相同field的document会分为同⼀个type下

Document

document就是ES中存储的⼀条数据,就像mysql中的⼀⾏记录⼀样,可以是⼀条⽤户的记录,⼀个商品的记录等等

⼀个不严谨的⼩结:

为什么说这是不严谨的⼩结呢? 就是说下⾯三个对应关系只能说的从表⾯上看起来⽐较相似,但是ES中的type其实是⼀个逻辑上的划分,数据在存储是时候依然是混在⼀起存储的(往下看下⽂中有写,),然⽽mysql中的不同表的两个列是绝对没有关系的Elasticsearch关系型数据库Documenttypeindex

⾏表数据库

Shards & Replicas

问题引⼊:

如果让⼀个Index⾃⼰存储1TB的数据,响应的速度就会下降为了解决这个问题,ES提供了⼀种将⽤户的Index进⾏subdivide的骚操作,就是将index分⽚, 每⼀⽚都叫⼀个Shards,实现了将整体庞⼤的数据分布在不同的服务器上存储什么是shard?

shard分成replica shard和primary shard,顾名思义⼀个是主shard⼀个是备份shard, 负责容错以及承担部分读请求

shard可以理解成是ES中最⼩的⼯作单元,所有shard中的数据之和,才是整个ES中存储的数据, 可以把shard理解成是⼀个luncene的实现,拥有完成的创建索引,处理请求的能⼒下图是两个node,6个shard的组成的集群的划分情况

⼤家可以看到,这时⽆论java应⽤程序访问的是node1还是node2,其实都能获取到数据shard的默认数量

新创建的节点会存在5个primary shard,后续不然能再改动primary shard的值,如果每⼀个primary shard都对应⼀个replica shard,按理说单台es启动就会存在10个分⽚,但是现实是,同⼀个节点的replica shard和primary shard不能存在于⼀个server中,因此单台es默认启动后的分⽚数量还是5个如何拓容Cluster

⾸先明确⼀点: ⼀旦index创建完成了,primary shard的数量就不可能再发⽣变化

因此横向拓展就得添加replica的数量, 因为replica shard的数量后续是可以改动的, 也就是说,如果后续我们将他的数量改成了2, 就意味着让每个primary shard都拥有了两个replicashard, 计算⼀下: 5+5*2=15 集群就会拓展成15个节点

如果想让每⼀个shard都有最多的系统的资源,就增加服务器的数量,让每⼀个shard独占⼀个服务器,举个例⼦:

上图中存在上下两个node,每⼀个node,每个node中都有⼀个 ⾃⼰的primary shard和其他节点的replica shard,为什么是强调⾃⼰和其他呢? 因为ES中规定,同⼀个节点的replicashard和primary shard不能存在于⼀个server中,但是不同节点的primary shard可以存在于同⼀个server上

当primary shard宕机时,它对应的replicas在其他的server不会受到影响,可以继续响应⽤户的读请求,通过这种分⽚的机制,并且分⽚的地位相当,假设单个shard可以处理2000/s的请求,通过横向拓展可以在此基础上成倍提升系统的吞吐量,天⽣分布式,⾼可⽤

此外:每⼀个document肯定存在于⼀个primary shard和这个primary shard 对应的replica shard中, 绝对不会出现同⼀个document同时存在于多个primary shard中的情况

⼊门探索:

集群的健康状况

Copy

GET /_cat/health?v

执⾏结果如下:Copy

epoch timestamp cluster status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent1572595632 16:07:12 elasticsearch yellow 1 1 5 5 0 0 5 0 - 50.0%

解读上⾯的信息,默认的集群名是elasticsearch,当前集群的status是yellow,后续列出来的是集群的分⽚信息,最后⼀个active_shards_percent表⽰当前集群中仅有⼀半shard是可⽤的状态

存在三种状态分别是red green yellow

green : 表⽰当前集群所有的节点全部可⽤

yellow: 表⽰所有的数据是可以访问的,但是并不是所有的replica shard都是可以使⽤的(我现在是默认启动⼀个node,⽽ES⼜不允许同⼀个node的primary shard和replicashard共存,因此我当前的node中仅仅存在5个primary shard,为status为黄⾊)red: 集群宕机,数据不可访问

集群的索引信息

Copy

GET /_cat/indices?v

结果:Copy

health status index uuid pri rep docs.count docs.deleted store.size pri.store.size

yellow open ai_answer_question cl_oJNRPRV-bdBBBLLL05g 5 1 203459 0 172.3mb 172.3mb

显⽰,状态yellow表⽰存在replica shard不可⽤, 存在5个primary shard,并且每⼀个primary shard都有⼀个replica shard , ⼀共20多万条⽂档,未删除过⽂档,⽂档占⽤的空间情况为172.3兆

创建index

Copy

PUT /customer?pretty

ES 使⽤的RestfulAPI,新增使⽤put,这是个很亲民的举动

添加 or 修改

如果是ES中没有过下⾯的数据则添加进去,如果存在了id=1的元素就修改(全量替换)

格式:PUT /index/type/id

全量替换时,原来的document是没有被删除的,⽽是被标记为deleted,被标记成的deleted是不会被检索出来的,当ES中数据越来越多时,才会删除它Copy

PUT /customer/_doc/1?pretty{

\"name\": \"John Doe\"}

响应:Copy

{

\"_index\": \"customer\ \"_type\": \"_doc\ \"_id\": \"1\ \"_version\": 1,

\"result\": \"created\ \"_shards\": { \"total\": 2,

\"successful\": 1, \"failed\": 0 },

\"_seq_no\": 0,

\"_primary_term\": 1}

强制创建,加添_create或者?op_type=createCopy

PUT /customer/_doc/1?op_type=createPUT /customer/_doc/1/_create

局部更新(Partial Update)不指定id则新增documentCopy

POST /customer/_doc?pretty{

\"name\": \"Jane Doe\"}

指定id则进⾏doc的局部更新操作Copy

POST /customer/_doc/1?pretty{

\"name\": \"Jane Doe\"}

并且POST相对于上⾯的PUT⽽⾔,不论是否存在相同内容的doc,只要不指定id,都会使⽤⼀个随机的串当成id,完成doc的插⼊

Partial Update先获取document,再将传递过来的field更新进document的json中,将⽼的doc标记为deleted,再将创建document,相对于全量替换中间会省去两次⽹络请求

检索

格式: GET /index/type/Copy

GET /customer/_doc/1?pretty

响应:Copy

{

\"_index\": \"customer\ \"_type\": \"_doc\ \"_id\": \"1\ \"_version\": 1, \"found\": true, \"_source\": {

\"name\": \"John Doe\" }}

删除

删除⼀条document

⼤部分情况下,原来的document不会被⽴即删除,⽽是被标记为deleted,被标记成的deleted是不会被检索出来的,当ES中数据越来越多时,才会删除它Copy

DELETE /customer/_doc/1

响应:Copy

{

\"_index\": \"customer\ \"_type\": \"_doc\ \"_id\": \"1\ \"_version\": 2,

\"result\": \"deleted\ \"_shards\": { \"total\": 2,

\"successful\": 1, \"failed\": 0 },

\"_seq_no\": 1,

\"_primary_term\": 1}

删除indexCopy

DELETE /index1

DELETE /index1,index2DELETE /index*DELETE /_all

可以在elasticsearch.yml中将下⾯这个设置置为ture,表⽰禁⽌使⽤ DELETE /_allaction.destructive_required_name:true

响应Copy

{

\"acknowledged\": true}

更新⽂档

上⾯说了POST关键字,可以实现不指定id就完成document的插⼊, POST + _update关键字可以实现更新的操作Copy

POST /customer/_doc/1/_update?pretty{

\"doc\": { \"name\": \"changwu\" }}

**POST+_update进⾏更新的动作依然需要执⾏id, 但是它相对于PUT来说,当使⽤POST进⾏更新时,id不存在的话会报错,⽽PUT则会认为这是在新增**此外: 针对这种更新操作,ES会先删除原来的doc,然后插⼊这个新的doc

document api

multi-index & multi-type

检索所有索引下⾯的所有数据Copy

/_search

搜索指定索引下的所有数据Copy

/index/_search

更多模式Copy

/index1/index2/_search/*1/*2/_search

/index1/index2/type1/type2/_search/_all/type1/type2/_search

_mget api 批量查询

在docs中指定_index,_type,_id

Copy

GET /_mget{

\"docs\" : [ {

\"_index\" : \"test\ \"_type\" : \"_doc\ \"_id\" : \"1\" }, {

\"_index\" : \"test\ \"_type\" : \"_doc\ \"_id\" : \"2\" } ]}

在URL中指定indexCopy

GET /test/_mget{

\"docs\" : [ {

\"_type\" : \"_doc\ \"_id\" : \"1\" }, {

\"_type\" : \"_doc\ \"_id\" : \"2\" } ]}

在URL中指定 index和typeCopy

GET /test/type/_mget{

\"docs\" : [ {

\"_id\" : \"1\" }, {

\"_id\" : \"2\" }

在URL中指定index和type,并使⽤ids指定id范围Copy

GET /test/type/_mget{

\"ids\" : [\"1\}

为不同的doc指定不同的过滤规则Copy

GET /_mget{

\"docs\" : [ {

\"_index\" : \"test\ \"_type\" : \"_doc\ \"_id\" : \"1\

\"_source\" : false }, {

\"_index\" : \"test\ \"_type\" : \"_doc\ \"_id\" : \"2\

\"_source\" : [\"field3\ }, {

\"_index\" : \"test\ \"_type\" : \"_doc\ \"_id\" : \"3\ \"_source\" : {

\"include\": [\"user\"],

\"exclude\": [\"user.location\"] } } ]}

_bulk api 批量增删改

基本语法Copy

{\"action\":{\"metadata\{\"data\

存在哪些类型的操作可以执⾏呢?

delete: 删除⽂档create: _create 强制创建

index: 表⽰普通的put操作,可以是创建⽂档也可以是全量替换⽂档update: 局部替换

上⾯的语法中并不是⼈们习惯阅读的json格式,但是这种单⾏形式的json更具备⾼效的优势ES如何处理普通的json如下:

将json数组转换为JSONArray对象,这就意味着内存中会出现⼀份⼀模⼀样的拷贝,⼀份是json⽂本,⼀份是JSONArray对象但是如果上⾯的单⾏JSON,ES直接进⾏切割使⽤,不会在内存中整⼀个数据拷贝出来delete

delete⽐较好看仅仅需要⼀⾏json就okCopy

{ \"delete\" : { \"_index\" : \"test\

create

两⾏json,第⼀⾏指明我们要创建的json的index,type以及id第⼆⾏指明我们要创建的doc的数据Copy

{ \"create\" : { \"_index\" : \"test\{ \"field1\" : \"value3\" }

index

相当于是PUT,可以实现新建或者是全量替换,同样是两⾏json第⼀⾏表⽰将要新建或者是全量替换的json的index type 以及 id第⼆⾏是具体的数据Copy

{ \"index\" : { \"_index\" : \"test\{ \"field1\" : \"value1\" }

update

表⽰ parcial update,局部替换

他可以指定⼀个retry_on_conflict的特性,表⽰可以重试3次Copy

POST _bulk

{ \"update\" : {\"_id\" : \"1\{ \"doc\" : {\"field\" : \"value\

{ \"update\" : { \"_id\" : \"0\

{ \"script\" : { \"source\": \"ctx._source.counter += params.param1\{ \"update\" : {\"_id\" : \"2\{ \"doc\" : {\"field\" : \"value\

{ \"update\" : {\"_id\" : \"3\{ \"doc\" : {\"field\" : \"value\

{ \"update\" : {\"_id\" : \"4\{ \"doc\" : {\"field\" : \"value\

滚动查询技术

滚动查询技术和分页技术在使⽤场景⽅⾯还是存在出⼊的,这⾥的滚动查询技术同样适⽤于系统在海量数据中进⾏检索,⽐如过⼀次性存在10条数据被命中可以被检索出来,那么性能⼀定会很差,这时可以选择使⽤滚动查询技术,⼀批⼀批的查询,直到所有的数据被查询完成他可以先搜索⼀批数据再搜索⼀批数据采⽤基于_doc的排序⽅式会获得较⾼的性能

每次发送scroll请求,我们还需要指定⼀个scroll参数,指定⼀个时间窗⼝,每次搜索只要在这个时间窗⼝内完成就ok⽰例Copy

GET /index/type/_search?scroll=1m{

\"query\":{

\"match_all\":{} },

\"sort\":[\"_doc\"], \"size\":3}

响应Copy

{

\"_scroll_id\": \"DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAACNFlJmWHZLTkFhU0plbzlHX01LU2VzUXcAAAAAAAAAkRZSZlh2S05BYVNKZW85R19NS1Nlc1F3AAAAAAAAAI8WUmZYdktOQWFTSmVvOUdfTUtTZXNRdwAAAAAAAACQFlJmWHZL \"took\": 9,

\"timed_out\": false, \"_shards\": { \"total\": 5,

\"successful\": 5, \"skipped\": 0, \"failed\": 0 },

\"hits\": { \"total\": 2,

\"max_score\": null, \"hits\": [ {

\"_index\": \"my_index\ \"_type\": \"_doc\ \"_id\": \"2\ \"_score\": null, \"_source\": {

\"title\": \"This is another document\ \"body\": \"This document has a body\" },

\"sort\": [ 0 ] }, {

\"_index\": \"my_index\ \"_type\": \"_doc\ \"_id\": \"1\ \"_score\": null, \"_source\": {

\"title\": \"This is a document\" },

\"sort\": [ 0 ] } ] }}

再次滚动查询Copy

GET /_search/scroll{

\"scroll\":\"1m\

\"_scroll_id\": \"DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAACNFlJmWHZLTkFhU0plbzlHX01LU2VzUXcAAAAAAAAAkRZSZlh2S05BYVNKZW85R19NS1Nlc1F3AAAAAAAAAI8WUmZYdktOQWFTSmVvOUdfTUtTZXNRdwAAAAAAAACQFlJmWHZ}

_search api 搜索api

query string search

_searchAPI + 将请求写在URI中

Copy

GET /bank/_search?q=*&sort=account_number:asc&pretty

同样使⽤的是RestfulAPI, q=* ,表⽰匹配index=bank的下的所有doc,sort=account_number:asc表⽰告诉ES,结果按照account_number字段升序排序,pretty是告诉ES,返回⼀个漂亮的json格式的数据

上⾯的q还可以写成下⾯这样Copy

GET /bank/_search?q=⾃定义field:期望的值GET /bank/_search?q=+⾃定义field:期望的值GET /bank/_search?q=-⾃定义field:期望的值

响应:Copy

{

\"took\" : 63, // 耗费的时间

\"timed_out\" : false, // 是否超时了 \"_shards\" : { // 分⽚信息

\"total\" : 5, // 总共5个分⽚,它的搜索请求会被打到5个分⽚上去,并且都成功了 \"successful\" : 5, //

\"skipped\" : 0, // 跳过了0个 \"failed\" : 0 // 失败了0个 },

\"hits\" : { //命中的情况

\"total\" : 1000, // 命中率 1000个

\"max_score\" : null, // 相关性得分,越相关就越匹配 \"hits\" : [ {

\"_index\" : \"bank\索引 \"_type\" : \"_doc\ \"_id\" : \"0\ \"sort\": [0],

\"_score\" : null, // 相关性得分

// _source⾥⾯存放的是数据

\"_source\" : {\"account_number\":0,\"balance\":16623,\"firstname\":\"Bradshaw\ }, {

\"_index\" : \"bank\ \"_type\" : \"_doc\ \"_id\" : \"1\ \"sort\": [1],

\"_score\" : null,

\"_source\" : {\"account_number\":1,\"balance\":39225,\"firstname\":\"Amber\ }, ... ] }}

指定超时时间: GET /_search?timeout=10ms 在进⾏优化时,可以考虑使⽤timeout, ⽐如: 正常来说我们可以在10s内获取2000条数据,但是指定了timeout,发⽣超时后我们可以获取10ms中获取到的 100条数据

query dsl (domain specified language)

下⾯我仅仅列出来了⼀点点, 更多的⽰例,参见官⽹

_searchAPI +将请求写在请求体中

Copy

GET /bank/_search{

\"query\": { \"match_all\": {} }, # 查询全部

\"query\": { \"match\": {\"name\":\"changwu zhu\全⽂检索,户将输⼊的字符串拆解开,去倒排索引中⼀⼀匹配, 哪怕匹配上了⼀个也会将结果返回 # 实际上,上⾯的操作会被ES转换成下⾯的格式 # # {

# \"bool\":{

# \"should\":[

# {\"term\":{\"title\":\"changwu\ # {\"term\":{\"title\":\"zhu\ # ] # } # } #

\"query\": {

\"match\": { # ⼿动控制全⽂检索的精度, \"name\":{

\"query\":\"changwu zhu\

\"operator\":\"and\表⽰,只有同时出现changwu zhu 两个词的doc才会被命中 \"minimum_should_match\":\"75%\" # 去长尾,控制⾄少命中3/4才算是真正命中 } }

}, # 全⽂检索,operator 表⽰

# 添加上operator 操作会被ES转换成下⾯的格式,将上⾯的should转换成must # # {

# \"bool\":{ # \"must\":[

# {\"term\":{\"title\":\"changwu\ # {\"term\":{\"title\":\"zhu\ # ] # } # } #

# 添加上 minimum_should_match 操作会被ES转换成下⾯的格式 # # {

# \"bool\":{

# \"should\":[

# {\"term\":{\"title\":\"changwu\

# {\"term\":{\"title\":\"zhu\ # ],

# \"minimum_should_match\":3 # } # } #

\"query\": {

\"match\": { #控制权重, \"name\":{

\"query\":\"changwu zhu\

\"boost\":3 # 将name字段的权重提升成3,默认情况下,所有字段的权重都是样的,都是1 } } },

\"query\": {

# 这种⽤法不容忽略

\"dis_max\": { # 直接取下⾯多个query中得分最⾼的query当成最终得分 \"queries\":[

{\"match\":{\"name\":\"changwu zhu\ {\"match\":{\"content\":\"changwu\ ] } },

# best field策略

\"query\": { # 基于 tie_breaker 优化dis_max

# tie_breaker可以使dis_max考虑其他field的得分影响 \"multi_match\":{

\"query\":\"⽤于去匹配的字段\

\"type\":\"most_fields\指定检索的策略most_fields \"fields\":[\"field1\ } },

# most field 策略, 优先返回命中更多关键词的doc, (忽略从哪个,从多少个field中命中的,只要命中就⾏) \"query\": { # 基于 tie_breaker 优化dis_max

# tie_breaker可以使dis_max考虑其他field的得分影响

\"dis_max\": { # 直接取下⾯多个query中得分最⾼的query当成最终得分, 这也是best field策略 \"queries\":[

{\"match\":{\"name\":\"changwu zhu\ {\"match\":{\"content\":\"changwu\ ],

\"tie_breaker\":0.4 } },

\"query\": { \"match_none\": {} }

\"query\": { \"term\": {\"test_field\":\"指定值\精确匹配

\"query\": { \"exits\": {\"field\":\"title\不为空(但是这时ES2.0中的⽤法,现在不再提供了) \"query\": { # 短语检索

# 顺序的保证是通过 term position来保证的 # 精准度很⾼,但是召回率低

\"match_phrase\": { # 只有address字段中包含了完整的 mill lane (相连,顺序也不能变) 时,这个doc才算命中 \"address\": \"mill lane\" } },

\"query\": { # 短语检索 \"match_phrase\": {

\"address\": \"mill lane\

# 指定了slop就不再要求搜索term之间必须相邻,⽽是可以最多间隔slop距离

# 在指定了slop参数的情况下,离关键词越近,移动的次数越少, relevance score 越⾼ # match_phrase + slop 和 proximity match 近似匹配作⽤类似 # 平衡精准度和召回率

\"slop\":1 # 指定搜索⽂本中的⼏个term经过⼏次移动后可以匹配到⼀个doc } },

# 混合使⽤match和match_phrase 平衡精准度和召回率 \"query\": { \"bool\": { \"must\": {

# 全⽂检索虽然可以匹配到⼤量的⽂档,但是它不能控制词条之间的距离

# 可能java elasticsearch在Adoc中距离很近,但是它却被ES排在结果集的后⾯ # 它的性能⽐match_phrase⾼10倍,⽐proximity⾼20倍 \"match\": {

\"address\": \"java elasticsearch\" } },

\"should\": {

# 借助match_phrase+slop可以感知term position的功能,为距离相近的doc贡献分数,让它们靠前排列 \"match_phrase\":{ \"title\":{

\"query\":\"java elasticsearch\ \"slop\":50 } } } },

# 重打分机制 \"query\": { \"match\":{ \"title\":{

\"query\":\"java elasticsearch\ \"minimum_should_match\":\"50%\" } },

\"rescore\":{ # 对全⽂检索的结果进⾏重新打分

\"window_size\":50, # 对全⽂检索的前50条进⾏重新打分 \"query\": {

\"rescore_query\":{ # 关键字

\"match_phrase\":{ # match_phrase + slop 感知 term persition,贡献分数 \"title\":{

\"query\":\"java elasticsearch\ \"slop\":50 } } } } }

# 前缀匹配, 相对于全⽂检索,前缀匹配是不会进⾏分词的,⽽且每次匹配都会扫描整个倒排索引,直到扫描完⼀遍才会停下来 # 不会计算相关性得分,前缀越短拼配到的越多,性能越不好

\"query\": { # 查询多个, 在下⾯指定的两个字段中检索含有 `this is a test`的doc \"multi_match\" : {

\"query\": \"this is a test\

\"fields\": [ \"subject\ } },

\"query\": { # 前缀搜索,搜索 user字段以ki开头的 doc \"prefix\" : { \"user\" : \"ki\" } },

\"query\": { # 前缀搜索 + 添加权重

\"prefix\" : { \"user\" : { \"value\" : \"ki\ },

# 通配符搜索 \"query\": {

\"wildcard\" : { \"user\" : \"ki*y\" } },

\"query\": {

\"wildcard\" : { \"user\" : { \"value\" : \"ki*y\ }

# 正则搜索 \"query\": { \"regexp\":{

\"name.first\": \"s.*y\" } },

\"query\": {# 正则搜索 \"regexp\":{

\"name.first\":{ \"value\":\"s.*y\ \"boost\":1.2 } } },

# 搜索推荐, 类似于百度,当⽤户输⼊⼀个词条后,将其他符合条件的词条的选项推送出来 # 原理和match_pharse相似,但是唯⼀的区别就是会将最后⼀个term当作前缀去搜索

# 下例中: 使⽤quick brown进⾏match 使⽤f进⾏前缀搜索,使⽤slop调整term persition,贡献得分 \"query\": {

\"match_phrase_prefix\" : {# 前缀匹配 \"message\" : {

\"query\" : \"quick brown f\

\"max_expansions\" : 10, # 指定前缀最多匹配多少个term,超过这个数量就不在倒排索引中检索了,提升性能 \"slop\":10 } } },

# Function Score Query

# ⽤户可以⾃定义⼀个function_secore 函数,然后将某个field的值和ES计算出来的分数进⾏运算 # 最终实现对⾃⼰指定的field进⾏分数的增强功能 \"query\": {

\"function_score\": {

\"query\": { \"match_all\": {} }, \"boost\": \"5\

\"random_score\": {}, \"boost_mode\":\"multiply\" } },

# Fuzzy Query 模糊查询会提供容错的处理 \"query\": { \"fuzzy\" : { \"user\" : {

\"value\": \"ki\ \"boost\": 1.0,

\"fuzziness\": 2, # 做⼤的纠错数量

\"prefix_length\": 0,# 不会被“模糊化”的初始字符数。这有助于减少必须检查的术语的数量。默认值为0。 \"max_expansions\": 100 # 模糊查询将扩展到的最⼤项数。默认值为50 transpositions:true # 是否⽀持模糊变换(ab→ba)。默认的是假的 } } }

\"query\": {

\"bool\": { # 布尔查询, 最终通过将它内置must,should等查询的得分加起来/should,must的总数, 得到最终的得分 \"must\": [ # 必须匹配到XXX, 并且会得出相关性得分

{ \"match\": { \"address\": \"mill\" } }, # address中必须包含mill ],

# 在满⾜must的基础上,should条件不满⾜也可以,但是如果也匹配上了,相关性得分会增加 # 如果没有must的话,should中的条件必须满⾜⼀个

\"should\": [ # 指定可以包含的值, should是可以影响相关性得分的 { \"match\": { \"address\": \"lane\" } } ],

\"must_not\": [ # ⼀定不包含谁 { \"match\": { \"address\": \"mill\" } }, ],

\"filter\": { # 对数据进⾏过滤 \"range\": { # 按照范围过滤

\"balance\": { # 指定过滤的字段 \"gte\": 20000, # ⾼于20000 \"lte\": 30000 # 低于30000 } } } } }

在上⾯的组合查询中,每⼀个⼦查询都会计算⼀下他的相关性分数,然后由最外层的bool综合合并⼀个得分,但是 filter是不会计算分数的

默认的排序规则是按照score降序排序,但像上⾯说的那样,如果全部都是filter的话他就不会计算得分,也就是说所有的得分全是1,这时候就需要定制排序规则,定义的语法我在上⾯写了

其他辅助API

⽐如下⾯的⾼亮,排序,分页,以及_source 指定需要的字段都可以进⼀步作⽤在query的结果上Copy

\"highlight\":{ # ⾼亮显⽰

\"fields\":{ # 指定⾼亮的字段 \"balance\":{} },

\"sort\": [ # 指定排序条件

{ \"account_number\": \"asc\" } # 按照账户余额降序 ],

\"from\": 0, # 分页

\"size\": 10, # 每页的⼤⼩4,通过执⾏size=0,可以实现仅显⽰聚合结果⽽不显⽰命中的信息详情

\"_source\": [\"account_number\默认情况下,ES会返回全⽂JSON,通过_source可以指定返回的字段

聚合分析

聚合分析是基于doc value这样⼀个数据结果进⾏的,前⾯有说过,这个doc value 其实就是正排索引, 聚合分析就是根据某⼀个字段进⾏分组,要求这个字段是不能被分词的,如果被聚合的字段被分词,按照倒排索引的⽅式去索引的话,就不得不去扫描整个倒排索引(才可能将被聚合的字段找全,效率很低)三个概念:

什么是bucket?bucket就是聚合得到的结果

什么是metric?

metric就是对bucket进⾏分析,如最最⼤值,最⼩值,平均值

什么是下钻?

下钻就是在现有的分好组的bucket继续分组,⽐如⼀个先按性别分组,再按年龄分组聚合的关键字: aggs 和 query地位并列Copy

# 使⽤聚合时,天然存在⼀个metric,就是当前bucket的count \"aggs\": { # 聚合

\"group_by_state\": { # ⾃定义的名字 \"term\": {

\"field\": \"balance\" # 指定聚合的字段, 意思是 group by balance },

\"terms\": { # terms

\"field\": {\"value1\指定聚合的字段, 意思是 group by balance } } },

\"aggs\": { # 聚合中嵌套聚合 \"group_by_state\": { \"terms\": {

\"field\": \"field1\" },

\"aggs\": { # 聚合中嵌套聚合 \"average_balance\": { \"avg\": {

\"field\": \"field2\" } } } } },

\"aggs\": { #嵌套聚合,并且使⽤内部聚合的结果集 \"group_by_state\": { \"terms\": {

\"field\": \"state.keyword\ \"order\": {

\"average_balance\": \"desc\" # 使⽤的下⾯聚合的结果集 } },

\"aggs\": {

\"average_balance\": {

\"avg\": { # avg 求平均值 metric \"field\": \"balance\" } },

\"min_price\": {

\"min\": { # metric 求最⼩值 \"field\": \"price\" } },

\"max_price\": {

\"max\": { # metric 求最⼤值 \"field\": \"price\" } },

\"sum_price\": {

\"sum\": { # metric 计算总和 \"field\": \"price\" } }, } } },

\"aggs\": { # 先按照年龄分组,在按照性别分组,再按照平均⼯资聚合 # 最终的结果就得到了每个年龄段,每个性别的平均账户余额 \"group_by_age\": { \"range\": {

\"field\": \"age\ \"ranges\": [ {

\"from\": 20, \"to\": 30 } ] },

\"aggs\": {

\"group_by_gender\": { \"terms\": {

\"field\": \"gender.keyword\" },

\"aggs\": {

\"average_balance\": { \"avg\": {

\"field\": \"balance\" } } } } },

# histogram,类似于terms, 同样会进⾏bucket分组操作,接受⼀个field,按照这个field的值的各个范围区间进⾏分组操作 # ⽐如我们指定为2000, 它会划分成这样 0-2000 2000-4000 4000-6000 ... \"aggs\": { # 聚合中嵌套聚合 \"group_by_price\": { \"histogram\": { \"field\": \"price\ \"interval\":2000 },

\"aggs\": { # 聚合中嵌套聚合 \"average_price\": { \"avg\": {

\"field\": \"price\" } } } } },

\"aggs\" : {

\"sales_over_time\" : { # 根据⽇期进⾏聚合 \"date_histogram\" : { \"field\" : \"date\

\"interval\" : \"1M\⼀个⽉为⼀个跨度 \"format\" : \"yyyy-MM-dd\

\"min_doc_count\":0 #即使这个区间中⼀条数据都没有,这个区间也要返回 } }

} } }}

filter aggregate

过滤加聚合,统计type=t-shirt的平均价格Copy

POST /sales/_search?size=0{

\"aggs\" : {

\"t_shirts\" : {

\"filter\" : { \"term\": { \"type\": \"t-shirt\" } }, \"aggs\" : {

\"avg_price\" : { \"avg\" : { \"field\" : \"price\" } } } } }}

嵌套聚合-⼴度优先

说⼀个应⽤于场景: 我们检索电影的评论, 但是我们先按照演员分组聚合,在按照评论的数量进⾏聚合

分析: 如果我们选择深度优先的话, ES在构建演员电影相关信息时,会顺道计算出电影下⾯评论数的信息,假如说有10万个演员的话, 10万*10=100万个电影 每个电影下⼜有很多影评,接着处理影评, 就这样内存中可能会存在⼏百万条数据,但是我们最终就需要50条,这种开销是很⼤的

⼴度优先的话,是我们先处理电影数,⽽不管电影的评论数的聚合情况,先从10万演员中⼲掉99990条数据,剩下10个演员再聚合Copy

\"aggs\":{

\"target_actors\":{ \"terms\":{

\"field\":\"actors\ \"size\":10,

\"collect_mode\":\"breadth_first\" # ⼴度优先 } } }

global aggregation

全局聚合,下⾯先使⽤query进⾏全⽂检索,然后进⾏聚合, 下⾯的聚合实际上是针对两个不同的结果进⾏聚合,第⼀个聚合添加了global关键字,意思是ES中存在的所有doc进⾏聚合计算得出t-shirt的平均价格

第⼆个聚合针对全⽂检索的结果进⾏聚合Copy

POST /sales/_search?size=0{

\"query\" : {

\"match\" : { \"type\" : \"t-shirt\" } },

\"aggs\" : {

\"all_products\" : { \"global\" : {}, \"aggs\" : {

\"avg_price\" : { \"avg\" : { \"field\" : \"price\" } } } },

\"t_shirts\": { \"avg\" : { \"field\" : \"price\" } } }}

Cardinality Aggregate 基数聚合

作⽤类似于count(distcint),会对每⼀个bucket中指定的field进⾏去重,然后取去重后的count虽然她会存在5%左右的错误率,但是性能特别好Copy

POST /sales/_search?size=0{

\"aggs\" : {

\"type_count\" : {

\"cardinality\" : { # 关键字 \"field\" : \"type\" } } }}

对Cardinality Aggregate的性能优化, 添加 precision_threshold 优化准确率和内存的开销

下⾯的⽰例中将precision_threshold的值调整到100意思是当 type的类型⼩于100时,去重的精准度为100%, 此时内存的占⽤情况为 100*8=800字节加⼊我们将这个值调整为1000,意思是当type的种类在1000个以内时,去重的精准度100%,内存的占⽤率为1000*8=80KB官⽅给出的指标是, 当将precision_threshold设置为5时,错误率会被控制在5%以内Copy

POST /sales/_search?size=0{

\"aggs\" : {

\"type_count\" : {

\"cardinality\" : { # 关键字 \"field\" : \"type\

\"precision_threshold\":100 } } }}

进⼀步优化,Cardinality底层使⽤的算法是 HyperLogLog++, 可以针对这个算法的特性进⾏进⼀步的优化,因为这个算法的底层会对所有的 unique value取hash值,利⽤这个hash值去近似的求distcint count, 因此我们可以在创建mapping时,将这个hash的求法设置好,添加doc时,⼀并计算出这个hash值,这样 HyperLogLog++ 就⽆需再计算hash值,⽽是直接使⽤Copy

PUT /index/{

\"mappings\":{ \"my_type\":{ \"properties\":{ \"my_field\":{ \"type\":\"text\ \"fields\":{ \"hash\":{

\"type\":\"murmu3\" } } } } } }}

控制聚合的升降序

先按照颜⾊聚合,在聚合的结果上,再根据价格进⾏聚合, 最终的结果中,按照价格聚合的分组中升序排序, 这算是个在下转分析时的排序技巧Copy

GET /index/type/_search{

\"size\":0, \"aggs\":{

\"group_by_color\":{ \"term\":{

\"field\":\"color\ \"order\":{ #

\"avg_price\":\"asc\" } } },

\"aggs\":{

\"avg_price\":{ \"avg\":{

\"field\":\"price\" } } } }}

Percentiles Aggregation

计算百分⽐, 常⽤它计算如,在200ms内成功访问⽹站的⽐率,在500ms内成功访问⽹站的⽐例,在1000ms内成功访问⽹站的⽐例, 或者是销售价为1000元的商品,占总销售量的⽐例,销售价为2000元的商品占总销售量的⽐例等等

⽰例: 针对doc中的 load_time字段, 计算出在不同百分⽐下⾯的 load_time_outliner情况Copy

GET latency/_search{

\"size\": 0, \"aggs\" : {

\"load_time_outlier\" : { \"percentiles\" : {

\"field\" : \"load_time\" } } }}

响应 : 解读: 在百分之50的加载请求中,平均load_time的时间是在445.0, 在99%的请求中,平均加载时间980.1Copy

{ ...

\"aggregations\": {

\"load_time_outlier\": { \"values\" : { \"1.0\": 9.9,

\"5.0\": 29.500000000000004, \"25.0\": 167.5, \"50.0\": 445.0, \"75.0\": 722.5, \"95.0\": 940.5,

\"99.0\": 980.1000000000001 } } }}

还可以⾃⼰指定百分⽐跨度间隔Copy

GET latency/_search{

\"size\": 0, \"aggs\" : {

\"load_time_outlier\" : { \"percentiles\" : {

\"field\" : \"load_time\

\"percents\" : [95, 99, 99.9] } } }}

优化: percentile底层使⽤的是 TDigest算法,⽤很多个节点执⾏百分⽐计算,近似估计,有误差,节点越多,越精准

可以设置compression的值, 默认是100 , ES限制节点的最多是 compression*20 =2000个node去计算 , 因为节点越多,性能就越差⼀个节点占⽤ 32字节, 1002032 = 64KBCopy

GET latency/_search{

\"size\": 0, \"aggs\" : {

\"load_time_outlier\" : { \"percentiles\" : {

\"field\" : \"load_time\

\"percents\" : [95, 99, 99.9],

\"compression\":100 # 默认值100 } } }}

优化相关性得分

第⼀种⽅式:

在content字段中全⽂检索 java elasticsearch时,给title中同时出现java elasticsearch的doc的权重加倍Copy

\"query\": {

\"bool\" : {# 前缀匹配 \"match\":{ \"content\":{

\"query\":\"java elasticsearch\" } },

\"should\":[ \"match\":{ \"title\":{

\"query\":\"java elasticsearch\ \"boost\":2 } } ] } }

第⼆种: 更换写法,改变占⽤的权重⽐例Copy

GET my_index/_doc/_search{

\"query\":{ \"should\":[

{ \"match\":{\"title\":\"this is\ { \"match\":{\"title\":\"this is\ {

\"bool\":{ \"should\":[

{\"match\":{\"title\":\"this is\ {\"match\":{\"title\":\"this is\ ] } } ] }}

第三种: 如果不希望使⽤相关性得分,使⽤下⾯的语法Copy

GET my_index/_doc/_search{

\"query\": {

\"constant_score\" : { \"filter\" : {

\"term\" : { \"title\" : \"this\ },

\"boost\" : 1.2 } }}

第四种: 灵活的查询

查询必须包含XXX,必须不包含YYY的docCopy

GET my_index/_doc/_search{

\"query\":{ \"bool\": { \"must\":{ \"match\":{

\"title\":\"this is a \" } },

\"must_not\":{ \"match\":{

\"title\":\"another\" } } } }}

第五种: 查询必须包含XXX,可以包含YYY,但是包含了YYY后它的权重就会减少指定的值Copy

GET my_index/_doc/_search{

\"query\":{

\"boosting\": { \"positive\":{ \"match\":{

\"title\":\"this is a \" } },

\"negative\":{ \"match\":{

\"title\":\"another\" } },

\"negative_boost\": 0.2 } }}

第六种: 重打分机制

Copy

\"query\": { \"match\":{ \"title\":{

\"query\":\"java elasticsearch\ \"minimum_should_match\":\"50%\" } },

\"rescore\":{ # 对全⽂检索的结果进⾏重新打分

\"window_size\":50, # 对全⽂检索的前50条进⾏重新打分 \"query\": {

\"rescore_query\":{ # 关键字

\"match_phrase\":{ # match_phrase + slop 感知 term persition,贡献分数 \"title\":{

\"query\":\"java elasticsearch\ \"slop\":50 } } } } }

第七种: 混⽤match和match_phrase提⾼召回率Copy

\"query\": { \"bool\": { \"must\": {

# 全⽂检索虽然可以匹配到⼤量的⽂档,但是它不能控制词条之间的距离

# 可能java elasticsearch在Adoc中距离很近,但是它却被ES排在结果集的后⾯ # 它的性能⽐match_phrase⾼10倍,⽐proximity⾼20倍 \"match\": {

\"address\": \"java elasticsearch\" } },

\"should\": {

# 借助match_phrase+slop可以感知term position的功能,为距离相近的doc贡献分数,让它们靠前排列 \"match_phrase\":{ \"title\":{

\"query\":\"java elasticsearch\ \"slop\":50 } } } }

欢迎⼤家点赞⽀持

分类: 标签:

因篇幅问题不能全部显示,请点此查看更多更全内容