ES(ElasticSearch)-RestfulAPI

Restful-API使用

接口文档可参考上一边ES文章8.x版本的文档,下文都以8.x语法为例

https://www.elastic.co/guide/en/elasticsearch/reference/8.18/search-with-elasticsearch.html

RestfulAPI使用

操作索引

新增

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#创建一个索引
PUT music_V1
{
"mappings": {
"properties": {
"singers": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
},
"pinyin": {
"type": "text",
"analyzer": "pinyin"
},
"zh": {
"type": "text",
"analyzer": "smartcn"
}
}
},
"song_name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
},
"pinyin": {
"type": "text",
"analyzer": "pinyin"
},
"zh": {
"type": "text",
"analyzer": "smartcn"
}
}
},
"lyrics": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
},
"pinyin": {
"type": "text",
"analyzer": "pinyin"
},
"zh": {
"type": "text",
"analyzer": "smartcn"
}
}
},
"md5": {
"type": "keyword"
},
"music_score_data": {
"type": "text"
},
"origin": {
"type": "keyword"
},
"chord_item_id": {
"type": "keyword"
},
"song_item_id": {
"type": "keyword"
}
}
}
}
//起别名,起别名有利于后续动态调整mappings结构,或者一次差多个别名映射的索引
POST /_aliases
{
"actions" : [
{ "add" : { "index" : "music_V1", "alias" : "music" } }
]
}

注意song_name原本是text类型,但是创建索引时通过fields字段定义了多个类型数据,比如keyword和不同分词效果的text,在全文检索中便于用这些field参与搜索

查询

1
GET /person

删除

1
2
# 删除索引
DELETE /person

属性类型

字符串类型:

text: 一把被用于全文检索。将当前Field进行分词。

keyword: 当前Field不会被分词。

数值类型

long: 取值范围为-9223372036854774808~922337203685477480(-2的63次方到2的63次方-1),占用8个字节

integer: 取值范围为-2147483648~2147483647(-2的31次方到2的31次方-1),占用4个字节

short: 取值范围为-32768~32767(-2的15次方到2的15次方-1),占用2个字节

byte: 取值范围为-128~127(-2的7次方到2的7次方-1),占用1个字节

double:1.797693e+308~4.9000000e-324(e+308表示是乘以10的308次方,e-324表示乘以10的负324次方)占用8个字节

float:3.402823e+38~1.401298e-45(e+38表示是乘以10的38次方,e-45表示乘以10的负45次方),占用4个字节

half_float: 精 度 比float小 一 半 。

scaled_float: 根据一个long 和scaled来表达一个浮点型,long-345,scaled-100->3.45

时间类型 :

date类型,针对时间类型指定具体的格式

布尔类型 :

boolean类型,表达true和false

二进制类型:

binary类型暂时支持Base64 encode string

范围类型 :

long_range: 赋值时,无需指定具体的内容,只需要存储一个范围即可,指定gt,It,gte,Ite

integer_range: 同 上

double_range: 同上

float_range: 同上

date_range: 同上

ip_range: 同上

经纬度类型:

geo_point: 用来存储经纬度的

ip类型:

ip: 可以存储IPV4或者IPV6

操作文档

新建文档

自动生成_id

1
2
3
4
5
6
7
8
9
#添加文档,自动生成id
POST /novel
{
"name": “盘龙”,
"author": "我吃西红柿",
"count":100000,
"on-sale": "2000-01-01",
"descr": “山重水复疑无路,柳暗花明又一村”
}

手动指定_id

1
2
3
4
5
6
7
8
9
#添加文档,手动指定id
PUT /book/novel/1
{
"name":" 红楼梦",
"author": “ 曹雪芹”,
"count":10000000 ,
"on-sale":" 1985-01-01",
"descr": “一个是阆苑仙葩,一个是美玉无瑕”
}

修改文档

覆盖式修改

1
2
3
4
5
6
7
8
#添加文档,手动指定id
PUT /book/novel/1
{
"name" :" 红楼梦",
"author": "曹雪芹", "count" :4353453,
"on-sale": "1985-01-01",
"descr": “一个是阆苑仙葩,一个是美玉无瑕”
}

部分更新

1
2
3
4
5
6
POST test/_update/1
{
"doc": {
"name": "new_name"
}
}

删除文档

根据id删除

1
2
#根据id删除文档
DELETE /book/novel/_id

查询文档Search API

term&terms查询

term查询:term 的查询是代表完全匹配,搜索之前不会对你搜索的关键字进行分词,对你的关键字去文档分词库中去匹配内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
POST /sms-logs-index/_search
{
"query": {
"term": {
"user.id": "kimchy"
}
}
}

