版权声明:本文为博主原创文章,转载请注明出处:https://twocups.cn/index.php/2021/02/23/27/

RESTful API

Elasticsearch 中有丰富的 RESTful API 来帮助人明操作存储库中的数据。我们可以通过前端请求模拟工具(例如 Postman)来对后端的 Elasticsearch 发送指令。

提一句 RESTful API 。REST 的全称是 (Resource) Representational State Transfer,直译为表现层状态转移。通俗点说,就是通过 URL 定位资源,用 HTTP 动词(GET、POST、DELETE等)描述操作。RESTful API 的优点就是使前后端分离的数据交换变得更简洁和容易,它也是目前 Web 领域最受欢迎的软件架构设计模式。

举个简单的例子。

# 普通 API
https://xxx/getSomething?sth=zz
# RESTful 风格的 API
GET https://xxx/something/zz

但 RESTful API 的缺点也很明显,就是 API 容易冗余。举个例子,我之前在阿里云做容灾项目的后端的时候,第一次向前端提供了我们之前开会讨论好的 API,但实际做出来以后效果不是那么尽如人意,需要修改。这里我原本想说 API 该改就改,新增的功能可以加到已有的 API 中,一次性提供给前端。但是由于前端是动态的,涉及到一个数据刷新率的问题,所以还是不得不新增了很多 API。这里就渐渐地出现了 API 冗余的问题,因为很多 API 的功能是很相似的,但就是不能放在一起。幸好大家开会效率很高,否则频繁的改动会让冗余性越来越强。

现在又有一种新的 API 标准被提出——GraphQL,用来替代 RESTful API。GraphQL 的全称是 Graph Query Language,其中 Graph 指的是图状数据。我简单说一下,GraphQL 就像是在前端和后端中间加了一个中端,打破了过去前端向后端发送多个 HTTP 请求这种分散式的情况,而变成了聚合成一个 HTTP 请求,去访问中端 Graph 中的不同字段这种集中式的情况。通俗点说,RESTful API 像点菜上菜,GraphQL像自助餐

或许未来会出现一种岗位,叫中端开发?专门来处理前端的请求,同时也处理后端服务器的数据,并在前端后端传递数据时对数据进行一下筛选、组合、过滤之类的处理。好家伙,这不就是中间件嘛。

又扯远了,我们继续来说说如何操作 Elasticsearch 中的 RESTful API。

创建非结构化空索引

创建空索引 twocups。

PUT http://192.168.56.101:9200/twocups
{
    "settings": {
        "index": {
            "number_of_shards": "2", 
            "number_of_replicas": "0"
        }
    }
}

删除索引

删除索引 twocups。

DELETE http://192.168.56.101:9200/twocups

插入数据

在索引 twocups 中添加类型 user 的数据,其 id 为1001。并且,非结构化的索引不需要事先创建,插入数据的时候会默认创建索引。

POST http://192.168.56.101:9200/twocups/user/1001
{
    "id":1001,
    "name":"张三",
    "age":20,
    "sex":"男"
}

更新数据

虽然Elasticsearch 中的数据无法修改,但是可以通过覆盖来完成更新的操作。

PUT http://192.168.56.101:9200/twocups/user/1001
{
    "id":1001,
    "name":"张三",
    "age":21,
    "sex":"女"
}

局部更新数据

局部更新数据,本质上是从旧文档中检索 JSON,然后修改并删除旧文档,最后索引新文档。

POST http://192.168.56.101:9200/twocups/user/1001/_update
{
    "doc":{
        "age":23
    }
}

删除数据

删除索引 twocups 下类型 user 中 id 为1001的数据。

DELETE http://192.168.56.101:9200/twocups/user/1001

根据 ID 搜索数据

查询索引 twocups 下类型 user 中 id 为1001的数据。

GET http://192.168.56.101:9200/twocups/user/1001

搜索全部数据

查询索引 twocups 下类型 user 的全部数据,注意默认只会返回10条数据。

GET http://192.168.56.101:9200/twocups/user/_search

根据关键字搜索数据

查询索引 twocups 下类型 user 且年龄为20的数据。

GET http://192.168.56.101:9200/twocups/user/_search?q=age:20

DSL 搜索

DSL 的全称是 Domain Specific Language(特定领域语言),用来构建更加复杂、更加强大的查询。通俗点理解,它就是更复杂、更细致的查询。

POST http://192.168.56.101:9200/twocups/user/_search
{
    "query": {
        "bool": {
            "filter": {
                    "range": {
                        "age": {
                        "lt": 30
                    }
                }
            },
            "must": {
                "match": {
                	"sex": "男"
                }
            }
        }
    }
}

DSL 搜索——全文搜索

查询张三和李四的数据,并且高亮显示其姓名。

