第二章 ElasticSearch
2.1 ElasticSearch概念
文档 :
Elasticsearch 是一个分布式文档储存中间件,它不会将信息储存为列数据行,而是储存已序列化为 JSON 文档的复杂数据结构。当你在一个集群中有多个节点时,储存的文档分布在整个集群里面,并且立刻可以从任意节点去访问。
索引 :
当文档被储存时,它将建立索引并且近实时(1s)被搜索。 Elasticsearch 使用一种被称为倒排索引的数据结构,该结构支持快速全文搜索。在倒排索引里列出了所有文档中出现的每一个唯一单词并分别标识了每个单词在哪一个文档中。
索引可以被认为是文档的优化集合,每个文档索引都是字段的集合,这些字段是包含了数据的键值对。默认情况下,Elasticsearch 为每个字段中的所有数据建立倒排索引,并且每个索引字段都有专门的优化数据结构。例如:文本字段在倒排索引里,数值和地理字段被储存在 BKD 树中。正是因为通过使用按字段数据结构组合,才使得 Elasticsearch 拥有如此快速的搜索能力。
每个索引有一个或多个分片(shard),每个分片可以有多个副本(replica)。
映射 :
Elasticsearch 具备默认模式的能力,这意味着文档建立索引的时候无需明确指定每个字段的数据类型。当启用动态映射时,Elasticsearch 自动检测并将新字段添加到索引。该默认行为使索引和浏览数据变得容易,只要文档开始建立索引, Elasticsearch 就会检测布尔值,浮点数和整数值,日期和字符串,并将其映射到对应的数据类型中。
但是,最终你应该比程序更加了解自己的数据结构以及如何去使用它们。你可以定义动态映射的规则,并明确的定义 mapping 去更深度的控制字段的存储和索引方式。
类型:
一个索引对象可以存储很多不同用途的对象
每个文档可以有不同的结构
不同的文档类型不能为相同的属性设置不同的类型,例如在同一个索引中的所有文档类型中,一个叫title的字段必须具有相同的类型;
2.2 Rest API
ElasticSearch提供了丰富的API操作,包括基本的CRUD,创建索引和删除索引等。
2.2.1 创建非结构化索引
非结构化索引就是,不需要创建索引的结构,即可写入数据到索引中,实际上es底层会做结构化操作,类似mongodb,插入数据可以是任意结构的json。
http://172.31.132.130:9200/index_test_01
{
"settings": {
"index": {
"number_of_shards": 3,
"number_of_replicas": 2
}
}
}
2.2.2 删除索引
delete http://172.31.132.130:9200/index_test_01
2.2.3 插入数据
post /{索引}/{类型}/{id}
id插入可以有也可以没有,如果没有自动生成,如果指定必须是唯一的
post http://172.31.132.130:9200/index_test_01/user/1001
request:
{
"id":"1001",
"name":"张三",
"age":29,
"sex":"男"
}
response:
{
"_index": "index_test_01",
"_type": "user",
"_id": "1001",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1
}
页面查看:
不指定ID插入,生成_id : 唯一索引
2.2.4 更新数据
指定id存在会更新数据 : 实际上覆盖, 版本有变化,相应有变化
少字段更新 : 全量覆盖 -请求url没有改变
局部字段更新 : url改变
post : http://172.31.132.130:9200/index_test_01/user/1001/_update
{
"doc":{
"name":"张三"
}
}
2.2.5 删除数据
delete http://172.31.132.130:9200/index_test_01/user/1001
删除并没有立即删除,只是被标记为删除,在以后整理索引的时候批量删除;
2.2.6 查询数据
指定id查询
GET http://172.31.132.130:9200/index_test_01/user/PvuuWHgBcUaSHgP0gr8h
response:
{
"_index": "index_test_01",
"_type": "user",
"_id": "PvuuWHgBcUaSHgP0gr8h",
"_version": 1,
"_seq_no": 0,
"_primary_term": 1,
"found": true,
"_source": {
"id": "1002",
"name": "李四",
"age": 21,
"sex": "女"
}
}
查询所有
GET http://172.31.132.130:9200/index_test_01/user/_search
# 但是默认返回10条,查询更多需要分页查询
条件查询 :
GET http://172.31.132.130:9200/index_test_01/user/_search?q=age:15
response:
{
"took": 23,
"timed_out": false,
"_shards": {
"total": 3,
"successful": 3,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 1.0,
"hits": [
{
"_index": "index_test_01",
"_type": "user",
"_id": "1004",
"_score": 1.0,
"_source": {
"id": "1004",
"name": "王六",
"sex": "女",
"age": 15
}
}
]
}
}
2.3 DSL搜索
json请求体形式,传递需要查询的条件
http://172.31.132.130:9200/index_test_01/user/_search
request:
{
"query":{
"match":{
"age":15
}
}
}
大于30岁的男性
http://172.31.132.130:9200/index_test_01/user/_search
{
"query":{
"bool":{
"filter":{
"range":{
"age":{
"gt":30
}
}
},
"must":{
"match":{
"sex":"男"
}
}
}
}
}
全文检索 :
{
"query":{
"match":{
"name":"李四 王九"
}
}
}
2.4 聚合查询
聚合查询返回 age的命中数量
post : http://172.31.132.130:9200/index_test_01/user/_search
request:
{
"aggs":{
"all_interests":{
"terms":{
"field":"age"
}
}
}
}
response:
{
"took": 24,
"timed_out": false,
"_shards": {
"total": 3,
"successful": 3,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 4,
"relation": "eq"
},
"max_score": 1.0,
"hits": [
{
"_index": "index_test_01",
"_type": "user",
"_id": "PvuuWHgBcUaSHgP0gr8h",
"_score": 1.0,
"_source": {
"id": "1002",
"name": "李四",
"age": 21,
"sex": "女"
}
},
{
"_index": "index_test_01",
"_type": "user",
"_id": "1003",
"_score": 1.0,
"_source": {
"id": "1003",
"name": "王五",
"sex": "女",
"age": 30
}
},
{
"_index": "index_test_01",
"_type": "user",
"_id": "1004",
"_score": 1.0,
"_source": {
"id": "1004",
"name": "王六",
"sex": "男",
"age": 31
}
},
{
"_index": "index_test_01",
"_type": "user",
"_id": "1005",
"_score": 1.0,
"_source": {
"id": "1004",
"name": "王九",
"sex": "女",
"age": 15
}
}
]
},
"aggregations": {
"all_interests": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": 15,
"doc_count": 1
},
{
"key": 21,
"doc_count": 1
},
{
"key": 30,
"doc_count": 1
},
{
"key": 31,
"doc_count": 1
}
]
}
}
}
聚合有很多种,这里只是简单介绍
2.5 概念详解
文档
元数据信息 :
- "_index": "index_test_01", 索引信息
- "_type": "user", 文档类型(不能包含下划线和逗号)
- "_id": "PvuuWHgBcUaSHgP0gr8h", 唯一标识符
查询响应的定义 :
# 定义返回的JSON结构
GET http://172.31.132.130:9200/index_test_01/user/_search?_source=id,name
# 不返元数据信息
GET http://172.31.132.130:9200/index_test_01/user/1003/_source
# 返回指定ID的指定字段
GET http://172.31.132.130:9200/index_test_01/user/1003/_source?_source=id,name
2.6 判断文档是否存在
1. 查询结果如果not found 就是不存在
2. 发送Head请求
# 存在返回200 ,不存在返回404
HEAD http://172.31.132.130:9200/index_test_01/user/1005
2.7 批量操作
2.7.1 批量查询
POST http://172.31.132.130:9200/index_test_01/user/_mget
{
"ids":["1003","1005"]
}
2.7.2 批量插入和批量删除
批量插入和批量删除
POST http://172.31.132.130:9200/index_test_01/_bulk
# 每个json必须一行,每一行结束都要换行,最后一个换行要记得
{ "create" : { "_index": "index_test_01","_type": "user","_id": "2001"} }
{"id":"2001","name":"王1","sex":"女","age":21}
{ "create" : { "_index": "index_test_01","_type": "user","_id": "2002"} }
{"id":"2002","name":"王2","sex":"女","age":22}
{ "create" : { "_index": "index_test_01","_type": "user","_id": "2003"} }
{"id":"2003","name":"王3","sex":"女","age":23}
批量删除 :
POST http://172.31.132.130:9200/index_test_01/_bulk
# 每个json必须一行,每一行结束都要换行,最后一个换行要记得
{ "delete" : { "_index": "index_test_01","_type": "user","_id": "2001"} }
{ "delete" : { "_index": "index_test_01","_type": "user","_id": "2002"} }
{ "delete" : { "_index": "index_test_01","_type": "user","_id": "2003"} }
2.8 分页
from:跳过的条数
size: 返回的条数
#分页查询每次返回5条
http://172.31.132.130:9200/index_test_01/user/_search?size=5
http://172.31.132.130:9200/index_test_01/user/_search?size=5&from=5
http://172.31.132.130:9200/index_test_01/user/_search?size=5&from=10
注意不要深度分页,from跳过次数过多,性能越差
2.9 映射
前面的操作都是es自动判断类型的,实际业务中需要进行明确的字段类型, 自动判断和实际需求可能是不相符合的。
自动判断的规则如下:
Json Type | Field Type |
---|---|
Boolean true or false | boolbean |
Whole number :123 | long |
Floating point :123.45 | double |
String validate date:“2019-02-03” | date |
String “str” | string |
es 支持的数据类型:
类型 | 表示的数据类型 |
---|---|
String | string text keyword |
Whole number | byte short integer long |
Floating point | float double |
Boolean | boolean |
Date | date |
String类型说明 : string 旧版本字符串,目前不支持, text代表字段是需要被全局搜索,就是要分词, keywrod 结构化字段,不需要做分词;
创建明确类型的索引
PUT http://172.31.132.130:9200/index_test_02
{
"settings": {
"index": {
"number_of_shards": 3,
"number_of_replicas": 2
}
},
"mappings": {
"properties": {
"name": {
"type": "text"
},
"age": {
"type": "integer"
},
"mail": {
"type": "keyword"
},
"hobby": {
"type": "text"
}
}
}
}
说明 : 从7.x开始,一个Mapping只属于一个索引的type 默认type 为:_doc
- 每个文档属于一个type
- 一个type有且仅有一个Mapping定义
- 从7.x开始,不需要在Mapping中指定type信息,默认type为
_doc
查询mapping信息
GET http://172.31.132.130:9200/index_test_02/_mapping
插入数据
POST http://172.31.132.130:9200/index_test_02/_bulk
{ "create" : { "_index": "index_test_02","_type": "_doc","_id": "2001"} }
{"id":"2001","name":"王1","mail":"12345@qq.com","age":21,"hobby":"lol nongyao"}
{ "create" : { "_index": "index_test_02","_type": "_doc","_id": "2002"} }
{"id":"2002","name":"王2","mail":"12345@qq.com","age":22,"hobby":"lol nongyao"}
{ "create" : { "_index": "index_test_02","_type": "_doc","_id": "2003"} }
{"id":"2003","name":"王3","mail":"12345@qq.com","age":23,"hobby":"lol nongyao"}
{ "create" : { "_index": "index_test_02","_type": "_doc","_id": "2004"} }
{"id":"2004","name":"王4","mail":"12345@qq.com","age":24,"hobby":"lol nongyao"}
查询数据
POST http://172.31.132.130:9200/index_test_02/_doc/_search
{
"query":{
"match":{
"age":22
}
}
}
2.10 结构化查询
2.10.1 term查询
term查询,精确查询
POST http://172.31.132.130:9200/index_test_02/_doc/_search
{
"query":{
"term":{
"age":22
}
}
}
terms查询多个
POST http://172.31.132.130:9200/index_test_02/_doc/_search
{
"query":{
"terms":{
"age": [21,22,23]
}
}
}
2.10.1 range查询
gt : > , gte : ≥ , lt : < , let :≤
POST http://172.31.132.130:9200/index_test_02/_doc/_search
{
"query": {
"range": {
"age": {
"gte": 23,
"lt": 24
}
}
}
}
2.10.2 exists查询
查询文档中是否包含指定字段或者没有某个字段,类似于SQL语句中的IS_NULl条件
POST http://172.31.132.130:9200/index_test_02/_doc/_search
# 查询包含了mail字段的数据
{
"query": {
"exists": {
"field": "mail"
}
}
}
2.10.3 Match查询
match_all 查询
POST http://172.31.132.130:9200/index_test_02/_doc/_search
# match_all 查询简单的匹配所有文档。在没有指定查询方式时,它是默认的查询,它经常与 filter 结合使用
{
"query": {
"match_all": {}
}
}
multi_match 查询 : 指定字段
POST http://172.31.132.130:9200/index_test_02/_doc/_search
{
"query": {
"multi_match": {
"query": "nongyao lol",
"fields": [
"mail",
"hobby"
]
}
}
}
组合多查询
must
:文档 必须 匹配这些条件才能被包含进来。
must_not
: 文档 必须不 匹配这些条件才能被包含进来。
should
: 如果满足这些语句中的任意语句,将增加 _score
,否则,无任何影响。它们主要用于修正每个文档的相关性得分。
filter
必须 匹配,过滤模式来进行。根据过滤标准来排除或包含文档。
POST http://172.31.132.130:9200/index_test_02/_doc/_search
#多条件组合查询
{
"query": {
"bool": {
"must": {"match": {"hobby": "nongyao"}},
"must_not": { "match": { "hobby": "QQ"}},
"should": [
{"match": {"age": 21} },
{"match": {"hobby": "dnf"} }
],
"filter":{
"range": {
"age":{
"lte":21
}
}
}
}
}
}
2.11 查询方式比较
# 比较两种查询方式
POST http://172.31.132.130:9200/index_test_02/_doc/_search
# 查询方式1 math
{
"query":{
"match":{
"age":22
}
}
}
# 查询方式2 filter
{
"query": {
"bool": {
"filter": {
"term": {
"age": 22
}
}
}
}
}
查询与过滤对比
- 过滤语句会询问每个文档的字段是否包含特定值,查询语句会询问每个文档的字段值与特定值的匹配程度如何。
- 查询语句会计算每个文档与查询语句的相关性,会给出一个相关性评分_socre, 并且按照相关性对匹配的文档进行排序,这种评分方式非常适用于一个完全匹配结果的全文搜索。查询最终变成了评分的查询。
- 过滤就是询问是否包含,返回yes 或 no,过滤掉需要的;
- 过滤查询: 只是简单的检查包含或者排除,这就使得计算起来非常快。考虑到至少有一个过滤查询的结果是 “稀少的”(很少匹配的文档),并且经常使用不评分查询,结果会被缓存到内存中以便快速读取,所以有各种各样的手段来优化查询结果。
- 评分查询:不仅仅要找出匹配的文档,还要计算每个匹配文档的相关性,计算相关性使得它们比不评分查询费力的多。同时,查询结果并不缓存。
- 性能比较 : 评分查询在匹配少量文档时可能与一个涵盖百万文档的filter表现的一样好,甚至会更好。但是在一般情况下,一个filter 会比一个评分的query性能更优异,并且每次都表现的很稳定。过滤(filtering)的目标是减少那些需要通过评分查询(scoring queries)进行检查的文档。
- 选择规则 : 通常的规则是,使用查询(query)语句来进行全文搜索或者其它任何需要影响 相关性得分 的搜索。除此以外的情况都使用过滤(filters),精确匹配。