POST /sms-logs-index/_search
{
"query": {
"terms": {
"user.id": ["kimchy","kimchy2"]
}
}
}

id&ids

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#id 查询
GET /sms-logs-index/1
#ids 查询
POST /sms-logs-index/_search
{
"query": {
"ids": {
"values": [
"1",
"2",
"3"
]
}
}
}

范围查询

range查询

范围查询,只针对数值类型,对某一个Field进行大于或者小于的范围指定

支持的运算符:

  • gt (>) - 大于
  • gte (>=) - 大于等于
  • lt (<) - 小于
  • lte (<=) - 小于等于
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
POST /sms-logs-index/_search
{
"query": {
"range": {
"fee": {
"gt": 5,
"lte": 10
}
}
}
}

常用表达式:
now - 当前时间
+1h - 加1小时
-1d - 减1天
/d - 舍入到天
# 日期范围查询
GET /orders/_search
{
"query": {
"range": {
"order_date": {
"gte": "now-30d/d",
"lte": "now/d"
}
}
}
}

{
"range": {
"timestamp": {
"gte": "now-1h", // 1小时前到现在
"lte": "now"
}
}
}

date_range 时间

1
2
3
4
5
6
7
8
9
10
11
12
13
GET /logs/_search
{
"query": {
"date_range": {
"field": "timestamp",
"format": "yyyy-MM-dd",
"ranges": [
{ "from": "2023-01-01", "to": "2023-01-31" },
{ "from": "2023-03-01" }
]
}
}
}

numeric_range 数值

1
2
3
4
5
6
7
8
9
10
11
12
GET /products/_search
{
"query": {
"numeric_range": {
"field": "price",
"ranges": [
{ "from": 100, "to": 200 },
{ "from": 500 }
]
}
}
}

ip_range IP地址

1
2
3
4
5
6
7
8
9
10
11
GET /servers/_search
{
"query": {
"ip_range": {
"field": "ip_address",
"ranges": [
{ "from": "192.168.1.1", "to": "192.168.1.254" }
]
}
}
}

geo_distance 地理距离

查找距离某点特定范围内的文档

1
2
3
4
5
6
7
8
9
10
11
12
GET /locations/_search
{
"query": {
"geo_distance": {
"distance": "10km",
"location": {
"lat": 40.715,
"lon": -73.988
}
}
}
}

模糊匹配

prefix 前缀匹配

前缀查询,可以通过一个关键字去指定一个Field的前缀,从而查询到指定的文档。

一般用于keyword类型字段

区分大小写

1
2
3
4
5
6
7
8
POST /sms-logs-index/_search
{
"query": {
"prefix": {
"corpName": "途虎"
}
}
}

wildscard 通配查询

通配查询,和MySQL 中的like是一个套路,可以在查询时,在字符串中指定通配符*和占位符?

1
2
3
4
5
6
7
8
9
10
11
12
# wildcard 查询
POST /sms-logs-index/_search
{
"query": {
"wildcard": {
"corpName": {
"value": "中国*"
# 可以使用*和?指定通配符和占位符
}
}
}
}

fuzzy 近似匹配

模糊查询,我们输入字符的大概,ES就可以去根据输入的内容大概去匹配一下结果

核心参数说明

  1. fuzziness(模糊度):

    • 可设置为0, 1, 2"AUTO"

    • AUTO

      会根据词项长度自动选择(推荐):

      • 1-2个字符:必须精确匹配

      • 3-5个字符:允许1个编辑距离

      • 5个字符:允许2个编辑距离

  2. max_expansions(最大扩展数):

    • 控制生成的变体数量,默认50
  3. prefix_length(前缀长度):

    • 开头多少个字符必须精确匹配(默认0)
    • 可显著提高性能
  4. transpositions(相邻字母互换):

    • 是否将相邻字母互换视为1次编辑(默认true)
    • 如”ab”→”ba”计为1次编辑
1
2
3
4
5
6
7
8
9
10
11
12
# fuzzy查询
POST /sms-logs-index/_search
{
"query": {
"fuzzy": {
"corpName": {
"value": "盒马先生",
"prefix_length": 2 # 指定前面2个字符不允许出现错误
}
}
}
}

正则匹配regexp

正则查询,通过你编写的正则表达式去匹配内容

prefix/fuzzy/wildcard/regexp查询属于低效查询,在以下场景应避免使用:

  • 高频查询字段
  • 大数据量索引
  • 对延迟敏感的实时系统
