ElasticSearch学习笔记

一、安装

mac:
从官网(https://www.elastic.co/cn/elasticsearch ) 下载tar.gz包,解压即可

二、命令

2.1 启动:

bin/elasticsearch

本机查看:http://localhost:9200/ ,9200是默认端口

2.2 插件:

2.2.1 查看

bin/elasticsearch-plugins list

web页面查看

http://localhost:9200/_cat/plugins

2.2.2 安装插件

bin/elasticsearch-plugins install plugins_name

三、数据

3.1 搜索 (以索引名“users”为例)

query 和 filter 区别:
  1. query会进行相关性算分,filter不会算分并且会利用缓存来提高搜索性能。
  2. filter缓存:如果一个非评分查询在最近的 256 次查询中被使用过(次数取决于查询类型),那么这个查询就会作为缓存的候选。但是,并不是所有的片段都能保证缓存 bitset 。只有那些文档数量超过 10,000 (或超过总文档数量的 3% )才会缓存 bitset 。因为小的片段可以很快的进行搜索和合并,这里缓存的意义不大。剔除规则是基于 LRU 的:一旦缓存满了,最近最少使用的过滤器会被剔除。
  3. Filter如何实现缓存的?bitset?roaring bitmap:https://www.elastic.co/cn/blog/frame-of-reference-and-roaring-bitmaps
  4. termQuery不会对关键字分词;matchQuery会分词然后根据分好的term查询,如果字段类型是keyword,matchQuery会自动转为termQuery。

3.1.1 查看索引的信息(setting/mapping):

GET users
查看索引列表
GET _cat/indices
查看索引分片的文档数是否一致
GET /_cat/shards
索引可以设置别名 _aliases

别名有两种方式管理: _alias 用于单个操作, _aliases 用于执行多个原子级操作。别名可以实现零停机切换索引。

3.1.2 查看索引下所有数据,from/size分页

GET /users/_search
{
  "query": {
    "match_all" : {}
  },
  "from": 0,
  "size": 2
}

3.1.3 查看指定id=1的文档信息

GET users/_doc/1

3.1.4 查询id=123,term完全匹配

GET /users/_search
{
  "query": {
    "term": {
      "id": "123"
    }
  }
}

多字段属性,每个text会加个keyword,即完全匹配,查询:

GET /users/_search
{
  "query": {
    "term": {
      "name.keyword": "zhang li"
    }
  }
}

通配符查询wildcard

{
  "query": {
    "bool": {
      "must": [{
        "exists":{"field": "task"}
      },
      {
        "wildcard":{
          "task.action":"*reindex"
        }
      }]
    }
  }
}

3.1.5 Request Body Search

#
GET /users,movies/_search
{
  "_source":["order_date","cateory.keyword"] ,#默认查的就是source下文档,加上之后只查出这部分字段信息,过滤掉了其他字段
  "query": {
    "match_all" : {}
  },
  "from": 0,
  "size": 2,
  "profile":true,
  "sort":[{
      "order_date":"desc"
  }]
}

#painless脚本查询,可以做逻辑处理。结果中多一个字段new_field,值为订单时间+'hello'字符串
GET /users,movies/_search
{
  "script_field":{
      "new_field":{
          "script":{
              "lang":"painless",
              "source":"doc['order_date'].value+'hello'"
          }
      }
  },
  "query": {
    "match_all" : {}
  },
  "from": 0,
  "size": 2,
  "profile":true
}

#match query, 查 Last OR Christam
GET /users,movies/_doc/_search
{
    "query":{
        "match":{
            "comment":"Last Christam"
        }
    }
}

# match query, 查 Last AND Christam
GET /users,movies/_doc/_search
{
    "query":{
        "match":{
            "comment":{
                "query":"Last Christam",
                "operator":"AND" 
            }
        }
    }
}

# match phrase query, slop=1代表两个关键字间可以有一个其他字符介入,比如“Last en Christam”
GET /users,movies/_doc/_search
{
    "query":{
        "match_phrase":{
            "comment":{
                "query":"Last Christam",
                "slop":1
            }
        }
    }
}



#priducts的数据结构
PUT products
{
  "mappings": {
    "properties": {
      "id":{
        "type": "long"
      },
      "name":{
        "type": "text"
      },
      "price":{
        "type": "float"
      },
      "date":{
        "type": "date"
      }
    }
  }
}

# constant_score跳过打分,提高性能
GET products/_search
{
    "query":{
      "constant_score":{
        "filter":{
          "term":{
            "id":1
          }
        }
      }
    }
}

#区间查询 range 数字类型
GET /products/_search
{
    "query":{
      "constant_score":{
        "filter":{
            "range":{
                "price":{
                    "gte":20,
                    "lte":100
                }
            }
        }
      }
    }
}

#explain 打分方式
#区间查询 range 日期类型 now-1y : 一年前
#y-年 M-月 w-周 d-天 H/h-时 m-分 s-秒
GET /products/_search
{
    "explain":true,
    "query":{
      "constant_score":{
        "filter":{
            "range":{
                "date":{
                    "gte":"now-1y"
                }
            }
        }
      }
    }
}

3.1.6 boosting打分控制相关度

explain参数返回es如何解析,url使用方式1:query:GET /_validate/query?explain
explain=true 查看打分方式

# 使用boost字段来控制title、content字段中的关键字权重。boost > 1权重增加, boost < 1时权重降低,< 0 时贡献负分
GET /products/_search
{
    "explain":true,
    "query":{
        "bool":{
            "should":[
            {
                "match":{
                    "title":{
                        "query":"ipad",
                        "boost":1.1
                    }
                }
            },
            {
                "mathc":{
                    "content":{
                        "query":"iphone",
                        "boost":2
                    }
                }
            }
            ]
        }
    }
}

#boosting query : name中有iphone加分,有like关键字减分。可以将不关心数据排在最后,而不是不查询(must not是不查询)
GET /products/_search
{
    "explain":true,
    "query":{
        "boosting":{
            "positive":{
                "term":{
                    "name":"iphone"
                }
            },
            "negative":{
                "term":{
                    "name":"like"
                }
            },
            "negative_boost":0.2
        }
    }
}

3.1.6 query string 和 simple query string

# query string,AND表示并且,defaule_field即urlsearch中的df。也可以用fields字段指定多个,也可以在query中分组:"query":"(Ruan AND Yiming) OR (Elasticsearch AND Java)"
POST users/_search
{
    "query":{
        "query_string":{
            "defaule_field":"name",
            "query":"Last AND Christam" 
        }
    }
}

#simple query string,不支持"query":"Ruan -Yiming"条件中的AND,只能用 + - |,复杂的操作符禁止了
POST users/_search
{
    "query":{
        "simple_query_string":{
            "query":"Ruan -Yiming",
            "fields":["name"],
            "default_operator":"AND"
            
        }
    }
}

3.1.7 boolquery:must、must not、should

GET users/_search
{
  "query": {
    "bool": {
      "must": [{
        "exists":{"field": "message"}
      }],
      "should": [
        {"term": {"message":"trying out elasticsearch"}}
      ]
    }
  }
}

3.1.8 Disjunction max query

#将title、content两个字段中匹配分数最高的分数返回,来排序。should的话会将两个字段匹配分数相加求平均值,可能影响查询结果。
#tie_breaker会找到最佳评分(不止包括最好评分,还有其他评分)的结果,将其他结果分数乘以tie_breaker来包子最佳匹配的文档靠前
GET users/_search
{
  "query": {
    "dis_max": {
      "queries": [{
        "match":{"title": "apple company"},
        "match":{"content": "apple company"}
      }],
      "tie_breaker":0.2
    }
  }
}

3.1.9 Mutipart query

单字符串多字段查询。type:有多种。query:关键字。

#type 默认是best_fields:最佳匹配,最高分值返回。
GET /products/_search
{
    "query":{
        "multi_match":{
            "query":"telephone pad",
            "operator": "and", 
            "type":"best_fields",
            "fields":["name","content"]
        }
    }
}
指定子字段来提高查询匹配度

englist分词词来提高匹配度分值,子字段std标准分词查询,然后使用muti query同时查两个字段。cross_fields:跨字段,查询的词必须都出现在搜索的字段中。

PUT titles
{
    "mappings":{
        "properties":{
            "title":{
                "type":"text",
                "analyzer":"englist",
                "fields":["std":{
                    "type":"text",
                    "analyzer":"standard"
                }]
            }
        }
    }
}

#most_fields 将两个字段的分值叠加
GET /titles/_search
{
    "query":{
        "multi_match":{
            "query":"barking dog",
            "type":"most_fields",
            "fields":["title","title.std"]
        }
    }
}

#还可以增加title字段的权重title^10
GET /titles/_search
{
    "query":{
        "multi_match":{
            "query":"barking dog",
            "type":"most_fields",
            "fields":["title^10","title.std"]
        }
    }
}

Function Score Query

PUT blogs/_doc/1
{
    "title":"About popularity",
    "content":"In this post we will talk about...",
    "votes":6
}

#新的算分=老的算分*votes(投票数字段值)
#加上modifier后 新的算分=老的算分*log(votes+1)
#加上factor后 新的算分=老的算分*log(votes*factor+1)
#modifier和factor为了控制分数的曲线
GET blogs/_search
{
    "query":{
        "function_score":{
            "query":{
                "multi_match":{
                    "query":"popularity",
                    "fields":["title","content"]
                }
            },
            "field_value_factor":{
                "field":"votes",
                "modifier":"log1p",
                "factor":0.1
            }
        }
    }
}
Score query可以采用Boost Mode和Max Boost方式:
  1. Boost Mode
    • Multiply 算分与函数值的乘积 默认
    • Sum 算分与函数的和
    • Min/Max 算分与函数值取最大/最小值
    • Replace 使用函数值取代算分
  2. Max Boost可以将算分控制在一个最大值

Score query可以应用在广告的展示?

random_score一致性随机函数,只要seed值不变则排序不变。不同的seed造成排序会不同。可以每个人设置固定的seed,排序就不会变

GET blogs/_search
{
    "query":{
        "function_score":{
            "random_score":{
                "seed":91111
            }
        }
    }
}
rescore

rescore重新评分可以优化match查询的评分结果。重新评分阶段支持一个代价更高的评分算法— 比如 phrase 查询— 只是为了从每个分片中获得前 K 个结果。 然后会根据它们的最新评分 重新排序。

单词对

单词对shingle可用于优化相关性算分

Suggest API

搜索类似的功能,将输入的文本分解为Token,然后在索引字典中查找相似的term返回(返回的是相似的term并不是文档)。

4种类别Suggest API Term、Phrase、Complete、Context
  1. Term & Phrase Suggester
  2. Complete & Context Suggester
Term Suggester
POST articles/_doc/1
{
  "title":"1",
  "body":"lucene is perfect!"
}


#Term (lucen时错别字,正常是lucene)当正常查询不到时,走suggest,找到类似的值,从body中查询。
POST articles/_search
{
  "query": {
    "match": {
      "body": "lucen"
    }
  },
  "suggest": {
    "my-suggestion": {
      "text": "lucen",
      "term": {
        "suggest_mode":"missing",
        "field": "body"
      }
    }
  }
}

#返回。返回的是相似的term或者叫建议搜寻的term关键字,并非将文档返回
{
  "took" : 9,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 0,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "suggest" : {
    "term-suggestion" : [
      {
        "text" : "lucen",
        "offset" : 0,
        "length" : 5,
        "options" : [
          {
            "text" : "lucene",
            "score" : 0.8,
            "freq" : 1
          }
        ]
      }
    ]
  }
}

#Phrase suggeter,参数不同控制

自动补全Completion Suggester

自动补全用户每输入一个字符都要及时发送一个请求到后端查找匹配项,对性能要求较苛刻。es使用了不同的数据结构,不是倒排索引:将Analyze的数据编码为FST(Finite State Transducer)和索引一起存放,FST会被ES整个加载进内存,速度很快。FST只能前缀查找。

PUT articles
{
    "mappings":{
        "properties":{
            "title":{
                "type":"completion"
            }
        }
    }
}

POST articles/_doc/1
{
  "title":"expection",
  "body":"lucene is perfect!"
}

POST articles/_search?pretty
{
    "size":0,
    "suggest":{
        "article-suggester":{
            "prefix":"e",
            "completion":{
                "filed":"title"
            }
        }
    }
}

#返回建议的term(“exception”)
{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 0,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "suggest" : {
    "my-suggester-2" : [
      {
        "text" : "e",
        "offset" : 0,
        "length" : 1,
        "options" : [
          {
            "text" : "expection",
            "_index" : "articles",
            "_type" : "_doc",
            "_id" : "1",
            "_score" : 1.0,
            "_source" : {
              "title" : "expection",
              "body" : "lucene is perfect!"
            }
          }
        ]
      }
    ]
  }
}

3.1.8 UrlSearch的使用

GET /movies/_search?q=2012&df=title&sort=year:desc&from=0&size=10&imeout=1s
{
    "profile":true
}

q:指定查询语句,使用Query string syntax
df:默认字段,不使用会对所有字段查询
sort:排序,from、size:分页
profile:可以查看查询如何被执行的,执行计划?

#泛查询所有字段
GET /movies/_search?q=2012
{
    "profile":true
}


#指定字段,代替df
GET /movies/_search?q=title:2012
{
    "profile":true
}


#ParaseQuery查询,关键字加引号:Beautiful 和 Mind同时出现并且按顺序,挨着
GET /movies/_search?q=title:"Beautiful Mind"
{
    "profile":true
}


#分组,Bool查询,关键字加括号:title包含Beautiful 或 包含Mind。默认OR关系

GET /movies/_search?q=title:(Beautiful Mind)
{
    "profile":true
}

#布尔查询,还可以使用AND、OR、NOT 或者 &&、||、!
必须大写
title:(Beautiful NOT Mind)

分组:
+ 表示 Must
- 表示 Must_Not
title:(+Beautiful -Mind) : 等同于title:(Beautiful NOT Mind)

# 范围查询。[]闭区间、{}开区间,可行?
GET /movies/_search?q=year:>=1980
{
    "profile":true
}

#通配符查询,title含有b开头的关键字
GET /movies/_search?q=title:b*
{
    "profile":true
}

#模糊匹配、近似匹配
GET /movies/_search?q=title:beautifl~1 #一个字符差异的相近查询
{
    "profile":true
}

#模糊匹配,能搜出两词相隔一个和两个单词的结果,不必须挨着
GET /movies/_search?q=title:"Beautiful Mind"~2
{
    "profile":true
}

3.2 文档操作

3.2.1 创建文档

#生成文档,es自动分配id
POST users/_doc
{
  "user":"Mike",
  "post_date":"2021-05-27",
  "message":"trying out kibana"
}

# 自己指定id(1)创建文档,重复提交会报错,根据版本号判定冲突
PUT users/_doc/1?type=created
{
  "user":"Jack",
  "post_date":"2021-05-27",
  "message":"trying out kibana"
}
#如果不含?type=created,每次执行都会版本号加一


3.2.2 修改文档

#原来文档增加字段,修改字段
POST users/_update/1
{
  "doc":{
    "message" : "trying out elasticsearch",
    "age":18
  }
}

#  update by query,scripts是使用painless脚本设置最新值
POST users/_update_by_query
{
  "script": {
    "source": "ctx._source.post_date='2021-05-31'",
    "lang": "painless"
  },
  "query":{
    "match":{
      "user":"Queen"
    }
  }
}

3.2.3 删除文档

POST users/_delete_by_query
{
  
  "query": {
    "match_all" : {}
  }
}

3.2.4 BulkApi 一次请求中操作多个索引、文档

其中一个语句执行错误不会影响其他执行

#bulk
POST _bulk
{"index":{"_index":"test","_id":"1"}}
{"field1":"value1"} #这一行在上一行执行完创建索引操作后同时创建了文档{"field1":"value1"}
{"delete":{"_index":"test","_id":"2"}}
{"create":{"_index":"test2","_id":"3"}}
{"field1":"value3"}
{"update":{"_id":"1","_index":"test"}}

3.2.5 批量获取mget

#mget
GET /_mget
{
  "docs":[
    {"_index":"test","_id":"1"},
    {"_index":"test","_id":"2"}
  ]
}

3.2.6 批量搜索msearch

#msearch
POST users/_msearch
{}
{"query":{"match_all":{}},"size":1}
{"index":"test"}

3.2.7 多条件搜索,使用组合查询bool query

GET users/_search
{
    "query":{
        "bool":{
            "must":[
                {
                    "match":{
                        "user":"Queen"
                    }
                },
                {
                    "match":{
                        "post_date":"2021-05-31"
                    }
                }
            ]
        }
    }
}

Bool sholuld查询:如果没有 must 语句,那么至少需要能够匹配其中的一条 should 语句。但,如果存在至少一条 must 语句,则对 should 语句的匹配没有要求。有must语句should只影响分数_score评定。

3.3 Mapping(schema)

查看索引mapping:GET users/_mapping

3.3.1 Mapping后续能否被更改

  1. 已有字段,不能更改,如果需要更改必须Reindex API重建索引
  2. 新增字段
    • Dynamic设为true,一旦有新增字段的文档写入,Mapping也会更新
    • Dynamic设为false,有新增字段的文档写入,Mapping不会更新,数据无法所引,但是数据会出现在_source中
    • Dynamic设为Strict,写入文档失败

3.3.2 设置(创建索引)

PUT test_mapping
{
  "mappings": {
    "dynamic":"false",
    "properties": {
      "id":{
        "type":"long"
      },
      "name":{
        "type": "keyword",
        "index":false
      },
      "age":{
        "type": "text",
        "index_options":"offsets"
      },
      "city":{
        "type": "keyword",
        "null_value":"NULL"
      },
      "firstName":{
        "type": "text",
        "copy_to":"fullName",
        "analyzer":"standard",
        "search_analyzer":"standard"
      },
      "lastName":{
        "type": "text",
        "copy_to":"fullName"
      }
    }
  },
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 1
  }
}
index=false