POST http://192.168.56.101:9200/twocups/user/_search
{
    "query": {
        "match": {
        	"name": "张三 李四"
        }
    },
    "highlight": {
        "fields": {
        	"name": {}
        }
    }
}

DSL搜索——聚合搜索

聚合操作,可以理解为分类操作,类似 SQL 中的 group by 操作。

POST http://192.168.56.101:9200/twocups/user/_search
{
    "aggs": {
        "all_interests": {
            "terms": {
                "field": "age"
            }
        }
    }
}

指定响应字段

不需要返回全部的字段,而是指定某些需要的字段进行返回。

GET http://192.168.56.101:9200/twocups/user/1001?_source=id,name

返回原始数据(不需要元数据)

不需要元数据,仅仅返回原始数据。

GET http://192.168.56.101:9200/twocups/user/1001/_source?_source=id,name

解释一下,一个文档不止有数据,还包含了元数据(metadata),这些元数据记录了关于文档的信息。其中,有三个必须的元数据节点:

_index -> 文档存储的地方
_type -> 文档代表的对象的类
_id -> 文档的唯一标识

判断文档是否存在

不查询文档,而只查询文档是否存在。如果返回的状态码是200,则说明文档存在;如果返回的状态码是404,则说明文档不存在。

HEAD http://192.168.56.101:9200/twocups/user/1001

批量查询

通过批量操作以减少网络请求。这里批量查询 id 为1001和1003的数据,如果数据存在,则返回数据中 found 显示为 true,并包含查询数据的源数据;如果数据不存在,则返回数据中 found 显示为 false。

POST http://192.168.56.101:9200/twocups/user/_mget
{
    "ids" : [ "1001", "1003" ]
}

批量插入数据

批量插入、修改、删除操作,都是通过 _bulk 的 API 完成的。注意:最后一行的回车!

POST http://192.168.56.101:9200/twocups/user/_bulk
{"create":{"_index":"twocups","_type":"user","_id":2001}}
{"id":2001,"name":"name1","age": 20,"sex": "男"}
{"create":{"_index":"twocups","_type":"user","_id":2002}}
{"id":2002,"name":"name2","age": 20,"sex": "男"}
{"create":{"_index":"twocups","_type":"user","_id":2003}}
{"id":2003,"name":"name3","age": 20,"sex": "男"}

批量删除

批量删除特定的数据。注意:最后一行的回车!

POST http://192.168.56.101:9200/twocups/user/_bulk
{"delete":{"_index":"twocups","_type":"user","_id":2001}}
{"delete":{"_index":"twocups","_type":"user","_id":2002}}
{"delete":{"_index":"twocups","_type":"user","_id":2003}}

分页

类似 SQL 使用 LIMIT 关键字返回只有一页的结果,Elasticsearch 使用参数 from 和 size。参数 from 为跳过开始的结果数,默认为0;参数 size 为最终的结果数,默认为10。

GET http://192.168.56.101:9200/twocups/user/_search?size=5&from=1

创建明确类型的索引(结构化索引)

与之前创建非结构化索引、由 Elasticsearch 自动判断数据类型不同,创建结构化索引就需要明确字段的类型,这里涉及到映射的概念。Elasticsearch 支持的类型如下:

字符串:keyword、text、string(string从5.x版本开始则不再被支持)
整型:byte、short、integer、long
浮点型:float、double
布尔型:boolean
日期:data

其中,keyword 类型和 text 类型都表示字符串,但是 keyword 类型适用于索引结构化字段,只能通过精确值搜索到,例如 Email 地址、状态码、标签;而 text 类型的字符串是要被全文搜索的,字段内容会被分词器分成一个一个的词项,例如 Email 内容、一整段话。通俗点说,keyword 类型搜啥是啥,而 text 类型搜前会被拆开再搜。注意,下面的请求中设计到中文分词器 IK。关于 IK,我们下一篇再讲。

PUT http://192.168.56.101:9200/twocups2?include_type_name=true
{
    "settings":{
        "index":{
            "number_of_shards":"1",
            "number_of_replicas":"0"
        }
    },
    "mappings":{
        "person":{
            "properties":{
                "name":{
                    "type":"text"
                },
                "age":{
                    "type":"integer"
                },
                "mail":{
                    "type":"keyword"
                },
                "hobby":{
                    "type":"text",
                    "analyzer":"ik_max_word"
                }
            }
        }
    }
}

注意,Elasticsearch 7.4版本开始默认不再支持指定的索引类型(就比如上面代码中的 “person”),并且默认的索引类型是 _doc。如果想改变索引类型,则需要配置 include_type_name=true。但是,还是不建议这么做,因为 Elasticsearch 8版本后就不再提供该字段了。

查看映射

查看结构化索引的映射。

GET http://192.168.56.101:9200/twocups2/_mapping

