ElasticSearch RestFulAPI(DSL)
1 全局操作
1.1 查看集群健康情况
API:GET /_cat/health?v
?v表示显示头信息
集群的健康状态有红、黄、绿三个状态:
绿 – 一切正常(集群功能齐全)
黄 – 所有数据可用,但有些副本尚未分配(集群功能完全)
红 – 有些数据不可用(集群部分功能)
1.2 查看各个节点状态
API:GET /_cat/nodes?v
2 对索引的操作
2.1 查询各个索引状态
API:GET /_cat/indices?v
health: green(集群完整) yellow(单点正常、集群不完整) red(单点不正常)
status: 是否能使用
index: 索引名
uuid: 索引统一编号
pri: 主节点几个分片
rep: 从节点几个(副本数)
docs.count: 文档数
docs.deleted: 文档被删了多少
store.size: 整体占空间大小
pri.store.size: 主节点占空间大小
2.2 创建索引
API:PUT 索引名?pretty
使用PUT创建名为“movie_index”的索引。末尾追加pretty,可以漂亮地打印JSON响应(如果有的话)。红色警告说在7.x分片数会由默认的5改为1,我们忽略即可
索引名命名要求:
1 仅可能为小写字母,不能下划线开头
2 不能包括 , /, *, ?, ", <, >, |, 空格, 逗号, #
3 7.0版本之前可以使用冒号:,但不建议使用并在7.0版本之后不再支持
4 不能以这些字符 -, _, + 开头
5 不能包括 . 或 …
6 长度不能超过 255 个字符
2.3 查询某个索引的分片情况
API:GET /_cat/shards/索引名
默认5个分片,1个副本。所以看到一共有10个分片,5个主,每一个主分片对应一个副本,注意:同一个分片的主和副本肯定不在同一个节点上
2.4 删除索引
API:DELETE /索引名
3 对文档的操作
3.1 创建文档
API: PUT /索引名/类型名/文档id
现在向索引movie_index中放入文档,文档ID分别为1,2,3,注意:文档id和文档中的属性”id”不是一回事
PUT /movie_index/movie/1
{ "id":100,
"name":"operation red sea",
"doubanScore":8.5,
"actorList":[
{"id":1,"name":"zhang yi"},
{"id":2,"name":"hai qing"},
{"id":3,"name":"zhang han yu"}
]
}
PUT /movie_index/movie/2
{
"id":200,
"name":"operation meigong river",
"doubanScore":8.0,
"actorList":[
{"id":3,"name":"zhang han yu"}
]
}
PUT /movie_index/movie/3
{
"id":300,
"name":"incident red sea",
"doubanScore":5.0,
"actorList":[
{"id":4,"name":"zhang san feng"}
]
}
注意,Elasticsearch并不要求,先要有索引,才能将文档编入索引。创建文档时,如果指定索引不存在,将自动创建。默认创建的索引分片是5,副本是1,我们创建的文档会在其中的某一个分片上存一份,副本上存一份,所以看到的响应_shards-total:2
3.2 根据文档id查看文档
API:GET /索引名/类型名/文档id
这里有一个字段found为真,表示找到了一个ID为2的文档,另一个字段_source,该字段返回完整JSON文档。
3.3 查询所有文档
API:GET /索引名/_search
Kinana中默认显示10条,可以通过size控制
GET /movie_index/_search
{
"size":10
}
3.4 根据文档id删除文档
API: DELETE /索引名/类型名/文档id
注意:删除索引和删除文档的区别?
删除索引是会立即释放空间的,不存在所谓的“标记”逻辑。
删除文档的时候,是将新文档写入,同时将旧文档标记为已删除。 磁盘空间是否释放取决于新旧文档是否在同一个segment file里面,因此ES后台的segment merge在合并segment file的过程中有可能触发旧文档的物理删除。
也可以手动执行POST /_forcemerge进行合并触发
3.5 替换文档
PUT(幂等性操作)
API: PUT /索引名/类型名/文档id
当我们通过执行PUT /索引名/类型名/文档id命令的添加时候,如果文档id已经存在,那么再次执行上面的命令,ElasticSearch将替换现有文档
文档id3已经存在,会替换原来的文档内容
POST(非幂等性操作)
API: POST/索引名/类型名/文档id
创建文档时,ID部分是可选的。如果没有指定,Elasticsearch将生成一个随机ID,然后使用它来引用文档。
3.6 根据文档id更新文档
除了创建和替换文档外,ES还可以更新文档中的某一个字段内容。注意,Elasticsearch实际上并没有在底层执行就地更新,而是先删除旧文档,再添加新文档。
API:
POST /索引名/类型名/文档id/_update?pretty
{
"doc": { "字段名": "新的字段值" } doc固定写法
}
需求:把文档ID为3中的name字段更改为“wudang”:
POST /movie_index/movie/3/_update?pretty
{
"doc": {"name":"wudang"}
}
3.7 批处理
除了对单个文档执行创建、更新和删除之外,ElasticSearch还提供了使用_bulk API批量执行上述操作的能力。
API: POST /索引名/类型名/_bulk?pretty
_bulk表示批量操作
注意:Kibana要求批量操作的json内容写在同一行
需求1:在索引中批量创建两个文档
POST /movie_index/movie/_bulk
{"index":{"_id":66}}
{"id":300,"name":"incident red sea","doubanScore":5.0,"actorList":[{"id":4,"name":"zhang cuishan"}]}
{"index":{"_id":88}}
{"id":300,"name":"incident red sea","doubanScore":5.0,"actorList":[{"id":4,"name":"zhang cuishan"}]}
需求2:在一个批量操作中,先更新第一个文档(ID为66),再删除第二个文档(ID为88)
POST /movie_index/movie/_bulk
{"update":{"_id":"66"}}
{"doc": { "name": "wudangshanshang" } }
{"delete":{"_id":"88"}}
4 查询操作
4.1 搜索参数传递方法
请求体(request body)发送搜索参数查询所有数据
GET /movie_index/_search
{
"query": {
"match_all": {}
}
}
4.2 按条件查询(全部)
GET movie_index/movie/_search
{
"query":{
"match_all": {}
}
}
4.3 按分词查询(必须使用分词text类型)
GET movie_index/movie/_search
{
"query":{
"match": {"name":"operation red sea"}
}
}
ES中,name属性会进行分词,底层以倒排索引的形式进行存储,对查询的内容也会进行分词,然后和文档的name属性内容进行匹配,所以命中3次,不过命中的分值不同。
注意:ES底层在保存字符串数据的时候,会有两种类型text和keyword
text:分词
keyword:不分词
4.4 按分词子属性查询
GET movie_index/movie/_search
{
"query":{
"match": {"actorList.name":"zhang han yu"}
}
}
4.5 按短语查询(相当于like %短语%)
GET movie_index/movie/_search
{
"query":{
"match_phrase": {"actorList.name":"zhang han yu"}
}
}
按短语查询,不再利用分词技术,直接用短语在原始数据中匹配
4.6 通过term精准搜索匹配(必须使用keyword类型)
GET movie_index/movie/_search
{
"query":{
"term":{
"actorList.name.keyword":"zhang han yu"
}
}
}
4.7 fuzzy查询(容错匹配)
校正匹配分词,当一个单词都无法准确匹配,ES通过一种算法对非常接近的单词也给与一定的评分,能够查询出来,但是消耗更多的性能,对中文来讲,实现不是特别好。
GET movie_index/movie/_search
{
"query":{
"fuzzy": {"name":"rad"}
}
}
4.8 过滤—先匹配,再过滤
GET movie_index/movie/_search
{
"query":{
"match": {"name":"red"}
},
"post_filter":{
"term": {
"actorList.id": 3
}
}
}
4.9 过滤—匹配和过滤同时(推荐使用)
GET movie_index/movie/_search
{
"query": {
"bool": {
"must": [
{"match": {
"name": "red"
}}
],
"filter": [
{"term": { "actorList.id": "1"}},
{"term": {"actorList.id": "3"}}
]
}
}
}
4.10过滤–按范围过滤
GET movie_index/movie/_search
{
"query": {
"range": {
"doubanScore": {
"gte": 6,
"lte": 8.5
}
}
}
}
关于范围操作符:
gt: 大于
lt: 小于
gte: 大于等于 great than or equals
lte: 小于等于 less than or equals
4.11 排序
GET movie_index/movie/_search
{
"query":{
"match": {"name":"red sea"}
},
"sort":
{
"doubanScore": {
"order": "desc"
}
}
}
4.12 分页查询
from参数(基于0)指定从哪个文档序号开始,size参数指定返回多少个文档,这两个参数对于搜索结果分页非常有用。注意,如果没有指定from,则默认值为0。
GET movie_index/movie/_search
{
"query": { "match_all": {} },
"from": 1,
"size": 1
}
4.13 指定查询的字段
GET movie_index/movie/_search
{
"query": { "match_all": {} },
"_source": ["name", "doubanScore"]
}
4.14 高亮
对命中的词进行高亮显示
GET movie_index/movie/_search
{
"query":{
"match": {"name":"red sea"}
},
"highlight": {
"fields": {"name":{} }
}
}
4.15 聚合
聚合提供了对数据进行分组、统计的能力,类似于SQL中Group By和SQL聚合函数。在ElasticSearch中,可以同时返回搜索结果及其聚合计算结果,这是非常强大和高效的。
需求1:取出每个演员共参演了多少部电影
GET movie_index/movie/_search
{
"aggs": {
"myAGG": {
"terms": {
"field": "actorList.name.keyword"
}
}
}
}
aggs:表示聚合
myAGG:给聚合取的名字,
trems:表示分组,相当于groupBy
field:指定分组字段
需求2:每个演员参演电影的平均分是多少,并按评分排序
GET movie_index/movie/_search
{
"aggs": {
"groupby_actor_id": {
"terms": {
"field": "actorList.name.keyword" ,
"order": {
"avg_score": "desc"
}
},
"aggs": {
"avg_score":{
"avg": {
"field": "doubanScore"
}
}
}
}
}
}
思考:聚合时为何要加 .keyword后缀?
.keyword 是某个字符串字段,专门储存不分词格式的副本,在某些场景中只允许只用不分词的格式,比如过滤filter比如聚合aggs, 所以字段要加上.keyword的后缀。
5 分词
5.1 查看英文单词默认分词情况
GET _analyze
{
"text":"hello world"
}
按照空格对单词进行切分
5.2 中文分词器
查看中文默认分词情况
GET _analyze
{
"text":"小米手机"
}
按照每个汉字进行切分
通过上面的查询,我们可以看到ES本身自带的中文分词,就是单纯把中文一个字一个字的分开,根本没有词汇的概念。但是实际应用中,用户都是以词汇为条件,进行查询匹配的,如果能够把文章以词汇为单位切分开,那么与用户的查询条件能够更贴切的匹配上,查询速度也更加快速。
ik_smart分词方式
GET _analyze
{
"analyzer": "ik_smart",
"text":"小米手机"
}
IK分词器下载地址
https://github.com/medcl/elasticsearch-analysis-ik
6 关于mapping
Type可以理解为关系型数据库的Table,那每个字段的数据类型是如何定义的呢?实际上每个Type中的字段是什么数据类型,由mapping定义,如果我们在创建Index的时候,没有设定mapping,系统会自动根据一条数据的格式来推断出该数据对应的字段类型,具体推断类型如下
数据 | 类型 |
---|---|
true/false | boolean |
1020 | long |
20.1 | float |
“2018-02-01” | date |
“hello world” | text +keyword |
默认只有text会进行分词,keyword是不会分词的字符串。mapping除了自动定义,还可以手动定义,但是只能对新加的、没有数据的字段进行定义,一旦有了数据就无法再做修改了。
6.1 基于中文分词搭建索引-自动定义mapping
直接创建Document,这个时候index不存在,建立文档的时候自动创建index,同时mapping会自动定义
PUT /movie_chn_1/movie/1
{ "id":1,
"name":"红海行动",
"doubanScore":8.5,
"actorList":[
{"id":1,"name":"张译"},
{"id":2,"name":"海清"},
{"id":3,"name":"张涵予"}
]
}
PUT /movie_chn_1/movie/2
{
"id":2,
"name":"湄公河行动",
"doubanScore":8.0,
"actorList":[
{"id":3,"name":"张涵予"}
]
}
PUT /movie_chn_1/movie/3
{
"id":3,
"name":"红海事件",
"doubanScore":5.0,
"actorList":[
{"id":4,"name":"张三丰"}
]
}
查看自动定义的mapping
查询测试
分析结论
上面查询“海行”命中了三条记录,是因为我们在定义的Index的时候,没有指定分词器,使用的是默认的分词器,对中文是按照每个汉字进行分词的。
6.2 基于中文分词搭建索引-手动定义mapping
定义Index,指定mapping
PUT movie_chn_2
{
"mappings": {
"movie":{
"properties": {
"id":{
"type": "long"
},
"name":{
"type": "text",
"analyzer": "ik_smart"
},
"doubanScore":{
"type": "double"
},
"actorList":{
"properties": {
"id":{
"type":"long"
},
"name":{
"type":"keyword"
}
}
}
}
}
}
}
向Index中放入Document
PUT /movie_chn_2/movie/1
{ "id":1,
"name":"红海行动",
"doubanScore":8.5,
"actorList":[
{"id":1,"name":"张译"},
{"id":2,"name":"海清"},
{"id":3,"name":"张涵予"}
]
}
PUT /movie_chn_2/movie/2
{
"id":2,
"name":"湄公河行动",
"doubanScore":8.0,
"actorList":[
{"id":3,"name":"张涵予"}
]
}
PUT /movie_chn_2/movie/3
{
"id":3,
"name":"红海事件",
"doubanScore":5.0,
"actorList":[
{"id":4,"name":"张三丰"}
]
}
查看手动定义的mapping
查询测试
GET /movie_chn_2/movie/_search
{
"query": {
"match": {
"name": "海行"
}
}
}
分析结论
上面查询没有命中任何记录,是因为我们在创建Index的时候,指定使用ik分词器进行分词
6.3 索引数据拷贝
ElasticSearch虽然强大,但是却不能动态修改mapping到时候我们有时候需要修改结构的时候不得不重新创建索引;
ElasticSearch为我们提供了一个reindex的命令,就是会将一个索引的快照数据copy到另一个索引,默认情况下存在相同的_id会进行覆盖(一般不会发生,除非是将两个索引的数据copy到一个索引中),可以使用POST _reindex命令将索引快照进行copy
POST _reindex
{
"source": {
"index": "my_index_name"
},
"dest": {
"index": "my_index_name_new"
}
}
7 索引别名 _aliases
索引别名就像一个快捷方式或软连接,可以指向一个或多个索引,也可以给任何一个需要索引名的API来使用。
7.1 创建索引别名
创建Index的时候声明
PUT 索引名
{
"aliases": {
"索引别名": {}
}
}
创建索引的时候,手动mapping,并指定别名
PUT movie_chn_3
{
"aliases": {
"movie_chn_3_aliase": {}
},
"mappings": {
"movie":{
"properties": {
"id":{
"type": "long"
},
"name":{
"type": "text",
"analyzer": "ik_smart"
},
"doubanScore":{
"type": "double"
},
"actorList":{
"properties": {
"id":{
"type":"long"
},
"name":{
"type":"keyword"
}
}
}
}
}
}
}
为已存在的索引增加别名
POST _aliases
{
"actions": [
{ "add":{ "index": "索引名", "alias": "索引别名" }}
]
}
给movie_chn_3添加别名
POST _aliases
{
"actions": [
{ "add":{ "index": "movie_chn_3", "alias": "movie_chn_3_a2" }}
]
}
7.2 查询别名列表
GET _cat/aliases?v
7.3 使用索引别名查询
与使用普通索引没有区别
GET 索引别名/_search
7.4 删除某个索引的别名
POST _aliases
{
"actions": [
{ "remove": { "index": "索引名", "alias": "索引别名" }}
]
}
7.5 使用场景
给多个索引分组 (例如, last_three_months)
POST _aliases
{
"actions": [
{ "add": { "index": "movie_chn_1", "alias": "movie_chn_query" }},
{ "add": { "index": "movie_chn_2", "alias": "movie_chn_query" }}
]
}
给索引的一个子集创建视图
相当于给Index加了一些过滤条件,缩小查询范围
POST _aliases
{
"actions": [
{
"add":
{
"index": "movie_chn_1",
"alias": "movie_chn_1_sub_query",
"filter": {
"term": { "actorList.id": "4"}
}
}
}
]
}
在运行的集群中可以无缝的从一个索引切换到另一个索引
POST /_aliases
{
"actions": [
{ "remove": { "index": "movie_chn_1", "alias": "movie_chn_query" }},
{ "remove": { "index": "movie_chn_2", "alias": "movie_chn_query" }},
{ "add": { "index": "movie_chn_3", "alias": "movie_chn_query" }}
]
}
整个操作都是原子的,不用担心数据丢失或者重复的问题
8 索引模板
索引模板(Index Template),顾名思义就是创建索引的模具,其中可以定义一系列规则来帮助我们构建符合特定业务需求的索引的mappings和settings,通过使用索引模板可以让我们的索引具备可预知的一致性。
8.1 创建索引模板
PUT _template/template_movie2020
{
"index_patterns": ["movie_test*"],
"settings": {
"number_of_shards": 1
},
"aliases" : {
"{index}-query": {},
"movie_test-query":{}
},
"mappings": {
"_doc": {
"properties": {
"id": {
"type": "keyword"
},
"movie_name": {
"type": "text",
"analyzer": "ik_smart"
}
}
}
}
}
其中 “index_patterns”: [“movie_test*”]的含义就是凡是往movie_test开头的索引写入数据时,如果索引不存在,那么ES会根据此模板自动建立索引。
在 “aliases” 中用{index}表示,获得真正的创建的索引名。aliases中会创建两个别名,一个是根据当前索引创建的,另一个是全局固定的别名。
8.2 测试
向索引中添加数据
POST movie_test_202011/_doc
{
"id":"333",
"name":"zhang3"
}
查询Index的mapping,就是使用我们的索引模板创建的
根据模板中取的别名查询数据
GET movie_test-query/_search
8.3 查看系统中已有的模板清单
GET _cat/templates
8.4 查看某个模板详情
GET _template/template_movie2020
或者
GET _template/template_movie*
8.5 使用场景
分割索引
分割索引就是根据时间间隔把一个业务索引切分成多个索引。比如 把order_info 变成 order_info_20200101,order_info_20200102 ……这样做的好处有两个:
- 结构变化的灵活性
因为ES不允许对数据结构进行修改。但是实际使用中索引的结构和配置难免变化,那么只要对下一个间隔的索引进行修改,原来的索引维持原状。这样就有了一定的灵活性。要想实现这个效果,我们只需要在需要变化的索引那天将模板重新建立即可。 - 查询范围优化
因为一般情况并不会查询全部时间周期的数据,那么通过切分索引,物理上减少了扫描数据的范围,也是对性能的优化。
8.6 注意
使用索引模板,一般在向索引中插入第一条数据创建索引,如果ES中的Shard特别多,有可能创建索引会变慢,如果延迟不能接受,可以不使用模板,使用定时脚本在头一天提前建立第二天的索引。