1
2
3
4
5
6
7
8
9
# regexp 查询
POST /sms-logs-index/_search
{
"query": {
"regexp": {
"mobile": "180[0-9]{8}" # 匹配以180开头+8位数字的手机号
}
}
}

分词查询

match是全文查询的一种,他会使用分词器分析输入字符串成单独的词条,再去倒排索引中匹配计算这些词条,返回命中的索引文档。

过程如下:

1、分词 2、匹配计算 3、结果倒序返回

​ 查询的是日期或者是数值的话,他会将你基于的字符串查询内容转换为日期或者数值对待。
​ 查询的内容是一个不能被分词的内容 (keyword),match查询不会对你指定的查询关键字进行分词。
​ 查询的内容时 一个可以被分词的内容 (text),match会将你指定的查询内容根据一定的方式去分词,去分词库中匹配指定的内容

match 查询,实际底层就是多个term查询,将多个term查询的结果给你封装到了一起

分词器

1
2
3
4
5
GET /_analyze
{
"text": "中华人民共和国",
"analyzer": "ik_smart" // 可以替换为standard或其他分词器
}

match_all

查询全部内容,不指定任何查询条件

1
2
3
4
5
6
7
8
POST /sms-logs-index/ _search
{
"query":{
"match_all":{

}
}
}

match

制定一个field进行查询字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
POST /sms-logs-index/ _search
{
"query": {
"match": {
"smsContent": "收货安装"
}
}
}

POST /sms-logs-index/ _search
{
"query": {
"match": {
"smsContent": {
"query":"中国健康",
"operator":"and" //内容既包含中国也包含健康
}
}
}
}

POST /sms-logs-index/ _search
{
"query": {
"match": {
"smsContent": {
"query":"中国健康",
"operator":"or" //内容既包含中国也包含健康,默认值是or
}
}
}
}

multi_match

multi_match 针对多个field进行检索,多个field对应一个text

1
2
3
4
5
6
7
8
9
10
11
12
POST /sms-logs-index/ _search
{
"query": {
"multi_match": {
"query": " 北京",
"fields": [
"province",
"smsContent"
]
}
}
}

match_parse

用于精确匹配短语的查询方式,强调查询词项的顺序位置完全匹配

特别适合中文短句搜索场景。使用时需根据实际需求调整slop参数,并在性能和精度之间取得平衡。

slop:词项间最大间隔距离

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 案例1:商品名称精确匹配,slop默认=0,表示match_parse的输入内容不允许中间有间隔,
GET /products/_search
{
"query": {
"match_phrase": {
"name": "iPhone 13 Pro"
}
}
}

// 案例2:允许间隔的地址查询,某个文档:北京市xxx海淀区某公园被连续的分词为 北京市、xxx、海淀区,也能匹配中
GET /address/_search
{
"query": {
"match_phrase": {
"full_address": {
"query": "北京 海淀区",
"slop": 3
}
}
}
}
# 能搜索出的文本:

# 不能搜索出的文本:
海淀区 北京市 //词序颠倒
上海海淀区 //缺少关键字 北京
北京作为中国首都,其西北郊区的海淀区 // 间隔超过3个词

"北京市最核心的教育区域海淀区" 分词位置:
0:北京市 1:最 2:核心 3:的 4:教育 5:区域 6:海淀区
从"北京市"(位置0)到"海淀区"(位置6)需要跳过5个位置(6-0-1=5跳),超过slop=3,因此不会匹配

分页查询

普通分页from + size

深分页scoll

ES对from+size 是有限制的,from + size 不能超过 index.max_result_window(默认 10000) 原理:

● from+size 在ES查询数据的方式:

o 第一步现将用户指定的关键进行分词。

o 第二步将词汇去分词库中进行检索,得到多个文档的id。

o 第三步去各个分片中去拉取指定的数据。耗时较长。

o 第四步将数据根据score进行排序。耗时较长。

o 第五步根据from 的值,将查询到的数据舍弃一部分。

o 第六步返回结果。

● scroll+size在ES查询数据的方式:

o 第一步现将用户指定的关键进行分词。

o 第二步将词汇去分词库中进行检索,得到多个文档的id。

o 第三步将文档的id存放在一个ES的上下文中。

o 第四步根据你指定的size的个数去ES中检索指定个数的数据,拿完数据的文档id, 会从上下文中移除。 。

o 第五步如果需要下一页数据,直接去ES的上下文中,找后续内容。

o 第六步循环第四步和第五步