当不需要索引字段时,设置index=false,不创建索引、无法搜索

index_options

可以控制倒排索引内容,四种级别
docs freqs positions offsets。Text默认positions,其他默认docs

null_value

需要对null值进行搜索,设置null_value,只有keyword可以使用此设置。
"NULL"是特定字符串,并不一定是用NULL

GET test_mapping/_search
{
  "query":{
    "match": {
      "city": "NULL"
    }
  }
}
copy_to

copy_to将值拷贝到目标字段中,实现类似_all(废弃)的功能,目标字段不出现在_source中

新字段设置mapping
PUT employees/_mapping
{
  "properties": {
    "age":{
        "type":"text",
        "fields":{
          "keyword":{
            "type":"keyword"
          }
        }
      }
  }
}

#出问题时试一下这个
PUT employees/_mapping/_doc?include_type_name=true
{
  "properties": {
    "job":{
        "type":"text",
        "fields":{
          "keyword":{
            "type":"keyword"
          }
        }
      }
  }
}

3.3.3 查看Mappings

GET test_mapping/_mappings
GET test_mapping/_settings

3.3.2 字段类型

简单类型
  1. Text/keyword
  2. Date
  3. Integer/Floating
  4. Boolean
  5. IPv4 & IPv6

keyword:查询的时候已有的值不会被分词,所以查询都需要完全匹配;