批量插入数据(结构化数据)

结构化索引中的批量插入、修改、删除操作,都是通过 _bulk 的 API 完成的。注意:最后一行的回车!

POST http://192.168.56.101:9200/twocups2/_bulk
{"index":{"_index":"twocups2","_type":"person"}}
{"name":"张三","age": 20,"mail": "111@qq.com","hobby":"羽毛球、乒乓球、足球"}
{"index":{"_index":"twocups2","_type":"person"}}
{"name":"李四","age": 21,"mail": "222@qq.com","hobby":"羽毛球、乒乓球、足球、篮球"}
{"index":{"_index":"twocups2","_type":"person"}}
{"name":"王五","age": 22,"mail": "333@qq.com","hobby":"羽毛球、篮球、游泳、听音乐"}
{"index":{"_index":"twocups2","_type":"person"}}
{"name":"赵六","age": 23,"mail": "444@qq.com","hobby":"跑步、游泳、篮球"}
{"index":{"_index":"twocups2","_type":"person"}}
{"name":"孙七","age": 24,"mail": "555@qq.com","hobby":"听音乐、看电影、羽毛球"}

term 查询

term 查询代表的是精确查询、完全匹配。

POST http://192.168.56.101:9200/twocups2/_search
{
    "query":{
        "term":{
            "age":20
        }
    }
}

terms 查询

terms 查询同样是精确查询,只是 terms 查询允许指定多个匹配条件,并且匹配条件之间是“或”操作。

(这里我原来错写成“与”操作了,感谢博友“胖子”的指正!)

terms 查询官方文档:

https://www.elastic.co/guide/cn/elasticsearch/guide/current/_finding_multiple_exact_values.html

POST http://192.168.56.101:9200/twocups2/_search
{
    "query":{
        "terms":{
            "age":[20,21]
        }
    }
}

range 查询

通过范围的限定来查询想要的数据,属于过滤操作。

POST http://192.168.56.101:9200/twocups2/_search
{
    "query":{
        "range":{
            "age":{
                "gte":20,
                "lte":22
            }
        }
    }
}
# 缩写的含义
gt:大于
gte:大于等于
lt:小于
lte:小于等于

exists 查询

exists 查询指的是文档中是否包含某个字段,类似于 SQL 语句中的 IS_NULL 条件。

POST http://192.168.56.101:9200/twocups2/_search
{
    "query": {
        "exists": { 
        	"field": "title"
        }
    }
}

match 查询

match 查询是一个标准查询,在全文查询和精确查询中都要用到。

POST http://192.168.56.101:9200/twocups2/_search
{
    "query":{
     "match": { "age": 20 }
    }
}

bool 查询

bool 查询是用来合并多个条件查询结果的布尔逻辑,它包含:must(必须有)、must_not(必须没有)和 should(可以有也可以没有)。有一种情况是没有 must,但是有 should,那么这时在没有相似度影响的情况下,搜索结果中至少要包含一个 should,这部分下一篇会详细讲。

GET http://192.168.56.101:9200/twocups2/_search
{
    "query":{
        "bool":{
            "must":{
                "term":{
                    "folder":"inbox"
                }
            },
            "must_not":{
                "term":{
                    "tag":"spam"
                }
            },
            "should":[
                {
                    "term":{
                        "starred":true
                    }
                },
                {
                    "term":{
                        "unread":true
                    }
                }
            ]
        }
    }
}

过滤查询

前面提到过的,前面几个都属于过滤操作。过滤和查询最大的区别在于:过滤语句会询问每个文档的字段值是否包含着特定值,而查询语句会询问每个文档的字段值与特定值的匹配程度,并根据不同特定值的权重来呈现查询结果的顺序。还有一点,由于过滤语句可以缓存数据,所以精确匹配时最好用过滤语句。

POST http://192.168.56.101:9200/twocups2/_search
{
    "query":{
        "bool":{
            "filter":{
                "term":{
                    "age":20
                }
            }
        }
    }
}

下篇继续

【Elastic Stack系列】第三章:实际部署(三) Elasticsearch篇

林皓伟

《【Elastic Stack系列】第三章:实际部署(二) Elasticsearch篇》有 7 条评论
  1. terms 查询同样是精确查询,只是 terms 查询允许指定多个匹配条件,并且匹配条件之间是“与”操作。 terms下的多个匹配条件应该是”或”的操作?

    1. 是的,应该是“或”操作,我原来写错了!我文章中举的例子本意是年龄为20或者21的人。
      非常感谢您的指正!我也在文中附上了您的对文章修改的贡献(๑•̀ㅂ•́)و✧
      同时也加上了terms查询的官方文档,让大家都可以具体地了解一下。

回复 林皓伟 取消回复

您的电子邮箱地址不会被公开。