简

人生短暂,学海无边,而大道至简。


  • 首页

  • 归档

  • 分类

  • 标签

Elasticsearch搜索(API和DSL)

发表于 2019-03-14 | 分类于 Elasticsearch

搜索API

搜索API 端点地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
GET /bank/accounts/_search?q=account_number:222

#从索引bank里面搜索字段firstname为Rachelle的记录

GET /bank/_search?q=firstname:Rachelle

#从索引bank、user里面搜索字段firstname为Rachelle的记录

GET /bank,user/_search?q=firstname:Rachelle

#从所有索引里面搜索字段firstname为Rachelle的记录
GET /_all/_search?q=firstname:Rachelle
GET /_search?q=firstname:Rachelle

#说明:搜索的端点地址可以是多索引多mapping type的。
#搜索的参数可作为URI请求参数给出,也可用 request body 给出

URI Search

URI 搜索方式通过URI参数来指定查询相关参数。

GET /bank/_search?q=account_number:222

可用的参数请参考: https://www.elastic.co/guide/en/elasticsearch/reference/current/search-uri-request.html

查询结果说明

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
{
"took": 3,#耗时(ms)
"timed_out": false,#是否超时
"_shards": {#查询了多少分片
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {#命中结果
"total": 1, #命中总数
"max_score": 1,#最高得分
"hits": [#本页结果文档数组
{
"_index": "bank",
"_type": "accounts",
"_id": "222",
"_score": 1,
"_source": {
"account_number": 222,
"balance": 14764,
"firstname": "Rachelle",
"lastname": "Rice",
"age": 36,
"gender": "M",
"address": "333 Narrows Avenue",
"employer": "Enaut",
"email": "rachellerice@enaut.com",
"city": "Wright",
"state": "AZ"
}
}
]
}
}

特殊的查询参数用法

有多少文档匹配某个查询:GET /bank/_search?q=city:b*&size=0

有没有文档匹配某个查询:GET /bank/_search?q=city:b*&size=0&terminate_after=1 #”terminated_early”: true,

Request body Search

Request body 搜索方式以JSON格式在请求体中定义查询 query。请求方式可以是 GET 、POST 。

1
2
3
4
5
6
GET /bank/_search
{
"query":{
"term":{"firstname":"Effie"}
}
}

可用的参数:

1
2
3
4
5
6
7
timeout:请求超时时长,限定在指定时长内响应(即使没查完);
from: 分页的起始行,默认0;
size:分页大小;
request_cache:是否缓存请求结果,默认true。
terminate_after:限定每个分片取几个文档。如果设置,则响应将有一个布尔型字段terminated_early来指示查询执行是否实际已经terminate_early。缺省为no terminate_after;
search_type:查询的执行方式,可选值dfs_query_then_fetch or query_then_fetch ,默认: query_then_fetch ;
batched_reduce_size:一次在协调节点上应该减少的分片结果的数量。如果请求中的潜在分片数量可能很大,则应将此值用作保护机制以减少每个搜索请求的内存开销。

query 元素定义查询

query 元素用Query DSL 来定义查询。

1
2
3
4
5
6
GET /_search
{
"query" : {
"term" : {"firstname":"Effie"}
}
}

指定返回哪些内容

source filter 对_source字段进行选择

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
GET /_search
{
"_source": false,
"query" : {
"term" : {"firstname":"Virginia"}
}
}

#通配符查询

GET /_search
{
"_source": [ "obj1.*", "obj2.*" ],
"query" : {
"term" : {"firstname":"Virginia"}
}
}

GET /_search
{
"_source": "obj.*",
"query" : {
"term" : {"firstname":"Virginia"}
}
}

#包含什么不包含什么
GET /_search
{
"_source": {
"includes": [ "obj1.*", "obj2.*" ],
"excludes": [ "*.description" ]
},
"query" : {
"term" : {"firstname":"Virginia"}
}
}

stored_fields 来指定返回哪些stored字段

1
2
3
4
5
6
7
GET /_search
{
"stored_fields" : ["account_number", "age"],
"query" : {
"term" : {"firstname":"Virginia"}
}
}

说明:* 可用来指定返回所有存储字段

docValue Field 返回存储了docValue的字段值

1
2
3
4
5
6
7
GET /_search
{
"query" : {
"match_all": {}
},
"docvalue_fields" : ["account_number", "age"]
}

version 来指定返回文档的版本字段

1
2
3
4
5
6
7
GET /_search
{
"version": true,
"query" : {
"term" : {"firstname":"Virginia"}
}
}

explain 返回文档的评分解释

1
2
3
4
5
6
7
GET /_search
{
"explain": true,
"query" : {
"term" : {"firstname":"Virginia"}
}
}

Script Field 用脚本来对命中的每个文档的字段进行运算后返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
GET /bank/_search
{
"query": {
"match_all": {}
},
"script_fields": {
"test1": {
"script": {
"lang": "painless",
"source": "doc['balance'].value * 2"
}
},
"test2": {
"script": {
"lang": "painless",
"source": "doc['age'].value * params.factor",
"params": {
"factor": 2
}
}
} }}

说明:params _source 取 _source字段值,官方推荐使用doc,理由是用doc效率比取_source 高

min_score 限制最低评分得分

1
2
3
4
5
6
7
GET /_search
{
"min_score": 0.5,
"query" : {
"term" : {"firstname": "Virginia"}
}
}

post_filter 后置过滤:在查询命中文档、完成聚合后,再对命中的文档进行过滤。

如:要在一次查询中查询品牌为gucci且颜色为红色的shirts,同时还要得到gucci品牌各颜色的shirts的分面统计。

创建索引并指定mappping:

1
2
3
4
5
6
7
8
9
10
11
12
PUT /shirts
{
"mappings": {
"_doc": {
"properties": {
"brand": { "type": "keyword"},
"color": { "type": "keyword"},
"model": { "type": "keyword"}
}
}
}
}

往索引里面放入文档即类似数据库里面的向表插入一行数据,并立即刷新

1
2
3
4
5
6
7
8
9
10
11
12
PUT /shirts/_doc/1?refresh
{
"brand": "gucci",
"color": "red",
"model": "slim"
}
PUT /shirts/_doc/2?refresh
{
"brand": "gucci",
"color": "green",
"model": "seec"
}

执行查询:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
GET /shirts/_search
{
"query": {
"bool": {
"filter": {
"term": { "brand": "gucci" }
}
}
},
"aggs": {
"colors": {
"terms": { "field": "color" }
}
},
"post_filter": {
"term": { "color": "red" }
}
}

sort 排序

可以指定按一个或多个字段排序。也可通过_score指定按评分值排序,_doc按索引顺序排序。默认是按相关性评分从高到低排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
GET /bank/_search
{
"query": {
"match_all": {}
},
"sort": [{
"age": { "order": "desc" }
},
{
"balance": { "order": "asc" }
},
"_score"
]
}

说明:order 值:asc、desc。如果不给定,默认是asc,_score默认是desc

结果中每个文档会有排序字段值给出

** 多值字段排序 **

对于值是数组或多值的字段,也可进行排序,通过mode参数指定按多值的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
PUT /my_index/_doc/1?refresh
{
"product": "chocolate",
"price": [20, 4]
}

POST /_search
{
"query" : {
"term" : { "product" : "chocolate" }
},
"sort" : [
{"price" : {"order" : "asc", "mode" : "avg"}}
]
}

** Missing values 缺失该字段的文档 **

missing 的值可以是 _last, _first

1
2
3
4
5
6
7
8
9
GET /_search
{
"sort" : [
{ "price" : {"missing" : "_last"} }
],
"query" : {
"term" : { "product" : "chocolate" }
}
}

** 地理空间距离排序 **

官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-sort.html#geo-sorting

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
GET /_search
{
"sort" : [
{
"_geo_distance" : {
"pin.location" : [-70, 40],
"order" : "asc",
"unit" : "km",
"mode" : "min",
"distance_type" : "arc"
}
}
],
"query" : {
"term" : { "user" : "kimchy" }
}
}

参数说明:

1
2
3
4
_geo_distance 距离排序关键字
pin.location是 geo_point 类型的字段
distance_type:距离计算方式 arc球面 、plane 平面。
unit: 距离单位 km 、m 默认m

** Script Based Sorting 基于脚本计算的排序 **

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
GET /_search
{
"query" : {
"term" : { "user" : "kimchy" }
},
"sort" : {
"_script" : {
"type" : "number",
"script" : {
"lang": "painless",
"source": "doc['field_name'].value * params.factor",
"params" : {
"factor" : 1.1
}
},
"order" : "asc"
}
}
}

折叠

用 collapse指定根据某个字段对命中结果进行折叠

1
2
3
4
5
6
7
8
9
10
GET /bank/_search
{
"query": {
"match_all": {}
},
"collapse" : {
"field" : "age"
},
"sort": ["balance"]
}

** 高级折叠 **

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 指定inner_hits来解释折叠 
# 自命名"name": "details",
# 指定每组取几个文档 "size": 5,
# 组内排序 "sort": [{ "balance": "asc" }]
# 指定组查询的并发数 "max_concurrent_group_searches": 4

GET /bank/_search
{
"query": {
"match_all": {}
},
"collapse" : {
"field" : "age" ,

"inner_hits": {
"name": "details",
"size": 5,
"sort": [{ "balance": "asc" }]
},
"max_concurrent_group_searches": 4
},
"sort": ["balance"]
}

分页

** from and size **

1
2
3
4
5
6
7
GET /bank/_search
{
"from" : 0, "size" : 10,
"query" : {
"term" : { "firstname" : "Virginia" }
}
}

注:搜索请求耗用的堆内存和时间与 from + size 大小成正比。分页越深耗用越大,为了不因分页导致OOM或严重影响性能,ES中规定from + size 不能大于索引setting参数 index.max_result_window 的值,默认值为 10,000。

** Search after 在指定文档后取文档, 可用于深度分页 **
首次查询第一页

1
2
3
4
5
6
7
8
9
10
11
12
13
GET bank/_search
{
"size": 10,
"query": {
"match" : {
"age" : "37"
}
},
"sort": [
{"account_number": "asc"},
{"balance": "desc"}
]
}

后续页的查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
GET bank/_search
{
"size": 10,
"query": {
"match" : {
"age" : "37"
}
},
"search_after": [44, 34487],
"sort": [
{"account_number": "asc"},
{"balance": "desc"}
]
}

使用search_after,要求查询必须指定排序,并且这个排序组合值每个文档唯一(最好排序中包含_id字段)。 search_after的值用的就是这个排序值。 用search_after时 from 只能为0、-1。

高亮

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
GET /bank/_search
{
"query": {
"match": {
"lastname": "Justice"
}
},
"highlight": {
"fields": {
"firstname": {},
"lastname": {}
}
}
}

结果:
{
"took": 0,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 4.8520303,
"hits": [
{
"_index": "bank",
"_type": "accounts",
"_id": "59",
"_score": 4.8520303,
"_source": {
"account_number": 59,
"balance": 37728,
"firstname": "Malone",
"lastname": "Justice",
"age": 37,
"gender": "F",
"address": "721 Russell Street",
"employer": "Emoltra",
"email": "malonejustice@emoltra.com",
"city": "Trucksville",
"state": "HI"
},
"highlight": {
"lastname": [
"<em>Justice</em>"
]
}
}
]
}
}

多字段高亮,在highlight中加入:”require_field_match”: false,即可。

指定高亮标签

1
2
3
4
5
6
7
8
9
10
11
12
...
"highlight": {
"require_field_match": false,
"fields": {
"firstname": {
"pre_tags":["<strong>"],
"post_tags": ["</strong>"]
},
"firstname": {}
}
}
}

Profile 为了调试、优化

查询上加入上 profile 来获得详细的执行步骤、耗时信息。

1
2
3
4
5
6
7
8
9
GET /bank/_search
{
"profile": true,
"query": {
"match": {
"lastname": "Justice"
}
}
}

count api 查询数量

1
2
3
4
5
6
7
8
GET /bank/accounts/_count?q=firstname:Malone
# 或
GET /bank/accounts/_count
{
"query" : {
"term" : { "firstname" : "Malone" }
}
}

validate api

用来检查我们的查询是否正确,以及查看底层生成查询。

GET /bank/_validate/query?q=firstname:Malone

校验查询

1
2
3
4
5
6
7
8
9
GET /bank/accounts/_validate/query
{
"query": {
"query_string": {
"query": "firstname:Malone",
"lenient": false
}
}
}

获得查询解释

1
2
3
4
5
6
7
8
9
GET /bank/accounts/_validate/query?explain=true
{
"query": {
"query_string": {
"query": "firstname:Malone",
"lenient": false
}
}
}

** 用rewrite获得比explain 更详细的解释 **

1
2
3
4
5
6
7
8
9
GET /bank/accounts/_validate/query?rewrite=true
{
"query": {
"query_string": {
"query": "firstname:Malone",
"lenient": false
}
}
}
1
2
3
4
5
6
7
8
9
10
11
GET /bank/accounts/_validate/query?rewrite=true
{
"query": {
"more_like_this": {
"like": {
"_id": "3"
},
"boost_terms": 1
}
}
}

获得所有分片上的查询解释

1
2
3
4
5
6
7
8
GET /bank/_doc/_validate/query?rewrite=true&all_shards=true
{
"query": {
"match": {
"firstname": "Virginia"
}
}
}

Explain api

获得某个查询的评分解释,及某个文档是否被这个查询命中。

1
2
3
4
5
6
GET /bank/accounts/0/_explain
{
"query" : {
"match" : {"firstname": "Virginia"}
}
}

官网链接:https://www.elastic.co/guide/en/elasticsearch/reference/current/search-explain.html

Search Shards API

让我们可以获取查询的索引分片节点情况

GET /bank/_search_shards

指定routing值的查询将在哪些分片节点上执行

GET /bank/_search_shards?routing=25,56

这里使用默认的路由,就是文档ID

Search Template 查询模板

注册一个模板

1
2
3
4
5
6
7
8
9
10
11
12
13
POST _scripts/bank_accounts_tm1
{
"script": {
"lang": "mustache",
"source": {
"query": {
"match": {
"email": "{{query_string}}"
}
}
}
}
}

使用模板进行查询

1
2
3
4
5
6
7
GET _search/template
{
"id": "bank_accounts_tm1",
"params": {
"query_string": "virginiaayala@filodyne.com"
}
}

Query DSL

Domain Specific Language:领域特定语言

Elasticsearch 提供了一个完整的 query DSL,并且是 JSON 形式的。它和 AST 比较类似,并且包含两种类型的语句:

  • 叶子查询语句(Leaf Query),用于查询某个特定的字段,如 match , term 或 range 等

  • 复合查询语句 (Compound query clauses) 用于合并其他的叶查询或复合查询语句,也就是说复合语句之间可以嵌套,用来表示一个复杂的单一查询

    1
    2
    3
    ​ ** DSL ** (domain-specific language),领域特定语言指的是专注于某个应用程序领域的计算机语言,又译作领域专用语言。不同于普通的跨领域通用计算机语言(GPL),领域特定语言只用在某些特定的领域。
    ​ ** AST** (abstract syntax tree), 抽象语法树是源代码的抽象语法结构的树形表现形式。树上的每个节点都表示源代码中的一种结构。之所以说语法是“抽象”的,是因为这里的语法并不会表示出真实语法中出现的每个细节。比如,嵌套括号被隐含在树的结构中,并没有以节点的形式呈现;而类似于if-condition-then这样的条件跳转语句,可以使用带有两个分支的节点来表示。
    ——百度百科
    • Query and filter context **
      一个查询语句究竟具有什么样的行为和得到什么结果,主要取决于它到底是处于查询上下文(Query Context) 还是过滤上下文(Filter Context)。两者有很大区别,我们来看下:
  • Query context 查询上下文:这种语句在执行时既要计算文档是否匹配,还要计算文档相对于其他文档的匹配度有多高,匹配度越高,_score 分数就越高

  • Filter context 过滤上下文:过滤上下文中的语句在执行时只关心文档是否和查询匹配,不会计算匹配度,也就是得分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
GET /_search
{
"query": {
"bool": {
"must": [
{ "match": { "title": "Search" }},
{ "match": { "content": "Elasticsearch" }}
],
"filter": [
{ "term": { "status": "published" }},
{ "range": { "publish_date": { "gte": "2015-01-01" }}}
]
}
}
}