text,查询的时候已有的值会被分词,分词后只能根据单个分词查,不能整体查

复杂类型
  1. 对象类型/嵌套类型
特殊类型
  1. geo_piont & geo_shape/percolator

3.3.3 Dynamic Mapping

可以根据自定义规则自动识别字段类型,不用每个字段都设置类型

ES字段自动类型识别(基于json类型):
字符串:匹配为日期,设置为Date
        匹配为数字,设置为float或long,默认关闭
        其他,设置为text,并自动增加keyword子字段
        
布尔值:boolean

浮点数:float

整型:long

对象:Object

数组:由第一个非空数值的类型决定

空值:忽略

3.3.5 数据建模

思路:字段类型 -> 是否需要被搜索及分词 -> 是否需要聚合和排序 -> 是否需要额外存储
3.3.5.1 搜索分词:text keyword
keyword适用于filter精确匹配

text设置多字段类型,例如增加子字段,更改分词器(如english)来提高搜索精确度
3.3.5.2 聚合排序:text字段需要开启fielddata
3.3.5.3 数值类型,尽量选贴近类型 例如byte > long

枚举类型用keyword

其他:日期 布尔 地理信息

3.3.5.4 不需要检索、排序、聚合分析

字段不存储磁盘:enable=false

3.3.5.5 不需要检索,需要聚合、排序

字段属性:index=false

3.3.5.6 额外存储

store=true,会保存原始字段内容,结合_source=disable配合使用。指标性数据,文档不需要更新,不在磁盘保存source信息。

