Elasticsearch 入门
基本概念
The Elastic Stack, 包括 Elasticsearch、Kibana、Beats 和 Logstash(也称为 ELK Stack)。能够安全可靠地获取任何来源、任何格式的数据,然后实时地对数据进行搜索、分析和可视化。Elaticsearch,简称为 ES,
- ES 是一个开源的高扩展的分布式全文搜索引擎,是整个 Elastic Stack 技术栈的核心。
- 它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理 PB 级别的数据。
常规全文搜索性能较差场景
- 搜索的数据对象是大量的非结构化的文本数据。
- 文件记录量达到数十万或数百万个甚至更多。
- 支持大量基于交互式文本的查询。
- 需求非常灵活的全文搜索查询。
- 对高度相关的搜索结果的有特殊需求,但是没有可用的关系数据库可以满足。
- 对不同记录类型、非文本数据操作或安全事务处理的需求相对较少的情况。
- 为了解决结构化数据搜索和非结构化数据搜索性能问题,我们就需要专业,健壮,强大的全文搜索引擎 。
官方网址
- 官方网址
- 官方文档
直接下载,然后解压缩即可,文件目录如下:
目录 | 含义 |
---|---|
bin | 可执行脚本目录 |
config | 配置目录 |
jdk | 内置 JDK 目录 |
lib | 类库 |
logs | 日志目录 |
modules | 模块目录 |
plugins | 插件目录 |
- 解压后,进入 bin 文件目录,点击
elasticsearch.bat
文件启动 ES 服务 。 - 注意: 9300 端口为 Elasticsearch 集群间组件的通信端口, 9200 端口为浏览器访问的 http协议 RESTful 端口。
-
注意 elasticsearch 的文件路径不能有中文,否则会报错!!
Elasticsearch 基本操作
RESTful
REST 指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是 RESTful。Web 应用程序最重要的 REST 原则是:
- 客户端和服务器之间的交互在请求之间是无状态的。
- 从客户端到服务器的每个请求都必须包含理解请求所必需的信息。
- 如果服务器在请求之间的任何时间点重启,客户端不会得到通知。
- 此外,无状态请求可以由任何可用服务器回答,这十分适合云计算之类的环境。客户端可以缓存数据以改进性能。
在 REST 样式的 Web 服务中,每个资源都有一个地址。资源本身都是方法调用的目标,方法列表对所有资源都是一样的。
- 这些方法都是标准方法,包括 HTTP GET、 POST、PUT、 DELETE,还可能包括 HEAD 和 OPTIONS。
- 简单的理解就是,如果想要访问互联网上的资源,就必须向资源所在的服务器发出请求,请求体中必须包含资源的网络路径, 以及对资源进行的操作(增删改查)。
- REST 样式的 Web 服务若有返回结果,大多数以JSON字符串形式返回。
- 可以使用 Postman 进行不同HTTP请求的模拟。可以通过该软件提交表单并且附带任意类型的请求体。
数据格式-倒排索引
-
正排索引(传统):
id content 1001 my name is zhang san 1002 my name is li si -
倒排索引
keyword id name 1001, 1002 zhang 1001
Elasticsearch 是面向文档型数据库,一条数据在这里就是一个文档。 为了方便大家理解,我们将 Elasticsearch 里存储文档数据和关系型数据库 MySQL 存储数据的概念进行一个类比
HTTP-索引-创建-查询&删除
创建索引
对比关系型数据库,创建索引就等同于创建数据库。
在 Postman 中,向 ES 服务器发 PUT 请求 : http://127.0.0.1:9200/shopping
请求后,服务器返回响应:
- 相当于说 PUT 请求用于创建索引
{ "acknowledged": true,//响应结果 "shards_acknowledged": true,//分片结果 "index": "shopping"//索引名称 }
后台日志:
[2021-04-08T13:57:06,954][INFO ][o.e.c.m.MetadataCreateIndexService] [DESKTOP-LNJQ0VF] [shopping] creating index, cause [api], templates [], shards [1]/[1], mappings []
如果重复发 PUT 请求 : http://127.0.0.1:9200/shopping
添加索引,会返回错误信息 :
{
"error": {
"root_cause": [
{
"type": "resource_already_exists_exception",
"reason": "index [shopping/J0WlEhh4R7aDrfIc3AkwWQ] already exists",
"index_uuid": "J0WlEhh4R7aDrfIc3AkwWQ",
"index": "shopping"
}
],
"type": "resource_already_exists_exception",
"reason": "index [shopping/J0WlEhh4R7aDrfIc3AkwWQ] already exists",
"index_uuid": "J0WlEhh4R7aDrfIc3AkwWQ",
"index": "shopping"
},
"status": 400
}
查看全部索引
-
在 Postman 中,向 ES 服务器发 GET 请求 :
http://127.0.0.1:9200/_cat/indices?v
-
这里请求路径中的
_cat
表示查看的意思,indices
表示索引,所以整体含义就是查看当前 ES服务器中的所有索引,就好像 MySQL 中的show tables
的感觉,服务器响应结果如下 :health status index uuid pri rep docs.count docs.deleted store.size pri.store.size yellow open shopping J0WlEhh4R7aDrfIc3AkwWQ 1 1 0 0 208b 208b
头 含义 ealth 当前服务器健康状态: green(集群完整) yellow(单点正常、集群不完整) red(单点不正常) tatus 索引打开、关闭状态 ndex 索引名 uid 索引统一编号 ri 主分片数量 ep 副本数量 ocs.count 可用文档数量 ocs.deleted 文档删除状态(逻辑删除) tore.size 主分片和副分片整体占空间大小 ri.store.size 主分片占空间大小
查看单个索引:
- 在 Postman 中,向 ES 服务器发 GET 请求 :
http://127.0.0.1:9200/shopping
{ "shopping": {//索引名 "aliases": {},//别名 "mappings": {},//映射 "settings": {//设置 "index": {//设置 - 索引 "creation_date": "1617861426847",//设置 - 索引 - 创建时间 "number_of_shards": "1",//设置 - 索引 - 主分片数量 "number_of_replicas": "1",//设置 - 索引 - 主分片数量 "uuid": "J0WlEhh4R7aDrfIc3AkwWQ",//设置 - 索引 - 主分片数量 "version": {//设置 - 索引 - 主分片数量 "created": "7080099" }, "provided_name": "shopping"//设置 - 索引 - 主分片数量 } } } }
删除某个索引
-
在 Postman 中,向 ES 服务器发 DELETE 请求 :
http://127.0.0.1:9200/shopping
返回结果如下:{ "acknowledged": true }
-
再次查看所有索引,GET
http://127.0.0.1:9200/_cat/indices?v
,返回结果如下:health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
成功删除。
HTTP-文档操作
利用 Put & Post 创建文档
假设索引已经创建好了,接下来我们来创建文档,并添加数据。这里的文档可以类比为关系型数据库中的表数据,添加的数据格式为 JSON 格式
-
在 Postman 中,向 ES 服务器发 POST 请求 :
http://127.0.0.1:9200/shopping/phone
,请求体 JSON 内容为:{ "title":"小米手机", "category":"小米", "images":"http://www.gulixueyuan.com/xm.jpg", "price":3999.00 }
注意: 此处发送请求的方式必须为 POST,不能是 PUT,否则会发生错误 。
返回结果:{ "_index": "shopping", 【索引】 "_type": "phone",【文档类型】 "_id": "_KEi-XwBo-TVi9pGGySn",【如果没有指定id,会随机产生一个唯一标识】 "_version": 1,【版本】 "result": "created",【这里的 create 表示创建成功】 "_shards": {【分片】 "total": 2,【分片总数】 "successful": 1,【分片-成功】 "failed": 0【分片-失败】 }, "_seq_no": 3, "_primary_term": 2 }
-
如果指定利用POST请求时指定标志,
http://127.0.0.1:9200/shopping/phone/1
body为:{ "title":"小米手机", "category":"小米", "images":"http://www.gulixueyuan.com/xm.jpg", "price":3999.00 }
结果如下:
{ "_index": "shopping", "_type": "phone", "_id": "1", "_version": 1, "result": "created", "_shards": { "total": 2, "successful": 1, "failed": 0 }, "_seq_no": 4, "_primary_term": 2 }
主键查询 & 全查询
查看文档时,需要指明文档的唯一性标识,类似于 MySQL 中数据的主键查询。
主键查询:
- 在 Postman 中,向 ES 服务器发 GET 请求 :
http://127.0.0.1:9200/shopping/phone/1
。返回结果如下:{ "_index": "shopping", "_type": "phone", "_id": "1", "_version": 1, "_seq_no": 4, "_primary_term": 2, "found": true, "_source": { "title": "小米手机", "category": "小米", "images": "http://www.gulixueyuan.com/xm.jpg", "price": 3999 } }
- 如果查找的id不存在,如
http://127.0.0.1:9200/shopping/phone/1001
,则返回的结果如下:{ "_index": "shopping", "_type": "_doc", "_id": "1001", "found": false }
全查询:
- 查看索引下所有数据,向 ES 服务器发 GET 请求(关键字
_search
) :http://127.0.0.1:9200/shopping/_search
。
全量修改 & 局部修改 & 删除
全量修改
和新增文档一样,输入相同的 URL 地址请求,如果请求体变化,会将原有的数据内容覆盖。 在 Postman 中,向 ES 服务器发 POST 请求 : http://127.0.0.1:9200/shopping/phone/1
请求体JSON内容改为想要修改的内容即可:
{
"title":"华为手机",
"category":"华为",
"images":"http://www.gulixueyuan.com/hw.jpg",
"price":1999.00
}
修改成功后,服务器响应结果:
{
"_index": "shopping",
"_type": "_doc",
"_id": "1",
"_version": 2,
"result": "updated",//<-----------updated 表示数据被更新
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 2,
"_primary_term": 1
}
局部修改
修改数据时,使用 _update
修改某一给条数据的局部信息
- 在 Postman 中,向 ES 服务器发 POST 请求 :
http://127.0.0.1:9200/shopping/phone/1
。请求体的内容不写全即可:{ "phone": { "title":"小米手机", "category":"小米" } }
- 返回结果如下:
{ "_index": "shopping", "_type": "phone", "_id": "1", "_version": 3, "result": "updated",//<-----------updated 表示数据被更新 "_shards": { "total": 2, "successful": 1, "failed": 0 }, "_seq_no": 3, "_primary_term": 1 }
- 在 Postman 中,向 ES 服务器发 GET请求 :
http://127.0.0.1:9200/shopping/phone/1
,查看修改内容:{ "_index": "shopping", "_type": "phone", "_id": "1", "_version": 3, "_seq_no": 3, "_primary_term": 1, "found": true, "_source": { "title": "小米手机", "category": "小米", "images": "http://www.gulixueyuan.com/hw.jpg", "price": 1999 } }
文档的删除
传统删除文档
删除一个文档不会立即从磁盘上移除,它只是被标记成已删除(逻辑删除)。
- 在 Postman 中,向 ES 服务器发 DELETE 请求 :
http://127.0.0.1:9200/shopping/phone/1
- 结果返回:
{ "_index": "shopping", "_type": "_doc", "_id": "1", "_version": 4, "result": "deleted",//<---删除成功 "_shards": { "total": 2, "successful": 1, "failed": 0 }, "_seq_no": 4, "_primary_term": 1 }
- 在 Postman 中,向 ES 服务器发 GET 请求 :
http://127.0.0.1:9200/shopping/phone/1
,查看是否删除成功:{ "_index": "shopping", "_type": "_doc", "_id": "1", "found": false }
条件删除文档
- 一般删除数据都是根据文档的唯一性标识进行删除,实际操作时,也可以根据条件对多条数据进行删除。注意此时使用的是POST
命令为http://127.0.0.1:9200/shopping/_delete_by_query
{ "query":{ "match":{ "price":4000.00 } } }
- 结果如下:
条件查询
传统条件查询
条件查询使用 _search?关键词
的筛选方式
-
不带参数时,即为全查询: 在 Postman 中,向 ES 服务器发 GET请求 :
http://127.0.0.1:9200/shopping/_search
):{ "took": 5, 【划分时间,单位毫秒】 "timed_out": false,【是否超时】 "_shards": {【分片信息】 "total": 1,【总数】 "successful": 1,【成功】 "skipped": 0,【忽略】 "failed": 0【失败】 }, "hits": { "total": { "value": 6, "relation": "eq" }, "max_score": 1, "hits": [ { "_index": "shopping", "_type": "_doc", "_id": "ANQqsHgBaKNfVnMbhZYU", "_score": 1, "_source": { "title": "小米手机", "category": "小米", "images": "http://www.gulixueyuan.com/xm.jpg", "price": 3999 } }, { "_index": "shopping", "_type": "_doc", "_id": "A9R5sHgBaKNfVnMb25Ya", "_score": 1, "_source": { "title": "小米手机", "category": "小米", "images": "http://www.gulixueyuan.com/xm.jpg", "price": 1999 } }, { "_index": "shopping", "_type": "_doc", "_id": "BNR5sHgBaKNfVnMb7pal", "_score": 1, "_source": { "title": "小米手机", "category": "小米", "images": "http://www.gulixueyuan.com/xm.jpg", "price": 1999 } }, { "_index": "shopping", "_type": "_doc", "_id": "BtR6sHgBaKNfVnMbX5Y5", "_score": 1, "_source": { "title": "华为手机", "category": "华为", "images": "http://www.gulixueyuan.com/xm.jpg", "price": 1999 } }, { "_index": "shopping", "_type": "_doc", "_id": "B9R6sHgBaKNfVnMbZpZ6", "_score": 1, "_source": { "title": "华为手机", "category": "华为", "images": "http://www.gulixueyuan.com/xm.jpg", "price": 1999 } }, { "_index": "shopping", "_type": "_doc", "_id": "CdR7sHgBaKNfVnMbsJb9", "_score": 1, "_source": { "title": "华为手机", "category": "华为", "images": "http://www.gulixueyuan.com/xm.jpg", "price": 1999 } } ] } }
-
URL带参查询:
查找 category 为小米的文档,在 Postman 中,向 ES 服务器发 GET请求 :http://127.0.0.1:9200/shopping/_search?q=category:小米
,(其中q
表示query
的意思)。返回结果如下:{ "took": 3, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": { "value": 5, "relation": "eq" }, "max_score": 0.17402273, "hits": [ { "_index": "shopping", "_type": "phone", "_id": "-aEc-XwBo-TVi9pGjSRH", "_score": 0.17402273, "_source": { "title": "小米手机", "category": "小米", "images": "http://www.gulixueyuan.com/xm.jpg", "price": 3999 } }, { "_index": "shopping", "_type": "phone", "_id": "-6Eh-XwBo-TVi9pGliRB", "_score": 0.17402273, "_source": { "title": "小米手机", "category": "小米", "images": "http://www.gulixueyuan.com/xm.jpg", "price": 3999 } } ] } }
-
请求体带参查询
由于编码以及安全问题,一般不会在url中直接带参,可以使用body传递条件. 在 Postman 中,向 ES 服务器发 POST请求 :http://127.0.0.1:9200/shopping/_search
,附带JSON体如下:{ "query":{ "match":{ "category":"小米" } } }
返回结果如下(注意,由于此时GET无法传递body,因此所有传递body的请求都因为POST请求。然后根据索引后面的关键词判断请求类型):
-
请求体带参查找所有文档内容
查找所有文档内容,也可以这样,在 Postman 中,向 ES 服务器发 POST 请求 :http://127.0.0.1:9200/shopping/_search
,附带JSON体如下:{ "query":{ "match_all":{} } }
-
请求体带参查询指定字段的数据:
如果你想查询指定字段,在 Postman 中,向 ES 服务器发 POST 请求 : http://127.0.0.1:9200/shopping/_search,附带 JSON 体如下:{ "query":{ "match_all":{} }, "_source":["title"] }
分页查询:
- 在 Postman 中,向 ES 服务器发 POST 请求 :
http://127.0.0.1:9200/shopping/_search
,附带JSON体如下:{ "query":{ "match_all":{} }, "from":0,// "size":2 }
此时结果只显示了两条,即from 和 to 可以理解为第几条
查询排序
- 在body中添加
order
关键词{ "query":{ "match_all":{} }, "sort":{ "price":{ "order":"asc" } } }
- 结果如下:
多条件查询
且逻辑查询:must
- 假设想找出小米牌子,价格为3999元的, 使用 must 关键词来连接,
must相当于数据库的&&
- 在 Postman 中,向 ES 服务器发 POST 请求 :
http://127.0.0.1:9200/shopping/_search
,附带JSON体如下:
结果如下:{ "query":{ "bool":{ "must":[{ "match":{ "category":"小米" } },{ "match":{ "price":3999.00 } }] } } }
或逻辑查询:should
-
假设想找出小米和华为的牌子。
should相当于数据库的||
-
在 Postman 中,向 ES 服务器发 POST 请求 :
http://127.0.0.1:9200/shopping/_search
,附带JSON体如下:{ "query":{ "bool":{ "should":[{ "match":{ "category":"小米" } },{ "match":{ "category":"华为" } }] } } }
范围查询
- 范围查询使用
filter[range]
关键词 - 假设想找出小米和华为的牌子,价格大于2000元的手机。
- 在 Postman 中,向 ES 服务器发 POST 请求 :
http://127.0.0.1:9200/shopping/_search
,附带JSON体如下:{ "query":{ "bool":{ "should":[{ "match":{ "category":"小米" } },{ "match":{ "category":"华为" } }], "filter":{ "range":{ "price":{ "gt":2000 } } } } } }
全文检索
- 即搜索除最相关的数据,比如关键词为 “小华” 则会输出小米和华为。如果关键词为“米华”,则输出小米,华为和红米
- 全文检索直接使用
_search
关键词就可以完成,http://127.0.0.1:9200/shopping/_search
{
"query":{
"match":{
"category" : "小华"
}
}
}
完全匹配
- 使用
match_phrase
完成完全匹配,词中的每一字都能够得到匹配 - 代码如下:
{
"query":{
"match_phrase":{
"category" : "为"
}
}
}
高亮查询
-
即搜索出来的结果某个字段或某些字与其他字的颜色不同
-
我们可以使用
highlight"->"fields"
实现高亮设置:{ "query":{ "match_phrase":{ "category" : "为" } }, "highlight":{ "fields":{ "category":{}//<----高亮这字段 } } }
结果如下:
{ "took": 1, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": { "value": 1, "relation": "eq" }, "max_score": 1.2039728, "hits": [ { "_index": "shopping", "_type": "phone", "_id": "DaGU-XwBo-TVi9pGESXa", "_score": 1.2039728, "_source": { "title": "华为手机", "category": "华为", "images": "http://www.gulixueyuan.com/xm.jpg", "price": 2000 }, "highlight": { "category": [ "华<em>为</em>" ] } } ] } }
聚合查询
聚合查询允许使用者对 es 文档进行统计分析,类似与关系型数据库中的 group by,当然还有很多其他的聚合,例如取最大值max、平均值avg等等。
-
按 price 字段进行分组: 使用 agg 指明分组操作,使用
terms
指定分组字段
在 Postman 中,向 ES 服务器发 GET请求 :http://127.0.0.1:9200/shopping/_search
,附带JSON体如下:{ "aggs":{//聚合操作 "price_group":{//名称,随意起名 "terms":{//分组 "field":"price"//分组字段 } } } }
结果如下:
{ "took": 15, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": { "value": 5, "relation": "eq" }, "max_score": 1, "hits": [ { "_index": "shopping", "_type": "phone", "_id": "BaGP-XwBo-TVi9pG-SVQ", "_score": 1, "_source": { "title": "小米手机", "category": "小米", "images": "http://www.gulixueyuan.com/xm.jpg", "price": 3999 } }, { "_index": "shopping", "_type": "phone", "_id": "BqGP-XwBo-TVi9pG_CVf", "_score": 1, "_source": { "title": "小米手机", "category": "小米", "images": "http://www.gulixueyuan.com/xm.jpg", "price": 3999 } }, { "_index": "shopping", "_type": "phone", "_id": "B6GP-XwBo-TVi9pG_iWO", "_score": 1, "_source": { "title": "小米手机", "category": "小米", "images": "http://www.gulixueyuan.com/xm.jpg", "price": 3999 } }, { "_index": "shopping", "_type": "phone", "_id": "DaGU-XwBo-TVi9pGESXa", "_score": 1, "_source": { "title": "华为手机", "category": "华为", "images": "http://www.gulixueyuan.com/xm.jpg", "price": 2000 } }, { "_index": "shopping", "_type": "phone", "_id": "1", "_score": 1, "_source": { "phone": { "title": "小米手机", "category": "小米" } } } ] }, "aggregations": { "price_group": { "doc_count_error_upper_bound": 0, "sum_other_doc_count": 0, "buckets": [ { "key": 3999, "doc_count": 3 }, { "key": 2000, "doc_count": 1 } ] } } }
-
上面返回结果会附带原始数据的。
若不想要不附带原始数据的结果,在 Postman 中,向 ES 服务器发 GET请求 :http://127.0.0.1:9200/shopping/_search
,附带JSON体如下:
{
"aggs":{
"price_group":{
"terms":{
"field":"price"
}
}
},
"size":0
}
-
若想对所有手机价格求平均值。
结果如下:{ "aggs":{ "price_avg":{//名称,随意起名 "avg":{//求平均 "field":"price" } } }, "size":0 }
{ "took": 4, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": { "value": 5, "relation": "eq" }, "max_score": null, "hits": [] }, "aggregations": { "price_avg": { "value": 3499.25 } } }
-
混合聚合:先分组再求求和
结果如下:{ "aggs":{ "price_group":{ "terms":{ "field":"price" }, "aggs": { // 通过嵌套聚合查询,设置桶内指标聚合条件 "sum_price": { // 聚合查询名字 "sum": { // 聚合类型为: avg指标聚合 "field": "price" // 根据price字段计算平均值 } } } } }, "size":0 }