query 参数表示整个语句是处于 query context 中
bool 和 match 语句被用在 query context 中,也就是说它们会计算每个文档的匹配度(_score)
filter 参数则表示这个子查询处于 filter context 中
filter 语句中的 term 和 range 语句用在 filter context 中,它们只起到过滤的作用,并不会计算文档的得分。

Match all query

1
2
3
4
5
6
7
8
9
10
11
12
13
14
GET /_search
{
"query": {
"match_all": {}
}
}

# 什么都不查
GET /_search
{
"query": {
"match_none": {}
}
}

全文查询 Full text queries

全文查询,用于对分词的字段进行搜索。会用查询字段的分词器对查询的文本进行分词生成查询。可用于短语查询、模糊查询、前缀查询、临近查询等查询场景。

官网链接:https://www.elastic.co/guide/en/elasticsearch/reference/current/full-text-queries.html

match query

全文查询的标准查询,它可以对一个字段进行模糊、短语查询。 match queries 接收 text/numerics/dates, 对它们进行分词分析, 再组织成一个boolean查询。可通过operator 指定bool组合操作(or、and 默认是 or ), 以及minimum_should_match 指定至少需多少个should(or)字句需满足。还可用ananlyzer指定查询用的特殊分析器。包括模糊查询(fuzzy matching) 或者临近查询(proximity queries)。

新增文档:

1
2
3
4
5
6
7
8
9
10
11
PUT /ftq/_doc/1
{
"title": "lucene solr and elasticsearch",
"content": "lucene solr and elasticsearch for search"
}

PUT /ftq/_doc/2
{
"title": "java spring boot",
"content": "lucene is writerd by java"
}

查询:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
GET ftq/_search
{
"query": {
"match": {
"title": "lucene java" #分词后用or
}
}
}

GET ftq/_search
{
"query": {
"match": {
"title": {
"query": "lucene java",
"operator": "and" #指定分词后用and
}
}
}
}

** 模糊查询,可以指定fuzziness最大编辑数 **

1
2
3
最大编辑数为2,说明query字符串中分词后,每个词允许编辑两次单个字符,可删除、新增、修改字符
fuzziness 参数可以被设置为 AUTO,此时字符串只有 1 到 2 个字符时是 0;字符串有 3 、4 或者 5 个字符时是 1;字符串大于 5 个字符时是 2
有时编辑距离 2 仍然是太多了,返回的结果似乎并不相关。 把最大 fuzziness 设置为 1 ,可以得到更好的结果和更好的性能
1
2
3
4
5
6
7
8
9
10
11
GET ftq/_search
{
"query": {
"match": {
"title": {
"query": "ucen elatic",
"fuzziness": 2
}
}
}
}

** 指定最少需满足两个词匹配 **

1
2
3
4
5
6
7
8
9
10
11
12
GET ftq/_search
{
"query": {
"match": {
"content": {
"query": "ucen elatic java",
"fuzziness": 2,
"minimum_should_match": 2
}
}
}
}

** max_expansions 指定模糊匹配的最大词项数,默认是50。**

比如:反向索引中有 100 个词项与 ucen 模糊匹配,只选用前50 个。

1
2
3
4
5
6
7
8
9
10
11
12
13
GET ftq/_search
{
"query": {
"match": {
"content": {
"query": "ucen elatic java",
"fuzziness": 2,
"minimum_should_match": 2,
"max_expansions ": 50
}
}
}
}

match_phrase query

match_phrase 查询用来对一个字段进行短语查询,可以指定 analyzer、slop移动因子。和 match 查询比较类似,但是它会保留包含所有搜索词项,且位置与搜索词项相同的文档。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#短语查询
GET ftq/_search
{
"query": {
"match_phrase": {
"title": "lucene solr"
}
}
}

#指定移动因子
GET ftq/_search
{
"query": {
"match_phrase": {
"title": {
"query": "lucene elasticsearch",
"slop": 2
}
}
}
}

match_phrase_prefix query

是一种输入即搜索(search-as-you-type) 的查询,它和 match_phrase 比较类似,区别就是会将查询字符串的最后一个词作为前缀来使用。

1
2
3
4
5
6
7
8
GET /_search
{
"query": {
"match_phrase_prefix" : {
"message" : "quick brown f"
}
}
}

指定前缀匹配选用的最大词项数量

1
2
3
4
5
6
7
8
9
10
11
GET /_search
{
"query": {
"match_phrase_prefix" : {
"message" : {
"query" : "quick brown f",
"max_expansions" : 10
}
}
}
}

multi_match query

需要在多个字段上进行文本搜索,可用multi_match 。 multi_match在 match的基础上支持对多个字段进行文本查询。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
GET ftq/_search
{
"query": {
"multi_match" : {
"query": "lucene java",
"fields": [ "title", "content" ]
}
}
}

还可以使用*匹配多个字段:

GET ftq/_search
{
"query": {
"multi_match" : {
"query": "lucene java",
"fields": [ "title", "cont*" ]
}
}
}

query_string query

支持复杂的 Lucene query String 语法,可以直接用lucene查询语法写一个查询串进行查询,ES中接到请求后,通过查询解析器解析查询串生成对应的查询。使用它要求掌握lucene的查询语法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 单字段
GET /_search
{
"query": {
"query_string" : {
"default_field" : "content",
"query" : "this AND that OR thus"
}
}
}
# 多字段通配符查询
GET /_search
{
"query": {
"query_string" : {
"fields" : ["content", "name.*^5"],
"query" : "this AND that OR thus"
}
}
}

common terms query

common 常用词查询

1
2
3
4
5
6
7
8
9
问1、什么是停用词?索引时做停用词处理的目的是什么?
不再使用的词,做停用词处理的目的是提高索引的效率,去掉不需要的索引操作,即停用词不需要索引

问2、如果在索引时应用停用词处理,下面的两个查询会查询什么词项?
the brown fox—— brown fox
not happy——happy

问3、索引时应用停用词处理对搜索精度是否有影响?如果不做停用词处理又会有什么影响?如何协调这两个问题?如何保证搜索的精确度又兼顾搜索性能?
索引时应用停用词处理对搜索精度有影响,不做停用词处理又会影响索引的效率,要协调这两个问题就必须要使用tf-idf 相关性计算模型

** tf-idf 相关性计算模型 **
tf:term frequency 词频 :指一个词在一篇文档中出现的频率。

如“世界杯”在文档A中出现3次,那么可以定义“世界杯”在文档A中的词频为3。请问在一篇3000字的文章中出现“世界杯”3次和一篇150字的文章中出现3词,哪篇文章更是与“世界杯”有关的。也就是说,简单用出现次数作为频率不够准确。那就用占比来表示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
问:tf值越大是否就一定说明这个词更相关?
不是,出现太多了说明不重要

说明:tf的计算不一定非是这样的,可以定义不同的计算方式。
df:document frequency 词的文档频率 :指包含某个词的文档数(有多少文档中包含这个词)。 df越大的词越常见,哪些词会是高频词?

问:词的df值越大说明这个词在这个文档集中是越重要还是越不重要?
越不重要

问:词t的tf高,在文档集中的重要性也高,是否说明文档与该词越相关?举例:整个文档集中只有3篇文档中有“世界杯”,文档A中就出现了“世界杯”好几次。
不能说明文档与该词越相关

问:如何用数值体现词t在文档集中的重要性?df可以吗?
不可以

idf:inverse document frequency 词的逆文档频率 :用来表示词在文档集中的重要性。文档总数/ df ,df越小,词越重要,这个值会很大,那就对它取个自然对数,将值映射到一个较小的取值范围。

说明: +1 是为了避免除0(即词t在文档集中未出现的情况)

tf-idf 相关性性计算模型:tf-idf t = tf t,d * idf t

说明: tf-idf 相关性性计算模型的值为词频( tf t,d)乘以词的逆文档频率(idf t)

** Common terms query **

common 区分常用(高频)词查询让我们可以通过cutoff_frequency来指定一个分界文档频率值,将搜索文本中的词分为高频词和低频词,低频词的重要性高于高频词,先对低频词进行搜索并计算所有匹配文档相关性得分;然后再搜索和高频词匹配的文档,这会搜到很多文档,但只对和低频词重叠的文档进行相关性得分计算(这可保证搜索精确度,同时大大提高搜索性能),和低频词累加作为文档得分。实际执行的搜索是 必须包含低频词 + 或包含高频词。

1
2
3
思考:这样处理下,如果用户输入的都是高频词如 “to be or not to be”结果会是怎样的?你希望是怎样的?
优化:如果都是高频词,那就对这些词进行and 查询。
进一步优化:让用户可以自己定对高频词做and/or 操作,自己定对低频词进行and/or 操作;或指定最少得多少个同时匹配
1
2
3
4
5
6
7
8
9
10
11
GET /_search
{
"query": {
"common": {
"address": {
"query": "171 Putnam Avenue",
"cutoff_frequency": 0.001
}
}
}
}

说明:cutoff_frequency : 值大于1表示文档数,0-1.0表示占比。 此处界定 文档频率大于 0.1%的词为高频词。

1
2
3
4
5
6
7
8
9
10
11
12
GET /_search
{
"query": {
"common": {
"address": {
"query": "171 Putnam Avenue",
"cutoff_frequency": 0.001,
"low_freq_operator": "and"
}
}
}
}

说明:low_freq_operator指定对低频词做与操作。

可用参数:minimum_should_match (high_freq, low_freq), low_freq_operator (default “or”) and high_freq_operator (default “or”)、 boost and analyzer