3.3.5.7 可以使用index template自动化设置字段类型
3.3.5.8 当字段包含多个值并且有同时查询需求,使用nested嵌套字段
3.3.5.9关联文档更新十分频繁,使用父子关系文档

ps:kinbana对嵌套和父子关系文档支持不是很好,需要kibana做数据分析时需要谨慎考虑

3.3.5.10尽量避免字段过多

影响:

  1. 不易维护
  2. mapping存在cluster state中,导致cluster state过大,cluster state各节点同步时会变慢

线上使用dynamic=strict

3.3.5.11 避免正则查询

通配符放前面很可能造成查询灾难

解决方案:将字符串拆成对象,分多个字段,精确匹配查询。

3.3.5.12 避免使用空值引起的聚合不准

将null_value设置默认值

PUT titles
{
    "mappings":{
        "properties":{
            "title":{
                "type":"integer",
                "null_value": 0
            }
        }
    }
}
3.3.5.13 为索引的Mapping加入Meta信息
PUT titles
{
    "mappings":{
    		"_beta":{
    				"software_version_mapping":"1.0"
    		},
        "properties":{
            "title":{
                "type":"integer",
                "null_value": 0
            }
        }
    }
}

Mapping设置需要从两个维度考虑:

1. 功能:搜索、聚合、排序
2. 性能:存储的开销、内存的开销、搜索的性能

Mapping的设置是个迭代的过程:

  1. 加入新字段容易(必要时update_by_query使新字段可以索引搜索)
  2. 更新删除字段不允许,需要reindex重建数据
  3. 最好加入Meta信息,更好的进行版本管理
  4. 可以考虑将Mapping文件上传git进行管理

3.4 分词 analyze

分析器包含三部分,字符过滤器(如去掉html标签,&转化and,此时未分词)、分词器(分成单个词条)、token过滤器(词条通过token过滤器,如删除a,and等无用词条;或增加同义词词条等)。分析器可以自定义,自由组合这三个部分。

Elasticsearch对数组分析生成了与分析单个字符串一样几乎完全相同的语汇单元,把数据每个元素分词汇总到一起。可以使用position_increment_gap(增加数据元素分词间的距离)精确查询结果。

Character Filter

Tokenizer执行前对文本进行处理,会影响Tokenizer的position和offset信息

自带的:

  1. HTML strip :去html标签
  2. Mapping :字符替换
  3. Pattern replace :正则匹配替换

Tokenizer

将原来文本按一定规则切分为词

Token Filter

发生在Tokenizer分词之后,将Tokenizer输出的单词(term),进行增加、删除、修改

自带的:

  1. lowercase(小写)
  2. stop(去掉 on the等修饰词)
  3. synonym(添加近义词)
#测试分词,html变迁剥离,爬取到的数据剥离标签
POST _analyze
{
    "tokenizer":"keyword",
    "char_filter":["html_strip"],
    "text":"<b>hello world</b>"
}

#char filter替换 "-"" 替换为 "_" 
POST _analyze
{
    "tokenizer":"standard",
    "char_filter":[{
        "type":"mapping",
        "mappings":["- => _"]
    }],
    "text":"123-456, I-test! test-990 650-555-1234"
}

#pattern_replace 提取域名
POST _analyze
{
    "tokenizer":"standard",
    "char_filter":[{
        "type":"pattern_replace",
        "pattern":"http://(.*)",
        "replacement":"$1"
    }],
    "text":"http://www.elasticsearch.io"
}

#分离出从跟到下面各个目录
POST _analyze
{
    "tokenizer":"path_hierarchy",
    "text":"/data/users/zhangli/123"
}
#效果:
{
  "tokens" : [
    {
      "token" : "/data",
      "start_offset" : 0,
      "end_offset" : 5,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "/data/users",
      "start_offset" : 0,
      "end_offset" : 11,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "/data/users/zhangli",
      "start_offset" : 0,
      "end_offset" : 19,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "/data/users/zhangli/123",
      "start_offset" : 0,
      "end_offset" : 23,
      "type" : "word",
      "position" : 0
    }
  ]
}

#空格分词,token filter使用字母小写、去掉stop word
POST _analyze
{
    "tokenizer":"whitespace",
    "filter": ["stop","lowercase"], 
    "text":"The girl in China are playing the game!"
}

3.5 Index template 模板

应用es默认的settings和mappings < order值较低的Index template设定 < order值较高的Index template设定 < 应用创建索引时用户设置的mappings和settings,后面会覆盖前面的设置。

#设置默认的模板,"index_patterns":"*"匹配所有
PUT _template/template_default
{
  "index_patterns":"*",
  "order":0,
  "version":1,
  "settings":{
    "number_of_shards":1,
    "number_of_replicas":1
  }
}

#设置索引是test开头的模块
PUT _template/template_test
{
  "index_patterns":"test*",
  "order":1,
  "settings":{
    "number_of_shards":1,
    "number_of_replicas":2
  },
  "mappings":{
    "date_detection":false,
    "numeric_detection":true
  }
}

#查看模板
GET _template/template_test
#通配符查看
GET _template/temp*

#删除模板
DELETE _template/template_test

Dynamic Template

设置在具体的索引mapping中,Template有一个名称,匹配规则是一个数组,为匹配到的字段设置mapping。

字段后接’^'来改变字段的权重

#字段名是is开头的设置为boolean类型
PUT test_index
{
  "mappings": {
    "dynamic_templates": [{
      "string_as_boolean":{
        "match_mapping_type":"string",
        "path_match":"is*",
        "mapping":{
          "type":"boolean"
        }
      }
    }]
  }
}

Search Template

定义查询模板,开发和运维解耦

3.6 聚合 Aggregation

数据统计分析,实时性高

Bucket Aggregation

特定条件分为多个桶,类似sql的group by

Metric Aggregation

数学运算,可对文档进行统计分析。类似sql的max min count。支持painless脚本中运算。

Pipeline Aggregation

对其他聚合结果进行二次聚合

Matrix Aggregation

支持多个字段操作并提供一个结果矩阵

语法

#"query"同级
"aggregations":{
    "my_aggregation_name":{
        "aggregation_type":{
            <aggregation_body>
        },
        "aggregations":{<sub_aggregations 子聚合>}
    },
    "my_aggregation_name_2":{
        <多个聚合>
    }
}
获取最大、最小、平均工资

size=0可以防止返回不必要的数据。

#bulk用法
PUT _bulk
{"index":{"_index":"employees","_id":"6"}}
{"name":"zhou","salary":600}
{"index":{"_index":"employees","_id":"7"}}
{"name":"wu","salary":700}
{"index":{"_index":"employees","_id":"8"}}
{"name":"zheng","salary":800}
{"index":{"_index":"employees","_id":"9"}}
{"name":"wang","salary":900}
#bulk用法2
POST employees/_bulk
{"index":{"_id":"10"}}
{"name":"jiang","salary":1000}
{"index":{"_id":"11"}}
{"name":"wei","salary":1100}