Scroll查询方式,不适合做实时的查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 执行scroll查询,返回第一页数据,并且将文档id信息存放在ES上下文中,指定生存时间1m
POST /sms-logs-index/_search?scroll=1m
{
"query": {
"match_all": {}
},
"sort": [
{
"fee": {
"order": "desc"
}
}
],
"size": 10 #每批获取10
}

# 根据scroll查询下一页数据,查询结果和排序规则与第一步的一致
POST /_search/scroll
{
"scroll_id": "<根据上一步得到的scorll_id,每次查询会返回新的scroll_id>",
"scroll": "<scorll信息的生存时间>"
}

# 删除scroll在ES上下文中的数据
DELETE /_search/scroll/scroll_id

image-20250615163806243

Scroll分页 from+size分页
数据获取方式 首次查询建立数据快照,后续基于游标读取 每次请求重新计算所有匹配文档
排序开销 仅首次查询需要全局排序 每次请求都需全局排序(深度分页时O(n)复杂度)
内存消耗 维护固定大小的scroll上下文 每次请求需在协调节点缓存全量结果
适用场景 大数据量导出/非实时遍历(如日志分析) 实时用户查询(前1000条)

高亮查询

高亮查询就是你用户输入的关键字,以一定的特殊样式展示给用户,让用户知道为什么这个结果被检索出来。

高亮展示的数据,本身就是文档中的一个Field,单独将Field以highlight的形式返回给你。

ES提供了一个highlight属性,和query同级别的。

● fragment_size:指定高亮数据展示多少个字符回来。

● pre_tags:指定前缀标签,举个栗子

● post_tags:指定后缀标签,举个栗子

● fields:指定哪几个Field以高亮形式返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# highlight查询
POST /sms-logs-index/_search
{
"query": {
"match": {
"smsContent": "盒马"
}
},
"highlight": {
"fields": {
"smsContent": {
#这里也能写pre_tags这些
}
},
"pre_tags": "<font color='red'>",
"post_tags": "</font>",
"fragment_size": 10
}
}
# 预期结果
"hits": {
"hits": [
{
"_source": {
"smsContent": "盒马鲜生配送员已出发"
},
"highlight": {
"smsContent": [
"<font color='red'>盒马</font>鲜生配" // 关键词高亮+10字符片段
]
}
}
]
}

目前高亮web交互的方案主要由3种:

1、后端直接把ES查询时定好高亮的规则,把hightlight内容直接以带HTML的格式给到前端给前端显示

2、后端解析查询highlight字段,按照定好的标志位解析高亮的起始和结束位置,把连续高亮字段也做合并处理,返回合适的结构体给前端,这样前端就能做一些酷炫的高亮展示

1
2
3
4
5
{
"content:":"盒马鲜生配送员已出发",
"start":0,
"end":1
}

3、第三种就是后端不做高亮的任何处理,前端全文检索高亮文本内容(极其不推荐,效果不好且性能差)

全文检索案例

需求

​ 某个全文检索需要支持搜索歌曲,检索匹配歌曲的歌名、歌手名、歌词,并且权重以此递减

分析

1、新建索引,歌名、歌手名、歌词(都是多fields属性字段,即便type是keyword,子field也包含text方便分词搜索)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"mappings": {
"properties": {
"song_name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
},
"pinyin": {
"type": "text",
"analyzer": "pinyin"
},
"zh": {
"type": "text",
"analyzer": "smartcn"
}
}
},
......
}
}
}

2、歌名、歌手名、歌词权重递减,匹配查询时对不同的field查询时需要指定“boost”属性,通过该值的大小来设置权重

3、为了相关度高的排前面,采用should查询 + boost,组合使用,多次命中则分数相加,得分靠前

4、为了提升数据召回率 采用 [term查询keyword] + [march + march_parse 分词搜索text,中文分词field,拼音分词field] 同时匹配某一属性的方式。

最终查询举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
{
"size": 1500,
"from": 0,
"query": {
"bool": {
"should": [
{
"term": {
"song_name.keyword": {
"value": "搜索词",
"boost": "权重分数"
}
}
},
{
"match_phrase": {
"song_name.zh": {
"query": "搜索词",
"boost": "权重分数"
}
}
},
{
"match_phrase": {
"song_name.pinyin": {
"query": "搜索词",
"boost": "权重分数"
}
}
},
{
"match": {
"song_name.zh": {
"query": "搜索词",
"boost": "权重分数"
}
}
},
{
"match": {
"song_name.pinyin": {
"query": "搜索词",
"boost": "权重分数"
}
}
},
{
"match": {
"song_name": {
"query": "搜索词",
"boost": "权重分数"
}
}
},
......
],
"minimum_should_match": 1
}
}
}