文档
在ElasticSearch中,文档以JSON格式进行存储,可以是复杂的结构
{
"_index": "haoke",
"_type": "user",
"_id": "xrIe7XEBNovl8aBHQJyE",
"_score": 1,
"_source": {
"id": 1002,
"name": "李四",
"age": 40,
"sex": "男"
"card":{
"card_number":"123456789"
}
}
}
其中,card是一个复杂对象,嵌套的Card对象
元数据
一个文档不只有数据,它还包含了元数据(metadata) – 关于文档的信息.三个必须的元数据节点是
节点 | 说明 |
---|---|
_index | 文档存储的地方(索引) |
_type | 文档代表的对象的类(文档类型) |
_id | 文档的唯一标识(主键id) |
_index
索引(index)类似于关系型数据库里的数据库–它是我们存储和索引相关联数据的地方
提示
事实上,我们的数据被存储在分片(shards)中,索引只是一个把一个或多个分片分组在一起的逻辑空间,然而,这只是一些内部细节,我们的程序完全不用关心分片,对于我们的程序而言,文档存储在索引(index)中,剩下的细节由ElasticSearch关心即可
_type
在应用中,我们使用对象表示一些"事物",例如一个用户,一篇博客,一个评论,或者一封邮件.每个对象都属于一个类(class),这个类定义了属性或与对象关联的数据 user类的对象可能包含姓名,性别,年龄和email地址
在关系型数据库中,我们经常将相同类的对象存储在一个表里,因为他们有着相同的结构 ,同理,在ElasticSearch中,我们使用相同类型(type) 的文档表示相同的"事物",因为他们的数据结构也是相同的
每个类型(type) 都有自己的映射(mapping) 或者结构定义,就像传统数据库表中的列一样,所有类型下的文档都被存储在同一索引下,但是类型的映射(mapping)会告诉ElasticSearch不同的文档如何被索引
_type的名字可以是大写或小写,不能包含下划线或逗号,我们将使用blog作为类型名(表示博客类型)
_id
id仅仅是一个字符串,它与_index 和_type组合时,就可以在ElasticSearch中唯一标识一个文档,当创建一个文档,你可以自定义_id,也可以让ElasticSearch帮你自动生成(32位长度)
查询响应
查询结果美化
请求后接?pretty
例如:http://175.24.75.131:9200/haoke/user/xrIe7XEBNovl8aBHQJyE?pretty
指定响应字段
GET /haoke/user/1005?_soure=id,name
# 就会返回指定的字段了
不返回元数据
GET /haoke/user/1005/_source
# 注意这个_source与上面的_source不同,这个_source在/后面,而不是作为参数传递
如果这时候还是想就指定某几个字段
GET /haoke/user/1005/_source?_source=id,name
判断文档是否存在
如果我们只需要判断文档是否存在,而不是查询文档内容,可以这样
HEAD /haoke/user/{id}
# 这样通过返回的状态码200代表有数据 404代表没有数据 就可以看出是否存在了
这只表示查询的那一刻文档不存在,但不表示几毫秒后依旧不存在,另一个进程在这期间可能创建新文档
批量操作
有些情况下可以通过批量操作以减少网络请求,如:批量查询,批量插入数据
- 批量查询
POST /haoke/user/_mget
{
"ids":["xrIe7XEBNovl8aBHQJyE","yLIe7XEBNovl8aBHoZwk"]
}
如果某一条数据不存在,不影响整体响应,需要通过found的值进行判断是否查询到数据
- _bulk操作 (批量插入,修改,删除)
在ElasticSearch中,支持批量的插入,修改,删除操作,都是通过_bulk的api完成的
请求格式:
{ action:{metadata}}\n #注意这里有回车换行,这个代表要执行什么动作
{ request body}\n
{ action:{metadata}}\n
{request body}\n
批量插入数据
POST /haoke/user/_bulk
{"create":{"_index":"haoke","_type":"user","_id":2001}}
{"id":2001,"name":"name1","age": 20,"sex": "男"}
{"create":{"_index":"haoke","_type":"user","_id":2002}}
{"id":2002,"name":"name2","age": 20,"sex": "男"}
{"create":{"_index":"haoke","_type":"user","_id":2003}}
{"id":2003,"name":"name3","age": 20,"sex": "男"}
# 注意每一行都需要\n 最下面也有一个\n
批量删除
POST /haoke/user/_bulk
{"delete":{"_index":"haoke","_type":"user","_id":2001}}
{"delete":{"_index":"haoke","_type":"user","_id":2002}}
{"delete":{"_index":"haoke","_type":"user","_id":2003}}
# 注意最下面的回车
由于delete没有请求体,所以action的下一行直接就是下一个action
新创建的索引后最开始添加数据不指定_id的情况下需要把create换成index
注意 用create的话 必须指定_id
一次性请求多少性能最高?
- 整个批量请求需要被加载到接受我们请求节点的内存里,所以请求越大,给其他请求可用的内存就越小,有一个最佳的bulk请求大小,超过这个大小,性能不再提升而且可能降低
- 最佳大小,不是一个固定的数字,完全取决于硬件,文档的大小和复杂度以及索引和搜索的负载
- 幸运的是,这个最佳点还是容易找到的: 试着批量索引标准的文档,随着大小的增长,当性能开始降低,说明你每个批次的大小太大了.开始的数量可以在1000-5000个文档之间,如果你的文档非常大,可以使用较小的批次
- 通常着眼于你请求批次的物理大小是非常有用的 一千个1kb文档和一千个1Mb的文档大不相同,一个好的批次最好保持在5-15M大小之间
分页
和sql使用limit关键字返回只有一页的结果一样,ElasticSearch接收from和size参数
size: 结果数,默认10
from: 跳过开始的结果数,默认0
如果每页显示5个结果,页码从1到3,请求如下
GET /_search?size=5 #第一页
GET /_search?size=5&from=5 #第二页
GET /_search?size=5&from=10 #第三页
应该当心分页太深或者一次请求太多结果,结果在返回前会被排序,但是记住一个搜索请求常常涉及多个分片,每个分片生成自己排好序的结果,他们接着需要集中起来排序以确保整体排序正确
在集群系统中深度分页会产生一些问题
为了理解为什么深度分页是有问题的,假设在一个有5个主分片的索引中搜索,当我们请求结果的第一页(结果从1到10)时,每个分片产生自己最顶端10个结果然后返回他们给请求节点,它再排序这所有的50个结果以选出顶端的10个结果
假设我们请求第1000页–结果10001到10010.工作方式都相同,不同的是每个分片都必须产生顶端的10010个结果,然后请求节点排序这50050个结果并丢弃50040个
在分布式系统中,排序结果的花费随着分页的深入二成倍增长,这也是为什么网络搜索引擎中任何语句不能返回多于1000个结果的原因
映射
前面我们创建的索引以及插入的数据,都是由ElasticSearch进行自动判断类型的,有些时候我们是需要进行明确字段类型的,否则,自动判断的类型和实际需求是不相符的
自动判断的规则如下:
JSON type | Field type |
---|---|
Boolean:true or false | “boolean” |
wholenumber:123 | “long” |
Floating point: 123.45 | “double” |
String,valid date:“2014-09-15” | “date” |
String:“foo bar” | “string” |
ElasticSearch中支持的类型如下
类型 | 表示的数据类型 |
---|---|
String | string,text,keyword |
wholenumber | byte.short,integer,long |
Floating point | float,double |
Boolean | boolean |
Date | date |
- string类型在ElasticSearch旧版本中使用较多,从ElasticSearch 5.x 开始不再支持string,由text和keyword来兴代替
- text类型,当一个字段是要被全文检索的(要进行分词),比如产品描述,商品名等,应该使用text类型,设置text类型以后,字段内容会被分析,在生成倒排索引以前,字符串会被分析器分成一个一个词项.text类型的字段不用于排序,很少用于聚合
- keyword类型适用于索引结构化的字段(不需要进行分词),比如email地址 ,ip地址,状态码等,如果字段需要进行过滤(比如查找已发布博客中status属性为published的文章),排序\聚合 keyword类型的字段只能通过精确值搜索到
创建明确类型的索引:
PUT /itcast # 索引名
{
#指定分片及副本数
"settings":{
"index":{
"number_of_shards":"2",
"number_of_replicas":"0"
}
},
#创建索引时指定映射类型
"mappings":{
# 文档类型
"person":{
# 文档中的字段名及明确类型
"properties":{
# type:text 为字符串类型且需要分词
"name":{
"type":"text"
},
"age":{
"type":"integer"
},
# type:keyword 为字符串类型且不需要分词
"mail":{
"type":"keyword"
},
"hobby":{
"type":"text"
}
}
}
}
}
查看映射:
GET /itcast/_mapping
插入数据
{"index":{"_index":"itcast","_type":"person"}}
{"name":"张三","age": 20,"mail": "111@qq.com","hobby":"羽毛球,乒乓球,足球"}
{"index":{"_index":"itcast","_type":"person"}}
{"name":"李四","age": 21,"mail": "222@qq.com","hobby":"羽毛球,乒乓球,足球,篮球"}
{"index":{"_index":"itcast","_type":"person"}}
{"name":"王五","age": 22,"mail": "333@qq.com","hobby":"羽毛球,篮球,游泳,听音乐"}
{"index":{"_index":"itcast","_type":"person"}}
{"name":"赵六","age": 23,"mail": "444@qq.com","hobby":"跑步,游泳"}
{"index":{"_index":"itcast","_type":"person"}}
{"name":"孙七","age": 24,"mail": "555@qq.com","hobby":"听音乐,看电影"}
# 注意 这里最后一行需要有回车
# 注意 新创建的索引 第一次添加数据不指定_id需要用index操作
测试搜索:
# 使用DSL搜索
POST /itcast/person/_search
{
"query":{
"match":{
"hobby":"音乐"
}
}
}
结构化查询
- term查询
term 主要用于精确匹配哪些值,比如数字,日期,布尔或not_analyzed的字符串(未经分析的文本数据类型)
用法:
{"term":{"age":26}}
{"term":{"date":"2014-09-01"}}
{"term":{"public":true}}
{"term":{"tag":"full_text"}}
示例 DSL查询
POST /itcast/person/_search
{
"query":{
"term":{
"age":20
}
}
}
- terms查询
terms与term有点类似,但terms允许指定多个匹配条件,如果某个字段指定了多个值,那么文档需要一起去做匹配
用法:
{
"terms":{
"tag":["search","full_text","nosql"]
}
}
示例:
POST /itcast/person/_search
{
"query":{
"terms":{
"age":[20,21,22]
}
}
}
- range查询
range过滤允许我们按照指定范围查找一批数据:
用法:
{
"range":{
"age":{
"gte":20,
"lt":30
}
}
}
范围操作符包括
gt 大于
gte 大于等于
lt 小于
lte 小于等于
示例:
POST /itcast/person/_search
{
"query":{
"range":{
"age":{
"gte":20,
"lt":30
}
}
}
}
- exists查询
exists查询可以用于查找文档中是否包含指定字段或没有某个字段,类似于SQL语句中的IS_NULL条件
用法:
{
"exists":{
"field": "title" # 字段title不为空的数据
}
}
示例:
POST /itcast/person/_search
{
"query":{
"exists":{
# 查询age字段不为空的文档
"field":"age"
}
}
}
- match查询
match查询是一个标准查询,不管需要全文查询还是精切查询基本上都要用它(可以查需要分词的查询也可以做结构化查询 text keyword都能查)
如果使用match查询一个全文本字段,他会在真正查询之前用分析器先分析match一下查询字符
用法:
{
"match":{
"tweet": "About Search"
}
}
{"match":{"age": 26}}
{"match":{"date": "2014-09-01"}}
{"match":{"public": true}}
{"match":{"tag": "full_text"}}
如果用match下制定了一个确切值,在遇到数字,日期,布尔值或者not_analyzed 的字符串时,它将为你搜索你给定的值
示例:
POST /itcast/person/_search
{
"query":{
"match":{
# 全文搜索的(非结构化的)
"name":"张三"
}
}
}
{
"query":{
"match":{
#结构化的
"age":20
}
}
}
- 布尔查询 bool查询
bool查询可以用来合并多个条件查询结果的布尔逻辑值,它包含以下操作符
must: 多个查询条件的完全匹配 相当于and
must_not 多个查询条件的相反匹配 相当于not
should 至少有一个查询条件匹配,相当于or
这些参数可以分别集成一个查询条件或者一个查询条件的数组
用法:
{
"bool":{
"must": {"term":{"folder":"inbox"}},
"must_not":{"term":{"tag":"spam"}},
"should":[
{"term":{"starred":true}},
{"term":{"unread":true}}
]
}
}
举例:
POST /itcast/person/_search
{
"query":{
"bool":{
# 必须包含足球(一个match全文查询)
"must":{
"match":{
"hobby":"足球"
}
},
# 必须不包含音乐(一个match全文查询)
"must_not":{
"match":{
"hobby":"音乐"
}
}
}
}
}
过滤查询
前面讲过结构化查询,ElasticSearch也支持过滤查询,如term,range,match等
示例: 查询年龄为20岁的用户
POST /itcast/person/_search
{
"query":{
"bool":{
"filter":{
"term":{
"age":20
}
}
}
}
}
查询和过滤的对比
- 一条过滤语句会询问每个文档的字段值是否包含着特定值
- 查询语句会询问每个文档的字段值与特定值的匹配程度如何
- 一条查询语句会计算每个文档与查询语句的相关性,会给出一个相关性评分_score,并且按照相关性对匹配到的文档进行排序.这种评分方式非常适用于一个没有完全配置结构的全文本搜索
- 一个简单的文档列表,快速匹配运算并存入内存是十分方便的,每个文档仅需要1个字节,这些缓存的过滤结果集与后续请求的结合使用时非常高效的
- 查询语句不仅要查找相匹配的文档,还需要计算每个文档的相关性,所以一般来说查询语句要比过滤语句更耗时,并且查询结果也不可缓存
建议:
做精确匹配搜索时,最好使用过滤语句,因为过滤语句可以缓存数据
做全文搜索时,可以使用查询语句