#输出最低最高和平均工资
GET employees/_search
{
  "size": 0,
  "aggs": {
    "max_salary": {
      "max": {
        "field": "salary"
      }
    },
    "min_salary": {
      "min": {
        "field": "salary"
      }
    },
    "avg_salary": {
      "avg": {
        "field": "salary"
      }
    }
  }
}

#输出多个信息 stats
GET employees/_search
{
  "size": 0,
  "aggs": {
    "stats_salary": {
      "stats": {
        "field": "salary"
      }
    }
  }
}


Bucket Term 聚合需要在字段上打开fielddata

keyword类型默认支持fielddata,text类型需要在Mapping中enable。

#根据name分桶,找出每个名字各有几个人
GET employees/_search
{
  "size": 0,
  "aggs": {
    "names": {
      "terms": {
        "field": "name.keyword"
      }
    }
  }
}

terms聚合,如果是text类型,是分词后的term的聚合。对keyword是整体的。

eager_global_ordinae=true可以在新文件加入时就写入cache增加term聚合的效率

场景:经常有文件写入、对聚合查询有较高要求。

位置:在设置mapping时

PUT index
{
    "mappings":{
        "properties":{
            "foo":{
                "type":"keyword",
                "eager_global_ordinae":true
            }
        }
    }
}
查看name.keyword有多少个不同类型
GET employees/_search
{
  "size": 0,
  "aggs": {
    "names_cardinality": {
      "cardinality": {
        "field": "name.keyword"
      }
    }
  }
}
范围分桶

按工资范围分桶。
可以指定“key”,在结果中的key会返回指定的值。不指定es默认设置。

GET employees/_search
{
  "size": 0,
  "aggs": {
    "names_cardinality": {
      "range": {
        "field": "salary",
        "ranges": [
          {"to": 300},
          {"from": 300,"to":600},{
            "key":">600",
            "from": 600
          }
        ]
      }
    }
  }
}

#返回结果
{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 11,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "names_cardinality" : {
      "buckets" : [
        {
          "key" : "*-300.0",
          "to" : 300.0,
          "doc_count" : 2
        },
        {
          "key" : "300.0-600.0",
          "from" : 300.0,
          "to" : 600.0,
          "doc_count" : 3
        },
        {
          "key" : ">600",
          "from" : 600.0,
          "doc_count" : 6
        }
      ]
    }
  }
}

固定范围分桶tistogram

#每150一个区间进行分桶,extended_bounds取值范围
GET employees/_search
{
  "size": 0,
  "aggs": {
    "salary_histogram": {
      "histogram": {
        "field": "salary",
        "interval": 150,
        "extended_bounds": {
          "min": 0,
          "max": 1500
        }
      }
    }
  }
}

#返回
{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 11,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "salary_histogram" : {
      "buckets" : [
        {
          "key" : 0.0,
          "doc_count" : 1
        },
        {
          "key" : 150.0,
          "doc_count" : 1
        },
        {
          "key" : 300.0,
          "doc_count" : 2
        },
        {
          "key" : 450.0,
          "doc_count" : 1
        },
        {
          "key" : 600.0,
          "doc_count" : 2
        },
        {
          "key" : 750.0,
          "doc_count" : 1
        },
        {
          "key" : 900.0,
          "doc_count" : 2
        },
        {
          "key" : 1050.0,
          "doc_count" : 1
        },
        {
          "key" : 1200.0,
          "doc_count" : 0
        },
        {
          "key" : 1350.0,
          "doc_count" : 0
        },
        {
          "key" : 1500.0,
          "doc_count" : 0
        }
      ]
    }
  }
}

嵌套对象nested

在设置mapping时指定,会把两个字段联合写在一起(first_name和last_name
)

PUT movies
{
    "mappings":{
        "properties":{
            "actors":{
                "type":"nested",
                "properties":{
                    "first_name":{"type":"keyword"},
                    "last_name":{"type":"keyword"}
                    
                }
            },
            "title":{
                "type":"text",
                "fields": {
                  "keyword":{"type":"keyword","ignore_above":256}
                  
                }
            }
        }
    }
}

#数据
PUT movies/_doc/1
{
    "title":"speed",
    "actors":[
        {
            "first_name":"Keanu",
            "last_name":"Reeves"
        },
        {
            "first_name":"Dennis",
            "last_name":"Hopper"
        }
    ]
}

#查询 只有 first=Keanu last=Reeves 才能查出数据
POST movies/_search
{
    "query":{
      "bool": {
        "must": [
          {
            "nested": {
              "path": "actors",
              "query": {
                "bool": {
                  "must": [
                    {
                      "match": {
                        "actors.first_name": "Keanu"
                      }
                    },
                    {
                      "match": {
                        "actors.last_name": "Hopper"
                      }
                    }
                  ]
                }
              }
            }
          }
        ]
      }
    }
}

3.7 分词器

ICU分词器,中文

更好的中文分词器

IK分词

THULAC清华大学出的分词器

HanLP:自然语言分词

PinYin:拼音分词器

ES默认分词器

img

3.8 Update by Query / reindex

Update by Query

在原来基础上对数据做了一次索引操作。

#重新索引所有数据。如果mapping更新之前的数据没索引,可以用这个
POST blogs/update_by_query
{}

Reindex

更改mapping字段类型,改分片、数据迁移。设置一个新索引,把原来索引reindex api导入到新索引。

POST _reindex
{
    "source":{
        "index":"blogs"
    },
    "dest":{
        "index":"blogs_new"
    }
}

#op_type:只有目标中没有这个文档,才会创建文档;wait_for_completion=false异步操作。远程重建索引需要在目标集群加上原集群的白名单.conflicts=proceed遇到冲突继续
POST _reindex?wait_for_completion=false
{
    "source":{
        "index":"blogs",
        "remote": {
          "host": "http://:8080",
          "username": "",
          "password": ""
        }
    },
    "dest":{
        "index":"blogs_new",
        "op_type":"create"
    },
    "conflicts": "proceed"
}
查看任务信息

任务未完成等待10秒返回结果

GET _tasks/0SLAmElETj-BFjmDQGs2Yw:32352?wait_for_completion=true&timeout=10s

返回值样式

{
    "task": {
        "node": "ECEJKMsGT8OI9wJ3UNlvBA",
        "running_time_in_nanos": 29376630999,
        "action": "indices:data/write/reindex",
        "description": "reindex from [host=10.24.121.19 port=8080 query={\n  \"match_all\" : {\n    \"boost\" : 1.0\n  }\n}][grocery_aoi_info][info] to [aoi_for_reindex][info]",
        "start_time_in_millis": 1623398908755,
        "id": 369592,
        "type": "transport",
        "cancellable": true,
        "status": {
            "batches": 218,
            "retries": {
                "search": 0,
                "bulk": 0
            },
            "throttled_millis": 0,
            "total": 2556633,
            "deleted": 0,
            "noops": 0,
            "requests_per_second": -1,
            "created": 217000,
            "version_conflicts": 0,
            "throttled_until_millis": 0,
            "updated": 0
        }
    },
    "completed": false
}
查看重建中的任务
GET _tasks?actions=*search&detailed
其他迁移方式
  1. Shrink Api,将源索引移到新的索引,性能比reindex好,有限制:源分片数必须是目标的倍数,分片必须只读,所有分片在一个节点,状态green
  2. Rollover Api,索引过大时,将索引alia迁到另外一个索引中,分片是1