1
2
3
4
5
6
7
8
9
10
11
12
GET /_search
{
"query": {
"common": {
"address": {
"query": "171 Putnam Avenue",
"cutoff_frequency": 0.001,
"minimum_should_match": 2
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
GET /_search
{
"query": {
"common": {
"address": {
"query": "171 Putnam Avenue",
"cutoff_frequency": 0.001,
"minimum_should_match": {
"low_freq" : 2,
"high_freq" : 3
}
}
}
}
}

simple_query_string query

简化版的 query_string ,simple_query_string 查同 query_string 查询一样用lucene查询语法写查询串,较query_string不同的地方:更小的语法集;查询串有错误,它会忽略错误的部分,不抛出错误。更适合给用户使用。

1
2
3
4
5
6
7
8
9
10
GET /_search
{
"query": {
"simple_query_string" : {
"query": "\"fried eggs\" +(eggplant | potato) -frittata",
"fields": ["title^5", "body"],
"default_operator": "and"
}
}
}

词项查询

https://www.elastic.co/guide/en/elasticsearch/reference/current/term-level-queries.html

term/terms query

term 查询用于查询指定字段包含某个词项的文档。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
POST _search
{
"query": {
"term" : { "address" : "Putnam" }
}
}

# 权重boost
POST _search
{
"query": {
"term": {
"balance": {"value": 40540,"boost": 2}
}
}
}

# terms 查询用于查询指定字段包含某些词项的文档。
POST _search
{
"query": {
"terms" : { "address" : ["171","Putnam"] }
}
}

Terms 还可以查询支持嵌套查询的方式来获得查询词项,相当于 in (select term from other)

1
2
3
4
5
6
7
8
9
10
11
12
13
GET /bank/_search
{
"query": {
"terms" : {
"firstname" : {
"index": "bank",
"type": "accounts",
"id": "25",
"path": "followers"
}
}
}
}

range query

范围查询

1
2
3
4
5
gte:大于等于
gt:大于
lte:小于等于
lt:小于
boost:查询权重
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
GET _search
{
"query": {
"range" : {
"age" : {
"gte" : 10,
"lte" : 20,
"boost" : 2.0
}
}
}
}

GET _search
{
"query": {
"range" : {
"date" : {
"gte" : "now-1d/d", #当前时间减1天后转成天数
"lt" : "now/d" #当前时间转成条数
}
}
}
}

GET _search
{
"query": {
"range" : {
"born" : {
"gte": "01/01/2012",
"lte": "2013",
"format": "dd/MM/yyyy||yyyy"
}
}
}
}

时间舍入||说明:

1
2
3
4
gt:大于的情况下,四舍五入,比如2014-11-18||/M变成2014-11-30T23:59:59:999,不包含整个月
gte:大于等于的情况下,向下取整,比如2014-11-18||/M变成2014-11-01,包含整个月
lt:小于的情况下,向下取整,比如2014-11-18||/M变成2014-11-01,不包含整个月
lte:小于等于的情况下,四舍五入,比如2014-11-18||/M变成2014-11-30T23:59:59:999,包含整个月

exits query

查询指定字段值不为空的文档。相当 SQL 中的 column is not null

1
2
3
4
5
6
GET /_search
{
"query": {
"exists" : { "field" : "balance" }
}
}

prefix query 词项前缀查询

1
2
3
4
5
GET /_search
{ "query": {
"prefix" : { "address" : "171" }
}
}

wildcard query 通配符查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
GET /_search
{
"query": {
"wildcard" : { "lastname" : "A*a*" }
}
}
# 加权
GET /_search
{
"query": {
"wildcard": {
"lastname": {
"value": "ki*y",
"boost": 2
}
}
}
}

regexp query 正则查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
GET /_search
{
"query": {
"regexp":{
"name.first": "s.*y"
}
}
}

GET /_search
{
"query": {
"regexp":{
"name.first":{
"value":"s.*y",
"boost":1.2
}
}
}
}

正则语法参考:https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html#regexp-syntax

fuzzy query 模糊查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
GET /_search
{
"query": {
"fuzzy" : { "address" : "Avenue" }
}
}

GET /_search
{
"query": {
"fuzzy" : {
"address" : {
"value": "Avenue",
"boost": 1.0,
"fuzziness": 2,
"prefix_length": 0,
"max_expansions": 100
}
}
}
}

ids 根据文档id查询

1
2
3
4
5
6
7
8
9
GET /_search
{
"query": {
"ids" : {
"type" : "_doc",
"values" : ["1", "4", "100"]
}
}
}

复合查询

constant score query

用来包装另一个查询,将查询匹配的文档的评分设为一个常值。

1
2
3
4
5
6
7
8
9
10
11
GET /_search
{
"query": {
"constant_score" : {
"filter" : {
"term" : { "age" : "39"}
},
"boost" : 1.2
}
}
}

bool query

Bool 查询用bool操作来组合多个查询字句为一个查询。 可用的关键字:

1
2
3
4
must:必须满足
filter:必须满足,但执行的是filter上下文,不参与、不影响评分
should:或
must_not:必须不满足,在filter上下文中执行,不参与、不影响评分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
POST _search
{
"query": {
"bool" : {
"must" : {
"term" : { "lastname": "Ayala"}
},
"filter": {
"term" : { "state": "PA" }
},
"must_not" : {
"range" : {
"age" : { "gte" : 10, "lte" : 20 }
}
},
"should" : [
{ "term" : { "city": "Nicholson" } },
{ "term" : { "city": "Shaft" } }
],
"minimum_should_match" : 1,
"boost" : 1.0
}
}
}

Elasticsearch路由详解

发表于 2019-03-13 | 分类于 Elasticsearch

ELasticSearch路由

在ELasticSearch 里面,路由功能算是一个高级用法,大多数时候我们用的都是系统默认的路由功能,一个es索引可以分成shard和每个shard又可以有多个replia,那么添加进去的数据,在如何分布在各个shard上面的,而查询的时候文档是如何路由的。

默认情况下,索引数据的分片规则,是下面的公式:

shard = hash(_routing) % num_primary_shards

routing 是用来进行hash计算的路由值,默认是使用文档id值。我们可以在索引文档时通过routing参数指定别的路由值,number_of_primary_shards:创建索引时指定的主分片数

ES默认是基于hash的分片,保证在每个shard上数据量都近似平均,这样就不会出现负载不均衡的情况,然后在检索的时候,ES默认会搜索所有的shard上的数据,然后在master节点上面汇聚处理后,返回最终结果。

有时候,会有另外一种情况,比如说存储一年的数据,如果按照hash去索引,那就是分布非常的均匀,这样的话无论查询什么数据都会去所有的shard上面查询,如果数据量比较大,那么响应速度就比较慢,但这时,我们通过调查发现,一年12个月的数据本身分布并不均匀,有几个月的数据偏多,有几个月的数据偏少,理想情况下,数据偏少的月,查询性能应该更快,但如果是基于hash分片,那么我们并不能实现这种需求,因为hash分片,查询时候必须命中所有的shard之后,查询的结果才是准的,这样一来每次查询都要扫描所有的shard,比如我已经知道数据本身就是1月份的,那其实最好情况下,只查询1月份的数据就行,而不需要把一年的数据都扫描一遍,导致最终结果就是慢的更慢,快的也慢所以我们要针对性的做优化。

那么如何优化,其实思路也比较明确了,那就是按照月份分区,每一个月份的数据都存放在指定的分区中,如果mysql那就是每一个月份一张表,然后查询的时候,直接查询对应月份的数据即可,在es和solr中原理也大致如此,唯一不同的地方在以es 和 solr都比较方便的支持了路由字段的设置而如果数据库,则需要自己通过中间件的方式来搞定,比如说mycat等。

使用路由

es中使用路由字段,看一个官网给的一个例子

1
2
3
4
5
6
7
8
#指定了一个用户的属性作为路由进行分区,然后查询的时候也必须指定路由。

PUT my_index/my_type/1?routing=user1&refresh=true
{
"title": "This is a document"
}

GET my_index/my_type/1?routing=user1

注意,只要在索引的时候加入了路由字段,那么在以后的get、delete、update中都必须指定使用路由字段,否则会出现问题。

路由字段,也是可以被查询的:

1
2
3
4
5
6
7
8
GET my_index/_search
{
"query": {
"terms": {
"_routing": [ "user1" ]
}
}
}

也可以指定多个路由字段:

1
2
3
4
5
6
7
8
GET my_index/_search?routing=user1,user2 
{
"query": {
"match": {
"title": "document"
}
}
}

如果指定了多个用户属性,那么只会查询关联了这两个route属性的shard

如果加入路由字段之后,其他的操作都必须指定字段,为了避免在使用时忘了添加路由字段,导致同类型数据会分布在多个shard上面,这就违反了路由的原则,所以我们可以在mapping中设置路由字段是必需字段,否则会提示错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
PUT my_index2
{
"mappings": {
"my_type": {
"_routing": {
"required": true
}
}
}
}

PUT my_index2/my_type/1
{
"text": "No routing value provided"
}

缺失字段会抛出异常:routing_missing_exception

还需要注意的是如果使用了路由字段,那么_id字段只能由用户来保证唯一性,因为同一个id的数据,如果路由字段不一样,他是可以被存在到多个shard中的,而默认情况下是不会出现这种情况的。

集群

集群组成

首先启动的一定是主节点,主节点存储的是集群的元数据信息;

Node2节点启动之前会配置集群的名称Cluster-name:ess,然后配置可以作为主节点的ip地址信息discovery.zen.ping.unicast.hosts: [“10.0.1.11”,“10.0.1.12”],配置自己的ip地址networ.host: 10.0.1.12;

Node2启动的过程中会去找到主节点Node1告诉Node1我要加入到集群里面了,主节点Node1接收到请求以后看Node2是否满足加入集群的条件,如果满足就把node2的ip地址加入的元信息里面,然后广播给集群中的其他节点有新节点加入,并把最新的元信息发送给其他的节点去更新;

集群中的所有节点的元信息都是和主节点一致的,因为一旦有新的节点加入进来,主节点会通知其他的节点同步元信息。

集群有节点出现故障,如主节点挂了,会重新选择主节点。

集群中创建索引的流程

  • 1、首先请求转发到master节点;
  • 2、选择节点 的分片、副本、记录元信息;
  • 3、通知给参与存放索引分片、副本的节点从节点创建分片、副本;
  • 4、参与的节点向主节点反馈结果;
  • 5、等待时间到了,master向一个节点反馈信息,节点响应请求;
  • 6、主节点将元信息广播给所有的从节点。

在集群中索引文档

索引文档的步骤:

  • 1、node2计算文档的路由值得到文档存放的分片(假定路由选定的是分片0)。
  • 2、将文档转发给分片0(P0)的主分片节点 node1。
  • 3、node1索引文档,同步给副本(R0)节点node3索引文档。
  • 4、node1向node2反馈结果
  • 5、node2作出响应

文档是如何路由的

shard = hash(_routing) % num_primary_shards

1
2
3
number_of_primary_shards:创建索引时候指定的主分片数参数值
routing:用来进行hash计算的路由值,默认是使用文档id值。我们可以在索引文档时通过routing参数指定别的路由值
在索引、删除、更新、查询中都可以使用routing参数(可多值)指定操作的分片。

在集群中进行搜索流程

搜索的步骤:如要搜索 索引 s0

  • 1、node2解析查询。
  • 2、node2将查询发给索引s0的分片/副本(R1,R2,R0)节点
  • 3、各节点执行查询,将结果发给Node2
  • 4、Node2合并结果,作出响应。

Master节点的工作任务

    1. 存储集群的元信息,如集群名称、集群中的节点
    1. 转发创建索引和索引文档的请求
    1. 和其他的节点进行通信,告诉其他节点有新的节点加入等

Elasticsearch分词器

发表于 2019-03-12 | 分类于 Elasticsearch

因为Elasticsearch中默认的标准分词器分词器对中文分词不是很友好,会将中文词语拆分成一个一个中文的汉子,因此引入中文分词器。

在ES中一个Analyzer 由下面三种组件组合而成:

  • character filter :字符过滤器,对文本进行字符过滤处理,如处理文本中的html标签字符。处理完后再交给tokenizer进行分词。一个analyzer中可包含0个或多个字符过滤器,多个按配置顺序依次进行处理。
  • tokenizer:分词器,对文本进行分词。一个analyzer必需且只可包含一个tokenizer。
  • token filter:词项过滤器,对tokenizer分出的词进行过滤处理。如转小写、停用词处理、同义词处理。一个analyzer可包含0个或多个词项过滤器,按配置顺序进行过滤。

    测试默认的分词

    1
    2
    3
    4
    5
    GET /_analyze 
    {
    "analyzer":"standard",
    "text":"中国china"
    }
    #或 http://127.0.0.1:9200/_analyze
    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
    参数:
    {
    "analyzer":"standard",
    "text":"中国china"
    }
    结果:
    {
    "tokens": [
    {
    "token": "中",
    "start_offset": 0,
    "end_offset": 1,
    "type": "<IDEOGRAPHIC>",
    "position": 0
    },
    {
    "token": "国",
    "start_offset": 1,
    "end_offset": 2,
    "type": "<IDEOGRAPHIC>",
    "position": 1
    },
    {
    "token": "china",
    "start_offset": 2,
    "end_offset": 7,
    "type": "<ALPHANUM>",
    "position": 2
    }
    ]
    }

    position:第几个词
    offset:词的偏移位置

    character filter

    • HTML Strip Character Filter **
      1
      2
      3
      4
      5
      6
      HTML Strip Character Filter
        html_strip :过滤html标签,解码HTML entities like &.
      Mapping Character Filter
        mapping :用指定的字符串替换文本中的某字符串。
      Pattern Replace Character Filter
        pattern_replace :进行正则表达式替换。
      测试:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      GET _analyze
      {
      "tokenizer": "keyword",
      "char_filter": [ "html_strip" ],
      "text": "<p> I <br/> Love <br/> You!</p>"
      }
      结果:
      {
      "tokens" : [
      {
      "token" : """

      I
      Love
      You!

      """,
      "start_offset" : 0,
      "end_offset" : 31,
      "type" : "word",
      "position" : 0
      }
      ]
      }
      在索引中配置:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      PUT my_index
      {
      "settings": {
      "analysis": {
      "analyzer": {
      "my_analyzer": {
      "tokenizer": "keyword",
      "char_filter": ["my_char_filter"]
      }
      },
      "char_filter": {
      "my_char_filter": {
      "type": "html_strip",
      "escaped_tags": ["b"]
      }
      }
      }
      }
      }

      #escaped_tags 用来指定例外的标签。
    • Mapping character filter **
      创建一个:
      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
      PUT demo_index
      {
      "settings": {
      "analysis": {
      "analyzer": {
      "my_analyzer": {
      "tokenizer": "keyword",
      "char_filter": [
      "my_char_filter"
      ]
      }
      },
      "char_filter": {
      "my_char_filter": {
      "type": "mapping",
      "mappings": [
      "零 => 0",
      "一 => 1",
      "二 => 2",
      "三 => 3"
      ]
      }
      }
      }
      }
      }
      测试:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      POST demo_index/_analyze
      {
      "analyzer": "my_analyzer",
      "text": "一二三四五六零"
      }
      结果:
      {
      "tokens" : [
      {
      "token" : "123四五六0",
      "start_offset" : 0,
      "end_offset" : 7,
      "type" : "word",
      "position" : 0
      }
      ]
      }
    • Pattern Replace Character Filter **
      创建:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      PUT demo_index1
      {
      "settings": {
      "analysis": {
      "analyzer": {
      "my_analyzer": {
      "tokenizer": "standard",
      "char_filter": [
      "my_char_filter"
      ]
      }
      },
      "char_filter": {
      "my_char_filter": {
      "type": "pattern_replace",
      "pattern": "(\\d+)-(?=\\d)",
      "replacement": "$1_"
      }
      }
      }
      }
      }
      测试:
      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
      POST demo_index1/_analyze
      {
      "analyzer": "my_analyzer",
      "text": "My credit card is 123-456-789"
      }
      结果:
      {
      "tokens" : [
      {
      "token" : "My",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "<ALPHANUM>",
      "position" : 0
      },
      {
      "token" : "credit",
      "start_offset" : 3,
      "end_offset" : 9,
      "type" : "<ALPHANUM>",
      "position" : 1
      },
      {
      "token" : "card",
      "start_offset" : 10,
      "end_offset" : 14,
      "type" : "<ALPHANUM>",
      "position" : 2
      },
      {
      "token" : "is",
      "start_offset" : 15,
      "end_offset" : 17,
      "type" : "<ALPHANUM>",
      "position" : 3
      },
      {
      "token" : "123_456_789",
      "start_offset" : 18,
      "end_offset" : 29,
      "type" : "<NUM>",
      "position" : 4
      }
      ]
      }

      Tokenizer

      Standard Tokenizer
      1
      2
      3
      4
      5
      6
      7
      8
      Letter Tokenizer
      Lowercase Tokenizer
      Whitespace Tokenizer
      UAX URL Email Tokenizer
      Classic Tokenizer
      Thai Tokenizer
      NGram Tokenizer
      Edge NGram Tokenizer
      Keyword Tokenizer
      1
      2
      3
      4
      5
      6
      7
      8
      9
      Pattern Tokenizer
      Simple Pattern Tokenizer
      Simple Pattern Split Tokenizer
      Path Hierarchy Tokenizer
      Keyword Tokenizer
      Pattern Tokenizer
      Simple Pattern Tokenizer
      Simple Pattern Split Tokenizer
      Path Hierarchy Tokenizer
      测试:
      1
      2
      3
      4
      5
      POST _analyze
      {
      "tokenizer": "ik_smart",
      "text": "中国china"
      }

Token Filter

ES中内建了很多Token filter

1
2
3
4
5
Lowercase Token Filter :lowercase 转小写
Stop Token Filter :stop 停用词过滤器
Synonym Token Filter: synonym 同义词过滤器

说明:中文分词器Ikanalyzer中自带有停用词过滤功能。

** Synonym Token Filter 同义词过滤器 **

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
PUT /demo_index1
{
"settings": {
"index": {
"analysis": {
"analyzer": {
"my_ik_synonym": {
"tokenizer": "ik_smart",
"filter": [
"synonym"
]
}
},
"filter": {
"synonym": {
"type": "synonym",
"synonyms_path": "analysis/synonym.txt"
}
}
}
}
}
}

#synonyms_path 指定同义词文件(相对config的位置)

ES同义词格式支持 solr、 WordNet 两种格式。

1
2
3
张三,李四
电饭煲,电饭锅 => 电饭煲
电脑 => 计算机,computer

Analyzer

集成的中文分词器Ikanalyzer中提供的Analyzer:ik_smart 、 ik_max_word

内建的和集成的analyzer可以直接使用。如果它们不能满足我们的需要,则我们可自己组合字符过滤器、分词器、词项过滤器来定义自定义的analyzer

自定义 Analyzer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
PUT demo_index3{
"settings": {
"analysis": {
"analyzer": {
"my_ik_analyzer": {
"type": "custom",
"tokenizer": "ik_smart",
"char_filter": [
"html_strip"
],
"filter": [
"synonym"
]
}
},
"filter": {
"synonym": {
"type": "synonym",
"synonyms_path": "analysis/synonym.txt"
}
}
}
}
}

为字段指定分词器

1
2
3
4
5
6
7
8
9
PUT demo_index4/_mapping/user
{
"properties": {
"name": {
"type": "text",
"analyzer": "my_ik_analyzer"
}
}
}

如果该字段的查询需要使用不同的analyzer

1
2
3
4
5
6
7
8
9
10
PUT demo_index4/_mapping/user
{
"properties": {
"name": {
"type": "text",
"analyzer": "my_ik_analyzer",
"search_analyzer": "other_analyzer"
}
}
}

索引定义default分词器

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
PUT /demo_index4
{
"settings": {
"analysis": {
"analyzer": {
"default": {
"tokenizer": "ik_smart",
"filter": [
"synonym"
]
}
},
"filter": {
"synonym": {
"type": "synonym",
"synonyms_path": "analysis/synonym.txt"
}
}
}
},
"mappings": {
"user": {
"properties": {
"name": {
"type": "text"
}
}
}
}
}

** Analyzer的使用顺序 **

可以为每个查询、每个字段、每个索引指定分词器。

在索引阶段ES将按如下顺序来选用分词:

1
2
3
4
5
首先选用字段mapping定义中指定的analyzer
字段定义中没有指定analyzer,则选用 index settings中定义的名字为default 的analyzer。
如index setting中没有定义default分词器,则使用 standard analyzer.
``
查询阶段ES将按如下顺序来选用分词:

The analyzer defined in a full-text query.
The search_analyzer defined in the field mapping.
The analyzer defined in the field mapping.
An analyzer named default_search in the index settings.
An analyzer named default in the index settings.
The standard analyzer.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17












# 中文分词器IK

下载es的IK插件,解压重命名为ik,放到/usr/local/elasticsearch/plugins目录中,重启elasticsearch即可。

测试上面的例子得到:

{
“tokens”: [
{
“token”: “中国”,
“start_offset”: 0,
“end_offset”: 2,
“type”: “CN_WORD”,
“position”: 0
},
{
“token”: “china”,
“start_offset”: 2,
“end_offset”: 7,
“type”: “ENGLISH”,
“position”: 1
}
]
}

1
2

ik_max_word 和 ik_smart 区别:

IK 5.0 以后,移除名为 ik 的analyzer和tokenizer 只能使用 ik_smart 和 ik_max_word
ik_max_word: 会将文本做最细粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,中华人民,中华,华人,人民共和国,人民,人,民,共和国,共和,和,国国,国歌”,会穷尽各种可能的组合;
ik_smart: 会做最粗粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,国歌”。

1
2
3

# 扩展IK分词
扩展配置文件:ik\config\IKAnalyzer.cfg.xml
IK Analyzer 扩展配置
1
修改配置:
IK Analyzer 扩展配置 ./custom/new_word.dic
1
就可以在对应位置写一些分词。vi ./custom/new_word.dic 如:
# cat ./custom/new_word.dic 我爱你 中国china
1
2
3
4

例子:

请求体不变
{ "analyzer":"ik_smart", "text":"中国china" }
1
结果:
{ "tokens": [ { "token": "中国china", "start_offset": 0, "end_offset": 7, "type": "CN_WORD", "position": 0 } ] }
1
2
3
4
5
6
7
新增扩展词后让历史数据生效:

POST /索引,,,/_update_by_query?conflicts=proceed

# 项目中使用IK扩展分词器
1、把包引入pom.xml
2、在项目的src根目录下建立IKAnalyzer.cfg.xml文件,文件中配置扩展词库和停用词库的路径
IK Analyzer 扩展配置
1
2
3
4
5
6
在配置文件中,用户可一次配置多个词典文件。文件名使用;号分隔,文件路径为相对 java 包的起始根路径。

配置文件和字典文件都放在java包的根目录,上述配置文件的字典路径应该去掉开头的”/”,否则扩展无法生效。

3、通过代码扩展
IK 分词器支持使用 API 编程模型扩充您的词典和停止词典。API 如下:
类 org.wltea.analyzer.dic.Dictionary 说明: IK 分词器的词典对象。它负责中文词汇的加载,内存管理和匹配检索。 public static Dictionary initial(Configuration cfg) //初始化字典实例。字典采用单例模式,只执行一次。参数 1:Configuration cfg,词典路径配置 返回值:Dictionary IK 词典单例

 public static Dictionary getSingleton() //获取初始化完毕的字典单例 返回值:Dictionary IK 词典单例
 public void addWords(Collection words) //加载用户扩展的词汇列表到 IK 的主词典中,增加分词器的可识别词语。 参数 1:Collection words , 扩展的词汇列表
 public void disableWords(Collection words) //屏蔽词典中的词元 参数 1:Collection words, 待删除的词列表

1
2
3
4
5
6
7
8
9
示例:
```java
Configuration cfg=DefaultConfig.getInstance();
System.out.println(cfg.getMainDictionary());
Dictionary.initial(cfg);
Dictionary dic=Dictionary.getSingleton();
HashSet<String> set=new HashSet<>();
set.add("中国china");
dic.addWords(set);

** 通过java看分词 **

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
//构造一个IKAnalyzer对象,构造器参数为true说明使用智能分词,默认为最细粒度切分
IKAnalyzer analyzer=new IKAnalyzer(true);
TokenStream ts=null;
try{
ts=analyzer.tokenStream("title", "中国china");
CharTermAttribute cta=ts.addAttribute(CharTermAttribute.class);
//IKAnalyzer.tokenStream(fieldName,text)返回TokenStream对象,Token指分词后的词元;
//TokenStream对象中包含了text的分词结果,fieldName指文档的域名,一片文档包含多个域,如title,abstract,content等等,这里任意指定即可。
//TokenStream.incrementToken()的功能相当于迭代器,用来遍历每个词元。
//CharTermAttribute是具体的词元对象,每次调用incrementToken()方法后,该对象所持有的词元会更新。
ts.reset();
while (ts.incrementToken()) {
System.out.println("term: " + cta.toString());
}
}catch(Exception e){
e.printStackTrace();
}finally{
if(ts!=null)
try{
ts.close();
}catch(Exception e){
e.printStackTrace();
}
if(analyzer!=null){
try{
analyzer.close();
}catch(Exception e){
e.printStackTrace();
}
}
}

Elasticsearch文档操作

发表于 2019-03-11 | 分类于 Elasticsearch

新建文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PUT my_demo/_doc/1
{
"id": 1,
"user" : "hu",
"post_date" : "2021-12-15T15:22:32",
"message" : "dsgasdgsdg"
}

#自动生成索引
PUT my_demo/_doc/
{
"id": 1,
"user" : "hu",
"post_date" : "2021-12-15T15:22:32",
"message" : "dsgasdgsdg"
}

测试:

创建一个索引:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PUT my_demo
{
"settings" : {
"index" : {
"number_of_shards" : 3,
"number_of_replicas" : 2
}
},
"mappings" : {
"_doc" : {
"properties" : {
"user" : { "type" : "text" },
"post_date" : { "type" : "text" },
"message" : { "type" : "text" }
}
}
}
}

建立文档:

1
2
3
4
5
6
7
PUT  /my_demo/_doc/1
{
"id": 1,
"user" : "hu",
"post_date" : "2021-12-15T15:22:32",
"message" : "dsgasdgsdg"
}

返回值说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"_index" : "my_demo",
"_type" : "_doc", #所属mapping type
"_id" : "1", #文档id
"_version" : 1,
"result" : "created",
"_shards" : { #分片写入情况
"total" : 3,#所在分片有三个副本
"successful" : 1, #一个副本上成功写入
"failed" : 0 #失败副本数
},
"_seq_no" : 0, #第几次操作该文档
"_primary_term" : 1 #词项数
}

获取单个文档

HEAD my_demo/_doc/1

GET my_demo/_doc/1

不获取文档的source: GET my_demo/_doc/1?_source=false

获取文档的source:GET twitter/_doc/1/_source

获取多个文档 _mget

多种方式

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
GET /_mget
{
"docs" : [
{
"_index" : "my_demo",
"_type" : "_doc",
"_id" : "1"
},
{
"_index" : "my_demo1",
"_type" : "_doc",
"_id" : "2"
"stored_fields" : ["field3", "field4"]
}
]
}

GET /my_demo/_mget
{
"docs" : [
{
"_type" : "_doc",
"_id" : "1"
},
{
"_type" : "_doc",
"_id" : "2"
}
]
}


GET /my_demo/_doc/_mget
{
"docs" : [
{
"_id" : "1"
},
{
"_id" : "2"
}
]
}

GET /my_demo/_doc/_mget
{
"ids" : ["1", "2"]
}

删除文档

指定文档id进行删除 DELETE my_demo/_doc/1

用版本来控制删除 DELETE my_demo/_doc/1?version=1

查询删除

1
2
3
4
5
6
7
8
POST my_demo/_delete_by_query
{
"query": {
"match": {
"message": "4654515"
}
}
}

当有文档有版本冲突时,不放弃删除操作(记录冲突的文档,继续删除其他复合查询的文档)

1
2
3
4
5
6
7
8
9
10
11
POST my_demo/_doc/_delete_by_query?conflicts=proceed
{
"query": {
"match_all": {}
}
}

通过task api 来查看 查询删除任务
GET _tasks?detailed=true&actions=*/delete/byquery
查询具体任务的状态GET /_tasks/taskId:1
取消任务 POST _tasks/task_id:1/_cancel

更新文档

指定文档id进行修改

1
2
3
4
5
6
7
PUT my_demo/_doc/1
{
"id": 1,
"user" : "hu",
"post_date" : "fsadfs",
"message" : "sadgsd"
}

乐观锁并发更新控制

1
2
3
4
5
6
7
PUT my_demo/_doc/1?version=1
{
"id": 1,
"user" : "yun",
"post_date" : "adfasdfa",
"message" : "ewetgege"
}

通过脚本来更新文档

对文档的counter + 1

1
2
3
4
5
6
7
8
9
10
POST my_demo/_doc/1/_update
{
"script" : {
"source": "ctx._source.counter += params.count",
"lang": "painless",
"params" : {
"count" : 1
}
}
}

往数组中加入元素

1
2
3
4
5
6
7
8
9
10
POST my_demo/_doc/1/_update
{
"script" : {
"source": "ctx._source.tags.add(params.tag)",
"lang": "painless",
"params" : {
"tag" : "blue"
}
}
}

painless是es内置的一种脚本语言,ctx执行上下文对象(通过它还可访问_index, _type, _id, _version, _routing and _now (the current timestamp) ),params是参数集合

脚本更新要求索引的_source 字段是启用的。更新执行流程:

1
2
3
4
5
a、获取到原文档
b、通过_source字段的原始数据,执行脚本修改。
c、删除原索引文档
d、索引修改后的文档
它只是降低了一些网络往返,并减少了get和索引之间版本冲突的可能性。

其他操作

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
添加一个字段
POST my_demo/_doc/1/_update
{
"script" : "ctx._source.new_field = 'value_of_new_field'"
}
移除一个字段
POST my_demo/_doc/1/_update
{
"script" : "ctx._source.remove('new_field')"
}
判断删除或不做什么
POST my_demo/_doc/1/_update
{
"script" : {
"source": "if (ctx._source.tags.contains(params.tag)) { ctx.op = 'delete' } else { ctx.op = 'none' }",
"lang": "painless",
"params" : {
"tag" : "green"
}
}
}
合并传人的文档字段进行更新
POST my_demo/_doc/1/_update
{
"doc" : {
"name" : "new_name"
}
}
设置不做noop检测

复制代码
POST my_demo/_doc/1/_update
{
"doc" : {
"name" : "new_name"
},
"detect_noop": false
}

noop检测:已经执行过的脚本不再执行

upsert 操作:如果要更新的文档存在,则执行脚本进行更新,如不存在,则把 upsert中的内容作为一个新文档写入。

POST my_demo/_doc/1/_update
{
"script" : {
"source": "ctx._source.counter += params.count",
"lang": "painless",
"params" : {
"count" : 4
}
},
"upsert" : {
"counter" : 1
}
}

通过条件查询来更新文档

1
2
3
4
5
6
7
8
9
10
11
12
POST my_demo/_update_by_query
{
"script": {
"source": "ctx._source.likes++",
"lang": "painless"
},
"query": {
"term": {
"user": "hu"
}
}
}

批量操作

批量操作API /_bulk 可以在一次调用中执行多个索引、删除操作。这可以大大提高索引数据的速度。批量操作内容体需按如下以新行分割的json结构格式给出:

语法:

1
2
3
4
5
6
7
action_and_meta_data\n
optional_source\n
action_and_meta_data\n
optional_source\n
....
action_and_meta_data\n
optional_source\n

说明:

action_and_meta_data: action可以是 index, create, delete and update ,meta_data 指: _index ,_type,_id 请求端点可以是: /_bulk, /{index}/_bulk, {index}/{type}/_bulk

示例:

1
2
3
4
5
6
7
8
POST _bulk
{ "index" : { "_index" : "test", "_type" : "_doc", "_id" : "1" } }
{ "field1" : "value1" }
{ "delete" : { "_index" : "test", "_type" : "_doc", "_id" : "2" } }
{ "create" : { "_index" : "test", "_type" : "_doc", "_id" : "3" } }
{ "field1" : "value3" }
{ "update" : {"_id" : "1", "_type" : "_doc", "_index" : "test"} }
{ "doc" : {"field2" : "value2"} }

curl + json 文件 批量索引多个文档

类似于导入文件数据

1
curl -H "Content-Type: application/json" -XPOST "localhost:9200/my_demo/_doc/_bulk?pretty&refresh" --data-binary "@my_demo.json"

数据格式:

1
2
3
4
5
{"index":{"_id":"1"}}
{"字段1":值1,"字段2":值2...}
{"index":{"_id":"6"}}
{"字段1":值1,"字段2":值2...}
···

reindex 重索引

Reindex API /_reindex 让我们可以将一个索引中的数据重索引到另一个索引中(拷贝),要求源索引的_source 是开启的。目标索引的setting 、mapping 信息与源索引无关。

1
2
3
4
5
6
7
8
9
POST _reindex
{
"source": {
"index": "my_demo"
},
"dest": {
"index": "my_demo_1"
}
}

数据有重复的处理:

1、如果没有指定version_type 或指定为 internal,则会是采用目标索引中的版本,重索引过程中,执行的就是新增、更新操作。

1
2
3
4
5
6
7
8
9
10
POST _reindex
{
"source": {
"index": "my_demo"
},
"dest": {
"index": "my_demo_1",
"version_type": "internal"
}
}

2、如果想使用源索引中的版本来进行版本控制更新,则设置 version_type 为extenal。重索引操作将写入不存在的,更新旧版本的数据。

1
2
3
4
5
6
7
8
9
10
POST _reindex
{
"source": {
"index": "my_demo"
},
"dest": {
"index": "my_demo_1",
"version_type": "external"
}
}

3、如果只想从源索引中复制目标索引中不存在的文档数据,可以指定 op_type 为 create 。此时存在的文档将触发 版本冲突(会导致放弃操作),可设置“conflicts”: “proceed“,跳过继续。

1
2
3
4
5
6
7
8
9
10
11
POST _reindex
{
"conflicts": "proceed",
"source": {
"index": "my_demo"
},
"dest": {
"index": "my_demo_1",
"op_type": "create"
}
}

4、可以只索引源索引的一部分数据,通过 type 或 查询来指定你需要的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST _reindex
{
"source": {
"index": "my_demo",
"type": "_doc",
"query": {
"term": {
"user": "hu"
}
}
},
"dest": {
"index": "my_demo_1"
}
}

可以从多个源获取数据

1
2
3
4
5
6
7
8
9
10
POST _reindex
{
"source": {
"index": ["my_demo", "my_demo_1"],
"type": ["_doc", "post"]
},
"dest": {
"index": "my_demo_2"
}
}

可以限定文档数量

1
2
3
4
5
6
7
8
9
10
11
POST _reindex
{
"size": 10000,
"source": {
"index": "my_demo",
"sort": { "date": "desc" }
},
"dest": {
"index": "my_demo_1"
}
}

可以选择复制源文档的哪些字段

1
2
3
4
5
6
7
8
9
10
POST _reindex
{
"source": {
"index": "my_demo",
"_source": ["user", "_doc"]
},
"dest": {
"index": "my_demo_1"
}
}

可以用script来改变文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST _reindex
{
"source": {
"index": "my_demo"
},
"dest": {
"index": "my_demo_1",
"version_type": "external"
},
"script": {
"source": "if (ctx._source.foo == 'bar') {ctx._version++; ctx._source.remove('foo')}",
"lang": "painless"
}
}

可以指定路由值把文档放到哪个分片上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST _reindex
{
"source": {
"index": "source",
"query": {
"match": {
"company": "cat"
}
}
},
"dest": {
"index": "my_demo_1",
"routing": "=cat"
}
}

从远程源复制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
POST _reindex
{
"source": {
"remote": {
"host": "http://15151515:9200",
"username": "user",
"password": "pass"
},
"index": "my_demo",
"query": {
"match": {
"test": "data"
}
}
},
"dest": {
"index": "my_demo_1"
}
}

通过_task 来查询执行状态

GET _tasks?detailed=true&actions=*reindex

refresh

对于索引、更新、删除操作如果想操作完后立马重刷新可见,可带上refresh参数

PUT /demo/_doc/1?refresh

refresh 可选值说明

1
2
3
未给值或=true,则立马会重刷新读索引。
=false ,相当于没带refresh 参数,遵循内部的定时刷新。
=wait_for ,登记等待刷新,当登记的请求数达到index.max_refresh_listeners 参数设定的值时(defaults to 1000),将触发重刷新。

Elasticsearch索引详解

发表于 2019-03-10 | 分类于 Elasticsearch

在ES中创建一个mapping映射类似于在数据库中定义表结构,即表里面有哪些字段、字段是什么类型、字段的默认值,分词器等;也类似于solr里面的模式schema的定义。

获取映射关系

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
GET /mydemo_01/_mapping

结果:
{
"mydemo_01" : {
"mappings" : {
"user" : {
"properties" : {
"age" : {
"type" : "long"
},
"idcard" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"name" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
}
}
}

索引

1、创建索引

在ES中创建一个索引类似于在数据库中建立一个数据库(ES6.0之后类似于创建一个表)

1
2
3
4
5
6
7
8
9
PUT demo_index
{
"settings" : {
"index" : { #可以省略
"number_of_shards" : 3,
"number_of_replicas" : 2
}
}
}

创建索引时加入别名定义

1
2
3
4
5
6
7
8
9
10
11
12
PUT twitter
{
"aliases" : {
"alias_1" : {},
"alias_2" : {
"filter" : {
"term" : {"user" : "hu" }
},
"routing" : "hu"
}
}
}

2、创建mapping映射

mapping映射类似于在数据库中定义表结构,即表里面有哪些字段、字段是什么类型、字段的默认值等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PUT demo_index
{
"settings" : {
"index" : {
"number_of_shards" : 3,
"number_of_replicas" : 2
}
},
#映射
"mappings" : {
"type1" : {
"properties" : {
"field1" : { "type" : "text" }
}
}
}
}

返回说明

1
2
3
4
5
{
"acknowledged" : true, #索引创建成功
"shards_acknowledged" : true, #所需数量的分片和副本创建成功
"index" : "demo_index"
}

3、删除索引

DELETE /twitter 可以一次删除多个索引(以逗号间隔) 删除所有索引 _all 或 通配符 *

4、判断索引是否存在

HEAD twitter #HTTP status code 表示结果 404 不存在 , 200 存在

5、修改索引的settings信息

索引的设置信息分为静态信息和动态信息两部分。静态信息不可更改,如索引的分片数。动态信息可以修改。

/_settings 更新所有索引的。

{index}/_settings 更新一个或多个索引的settings。

6、修改备份数

1
2
3
4
5
6
PUT /demo_index/_settings
{
"index" : {
"number_of_replicas" : 2
}
}

7、设置其他

1
2
3
4
5
6
7
8
9
10
11
12
13
PUT /demo_index/_settings
{
"index" : {
"refresh_interval" : null #设置默认值
}
}

索引的读写
index.blocks.read_only:设为true,则索引以及索引的元数据只可读
index.blocks.read_only_allow_delete:设为true,只读时允许删除。
index.blocks.read:设为true,则不可读。
index.blocks.write:设为true,则不可写。
index.blocks.metadata:设为true,则索引元数据不可读写。

8、索引模板

模板中定义好settings、mapping、以及一个模式定义来匹配创建的索引。

注意:模板只在索引创建时被参考,修改模板不会影响已创建的索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#新增/修改名为tempae_1的模板,匹配名称为te* 或 demo*的索引创建
PUT _template/template_1
{
"index_patterns": ["te*", "demo*"],
"settings": {
"number_of_shards": 1
},
"mappings": {
"type1": {
"_source": {
"enabled": false
},
"properties": {
"host_name": {
"type": "keyword"
},
"created_at": {
"type": "date",
"format": "EEE MMM dd HH:mm:ss Z YYYY"
}
}
}
}
}

查看索引模板

GET /_template/template_1

GET /_template/temp*

GET /_template/template_1,template_2

GET /_template

删除模板

DELETE /_template/template_1

9、 打开/关闭索引

POST /demo_index/_close

POST /demo_index/_open

10、Shrink Index 收缩索引

索引的分片数是不可更改的,如要减少分片数可以通过收缩方式收缩为一个新的索引。新索引的分片数必须是原分片数的因子值,如原分片数是8,则新索引的分片数可以为4、2、1 。

收缩的流程:

1
2
3
4
5
先把所有主分片都转移到一台主机上;
在这台主机上创建一个新索引,分片数较小,其他设置和原索引一致;
把原索引的所有分片,复制(或硬链接)到新索引的目录下;
对新索引进行打开操作恢复分片数据;
(可选)重新把新索引的分片均衡到其他节点上。

将原索引设置为只读;

将原索引各分片的一个副本重分配到同一个节点上,并且要是健康绿色状态。

1
2
3
4
5
6
7
8
9
PUT /my_source_index/_settings
{
"settings": {
<!-- 指定进行收缩的节点的名称 -->
"index.routing.allocation.require._name": "shrink_node_name",
<!-- 阻止写,只读 -->
"index.blocks.write": true
}
}

进行收缩:

1
2
3
4
5
6
7
POST my_source_index/_shrink/my_target_index
{
"settings": {
"index.number_of_replicas": 1,
"index.number_of_shards": 1,
"index.codec": "best_compression"
}}

监控收缩过程:

1
2
GET _cat/recovery?v
GET _cluster/health

11、Split Index 拆分索引

当索引的分片容量过大时,可以通过拆分操作将索引拆分为一个倍数分片数的新索引。能拆分为几倍由创建索引时指定的index.number_of_routing_shards 路由分片数决定。这个路由分片数决定了根据一致性hash路由文档到分片的散列空间。

如index.number_of_routing_shards = 30 ,指定的分片数是5,则可按如下倍数方式进行拆分:

1
2
3
5 → 10 → 30 (split by 2, then by 3)
5 → 15 → 30 (split by 3, then by 2)
5 → 30 (split by 6)

压缩索引相反

但是只有在创建时指定了index.number_of_routing_shards 的索引才可以进行拆分,ES7开始将不再有这个限制。

和solr的区别是,solr是对一个分片进行拆分,es中是整个索引进行拆分。

拆分步骤:

准备一个索引来做拆分:

1
2
3
4
5
6
7
8
PUT my_source_index
{
"settings": {
"index.number_of_shards" : 1,
<!-- 创建时需要指定路由分片数 -->
"index.number_of_routing_shards" : 2
}
}

先设置索引只读:

1
2
3
4
5
6
PUT /my_source_index/_settings
{
"settings": {
"index.blocks.write": true
}
}

做拆分:

1
2
3
4
5
6
7
POST my_source_index/_split/my_target_index
{
"settings": {
<!--新索引的分片数需符合拆分规则-->
"index.number_of_shards": 2
}
}

监控拆分过程:

1
2
GET _cat/recovery?v
GET _cluster/health

12、Rollover Index 别名滚动指向新创建的索引

ES的rollover index API 让我们可以根据满足指定的条件(时间、文档数量、索引大小)创建新的索引,并把别名滚动指向新的索引。

创建一个名字为logs-0000001 、别名为logs_write 的索引:

1
2
3
4
5
6
PUT /logs-000001 
{
"aliases": {
"logs_write": {}
}
}

添加1000个文档到索引logs-000001,然后设置别名滚动的条件

1
2
3
4
5
6
7
8
POST /logs_write/_rollover 
{
"conditions": {
"max_age": "7d",
"max_docs": 1000,
"max_size": "5gb"
}
}

说明:如果别名logs_write指向的索引是7天前(含)创建的或索引的文档数>=1000或索引的大小>= 5gb,则会创建一个新索引 logs-000002,并把别名logs_writer指向新创建的logs-000002索引

** Rollover Index 新建索引的命名规则:**

如果索引的名称是-数字结尾,如logs-000001,则新建索引的名称也会是这个模式,数值增1。

如果索引的名称不是-数值结尾,则在请求rollover api时需指定新索引的名称

1
2
3
4
5
6
7
8
POST /my_alias/_rollover/my_new_index_name
{
"conditions": {
"max_age": "7d",
"max_docs": 1000,
"max_size": "5gb"
}
}

** 在名称中使用Date math(时间表达式)**

如果你希望生成的索引名称中带有日期,如logstash-2016.02.03-1 ,则可以在创建索引时采用时间表达式来命名:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# PUT /<logs-{now/d}-1> with URI encoding:
PUT /%3Clogs-%7Bnow%2Fd%7D-1%3E
{
"aliases": {
"logs_write": {}
}
}

PUT logs_write/_doc/1
{
"message": "a dummy log"
}

POST logs_write/_refresh
1
2
3
4
5
6
POST /logs_write/_rollover 
{
"conditions": {
"max_docs": "1"
}
}

** Rollover时可对新的索引作定义 **

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PUT /logs-000001
{
"aliases": {
"logs_write": {}
}
}

POST /logs_write/_rollover
{
"conditions" : {
"max_age": "7d",
"max_docs": 1000,
"max_size": "5gb"
},
"settings": {
"index.number_of_shards": 2
}
}

Dry run 实际操作前先测试是否达到条件:

1
2
3
4
5
6
7
8
POST /logs_write/_rollover?dry_run
{
"conditions" : {
"max_age": "7d",
"max_docs": 1000,
"max_size": "5gb"
}
}

说明:

rollover是你请求它才会进行操作,并不是自动在后台进行的。你可以周期性地去请求它。

索引监控

查看索引状态信息

查看所有的索引状态:

GET /_stats

查看指定索引的状态信息:

GET /index1,index2/_stats

查看索引段信息

https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-segments.html

GET /test/_segments

GET /index1,index2/_segments

GET /_segments

查看索引恢复信息

GET index1,index2/_recovery?human

GET /_recovery?human

查看索引分片的存储信息

GET /test/_shard_stores

GET /test1,test2/_shard_stores

GET /_shard_stores

索引状态管理

** Clear Cache 清理缓存**

POST /demo_index/_cache/clear

默认会清理所有缓存,可指定清理query, fielddata or request 缓存

POST /_cache/clear

** Refresh,重新打开读取索引**

POST /demo_index/_refresh

POST /_refresh

** Flush,将缓存在内存中的索引数据刷新到持久存储中 **

POST demo_index/_flush

** Force merge 强制段合并 **

POST /demo_index/_forcemerge?only_expunge_deletes=false&max_num_segments=100&flush=true
可选参数说明:

1
2
3
4
5
6
max_num_segments 合并为几个段,默认1
only_expunge_deletes 是否只合并含有删除文档的段,默认false
flush 合并后是否刷新,默认true

POST /demo_index,elasticsearch/_forcemerge
POST /_forcemerge

映射Mapping

静态映射

在ElasticSearch中也可以事先定义好映射,包含文档的各个字段及其类型等,这种方式称之为静态映射。

动态映射

ElasticSearch中不需要事先定义映射(Mapping),文档写入ElasticSearch时,会根据文档字段自动识别类型,这种机制称之为动态映射。

索引创建mapping

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PUT demo_index
{
<!--映射定义 -->
"mappings" : {
<!--名为type1的映射类别 mapping type-->
"type1" : {
<!-- 字段定义 -->
"properties" : {
<!-- 名为field1的字段,它的field datatype 为 text -->
"field1" : { "type" : "text" }
}
}
}
}

映射类别 Mapping type

从6.0.0开始限定仅包含一个映射类别定义( “index.mapping.single_type”: true ),兼容5.x中的多映射类别。从7.0开始将移除映射类别。为了与未来的规划匹配,请现在将这个唯一的映射类别名定义为“_doc”,因为索引的请求地址将规范为:PUT {index}/_doc/{id} and POST {index}/_doc

字段类型 datatypes

Core Datatypes 核心类型

1
2
3
4
5
6
7
8
9
10
11
12
string
text and keyword
Numeric datatypes
long, integer, short, byte, double, float, half_float, scaled_float
Date datatype
date
Boolean datatype
boolean
Binary datatype
binary
Range datatypes 范围
integer_range, float_range, long_range, double_range, date_range

Complex datatypes 复合类型

1
2
3
4
5
6
Array datatype
数组就是多值,不需要专门的类型
Object datatype
object :表示值为一个JSON 对象
Nested datatype
nested:for arrays of JSON objects(表示值为JSON对象数组 )

Geo datatypes 地理数据类型

1
2
3
4
Geo-point datatype
geo_point: for lat/lon points (经纬坐标点)
Geo-Shape datatype
geo_shape: for complex shapes like polygons (形状表示)

Specialised datatypes 特别的类型

1
2
3
4
5
6
7
8
9
10
11
12
IP datatype
ip: for IPv4 and IPv6 addresses
Completion datatype
completion: to provide auto-complete suggestions
Token count datatype
token_count: to count the number of tokens in a string
mapper-murmur3
murmur3: to compute hashes of values at index-time and store them in the index
Percolator type
Accepts queries from the query-dsl
join datatype
Defines parent/child relation for documents within the same index

字段定义属性介绍

字段的type (Datatype)定义了如何索引存储字段值,还有一些属性可以让我们根据需要来覆盖默认的值或进行特别定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
analyzer   指定分词器
normalizer 指定标准化器
boost 指定权重值
coerce 强制类型转换
copy_to 值复制给另一字段
doc_values 是否存储docValues
dynamic
enabled 字段是否可用
fielddata
eager_global_ordinals
format 指定时间值的格式
ignore_above
ignore_malformed
index_options
index
fields
norms
null_value
position_increment_gap
properties
search_analyzer
similarity
store
term_vector

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PUT my_index
{
"mappings": {
"_doc": {
"properties": {
"date": {
"type": "date",
<!--格式化日期 -->
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
}
}
}
}
}

Multi Field 多重字段

当我们需要对一个字段进行多种不同方式的索引时,可以使用fields多重字段定义。如一个字符串字段即需要进行text分词索引,也需要进行keyword 关键字索引来支持排序、聚合;或需要用不同的分词器进行分词索引。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PUT my_index
{
"mappings": {
"_doc": {
"properties": {
"city": {
"type": "text",
"fields": {
"raw": {
"type": "keyword"
}
}
}
}
}
}
}

往多重字段里面添加文档

1
2
3
4
5
6
7
8
9
PUT my_index/_doc/1
{
"city": "New York"
}

PUT my_index/_doc/2
{
"city": "York"
}

获取多重字段的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
GET my_index/_search
{
"query": {
"match": {
"city": "york"
}
},
"sort": {
"city.raw": "asc"
},
"aggs": {
"Cities": {
"terms": {
"field": "city.raw"
}
}
}
}

元字段

元字段是ES中定义的文档字段,有这几种:_index,_uid,_type,_id。

动态映射

动态映射:ES中提供的重要特性,让我们可以快速使用ES,而不需要先创建索引、定义映射。 如我们直接向ES提交文档进行索引:

1
2
PUT data/_doc/1 
{ "count": 5 }

ES将自动为我们创建data索引、_doc 映射、类型为 long 的字段 count

索引文档时,当有新字段时, ES将根据我们字段的json的数据类型为我们自动加人字段定义到mapping中。

字段动态映射规则

Date detection 时间侦测:指我们往ES里面插入数据的时候会去自动检测我们的数据是不是日期格式的,是的话就会给我们自动转为设置的格式

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
date_detection 默认是开启的,默认的格式dynamic_date_formats为:

[ "strict_date_optional_time","yyyy/MM/dd HH:mm:ss Z||yyyy/MM/dd Z"]
PUT my_index/_doc/1
{
"create_date": "2015/09/02"
}

GET my_index/_mapping

自定义时间格式:

PUT my_index
{
"mappings": {
"_doc": {
"dynamic_date_formats": ["MM/dd/yyyy"]
}
}
}

禁用时间侦测:

PUT my_index
{
"mappings": {
"_doc": {
"date_detection": false
}
}
}

Numeric detection 数值侦测

开启数值侦测(默认是禁用的)

1
2
3
4
5
6
7
8
PUT my_index
{
"mappings": {
"_doc": {
"numeric_detection": true
}
}
}

索引别名

  1. 别名的用途
    如果希望一次查询可查询多个索引。
    如果希望通过索引的视图来操作索引,就像数据库库中的视图一样。

索引的别名机制,就是让我们可以以视图的方式来操作集群中的索引,这个视图可是多个索引,也可是一个索引或索引的一部分。

  1. 新建索引时定义别名

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    PUT /logs_20162801
    {
    "mappings" : {
    "type" : {
    "properties" : {
    "year" : {"type" : "integer"}
    }
    }
    },
    <!-- 定义了两个别名 -->
    "aliases" : {
    "current_day" : {},
    "2016" : {
    "filter" : {
    "term" : {"year" : 2016 }
    }
    }
    }
    }
  2. 创建别名 /_aliases

1
2
3
4
5
6
POST /_aliases
{
"actions" : [
{ "add" : { "index" : "test1", "alias" : "alias1" } }
]
}
  1. 删除别名

    1
    2
    3
    4
    5
    6
    7
    8
    9
    POST /_aliases
    {
    "actions" : [
    { "remove" : { "index" : "test1", "alias" : "alias1" } }
    ]
    }
    还可以这样写

    DELETE /{index}/_alias/{name}
  2. 批量操作别名

    删除索引test1的别名alias1,同时为索引test2添加别名alias1

1
2
3
4
5
6
7
POST /_aliases
{
"actions" : [
{ "remove" : { "index" : "test1", "alias" : "alias1" } },
{ "add" : { "index" : "test2", "alias" : "alias1" } }
]
}
  1. 为多个索引定义一样的别名

方式1:

1
2
3
4
5
6
7
POST /_aliases
{
"actions" : [
{ "add" : { "index" : "test1", "alias" : "alias1" } },
{ "add" : { "index" : "test2", "alias" : "alias1" } }
]
}

方式2:

1
2
3
4
5
6
POST /_aliases
{
"actions" : [
{ "add" : { "indices" : ["test1", "test2"], "alias" : "alias1" } }
]
}

注意:只可通过多索引别名进行搜索,不可进行文档索引和根据id获取文档。

方式3:通过统配符*模式来指定要别名的索引

1
2
3
4
5
6
7
POST /_aliases
{
"actions" : [
{ "add" : { "index" : "test*", "alias" : "all_test_indices" } }
]
}
注意:在这种情况下,别名是一个点时间别名,它将对所有匹配的当前索引进行别名,当添加/删除与此模式匹配的新索引时,它不会自动更新。
  1. 带过滤器的别名

索引中需要有字段

1
2
3
4
5
6
7
8
9
10
11
12
PUT /test1
{
"mappings": {
"type1": {
"properties": {
"user" : {
"type": "keyword"
}
}
}
}
}

过滤器通过Query DSL来定义,将作用于通过该别名来进行的所有Search, Count, Delete By Query and More Like This 操作。

1
2
3
4
5
6
7
8
9
10
11
12
POST /_aliases
{
"actions" : [
{
"add" : {
"index" : "test1",
"alias" : "alias2",
"filter" : { "term" : { "user" : "kimchy" } }
}
}
]
}
  1. 带routing的别名

可在别名定义中指定路由值,可和filter一起使用,用来限定操作的分片,避免不需要的其他分片操作。

1
2
3
4
5
6
7
8
9
10
11
12
POST /_aliases
{
"actions" : [
{
"add" : {
"index" : "test",
"alias" : "alias1",
"routing" : "1"
}
}
]
}

为搜索、索引指定不同的路由值

1
2
3
4
5
6
7
8
9
10
11
12
13
POST /_aliases
{
"actions" : [
{
"add" : {
"index" : "test",
"alias" : "alias2",
"search_routing" : "1,2",
"index_routing" : "2"
}
}
]
}
  1. 以PUT方式来定义一个别名
    1
    2
    PUT /{index}/_alias/{name}
    PUT /logs_201305/_alias/2013
    带filter 和 routing
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    PUT /users
    {
    "mappings" : {
    "user" : {
    "properties" : {
    "user_id" : {"type" : "integer"}
    }
    }
    }
    }
1
2
3
4
5
6
7
8
9
PUT /users/_alias/user_12
{
"routing" : "12",
"filter" : {
"term" : {
"user_id" : 12
}
}
}
  1. 查看别名定义信息
    1
    2
    3
    4
    GET /{index}/_alias/{alias}
    GET /logs_20162801/_alias/*
    GET /_alias/2016
    GET /_alias/20*

Elasticsearch介绍和基本使用

发表于 2019-03-09 | 分类于 Elasticsearch

ES特性

官网的介绍: https://www.elastic.co/cn/products/elasticsearch

速度快、易扩展、弹性、灵活、操作简单、多语言客户端、X-Pack、hadoop/spark强强联手、开箱即用。

  • 分布式:横向扩展非常灵活
  • 全文检索:基于lucene的强大的全文检索能力;
  • 近实时搜索和分析:数据进入ES,可达到近实时搜索,还可进行聚合分析
  • 高可用:容错机制,自动发现新的或失败的节点,重组和重新平衡数据
  • 模式自由:ES的动态mapping机制可以自动检测数据的结构和类型,创建索引并使数据可搜索。
  • RESTful API:JSON + HTTP

    ES架构

说明:

1
2
3
4
5
6
Gateway是ES用来存储索引的文件系统,支持多种类型。
Gateway的上层是一个分布式的lucene框架。
Lucene之上是ES的模块,包括:索引模块、搜索模块、映射解析模块等
ES模块之上是 Discovery、Scripting和第三方插件。Discovery是ES的节点发现模块,不同机器上的ES节点要组成集群需要进行消息通信,集群内部需要选举master节点,这些工作都是由Discovery模块完成。支持多种发现机制,如 Zen 、EC2、gce、Azure。Scripting用来支持在查询语句中插入javascript、python等脚本语言,scripting模块负责解析这些脚本,使用脚本语句性能稍低。ES也支持多种第三方插件。
再上层是ES的传输模块和JMX.传输模块支持多种传输协议,如 Thrift、memecached、http,默认使用http。JMX是java的管理框架,用来管理ES应用。
最上层是ES提供给用户的接口,可以通过RESTful接口和ES集群进行交互。

ES应用场景

1
2
3
4
5
6
7
8
9
维基百科
The Guardian(国外新闻网站)
Stack Overflow(国外的程序异常讨论论坛)
GitHub(开源代码管理)
电商网站
日志数据分析
商品价格监控网站
BI系统
站内搜索

Elasticsearch的使用场景深入详解

场景—:使用Elasticsearch作为主要的后端

传统项目中,搜索引擎是部署在成熟的数据存储的顶部,以提供快速且相关的搜索能力。这是因为早期的搜索引擎不能提供耐用的​​存储或其他经常需要的功能,如统计。

Elasticsearch是提供持久存储、统计等多项功能的现代搜索引擎。

如果你开始一个新项目,考虑使用Elasticsearch作为唯一的数据存储,此种场景不支持包含频繁更新、事务(transaction)的操作。

例如新建一个博客系统使用es作为存储。

1)我们可以向ES提交新的博文;

2)使用ES检索、搜索、统计数据。

ES作为存储的优势:

如果一台服务器出现故障时会发生什么?你可以通过复制 数据到不同的服务器以达到容错的目的。

注意:整体架构设计时,需要我们权衡是否有必要增加额外的存储。

场景二:在现有系统中增加elasticsearch

由于ES不能提供存储的所有功能,一些场景下需要在现有系统数据存储的基础上新增ES支持。

举例1:ES不支持事务、复杂的关系(至少1.X版本不支持,2.X有改善,但支持的仍然不好),如果你的系统中需要上述特征的支持,需要考虑在原有架构、原有存储的基础上的新增ES的支持。

举例2:如果你已经有一个在运行的复杂的系统,你的需求之一是在现有系统中添加检索服务。一种非常冒险的方式是重构系统以支持ES。而相对安全的方式是:将ES作为新的组件添加到现有系统中。

如果你使用了如下图所示的SQL数据库和ES存储,你需要找到一种方式使得两存储之间实时同步。需要根据数据的组成、数据库选择对应的同步插件。可供选择的插件包括:

1)mysql、oracle选择 logstash-input-jdbc 插件。

2)mongo选择 mongo-connector工具。

假设你的在线零售商店的产品信息存储在SQL数据库中。 为了快速且相关的搜索,你安装Elasticsearch。

为了索引数据,您需要部署一个同步机制,该同步机制可以是Elasticsearch插件或你建立一个自定义的服务。此同步机制可以将对应于每个产品的所有数据和索引都存储在Elasticsearch,每个产品作为一个document存储(相当于关系据库中的一行/row数据)。

当在该网页上的搜索条件中输入“用户的类型”,店面网络应用程序通过Elasticsearch查询该信息。 Elasticsearch返回符合标准的产品documents,并根据你喜欢的方式来分类文档。 排序可以根据每个产品的被搜索次数所得到的相关分数,或任何存储在产品document信息,例如:最新最近加入的产品、平均得分,或者是那些插入或更新信息。 所以你可以只使用Elasticsearch处理搜索。这取决于同步机制来保持Elasticsearch获取最新变化。

场景三:使用elasticsearch和现有的工具

在一些使用情况下,不必写一行代码就能通过elasticssearch完成一项工作。很多工具都可以与Elasticsearch一起工作,所以你不必到你从头开始编写。

例如,假设要部署一个大规模的日志框架存储,搜索,并分析了大量的事件。

如图下图,处理日志和输出到Elasticsearch,您可以使用日志记录工具,如rsyslog(www.rsyslog.com),Logstash(www.elastic.co/products/logstash),或Apache Flume(http://flume.apache.org)。
搜索和可视化界面分析这些日志,你可以使用Kibana(www.elastic.co/产品/ kibana)。

出处:http://blog.csdn.net/laoyang360/article/details/52227541

ES的特点

可以作为一个大型分布式集群(数百台服务器)技术,处理PB级数据,服务大公司;也可以运行在单机上,服务小公司

Elasticsearch不是什么新技术,主要是将全文检索、数据分析以及分布式技术,合并在了一起

对用户而言,是开箱即用的,非常简单,作为中小型的应用,直接3分钟部署一下ES

Elasticsearch作为传统数据库的一个补充,比如全文检索,同义词处理,相关度排名,复杂数据分析,海量数据的近实时处理;

ES的核心概念

  • ** Near Realtime(NRT)** 近实时。数据提交索引后,立马就可以搜索到。
  • ** Cluster 集群**,一个集群由一个唯一的名字标识,默认为“elasticsearch”。集群名称非常重要,具有相同集群名的节点才会组成一个集群。集群名称可以在配置文件中指定。
  • ** Node 节点**:存储集群的数据,参与集群的索引和搜索功能。像集群有名字,节点也有自己的名称,默认在启动时会以一个随机的UUID的前七个字符作为节点的名字,你可以为其指定任意的名字。通过集群名在网络中发现同伴组成集群。一个节点也可是集群。
  • ** Index 索引**: 一个索引是一个文档的集合(等同于solr中的集合)。每个索引有唯一的名字,通过这个名字来操作它。一个集群中可以有任意多个索引。
  • ** Type 类型**:指在一个索引中,可以索引不同类型的文档,如用户数据、博客数据。从6.0.0 版本起已废弃,一个索引中只存放一类数据。
  • ** Document 文档**:被索引的一条数据,索引的基本信息单元,以JSON格式来表示。
  • ** Shard 分片**:在创建一个索引时可以指定分成多少个分片来存储。每个分片本身也是一个功能完善且独立的“索引”,可以被放置在集群的任意节点上。分片的好处:

-允许我们水平切分/扩展容量
-可在多个分片上进行分布式的、并行的操作,提高系统的性能和吞吐量。

注意:分片数创建索引时指定,创建后不可改了。备份数可以随时改。

Replication 备份: 一个分片可以有多个备份(副本)。备份的好处:高可用。一个主分片挂了,副本分片就顶上去;扩展搜索的并发能力、吞吐量。搜索可以在所有的副本上并行运行。-高并发下副本也可搜索。

安装配置

略

注意:在linux 虚拟机上运行可能的失败问题

  • 内存不够用,默认es配置使用1G堆内存,如果的你学习用的虚拟机没有这么大的内存,请在config/jvm.options中调整
  • 可能会报如下的错误
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
问题一:max file descriptors [4096] for elasticsearch process likely too low, increase to at least [65536]
解决:修改切换到root用户修改配置limits.conf 添加下面两行
命令:vi /etc/security/limits.conf
* hard nofile 65536
* soft nofile 65536

问题二:max number of threads [1024] for user [lish] likely too low, increase to at least [2048]
解决:切换到root用户,进入limits.d目录下修改配置文件。
vi /etc/security/limits.d/90-nproc.conf
修改如下内容:
* soft nproc 1024
#修改为
* soft nproc 2048

问题三:max virtual memory areas vm.max_map_count [65530] likely too low, increase to at least [262144]
解决:切换到root用户修改配置sysctl.conf
vi /etc/sysctl.conf
添加下面配置:
vm.max_map_count=655360

并执行命令:
sysctl -p

切换到es的用户。
然后,重新启动elasticsearch,即可启动成功。

** 后台运行ES **
使用守护进程运行:./elasticsearch -d

** ES 重要的配置参数 **
** 1、** 数据目录和日志目录,生成环境下应与软件分离

1
2
3
path:
logs: /var/.....
data: /var/data/...

** 2、** 所属的集群名,默认为 elasticsearch ,可修改:cluster.name: dddd

** 3、** 节点名称,默认为 UUID前7个字符,可自定义

** 4、** network.host IP绑定

** 5、** http.port: 9200-9300 对外服务的http 端口, 默认 9200-9300 。可以为它指定一个值或一个区间,当为区间时会取用区间第一个可用的端口。

** 6、** transport.tcp.port: 9300-9400 节点间交互的端口, 默认 9300-9400 。

** 7、** Discovery Config 节点发现配置

ES中默认采用的节点发现方式是 zen(基于组播(多播)、单播)。在应用于生产前有两个重要参数需配置:

1
2
discovery.zen.ping.unicast.hosts: ["host1","host2:port","host3[portX-portY]"] 单播模式下,设置具有master资格的节点列表,新加入的节点向这个列表中的节点发送请求来加入集群
discovery.zen.minimum_master_nodes: 1 这个参数控制的是,一个节点需要看到具有master资格的节点的最小数量,然后才能在集群中做操作。官方的推荐值是(N/2)+1,其中N是具有master资格的节点的数量。

** 8、** Jvm heap 大小设置

生产环境中一定要在jvm.options中调大它的jvm内存。

** 9、** JVM heap dump path 设置

生产环境中指定当发生OOM异常时,heap的dump path,好分析问题。在jvm.options中配置:

-XX:HeapDumpPath=/var/lib/elasticsearch

** 10、** 其他配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
transport.tcp.compress: false
是否压缩tcp传输的数据,默认false
http.cors.enabled: true
是否使用http协议对外提供服务,默认true
http.max_content_length: 100mb
http传输内容的最大容量,默认100mb
node.master: true
指定该节点是否可以作为master节点,默认是true。ES集群默认是以第一个节点为master,如果该节点出故障就会重新选举master。
node.data: true
该节点是否存索引数据,默认true。
discover.zen.ping.timeout: 3s
设置集群中自动发现其他节点时ping连接超时时长,默认为3秒。在网络环境较差的情况下,增加这个值,会增加节点等待响应的时间,从一定程度上会减少误判。
discovery.zen.ping.multicast.enabled: false
是否启用多播来发现节点。

ES-Spring JPA操作

发表于 2019-03-08 | 分类于 Elasticsearch

ElasticSearch在JPA与原生操作接口的对应关系

关键字 例子 Elasticsearch查询语句
And findByNameAndPrice {“bool” : {“must” : [ {“field” : {“name” : “?”}}, {“field” : {“price” : “?”}} ]}}
Or findByNameOrPrice {“bool” : {“should” : [ {“field” : {“name” : “?”}}, {“field” : {“price” : “?”}} ]}}
Is findByName {“bool” : {“must” : {“field” : {“name” : “?”}}}}
Not findByNameNot {“bool” : {“must_not” : {“field” : {“name” : “?”}}}}
Between findByPriceBetween {“bool” : {“must” : {“range” : {“price” : {“from” : ?,“to” : ?,“include_lower” : true,“include_upper” : true}}}}}
LessThanEqual findByPriceLessThan {“bool” : {“must” : {“range” : {“price” : {“from” : null,”to” : ?,”include_lower” : true,”include_upper” : true}}}}}
GreaterThanEqual findByPriceGreaterThan {“bool” : {“must” : {“range” : {“price” : {“from” : ?,”to” : null,”include_lower” : true,”include_upper” : true}}}}}
Before findByPriceBefore {“bool” : {“must” : {“range” : {“price” : {“from” : null,”to” : ?,”include_lower” : true,”include_upper” : true}}}}}
After findByPriceAfter {“bool” : {“must” : {“range” : {“price” : {“from” : ?,”to” : null,”include_lower” : true,”include_upper” : true}}}}}
Like findByNameLike {“bool” : {“must” : {“field” : {“name” : {“query” : “?*”,”analyze_wildcard” : true}}}}}
StartingWith findByNameStartingWith {“bool” : {“must” : {“field” : {“name” : {“query” : “?*”,”analyze_wildcard” : true}}}}}
EndingWith findByNameEndingWith {“bool” : {“must” : {“field” : {“name” : {“query” : “*?”,”analyze_wildcard” : true}}}}}
Contains/Containing findByNameContaining {“bool” : {“must” : {“field” : {“name” : {“query” : “?”,”analyze_wildcard” : true}}}}}
In findByNameIn(Collectionnames) {“bool” : {“must” : {“bool” : {“should” : [ {“field” : {“name” : “?”}}, {“field” : {“name” : “?”}} ]}}}}
NotIn findByNameNotIn(Collectionnames) {“bool” : {“must_not” : {“bool” : {“should” : {“field” : {“name” : “?”}}}}}}
Near findByStoreNear 暂不支持
True findByAvailableTrue {“bool” : {“must” : {“field” : {“available” : true}}}}
False findByAvailableFalse {“bool” : {“must” : {“field” : {“available” : false}}}}
OrderBy findByAvailableTrueOrderByNameDesc {“sort” : [{ “name” : {“order” : “desc”} }],”bool” : {“must” : {“field” : {“available” : true}}}}

实体声明

  • 1、类上注解:@Document (相当于Hibernate实体的@Entity/@Table)
类型 属性名 默认值 说明
String indexName 无 索引库的名称
String type “” 类型
short shards 5 默认分区数
short replica 1 每个分区默认的备份数
String refreshInterval “1s” 刷新间隔
String indexStoreType “fs” 索引文件存储类型
  • 2、主键注解:@Id (相当于Hibernate实体的主键@Id注解) 只是一个标识,并没有属性。
  • 3、属性注解 @Field (相当于Hibernate实体的@Column注解)

@Field默认是可以不加的,默认所有属性都会添加到ES中。

类型 属性名 默认值 说明
FileType type FieldType.Auto 自动检测属性的类型
FileType index FieldIndex.analyzed 默认情况下分词
boolean store false 默认情况下不存储原文
String searchAnalyzer “” 指定字段搜索时使用的分词器
String indexAnalyzer “” 指定字段建立索引时指定的分词器
String[] ignoreFields {} 如果某个字段需要被忽略
实例

实体:

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
/**
* index
* 索引,相当于Mysql中的一个库,例如有一个叫『jd』的库,那么里面可以建立很多表,存储不同类型的数据,而表在ES中就是type。
* type
* 类型,相当于Mysql中的一张表,存储json类型的数据
* document
* 文档,一个文档相当于Mysql一行的数据
* shards
* 分片,通俗理解,就是数据分成几块区域来存储,可以理解为mysql中的分库分表(不太恰当)
* replicas
* 备份,就是分片的备份数,相当于mysql的备份库
*
*/
@Data
@Document(indexName = "account", type = "_doc", refreshInterval = "0s")
public class Account {

@Id
private Integer id;
private String userName;
private String realName;
private String password;
private Integer age;
//这三个注解是为了前台序列化java8 LocalDateTime使用的,需要引入jsr310的包才可以使用
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
private LocalDateTime birth;

}

DAO:

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
/**
* @Description TODO
* @Author huyunshun 2019/8/16
*/
public interface AccountRepository extends ElasticsearchRepository<Account, Integer> {

/**
* 查询账户名为username的账户
* @param userName
* @return
*/
List<Account> findByUserName(String userName);

/**
* 查询账户名为username并且真实姓名为realname的账户
* @param userName
* @param realName
*/
List<Account> findByUserNameAndRealName(String userName, String realName);

/**
* 查询账户名为userName或者姓名为realName的账户
*/
List<Account> findByUserNameOrRealName(String userName, String realName);

/**
* 查询账户名不是username的所有账户
* @param userName
* @return
*/
List<Account> findByUserNameNot(String userName);


/**
* 查询年龄段为ageFrom到ageTo的账户
* @param ageFrom
* @param ageTo
* @return
*/
List<Account> findByAgeBetween(Integer ageFrom, Integer ageTo);

/**
* 查询生日小于birth的账户
*/
List<Account> findByBirthLessThan(LocalDateTime birth);


/**
* 查询生日段大于birthFrom的账户
* @param birth
* @return
*/
List<Account> findByBirthGreaterThan(LocalDateTime birth);

/**
* 查询年龄小于或等于age的账户
*/
List<Account> findByAgeBefore(Integer age);

/**
* 查询年龄大于或等于age的账户
* @param age
* @return
*/
List<Account> findByAgeAfter(Integer age);

/**
* 账户名模糊查询
* @param userName
* @return
*/
List<Account> findByUserNameLike(String userName);


/**
* 查询以start开头的账户
* @param start
* @return
*/
List<Account> findByUserNameStartingWith(String start);

/**
* 查询以end结尾的账户
* @return
*/
List<Account> findByUserNameEndingWith(String end);

/**
* 查询账户名包含word的账户
* @param word
* @return
*/
List<Account> findByUserNameContaining(String word);

/**
* 查询名字属于usernames中的账户
* @param userNames
* @return
*/
List<Account> findByUserNameIn(Collection<String> userNames);

/**
* 查询名字不属于userNames中的账户
* @param userNames
* @return
*/
List<Account> findByUserNameNotIn(Collection<String> userNames);

/**
*查询年龄小于age,姓名以start开头,id大于id的账户,并且按照年龄倒序
* @return
*/
List<Account> findByAgeBeforeAndUserNameStartingWithAndIdGreaterThanOrderByAgeDesc(Integer age, String start, Integer id);

/** 写ES的查询语句
* 查询名字为name的账户 分页
* @param name
* @param pageable
* @return
*/
@Query("{\"bool\" : {\"must\" : {\"field\" : {\"name\" : \"?0\"}}}}")
Page<Account> findByName(String name, Pageable pageable);

}

Hibernate中criteria的方式进行查询

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
public Page<Account> getAccounts() {
//创建builder
BoolQueryBuilder builder = QueryBuilders.boolQuery();
//设置模糊搜索,真实姓名中包含胡的用户
builder.must(QueryBuilders.fuzzyQuery("realName", "胡"));
//设置用户名为huing
builder.must(new QueryStringQueryBuilder("huing").field("userName"));

//排序
FieldSortBuilder sort = SortBuilders.fieldSort("age").order(SortOrder.DESC);

//设置分页
PageRequest page = new PageRequest(0, 2);

//构建查询
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
//将搜索条件设置到构建中
nativeSearchQueryBuilder.withQuery(builder);
//将分页设置到构建中
nativeSearchQueryBuilder.withPageable(page);
//将排序设置到构建中
nativeSearchQueryBuilder.withSort(sort);
//生产NativeSearchQuery
NativeSearchQuery query = nativeSearchQueryBuilder.build();

//执行,返回包装结果的分页
Page<Account> resutlList = accountRepository.search(query);

return resutlList;
}
术语
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
Analysis(分析)
​ 分析的过程就是将全文(full text)转换成 术语/分词(terms)。 这取决于使用那个分析器,这些短语:“FOO BAR”, “Foo-Bar”, “foo,bar”,可能会拆分成” foo”和“bar”。这些拆分后的词实际上是存储在索引中。一个完整的文本查询(而不是一个分词查询) “FoO:bAR”,将被分解成 “foo”,“bar”,来匹配存储在索引中的分词。正是这一过程的分析(包括在索引时间和搜索时间),允许elasticsearch执行全文查询。

Cluster (集群)
​ 一个集群包含一个或多个分配了相同的集群名称的节点。每个集群都有一个主节点是集群自动选择产生,并且可以决定如果当前主节点失败,哪些可以替换。

Document(文档)
​ 文档是存储在elasticsearch中的一个JSON文件。这是相当与关系数据库中表的一行数据。每个文档被存储在索引中,并具有一个类型和一个id。一个文档是一个JSON对象(也被称为在其他语言中的 hash / hashmap / associative array(关联数组)),其中包含零个或多个字段 或者 键值对。原始JSON文档将被存储在索引的_source字段,在获得(getting)或者 搜索(searching)默认的返回时,得到或搜索文档。

Id(标识)
​ 每个文档ID标识了一个文档。一个文档的索引/类型/ ID必须是唯一的。如果没有提供ID,将是自动生成。(还可以看到路由)

Field(字段)
​ 文档中包含的一组字段或键值对。字段的值可以是一个简单的(标量)值(如字符串,整数,日期),或者一个嵌套的结构就像一个数组或对象。一个字段就是类似关系数据库表中的一列。映射的每个字段有一个字段的类型“type”(不要与文档类型混淆),表示那种类型的数据可以存储在该字段里,如:整数,字符串,对象。映射还允许你定义(除其他事项外)一个字段的值如何进行分析。

Index(索引)
​ 索引就是像关系数据库中的“数据库”。通过映射可以定义成多种类型。索引是一个逻辑命名空间映射到一个或多个主要的分片,可以有零个或多个副本分片。

Mapping(映射)
​ 映射是像关系数据库中的”模式定义“。每个索引都有一个映射,它定义了每个索引的类型,再加上一些索引范围的设置。映射可以被明确地定义,或者在一个文档被索引的时候自动生成。

Node(节点)
​ 节点是属于elasticsearch群集的运行实例。测试的时候,在一台服务器可以启动多个节点,但通常情况下应该在一台服务器运行一个节点。在启动时,节点将使用单播(或组播,但是必须指定)来发现使用相同的群集名称的群集,并会尝试加入该群集。

Primary shard(主分片)
​ 每个文档都存储在一个主要分片上。当你索引一个文档时,索引首先生成在主分片上,然后才到主分片的所有副本上。默认情况下,索引有5个主分片。您可以指定更多或更少的主分片来适应索引可以处理的文档数。一旦创建了索引,就不能改变索引中主分片的数量。

Replica shard(副本分片)
​ 每个主分片可以有零个或多个副本。副本是主分片的一个拷贝,有两个作用:

​ 1、故障转移:如果主分片有问题,副本分片可以提升为主分片;

​ 2、提高性能:获取和搜索请求可以处理主分片或副本分片。

​ 默认情况下,每个主分片有一个副本,不过索引的副本数量可以动态地改变。在同一个节点上,一个副本分片将永远不会和其主分片一起运行。

Routing(路由)
​ 当你索引一个文档,它是存储在一个主分片里。这分片的选择是通过哈希的路由值。默认情况下,路由值来自文档的ID;如果该文档指定了父文档,则使用父文档的ID(以确保这个子文档和父文件都存储在相同的分片上)。这个路由值可以在索引的时候,通过指定数值或者配置字段映射来覆盖。

Shard(分片)
​ 一个分片是一个单一的Lucene的实例。这是一个低级别的通过ElasticSearch自动管理的“工作者”单元。索引是一个逻辑命名空间指向主分片和副本分片。索引的主分片和副本分片的数量需要明确的指定。然而你的代码应该只处理一个索引。Elasticsearch分配集群中所有节点的分片。在节点出现故障或增加新节点的时候,可以自动的将一个节点上的分片移动到另一个节点上。

Source field(源字段)
​ 默认情况下,你的JSON文档将被索引存储在_source字段里面,所有的get(获取)和search(搜索)请求将返回的该字段。这将允许你直接从搜索结果中访问到源数据,而不需要再次发起请求检索。

​ 注:索引将返回完整的的JSON字符串给你,即使它包含无效的JSON。此字段里的内容不表示任何该对象里面的数据如何被索引。

Term(术语)
​ 在elasticsearch里,术语(term)是一个被索引的精确值。术语 foo, Foo,FOO 是不想等的。术语(即精确值)可以使用“term”查询接口来查询。

Text(文本)
​ 文本(或全文)是普通非结构化的文本,如本段。默认情况下,文本将被分析成术语,术语才是实际存储在索引中。文本字段在索引时需要进行分析,以便全文搜索,全文查询的关键字在搜索时,必须分析产生(搜索)与索引时相同的术语。

Type(类型)
​ Type是相当于关系数据库中的“表”。每种类型都有一列字段,用来定义文档的类型。映射定义了对在文档中的每个字段如何进行分析。
QueryBuilders构建查询

QueryBuilder如:MatchAllQueryBuilder.class,FuzzyQueryBuilder.class等,它们是具体的查询实现类,它们是QueryBuilders抽象类的子类并实现了BoostableQueryBuilder<>接口。

而QueryBuilders的主要作用就是提供了静态方法去创建一个子类的对象,方便QueryBuilder的构建与管理。

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
匹配所有文档的查询。
matchAllQuery()
为提供的字段名和文本创建类型为“BOOLEAN”的匹配查询。(解释过来就是单个匹配,可以模糊匹配)
matchQuery(String name, Object text) //name 字段值 ,text 查询文本(不支持通配符)
为提供的字段名和文本创建一个通用查询。
commonTermsQuery(String name, Object text)
为提供的字段名和文本创建类型为“BOOLEAN”的匹配查询。
multiMatchQuery(Object text, String... fieldNames)
为提供的字段名和文本创建一个文本查询,并输入“短句”。
matchPhraseQuery(String name, Object text)
为提供的字段名和文本创建一个与类型“PHRASE_PREFIX”匹配的查询。
matchPhrasePrefixQuery(String name, Object text)
匹配包含术语的文档的查询。
termQuery(String name, Object value)
使用模糊查询匹配文档的查询
fuzzyQuery(String name, Object value)
与包含指定前缀的术语的文档相匹配的查询。
prefixQuery(String name, String prefix)
在一定范围内匹配文档的查询。
rangeQuery(String name)
实现通配符搜索查询。支持的通配符是*,它匹配任何字符序列(包括空字符),而?它匹配任何单个字符。注意,这个查询可能很慢,因为它需要遍历许多项。为了防止异常缓慢的通配符查询,通配符项不应该以一个通配符*或?开头。

wildcardQuery(String name, String query) //query 通配符查询字符串
将包含术语的文档与指定的正则表达式匹配的查询
regexpQuery(String name, String regexp) //regexp的正则表达式
解析查询字符串并运行它的查询。有两种模式。第一,当没有字段添加(使用QueryStringQueryBuilder.field(字符串),将运行查询一次,非前缀字段将使用QueryStringQueryBuilder.defaultField(字符串)。第二,当一个或多个字段添加(使用QueryStringQueryBuilder.field(String)),将运行提供的解析查询字段,并结合使用DisMax或普通的布尔查询(参见QueryStringQueryBuilder.useDisMax(布尔))。

queryStringQuery(String queryString)
类似于query_string查询的查询,但不会为任何奇怪的字符串语法抛出异常。
simpleQueryStringQuery(String queryString)
可以使用BoostingQuery类来有效地降级与给定查询匹配的结果。
boostingQuery()
匹配与其他查询的布尔组合匹配的文档的查询
boolQuery()
创建一个可用于实现MultiTermQueryBuilder的子查询的SpanQueryBuilder。
spanMultiTermQueryBuilder(MultiTermQueryBuilder multiTermQueryBuilder)
允许定义自定义得分函数的查询。
functionScoreQuery(QueryBuilder queryBuilder, ScoreFunctionBuilder function)
更像这样的查询,查找“like”提供的文档,例如提供的MoreLikeThisQueryBuilder.likeText(String),它是针对查询所构造的字段进行检查的

moreLikeThisQuery(String... fields)
构造一个新的非计分子查询,包含子类型和要在子文档上运行的查询。这个查询的结果是这些子文档匹配的父文档。
hasChildQuery(String type, QueryBuilder query)
构造一个新的非评分父查询,父类型和在父文档上运行的查询。这个查询的结果是父文档匹配的子文档。
hasParentQuery(String type, QueryBuilder query)
基于对其中任何一个项进行匹配的若干项的字段文件
termsQuery(String name, String... values)
一个查询构建器,它允许构建给定JSON字符串或二进制数据作为输入的查询。当您希望使用Java Builder API,但仍然需要将JSON查询字符串与其他查询构建器结合时,这是非常有用的。

wrapperQuery(String source)
这些都是官方提供的注释,有些晦涩难懂,还有很多,我这里不一一列举。

我们使用上述静态方法只是为了方便创建QueryBuilder系列对象。

条件查询

调用QueryBuilders 的静态方法创建完具体的QueryBuilder对象之后,传入.withQuery(QueryBuilder)方法就可以实现的自定义查询。

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
1.完全匹配
MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("字段名", "查询文本");
2.短语匹配
比如你要查询短语Love You,当你使用QueryBuilders.matchQuery("name", "Love You")时,可能会查询到Love And You这样分开的结果,这时候我们指定为短语进行查询:

QueryBuilder queryBuilder = QueryBuilders.matchPhraseQuery("name", "Love You");
3.模糊匹配
MoreLikeThisQueryBuilder queryBuilder = QueryBuilders
.moreLikeThisQuery("name")// 要匹配的字段, 不填默认_all
.like("西游")// 匹配的文本
.minTermFreq(1);// 文本最少出现的次数(默认是2,我们设为1)
我们再从源码去看其它方法

添加一些文本以查找“类似”的文档
addLikeText(String... likeTexts)
查找类似文档
like(Item... likeItems)
设置不从其中选择(比如我们调用.like("西游").unlike("西游记")这样会导致啥也查不到)
unlike(String... unlikeTexts)
添加一些文本以查找与此不同的文档
addUnlikeText(String... unlikeTexts)
设置将包含在任何生成查询中的查询条件的最大数量。默认25
maxQueryTerms(int maxQueryTerms)
设置单词被忽略的频率,默认5,小于将不会被发现
minDocFreq(int minDocFreq)
设置单词仍然出现的最大频率。单词出现更多的文档将被忽略。默认为无限
maxDocFreq(int maxDocFreq)
设置将被忽略的单词的最小单词长度,默认0
minWordLength(int minWordLength)
设置将被忽略的单词的最大单词长度,默认无限
maxWordLength(int maxWordLength)
设置停止词,匹配时会忽略停止词
stopWords(String... stopWords)
设置词语权重,默认是1
boostTerms(float boostTerms)
查询权重(默认1)
boost(float boost)
设置不从其中选择术语的文本(文档Item)
ignoreLike(String... likeText)
4.组合查询
QueryBuilder queryBuilder = QueryBuilders.boolQuery()
.must(QueryBuilders.matchQuery("name", "西游")) //AND
.must(QueryBuilders.wildcardQuery("name", "西游?")) //Not
.should(QueryBuilders.matchQuery("name", "西游记")); //Or
5.包裹查询
高于设定分数, 不计算相关性

QueryBuilder queryBuilder = QueryBuilders
.constantScoreQuery(QueryBuilders.matchQuery("name", "西游记"))
.boost(2.0f);
6.范围查询
查询id值在20-50之间的

QueryBuilder queryBuilder = QueryBuilders.rangeQuery("id")
.from("20")
.to("50")
.includeLower(true) // 包含上界
.includeUpper(true); // 包含下届
7.通配符查询
通配符查询, 支持 * 匹配任何字符序列, 包括空

避免* 开始, 会检索大量内容造成效率缓慢

单个字符用?

QueryBuilder queryBuilder = QueryBuilders.wildcardQuery("user", "ki*hy");
8.过滤查询
QueryBuilders.constantScoreQuery(FilterBuilders.termQuery("name", "kimchy")).boost(2.0f);

ES原生查询操作总结

发表于 2019-03-07 | 分类于 Elasticsearch

ES的查询有query、URL两种方式,一种是通过REST请求URI发送检索参数,另一种是通过REST请求体发送检索参数。

1、URL方式

1.1、语法

1
curl [ -s][ -g][ -X<REST Verb>][ -H 'Content-Type: application/json'] '<Node>:<Port>/<Index>[/Type][/ID]/_search?pretty&q=<search string>'

  -s 不输出查询的时间那些东西
  -g 做转义用  
  :REST风格的语法谓词,GET/POST/PUT
  :节点ip,默认使用localhost
  :节点端口号,默认80,ES默认使用9200
  :索引名,支持通配符,power_json*
  :索引类型,由于一个index只有一个type,可不输入
  :操作对象的ID号,可不输入
  q :前面加&,后跟查询语句

1.2、常用参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
q :查询字符串
sort :排序执行。 fieldName:asc/fieldName:desc。fieldName可以是文档中的实际字段,也可以是特殊_score名称,表示基于分数的排序。可以有几个sort参数 。
from :从命中的索引开始返回。默认为0。
size :表示要返回的命中数。默认值为10。
_source_include :查询包含某些source字段的文档。
_source_exclude :查询不包含某些source字段的文档。
timeout :搜索超时,将搜索请求限制在指定的时间值内执行,默认为无超时。
default_field :默认为index.query.default_field,即未指定字段前缀时返回所有字段,索引设置为*
default_operator :默认查询运算符,未指定时默认为OR。
analyzer :用于分析查询字符串的分析器名称。
_source :设置为false禁用_source字段检索。
analyze_wildcard :是否应分析通配符和前缀查询,默认为false
status:active :where the status field contains active ——(status相当于fieldname,active相当于值——>TESTID:39232032303039,由于=被用在了前面“q=”,所以这里用“:”代替了“=”)
title:(quick OR brown) ——where the title field contains quick or brown. If you omit the OR operator the default operator will be used
author:"John Smith"——where the author field contains the exact phrase "john smith"
_exists_:title——where the field title has any non-null value
date:[2012-01-01 TO 2012-12-31]——All days in 2012
count:[10 TO *]——Numbers from 10 upwards
count:>=10——Numbers from 10 upwards

1.3、示例

** 基本操作 **

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
#创建索引
PUT /mydemo_01
{
"settings" : {
"index" : {
"number_of_shards" : 3,
"number_of_replicas" : 2
}
}
}
#索引的分片数为3,备份数为2

GET /mydemo_01

#创建文档 /索引/类型/ID 如果没有id会自动生成id
PUT /mydemo_01/user/3
{
"name":"hu",
"idcard":"2430",
"age":24
}

GET /mydemo_01/user/1

{
"took": 1, #耗时
"timed_out": false,#是否超时
"_shards": {
"total": 3, #查询了多少分片
"successful": 3,
"skipped": 0,
"failed": 0
},
"hits": {#命中结果
"total": 1,#命中数
"max_score": 0.2876821,#最高得分
"hits": [
{
"_index": "mydemo_01",
"_type": "_doc",
"_id": "3",
"_score": 0.2876821,
"_source": {
"name": "hu",
"idcard": "2430",
"age": 24
}
}
]
}
}

#删除文档
DELETE /mydemo_01/user/2?pretty

#删除索引
DELETE /mydemo_01

#查数据
GET /mydemo_01/_search

GET /mydemo_01?pretty

** 查看节点情况 **

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#查看node状态
GET /_cat/nodes?v

#查看集群健康状况  s=sort排序
GET /_cat/health?v
#查看索引,并以索引名字排序
GET /_cat/indices?v&s=index

#只看mydemo_01索引,并以索引名字排序
GET /_cat/indices/mydemo_01*?v&s=index

#查询索引的具体记录信息 #pretty表示返回漂亮打印的JSON结果
GET /mydemo_01*?pretty

GET /_cat/indices?v&s=index' //查看索引,并以索引名字排序(s=sort)

#查看所有索引
http://localhost:9200/_cat/indices?v

#disk.percent显示占用的硬盘空间
GET /_cat/allocation?v&s=node
  • Green - everything is good (cluster is fully functional),即最佳状态
  • Yellow - all data is available but some replicas are not yet allocated (cluster is fully functional),即数据和集群可用,但是集群的备份有的是坏的
  • Red - some data is not available for whatever reason (cluster is partially functional),即数据和集群都不可用

结果解释:

1
2
3
4
heap.percent:堆内存,1/2最大内存-1和31之间取较小的值,高的话就是ES集群负担比较重,解决:关闭一些索引(阈值差不多可以定在80左右)后会释放一部分heap.percent,但不会释放disk.percent
ram.percent:一直挺高,物理内存的使用情况,Lucene会将闲置的内存都占用做cache,如果有应用使用内存时,cache会被释放出来
cpu:一般不会很高,5以下吧,如果有很多query的话会高,如果一直高释放不掉可能查询语句有问题
node.role:【mdi】master data i查询接口(可否在节点上查询)

** 数据查询 **

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
GET /mydemo_01*/_search?pretty&q=_exists_:name #查指定字段是否存在
GET /mydemo_01*/_search?pretty&q=name:hu&size=3 #查指定字段值显示3个
#如果在浏览器中可以直接http://127.0.0.1:9200/mydemo_01*/_search?pretty&q=name:hu&size=3

GET /mydemo_01*/_search?pretty&q=name:hu&from=2&size=3 #从第3个开始只显示3个
GET /mydemo_01*/_search?pretty&sort=id:desc # 排序desc降序,默认为升序
GET /mydemo_01*/_search?pretty&analyze_wildcard&q=idcard:12 #模糊查询
GET /mydemo_01*/_search?pretty&q=age:<22 #比较大小
GET /mydemo_01*/_search?pretty&q=age[10 TO 30] #范围
GET /mydemo_01*/_search?pretty&_source=false #是否显示
GET /mydemo_01*/_search?pretty&_source_includes=name,idcard #查询包含的字段
GET /mydemo_01*/_search?pretty&q=(name:hu AND idcard:243) #组合查询

#查询多个索引(index),多个类型(type)
GET /mydemo_01,demo/_search?q=name:hu

#还可以指定类型参数,例如
GET /mydemo_01/user,fdf/_search?q=name:hu
#由于类型在未来的版本中将被移除,所以这种用法也不那么重要了。

#或者要在全部的索引中查询
GET /_all/_search?q=name:hu

或者

#多索引(搜索存在于所有索引或一些特定索引中的文档)
GET /_search?q=name:huangxiaoming

#多类型(搜索所有类型或某种指定类型的索引中搜索所有文档)
GET /mydemo_01/_search?q=age:100


#关闭和打开索引

POST /mydemo_01/_close
POST /mydemo_01/_open

#删除符合条件的记录

POST /mydemo_01*/_delete_by_query?pretty&q=name:zhangsanfeng

#查询多个Id
GET /mydemo_01/user/_mget
{
"ids":[1,3,2,4]
}

#为了保持_version与外部版本控制的数值一致,使用version_type=external检查数据当前的version值是否小于请求中的version值
#查询指定版本数据

PUT /mydemo_01/user/3?version=2
{
"name":"huv2",
"idcard":"2430",
"age":24
}

GET /mydemo_01/user/3?version=2

** 批量操作 **
Elasticsearch还可以使用_bulk API批量执行上述任何操作

1
2
3
4
5
6
7
8
9
10
11
12
13
#同时更新两个文档
POST /mydemo_01/user/_bulk?pretty
{"index":{"_id":"1"}}
{"name": "xiaoming1" }
{"index":{"_id":"3"}}
{"name": "xiaoming2" }

GET /mydemo_01/_search
#更新第一个文档(ID为1),删除第二个文档(ID为2)
POST /mydemo_01/user/_bulk?pretty
{"update":{"_id":"1"}}
{"doc": { "name": "chas" } }
{"delete":{"_id":"2"}}

2、结构化查询(DSL)

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
GET /mydemo_01/user/_search
#精确查询
#Term查询不会对字段进行分词查询,会采用精确匹配。
GET /mydemo_01/user/_search
{
"query": {
"term": {
"name": "宋小明"
}
}
}
#term是代表完全匹配,即不进行分词器分析,文档中必须包含整个搜索的词汇

#模糊查询
#Match会根据该字段的分词器,进行分词查询。
GET /mydemo_01/user/_search
{
"from": 0,
"size": 2,
"query": {
"match": {
"name": "明"
}
}
}

#filter过滤查询
GET /mydemo_01/user/_search
{
"query": {
"bool": {
"must": [
{
"match_all": {

}
}
],
"filter": {
"range": {
"age": {
"gt": 10,
"lte": 20
}
}
}
}
},
"from": 0,
"size": 10,
"_source": [
"name",
"age"
]
}

ES在docker中部署

发表于 2019-03-05 | 分类于 Elasticsearch
部署Elasticsearch

拉取部署Elasticsearch:

1
docker pull docker.elastic.co/elasticsearch/elasticsearch:7.3.0

运行:

1
2
PS C:\Users\hua> docker run -d --name Elasticsearch-7.3 -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:7.3.0
0c93badc5f5f3474e79ebd5711e4173a8492c5cc66a39540be44db0f5a5e0d0d

配置跨域:

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
PS C:\Users\hua> docker exec -it Elasticsearch-7.3 /bin/bash
[root@0c93badc5f5f elasticsearch]# ls
LICENSE.txt NOTICE.txt README.textile bin config data jdk lib logs modules plugins

[root@0c93badc5f5f elasticsearch]# cd config/
[root@0c93badc5f5f config]# ll
total 28
-rw-rw---- 1 elasticsearch root 199 Aug 14 05:42 elasticsearch.keystore
-rw-r--r-- 1 elasticsearch root 53 Jul 24 18:32 elasticsearch.yml
-rw-rw---- 1 elasticsearch root 3524 Jul 24 18:26 jvm.options
-rw-r--r-- 1 elasticsearch root 7543 Jul 24 18:32 log4j2.properties
-rw-rw---- 1 elasticsearch root 473 Jul 24 18:32 role_mapping.yml
-rw-rw---- 1 elasticsearch root 197 Jul 24 18:32 roles.yml
-rw-rw---- 1 elasticsearch root 0 Jul 24 18:32 users
-rw-rw---- 1 elasticsearch root 0 Jul 24 18:32 users_roles
[root@0c93badc5f5f config]# vi elasticsearch.yml

# 加入跨域配置
http.cors.enabled: true
http.cors.allow-origin: "*"

#保存退出
[root@0c93badc5f5f config]# exit
exit
#重启
PS C:\Users\hua> docker restart Elasticsearch-7.3
Elasticsearch-7.3

浏览,http://127.0.0.1:9200/ :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"name" : "0c93badc5f5f",
"cluster_name" : "docker-cluster",
"cluster_uuid" : "C1imvsbpT36iC98Yd2nEyQ",
"version" : {
"number" : "7.3.0",
"build_flavor" : "default",
"build_type" : "docker",
"build_hash" : "de777fa",
"build_date" : "2019-07-24T18:30:11.767338Z",
"build_snapshot" : false,
"lucene_version" : "8.1.0",
"minimum_wire_compatibility_version" : "6.8.0",
"minimum_index_compatibility_version" : "6.0.0-beta1"
},
"tagline" : "You Know, for Search"
}
部署kibana

拉取:

1
docker pull docker.elastic.co/kibana/kibana:7.3.0

部署:

1
2
3
4
5
6
7
8
9
10
11
12
13
PS C:\Users\hua> docker run --name kibana-7.3 -d -p 5601:5601 --link Elasticsearch-7.3 -e "ELASTICSEARCH_URL=http://127.0.0.1:9200" 8bcee4a4f79d
071ee706a4a22b548c409d3ee06e1890a7ed5239a56a49a85449a140b6431745
PS C:\Users\hua> docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3443c83c30c4 8bcee4a4f79d "/usr/local/bin/kiba…" 32 seconds ago Up 30 seconds 0.0.0.0:5601->5601/tcp kibana-7.3
0c93badc5f5f docker.elastic.co/elasticsearch/elasticsearch:7.3.0 "/usr/local/bin/dock…" 19 minutes ago Up 8 minutes 0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp Elasticsearch-7.3

#进入容器配置
PS C:\Users\hua> docker exec -it c69996736e3b /bin/bash
bash-4.2$ vi config/kibana.yml

编辑 config\kibana.yml 配置文件,将配置项 elasticsearch.hosts 的地址改成 Elasticsearch 地址。不要使用 location、127.0.0.1 或者容器名:端口,这样是无法访问的,会造成 Kibana 的 [Kibana server is not ready yet] 错误。
启动成功后访问 ip:5601 看到 Kibana 界面即表示安装成功。

浏览: http://192.168.6.238:5601

部署Head
1
2
3
PS C:\Users\hua> docker pull mobz/elasticsearch-head:5
PS C:\Users\hua> docker run -di --name myhead -p 9100:9100 b19a5c98e43b
85fc9db69f43cea485b0a61004a296469727847f4563260cf09a4561c89c8bea

Sense

Sense是一款Kibana应用。要启动并运行,首先需要下载Kibana并按照此处的说明进行安装。您将需要Kibana 4.2或更高版本。安装Kibana后,您可以从Kibana文件夹中运行Sense运行以下命令:

$./bin/kibana plugin –install elastic/sense

这将下载并安装最新版本的Sense。

你现在需要开始Kibana:

$ ./ bin / kibana
您现在应该可以使用http:// localhost:5601 / app / sense上的Web浏览器访问Sense (如果您在Kibana中以不同方式配置它们,则替换主机和端口)。

这里写图片描述

可以看到在内存为2G的主机上,Elasticsearch的运行内存为 -Xms256m -Xmx1g

内存优化

查看内存 ps -ef|grep elasticsearch
1、设置内存:指定ES_HEAP_SIZE环境变量。服务进程在启动时候会读取这个变量,并相应的设置堆的大小。设置命令:export ES_HEAP_SIZE=1g

2、可以在启动程序时限制内存大小,并不是每个情况都可以成功的,命令行如下 ./elasticsearch -Xms256m -Xmx256m -d

备注: 设置时Xmx和Xms相同,目的是为了能够在java垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小而浪费资源,可以减轻伸缩堆大小带来的压力。 一般来说设置ES_HEAP_SIZE环境变量,比直接写-Xmx10g -Xms10g更好一点。

配置head插件

发表于 2019-03-04 | 分类于 Elasticsearch

之前都安装好ES,接下来直接安装插件,安装head插件,必然是要先安装好nodejs和grunt才行。
(1)安装nodejs
(2)安装grunt:nodejs的目录下,输入指令:npm install -g grunt-cli
(3)配置head
进入https://github.com/mobz/elasticsearch-head地址,下载zip,解压head直接放在了D:\elasticsearch\elasticsearch-head-master
在head/Gruntfile.js里,添加一行 hostname: ‘*’

在head/_site/app.js,把localhost修改成你es的服务器地址,如:this.base_uri = this.config.base_uri || this.prefs.get(“app-base_uri”) || “https://000:9200";非必须

修改elasticsearch:D:\elasticsearch\elasticsearch-6.3.1\config\elasticsearch.yml里

文件的最后添加
http.cors.enabled: true
http.cors.allow-origin: “*”

修改配置(可略)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#集群的名字 
cluster.name: es_cmazxiaoma_cluster
#节点名字
node.name: node-1
#数据存储目录(多个路径)
path.data: /home/elasticsearch/data
#日志目录
path.logs: /home/elasticsearch/logs
#本机的ip地址
network.host: 192.168.12.6
#设置集群中master节点的初始列表,可以通过这些节点来自动发现新加入集群的节点
discovery.zen.ping.unicast.hosts: ["192.168.12.6"]
#设置节点间tcp端口(集群),默认9300
transport.tcp.port: 9300
#监听端口(默认)
http.port: 9200
#增加参数,使head插件可以访问es
http.cors.enabled: true
http.cors.allow-origin: "*"

保存完毕之后,到bin目录下,双击“elasticsearch.bat”启动。

然后在cmd命令行里,转到head目录下,输入 npm install

会很慢,修改npm镜像:npm install -g cnpm –registry=https://registry.npm.taobao.org

cnpm install

我初次运行报错了,但是重新输入指令,再运行下就好了。

然后还是head目录下,输入grunt server 启动nodejs,出现下面的提示,就启动成功。

https://127.0.0.1:9100,正确浏览表示配置成功!

上一页1…8910…25下一页
初晨

初晨

永远不要说你知道本质,更别说真相了。

249 日志
46 分类
109 标签
近期文章
  • WebSocket、Socket、TCP、HTTP区别
  • Springboot项目的接口防刷
  • 深入理解Volatile关键字及其实现原理
  • 使用vscode搭建个人笔记环境
  • HBase介绍安装与操作
© 2018 — 2020 Copyright
由 Hexo 强力驱动
|
主题 — NexT.Gemini v5.1.4