ElasticSearch 使用技巧

其实在几年前就接触 Eleasticsearch 了,当时作为一个SA,并没有好好了解它的用法。最近在做搜索的功能,才发觉曾经对力量一无所知。

Mapping 创建 Schema

ES 使用前需要定义 mapping,相当于 MySQL 的 TableSchema,在创建 mapping 的时候,配置 type 的dynamic=strict,禁止未定义字段自动被添加到索引。

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
curl -XPUT "$URL/mall_goods_v1/?pretty" -H 'Content-Type:application/json' -d'
{
"mappings" : {
"goods": {
"dynamic": "strict",
"properties": {
"item_name": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"item_code": {
"type": "keyword"
},
"status": {
"type": "integer"
},
"start_time":{
"type" : "date"
},
"group_ids": {
"type": "text",
"analyzer": "whitespace"
},
}
}
}
}';

中文分词

ES提供了IK分词器应对中文分词,它有两种分词模式:

  • ik_max_word:会将文本做最细粒度的拆分
  • ik_smart:会做最粗粒度的拆分
    两种分词器使用的最佳实践是:索引时用ik_max_word,在搜索时用 ik_smart。
    索引时最大化的将文章内容分词,搜索时更精确的搜索到想要的结果。

别名

给 ES 的 Index 起一个别名(alias) 能优雅解决两个索引无缝切换的问题,此功能在某些场景下非常使用。
举个例子:
如果发现 student_v1 的 Index 有问题,需重建一个 student_v2 的 Index, 我们可以给 student_v1 赋予一个别名 student,外界使用别名访问这个索引, 建好 student_v2 之后,将这个 student 别名指向 student_v2, 这样就可在外部无感知的情况下完成切换。

1
2
3
curl -XPOST "$URL/_aliases?pretty" -H 'Content-Type:application/json'  -d' 
{ "actions" : [ { "add" : { "index" : "student_v2", "alias" : "student" } } ] } '

ES 查询技巧

  • 只要不是 text 类型的字段,尽量使用 filter 过滤,因为它支持结果缓存,性能最佳。全文检索必须使用 must 而不是 filter,否则将导致 filter 缓存失效。

  • 自然语言检索应该用 “match” 查询,”term” 是不分词当成整体的。

  • 通过 termvector 查看一个字段的切分状况,用来辅助我们的查询语句是否正确

1
2
3
4
5
6
7
8
curl -XGET -H 'Content-Type:application/json' "http://$URL/mall_goods/goods/12954895599938508532/_termvectors?pretty" -d ' {
"fields" : [ "item_name"],
"offsets" : true,
"payloads" : true,
"positions" : true,
"term_statistics" : true,
"field_statistics" : true
} '
  • 版本号更新
    Elasticsearch 是分布式的。当文档创建、更新或删除时, 新版本的文档必须复制到集群中的其他节点。Elasticsearch 也是异步和并发的,这意味着这些复制请求被并行发送,并且到达目的地时也许顺序是乱的 。 Elasticsearch 需要一种方法确保文档的旧版本不会覆盖新的版本。 检查当前 _version 是否 小于 指定的版本号。 如果请求成功,外部的版本号作为文档的新 _version 进行存储.
    常见的方法是通过增加 version_type = external , 然后使用时间戳做版本号更新来保证旧文档不会覆盖新文档。
1
2
3
4
5
PUT /website/blog/2?version=5&version_type=external
{
"title": "My first external blog entry",
"text": "Starting to get the hang of this..."
}
  • 提供排序权重
    ES默认是按照 BM25 来计算分数的。比如以下例子,
    用户输入qwe 我们需要搜索出名字中包含这些字符的记录
1
2
3
4
5
6
7
8
9
GET group_member_v1/_search
{
"query": {
"bool": {
"must": {"match": {"split_nickname": {"query": "q w e","operator": "and"}}}
}
},
"size": 100
}

实际上我们希望,qwe 连在一起的 nickname 权重可以更高,排序更前,于是引用进了 const_scorematch_phrase

1
2
3
4
5
6
7
8
9
10
11
12
13
GET group_member_v1/_search
{
"query": {
"bool": {
"minimum_should_match": 1,
"should": [
{"match": {"split_nickname": {"query": "q w e","operator": "and"}}},
{ "constant_score": {"boost": 1000,"filter": {"match_phrase": {"split_nickname": "q w e"}}}}
]
}
},
"size": 100
}

Suggester

completion suggest,常叫做自动完成(auto completion) 也叫搜索推荐。
比如说我们在搜索歌曲,搜索”月亮”,百度自动给你提示 “月亮代表我的心”, “月亮之上”, 搜索引擎会自动提示补全,借助 ES 的 Suggester 也有这个功能。看如下的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
PUT /songs_index
{
"mappings": {
"songs" : {
"properties" : {
"title" : {
"type": "text",
"analyzer": "ik_max_word",
"fields": {
"suggest" : {
"type" : "completion", // 这个特性就是我们想要的
"analyzer": "ik_max_word"
}
}
},
"content": {
"type": "text",
"analyzer": "ik_max_word"
}
}
}
}
}

查询语句

1
2
3
4
5
6
7
8
9
10
11
GET /song_indexs/songs/_search
{
"suggest": {
"my-suggest" : {
"text" : "华南",
"completion" : {
"field" : "title.suggest"
}
}
}
}

删除指定记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST school_rank_v1/rank/_delete_by_query
{
"query": {
"constant_score" : {
"filter" : {
"bool" : {
"must" : [
{"term" : { "qs_rank" : 0 } }
]
}
}
}
}
}