Reindex数据迁移方案实践:

1、在新集群中将原集群的一个(remote.host对应的)节点设置为白名单,再重建索引

测试环境演练时,不需要加白名单即可重建索引。生产环境需要提前验证。、

2、索引创建完成之后,更改refresh_interval=-1暂停refresh防止过多磁盘IO,number_of_replicas=0暂时关闭副本(时间可接受的话可以不做)
PUT grocery_aoi_info/_settings
{
  "settings": {
    "refresh_interval": -1,
    "number_of_replicas": 0
  }
}
3、Reindex api迁移数据

线上环境的eagle需要username和password,从“权限管理”中拿appKey和accessKey即可,测试环境不需要。全量迁移时去掉query条件。

POST _reindex
{
   "source": {
       "remote": {
           "host": "http://11.11.11.11:8080",
           "username": "com.zl",
           "password": "aaa"
       },
       "index": "sourceIndex",
       "type": "info",
       "query": {
           "bool": {
               "must": [
                   {
                       "term": {
                           "id": "123456"
                       }
                   }
               ]
           }
       }
   },
   "dest": {
       "index": "destIndex",
       "type": "info"
   }
}

查看迁移进度(新集群中):GET /_tasks?detailed=true&actions=*reindex

已知taskId查看某个集群中正在执行的任务:/_tasks/CW0QXrQdTpOGEYfTlYRTfw:200193,结果样式:

{
    "task": {
        "node": "ECEJKMsGT8OI9wJ3UNlvBA",
        "running_time_in_nanos": 29376630999,
        "action": "indices:data/write/reindex",
        "description": "reindex from [host=10.24.121.19 port=8080 query={\n  \"match_all\" : {\n    \"boost\" : 1.0\n  }\n}][grocery_aoi_info][info] to [aoi_for_reindex][info]",
        "start_time_in_millis": 1623398908755,
        "id": 369592,
        "type": "transport",
        "cancellable": true,
        "status": {
            "batches": 218,
            "retries": {
                "search": 0,
                "bulk": 0
            },
            "throttled_millis": 0,
            "total": 2556633,
            "deleted": 0,
            "noops": 0,
            "requests_per_second": -1,
            "created": 217000,
            "version_conflicts": 0,
            "throttled_until_millis": 0,
            "updated": 0
        }
    },
    "completed": false
}

在目标集群的tasks索引上查询(目前只有测试环境集群有.tasks这个索引,才能查询)

{

“query”: {

​ “wildcard”:{

​ “task.action”:“*reindex”

​ }

}

}

4、数据迁移完成恢复refresh_interval、number_of_replicas配置(恢复配置之后才能查到新索引中的数据,因为refresh_interval=-1不刷磁盘)
PUT grocery_aoi_info/_settings
{
  "settings": {
    "refresh_interval": "1s",
    "number_of_replicas": 1
  }
}

四、集群

ElasticSearch 安全

身份认证
类型:
  1. 用户名密码
  2. 提供秘钥或Kerberos票据
Realms:X-Pack中的认证
内置Realms,免费
  • FIle/Native(用户名密码保存在Es索引中)
外部Realms,收费
ES中使用RBAC
  1. 启动服务时加参数:xpack.security.enable=true
  2. 设置用户默认密码:bin/elasticsearch-setup-passwords
Kibana中使用用户密码

在Kibana的配置文件config/Kibana.yml中,打开elasticsearch.username elasticsearch.password两个选项。

在Kibana中的Security - Users、Roles 中可以创建用户、设置角色权限

通信加密(集群间)
TLS加密(X.509)
证书认证不同级别
  1. Certificate:新节点加入集群时使用同一个CA颁发的证书
  2. Full Verification:节点加入集群除了认证证书,还需要验证host name或ip地址
  3. No Verification:任何节点都可以加入

开启需要在elasticsearch.yml文件中的xpack.security.transport.ssl配置。

生产环境配置最佳实践

  1. Mater Node设置3台,只负责集群状态管理,出问题后选举新master,需要低配置CPU、RAM和磁盘
  2. Data Node数据节点,需要高磁盘、高CPU、高RAM
  3. Coordinate node需要高CPU。Coordinating node可以设置只做协调节点不和Data node混用,防止一次查询过多内存溢出影响Data node。Coordinating node前可以设置loadbalacne。
  4. Ingest Node负责数据处理(写),需要较高配置CPU,中等RAM和低磁盘。
  5. 读写分离:读LoadBalance对应Coordinating nodes,写LoadBalance对应Ingest nodes。
  6. 异地多活,把节点部署在不同的数据中心,master节点必须在不同的机房。方式:数据多写+GTM分发读请求。

查看节点

GET _cat/nodes

查看副本与主分片数据是否一致

GET _cat/shards可以查看每个分片和副本信息,对比副本和分片文档数量是否一致。小幅度不一致可能是写入同步不及时,刷新几次再看,数据能追上就没问题。

启动

bin/elasticsearch -E node.name=node0 -E cluster.name=zhanglicluster -E path.data=node0_data -d
bin/elasticsearch -E node.name=node1 -E cluster.name=zhanglicluster -E path.data=node1_data -d

生产环境配置

JVM配置

es6版本后只支持64位jvm

  1. xms和xmx设置为一样避免heap resize造成停顿
  2. xmx不要超过物理内存50%,需要预留给Lucene使用;单个节点上,最大内存建议不超过32G。小于32g有对象指针压缩,大于32g可能引起性能下降
  3. 生产环境JVM必须为server模式
  4. 关闭JVM swapping功能

ES配置

  1. elasticsearch.yml中尽量只配置必备参数,其他参数使用api动态配置
  2. 动态配置分为Transient(重启后不保存)和Persistent(重启后保存)两种
  3. 配置 cluster.routing.allocation.same_shard.host: true 。 这会防止同一个分片(shard)的主副本存在同一个物理机上。
  4. ES会等大于一半数量(默认quorum,还有1和all)的副本数活跃时才执行写操作,如果(集群启动中)副本数量不达标会等待,timeout可配置。只有副本数num_of_replica大于1时才生效。
  5. index.routing.allocation.toatl_share_per_node:设置每个索引在节点上的最大主分片数量
  6. circuit break断路器设置:可以设置bulk使用内存、fileddata使用内存等
内存设置
  1. 搜索类内存/磁盘比例1:16
  2. 日志类(写入量大,改动少):1:48 ~ 1:96之间
  3. 分配一半的内存给ES堆内存,给Lucene使用系统直接内存来缓存Segment
  4. 分配堆内存时最好不超过32GB,超过32GB JVM就不适用指针压缩功能,指针占用的空间会更大。
存储
  1. 推荐SSD存储
  2. 推荐本地存储避免网络存储
  3. 可以在本地指定多个"path.data"以支持多块磁盘
  4. Es本身提供了很好HA机制;无需使用RAID 1/5/10
  5. 可以在warm节点使用机械硬盘,需要关闭concurrent merges参数
服务器硬件
  1. 建议中等配置机器,避免非常大内存非常大硬盘的机器,无法发挥性能。不建议一台机器部署多个节点。
集群设置

Throttles限流

  1. 为Relocation何Recovery设置限流,避免过多任务对集群性能产生影响
  2. Recovery
    1. Cluster.routing.allocation.node_concurent_recoveries:2
  3. Relocation
    1. Cluster.routing.allocation.node_concurent_rebalance:2
  4. 关闭Dynamic Indexes功能,避免自动创建过多字段;或者通过模板设置白名单。

Kibana

执行命令工具Dev Tools

快捷键(mac)

cmd+/ :查看帮助文档
cmd+option+l
cmd+option+0
cmd+option+shift+0

docker

课件地址
初识 Elasticsearch

  1. 安装 Docker: https://www.docker.com/products/docker-desktop
  2. 安装 docker-compose: https://docs.docker.com/compose/install/
  3. 如何创建自己的 Docker Image:https://www.elastic.co/cn/blog/how-to-make-a-dockerfile-for-elasticsearch
  4. 如何在为 Docker Image 安装 Elasticsearch 插件:https://www.elastic.co/cn/blog/elasticsearch-docker-plugin-management
  5. 如何设置 Docker 网络: https://www.elastic.co/cn/blog/docker-networking
  6. Cerebro 源码(es集群监控): https://github.com/lmenezes/cerebro

五、Java客户端

pom引用

<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>7.12.1</version>
</dependency>

创建索引时,mapping数据格式:

{
    "properties":{
        "id":{
            "type":"keyword",
            "store":true,
            "analyzer":"standard"

        },
        "name":{
            "type":"text"
        },
        "idx":{
            "type":"keyword"
        }
    }
}

QueryBuilder

termQuery(“key”, obj) 完全匹配,不解析查询内容

matchPhraseQuery和matchQuery等的区别:

在使用matchQuery等时,在执行查询时,搜索的词会被分词器分词,只要其中任何一个分词匹配到都可以查询出来。

使用matchPhraseQuery时,不会被分词器分词,而是直接以一个短语的形式查询,而如果你在创建索引所使用的field的value中没有这么一个短语(顺序无差,且连接在一起),那么将查询不出任何结果。

match_phrase是分词的,text也是分词的。match_phrase的分词结果必须在text字段分词中都包含,而且顺序必须相同,而且必须都是连续的。

例如:

match_phrase查询  is silly 
可以匹配到 she is silly

和match_phrase区别的是,query_string查询text类型字段,不需要连续,顺序还可以调换

例如:

query_string查询t  silly she
可以匹配到 she is silly

组合查询

must(QueryBuilders) : AND

mustNot(QueryBuilders): NOT

should: : OR

Geo polygon 查询

get /index/_search
{
  "query": {
    "bool": {
      "must": {
        "match_all": {}
      },
      "filter": {
        "geo_polygon": {
          "location": {
            "points": [
              { "lat": 39.919688, "lon": 116.350783 },
              { "lat": 39.919697, "lon": 116.349655 },
              { "lat": 39.920384, "lon": 116.349651 },
              { "lat": 39.920398, "lon": 116.349672 },
              { "lat": 39.920414, "lon": 116.35073 },
              { "lat": 39.920408, "lon": 116.350762 },
              { "lat": 39.920382, "lon": 116.350785 },
              { "lat": 39.919688, "lon": 116.350783 }
            ]
          }
        }
      }
    }
  }
}

测试分词器:

Get _analyze
{
  "analyzer": "ik_max_word",
  "text": "1144 172888484748485785885 888888888888888888888888888"
}

es7查询超过10000条,设置

{
  "query": {
    "match_all": {}
  },
  "track_total_hits":true
}

六、原理

搜索

  1. 所有请求都可以设置routing参数(默认是id做路由),指定文档到分片的分发规则字段,无论读或者写。
  2. 单个buik请求体不要太大,官方建议1-15M,请求时间不要太短,建议60s以上
  3. 尽量少使用前缀* 通配符查询;少使用脚本计算查询,可以在写入时用Ingest Pipline方式提前将计算结果放入es的新字段中后续直接查询。
  4. 分片分配,search情景,单个分片控制在20G,Logging场景,当个分片控制在40G。比如时间维度索引,不写入之后可以设置只读,进行force merge操作,减少内存开销和Segment数量。

集群

集群中,只有master节点才能创建索引。创建索引请求会被coordinating node转发给master执行。集群中默认所有节点都是coordinating node。

  1. coordinating node:处理请求的节点。
  2. Data node可保存数据的节点,默认每个节点都是Data node。
  3. Master node:处理创建、删除索引;觉得分片分配到哪个节点;维护并更新cluster state。为一个集群设置多个Master节点,每个Master节点负责单一的职责。??
  4. Master Eligible Node:配置多个Master Eligible Node,在master出故障时参与选主成为master节点。每个节点启动后默认是Master Eligible Node节点。当集群里第一个Master Eligible Node启动时,会设置自己为master节点。

Cluster state集群状态

  1. 所有节点信息
  2. 所有索引和mapping、setting信息
  3. 分片和路由信息

每个节点都保存了集群的状态信息Cluster state,但是只有master节点能修改Cluster state信息并且同步给其他节点。

Master Eligible Node选主流程

节点间互相ping对方,Node Id小的成为主节点。一旦发现选中的主节点丢失,再次选举新的主节点。

脑裂问题:根据配置quorum仲裁数量来解决,即多数选举才能成为主节点。7之后没有这个配置,由es集群自己决定仲裁的节点数

分布式存储

文档映射到分片上的算法

hash算法,默认是文档id(hash(id)%shardConut),可以自己设置,比如将相同国家的分到一个分片上。所以为什么索引创建后不能改分片数,想改只能重新reindex。

es更新文档是先删除后新增。

Lucene index 其实就是Elasticsearch的每个分片

倒排索引不可变性。创建之后不能修改。好处:不用考虑并发、缓存容易维护、文件写入文件系统就不用变更。坏处:新加文档需要重建索引reindex。

一个segment就不可变的倒排索引。新文档生成新的segment。所有segment汇总到一个文件Commit point记录所有,删除有.del文件来做筛选。

删除不会立即删除,先存.del文件中。merge时删除

多个segment+Commit point+.del文件组成Lucene index

地理信息存储

GeoShape使用BKD tree,Geopoint使用的前缀树结构

压缩

Elasticsearch 在 _source 字段存储代表文档体的JSON字符串。和所有被存储的字段一样, _source 字段在被写入磁盘之前先会被压缩。_source字段可以禁用。

Hot节点、Warm节点

热部署方式控制节点所在节点,老数据不写和不大范围读的放到warm节点

Rack Awareness

控制分片在分配时在不同的节点上

分片内部原理

refresh

Lucene的index,多个segment,每次新写入数据时新建segment。写入有index buffer,buffer满了或者1秒钟进行一次refresh,refresh不进行fsync(落盘),refresh会将内容先写进缓存系统来提供搜索。(所以为什么es近实时搜索,需要1s搜到数据)。refresh时buffer清空,文档写入segment中。

index buffer大小默认是jvm 10%

Segment不可变好处

1、不需要锁 2、不可变,系统缓存,只要内存足够不用走磁盘,为什么需要给Lucene留一半内存。 3、filter缓存可以一直使用(直到merge操作)。

保证数据不丢失

Transition log。在文档写入index buffer同时也写入Transition log(落盘),所以在断电后es会从Transition log中恢复数据。

transaction log默认在每次写操作后fsync到磁盘,还可以设置async异步模式,指定多少时间间隔刷新一次,有丢失风险。

flush 落盘操作

默认30分钟一次,或者Transition log满(默认512M)的时候触发。

  1. 调用Refresh,清空index buffer并refresh
  2. 调用fync将缓存中的segment写入磁盘
  3. 清空Transition log
merge
  1. Segment越来越多需要定期合并,将已删除的真正删除(del文件中的),减少segment
  2. ES和Lucene会自动merge,可以人为触发:POST my_index/_forcemerge
主分片副本分片

写操作更新文档时,主分片先处理,然后把最新版本的文档转发给副本,而不是转发请求。

分页问题

查询过程Query then Fetch:

多个分片时,查询走到coordinating节点,然后coordinating node节点选取所有分片(主从分片随机选一个),分片中查询、排序拿from+size条数据返回给coordinating节点汇总。coordinating节点拿到数据后重新排序后选from到from+size个文档的id,最后使用multi get请求方式,根据id去不同的分片获取对应的文档数据。

ES设定搜索10000条以内的数据,超过之后报错,防止深度分页问题。
1:深度分页时,效率极低
  1. 依赖业务或产品上解决。
  2. search after 滚动翻页,不能支持指定from参数跳到某页。
GET products/_search
{
  "query": {"match_all": {}},
  "sort": [
    {
      "id": {
        "order": "desc"
      },
      "date":{
        "order": "desc"
      }
    }
  ]
}

#返回

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [
      {
        "_index" : "products",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : null,
        "_source" : {
          "id" : 1,
          "name" : "iPhone",
          "price" : "100",
          "date" : "2021-05-31"
        },
        "sort" : [
          1,
          1622419200000
        ]
      }
    ]
  }
}


#search after,将之前返回的最后一个文档的sort值赋到查询的search_after下,就会查这条数据之后的数据。必须保证sort唯一(所以加上id)
GET products/_search
{
  "query": {"match_all": {}},
  "sort": [
    {
      "id": {
        "order": "desc"
      },
      "date":{
        "order": "desc"
      }
    }
  ],
  "search_after":[
          1,
          1622419200000
        ]
}

  1. Scroll Api:生成一个快照,指定多长时间有效。每次查询输入上一个scroll id,缺点:有效时间内插入新的文档无法被查到。使用场景:想将全部文档导出,可以使用scroll api。
  2. Scroll查询原理:先做轻量级的Query阶段以后,免去了繁重的全局排序过程。 它只是将查询结果集,也就是doc_id列表保留在一个上下文里, 之后每次分批取回的时候,只需根据设置的size,在每个shard内部按照一定顺序(默认doc_id续), 取回这个size数量的文档即可。
2:相关性算分不准问题

每个分片查询时的算分根据自己分片上的数据进行算分(根据分片上的词频等数据),所以主分片越多算分越不准。

解决办法:
  1. 数据量小时将主分片设置为1(这是为什么默认主分片为1的原因),如果数据量大尽量保证文档均匀分配(这样各个词出现的频率会相对均匀,算分也会差异小一点)。
  2. 搜索URL中指定DFS Query Then Fetch参数:到每个分片把分片的词频和文档的词频进行搜索然后进行一次完整的相关性算分,会耗费更多cpu和内存,性能低下,一般不建议使用。
  3. 使用shard_size设置 (1.5 * size + 10)来设置协调节点向各个分片请求的词根个数,然后在协调节点进行聚合,最后只返回size个词根给到客户端,提高聚合精确度
3:如何提高查询精度
  1. 最近匹配dis_max;tie_breaker 这个参数将其他匹配语句的评分也考虑其中。tie_breaker 参数提供了一种 dis_max 和 bool 之间的折中选择,它的评分方式如下:1、获得最佳匹配语句的评分 _score 。2、将其他匹配语句的评分结果与 tie_breaker 相乘。3、对以上评分求和并规范化。
    有了 tie_breaker ,会考虑所有匹配语句,但最佳匹配语句依然占最终结果里的很大一部分。
  2. multi_match查询时用^标识权重。相同关键词可以同时查询多个字段。
  3. 提高全文相关性精度的常用方式是为同一文本建立多种方式的索引,搜索时不同的分词方式搜索相同的词项提高相关性分数
  4. most_fields匹配更多词项评分更高
{
  "query": {
    "multi_match": {
      "query":       "Poland Street W1V",
      "type":        "most_fields",
      "fields":      [ "street", "city", "country", "postcode" ]
    }
  }
}

排序

排序可以多个字段排序
Sort排序时_score分数失效;多级排序可以实现 先根据某个字段排序,然后再用相关性分数排序。
倒排索引失效,使用正排索引,通过文档id和字段快速得到字段的原始内容。

两种实现

  1. filedata:搜索时动态创建,放到jvm heap中,文档过多时动态创建开销大,占用jvm内存过多,但是索引速度快。
  2. Doc Values(列式存储,对text类型无效):2.x之后默认,文档创建时和倒排索引一起创建,放到磁盘中,避免大量内存占用,但是索引速度慢。明确不需要排序时可以关闭,占用空间小,创建文件快。

并发控制

乐观锁,每个文档有版本号。更新数据删除原来文档,然后插入一条新的文档,version+1.

  1. 通过if_seq_no和if_primary_term控制并发更新。
PUT products/_doc/1?if_seq_no=1&if_primary_term=0
{
    "title":"iphone",
    "count":100
}
  1. 通过version_type=external方式,场景:数据库作为主存储,es只是同步搜索,可以使用数据库中增加的版本号来更新(版本号不能比原来小),版本号会随着参数更新
PUT products/_doc/1?version=1000&version_type=external
{
    "title":"iphone",
    "count":100
}

常见问题

  1. 集群响应缓慢,没有特别多的读写。发现节点在持续full gc,查看es内存发现Segment.memery特别大。是segment过多导致fullgc问题,可以将segment强制合并。forcemerge之后问题依然存在则考虑扩容。

工具推荐

  1. Es压力测试工具,rally,基于python3.
  2. 管阀Curator工具,python,支持针对索引的一些操作,支持各种条件过滤出需要操作的索引。自动化:eBay Index managrment service平台。
  3. Jdbc input plugin可以实现logstash读mysql数据同步到es中,可以定时触发。
  4. Beats系列:filebeat收集文件数据。Metricbeat收集一些系统数据,比如可以收集mysql(已支持的模块)的一些系统指标,导入到Kibana dashboard。Packetbeat实时网络包抓取。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值