创建一个索引及文档
我们接下来创建一个叫做 twitter 的索引(index),并插入一个文档(document)。我们知道在 RDMS 中,我们通常需要有专用的语句来生产相应的数据库,表格,让后才可以让我们输入相应的记录,但是针对 Elasticsearch 来说,这个是不必须的。我们在左边的窗口中输入:
PUT twitter/_doc/1
{
"user": "GB",
"uid": 1,
"city": "Beijing",
"province": "Beijing",
"country": "China"
}
我们可以看到在 Kibana 右边的窗口中有下面的输出:
在上面,我们可以看出来我们已经成功地创建了一个叫做 twitter 的 index。通过这样的方法,我们可以自动创建一个 index。如果大家不喜欢自动创建一个 index,我们可以修改如下的一个设置:
PUT _cluster/settings
{
"persistent": {
"action.auto_create_index": "false"
}
}
请注意:在绝大多数情况下,我们并不需要这么做,除非你知道你要做什么。详细设置请参阅链接。如果你你想禁止自动创建索引,您必须配置action.auto_create_index以允许这些创建以下索引的组件:
PUT _cluster/settings
{
"persistent": {
"action.auto_create_index": ".monitoring*,.watches,.triggered_watches,.watcher-history*,.ml*"
}
}
如果使用 Logstash 或 Beats,则应在上面的列表中添加其他索引名称。我们也可以在 elasticsearch.yml 里进行配置。
通常对一个通过上面方法写入到 Elasticsearch 的文档,在默认的情况下并不马上可以进行搜索。这是因为在 Elasticsearch 的设计中,有一个叫做 refresh 的操作。它可以使更改可见以进行搜索的操作。通常会有一个 refresh timer 来定时完成这个操作。这个周期为1秒。这也是我们通常所说的 Elasticsearch 可以实现秒级的搜索。当然这个 timer 的周期也可以在索引的设置中进行配置。如果我们想让我们的结果马上可以对搜索可见,我们可以用如下的方法:
PUT twitter/_doc/1?refresh=true
{
"user": "GB",
"uid": 1,
"city": "Beijing",
"province": "Beijing",
"country": "China"
}
上面的方式可以强制使 Elasticsearch 进行 refresh 的操作,当然这个是有代价的。频繁的进行这种操作,可以使我们的 Elasticsearch 变得非常慢。另外一种方式是通过设置 refresh=wait_for。这样相当于一个同步的操作,它等待下一个 refresh 周期发生完后,才返回。这样可以确保我们在调用上面的接口后,马上可以搜索到我们刚才录入的文档:
PUT twitter/_doc/1?refresh=wait_for
{
"user": "GB",
"uid": 1,
"city": "Beijing",
"province": "Beijing",
"country": "China"
}
如果你想对 refresh 有更多的了解,请参阅我的文章 “Elasticsearch中的refresh和flush操作指南”。
它也创建了一个被叫做 _doc 的 type。自从 Elasticsearch 6.0 以后,一个 index 只能有一个 type。如果我们创建另外一个t ype 的话,系统会告诉我们是错误的。这里我们也会发现有一个版本信息,它显示的是4。如果这个 _id 为1的 document 之前没有被创建过的话,它会显示为1。之后如果我们更改这个 document,它的版本会每次自动增加1。比如,我们输入:
POST twitter/_doc/1
{
"user": "GB",
"uid": 1,
"city": "Shenzhen",
"province": "Guangdong",
"country": "China"
}
我们在左边修改了我们的数据,在右边,我们可以看到版本信息增加到6。这是因为我们把左边的命令执行了两次。同时,我们也可以看出来,我们也把左边的数据进行了修改,我们也看到了成功被修改的返回信息。在上面我们可以看出来,我们每次执行那个 POST 或者 PUT 接口时,如果文档已经存在,那么相应的版本就会自动加1,之前的版本抛弃。如果这个不是我们想要的,那么我们可以使 _create 端点接口来实现:
PUT twitter/_create/1
{
"user": "GB",
"uid": 1,
"city": "Shenzhen",
"province": "Guangdong",
"country": "China"
}
如果文档已经存在的话,我们会收到一个错误的信息:
上面的命令和如下的命令也是一样的效果:
PUT twitter/_doc/1?op_type=create
{
"user": "双榆树-张三",
"message": "今儿天气不错啊,出去转转去",
"uid": 2,
"age": 20,
"city": "北京",
"province": "北京",
"country": "中国",
"address": "中国北京市海淀区",
"location": {
"lat": "39.970718",
"lon": "116.325747"
}
}
在上面,我们在请求时带上 op_type。它可以有两种值:index 及 create。
我们必须指出的是,如果你是在 Linux 或 MacOS 机器上,我们也可以使用如下的命令行指令来达到同样的效果:
curl -XPUT 'http://localhost:9200/twitter/_doc/1?pretty' -H 'Content-Type: application/json' -d '
{
"user": "GB",
"uid": 1,
"city": "Shenzhen",
"province": "Guangdong",
"country": "China"
}'
本方法适用于一下所有的命令,如法炮制!
我们可以通过如下的命令来查看被修改的文档:
GET twitter/_doc/1
我们可以看到在右边显示了我们被修改的文档的结果。
如果我们只想得到这个文档的 _source 部分,我们可以使用如下的命令格式:
GET twitter/_doc/1/_source
在 Elasticsearch 7.0 之后,在 type 最终要被废除的情况下,我们建立使用如下的方法来获得 _source:
GET twitter/_source/1
自动 ID 生成
在上面,我特意为我们的文档分配了一个 ID。其实在实际的应用中,这个并不必要。相反,当我们分配一个 ID 时,在数据导入的时候会检查这个 ID 的文档是否存在,如果是已经存在,那么就更新到版本。如果不存在,就创建一个新的文档。如果我们不指定文档的 ID,转而让 Elasticsearch 自动帮我们生成一个 ID,这样的速度更快。在这种情况下,我们必须使用 POST,而不是 PUT。比如:
POST my_index/_doc
{
"content": "this is really cool"
}
返回的结果:
{
"_index" : "my_index",
"_type" : "_doc",
"_id" : "egiY4nEBQTokU_uEEGZz",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 1
}
从上面我们可以看出来,系统会为我们自动分配一个 ID 啊。
如果我们只对 source 的内容感兴趣的话,我们可以使用:
GET twitter/_doc/1/_source
这样我们可以直接得到 source 的信息:
{
"user" : "双榆树-张三",
"message" : "今儿天气不错啊,出去转转去",
"uid" : 2,
"age" : 20,
"city" : "北京",
"province" : "北京",
"country" : "中国",
"address" : "中国北京市海淀区",
"location" : {
"lat" : "39.970718",
"lon" : "116.325747"
}
}
我们也可以只获取 source 的部分字段:
GET twitter/_doc/1?_source=city,age,province
如果你想一次请求查找多个文档,我们可以使用 _mget 接口:
GET _mget
{
"docs": [
{
"_index": "twitter",
"_id": 1
},
{
"_index": "twitter",
"_id": 2
}
]
}
我们也可以只获得部分字段:
GET _mget
{
"docs": [
{
"_index": "twitter",
"_id": 1,
"_source":["age", "city"]
},
{
"_index": "twitter",
"_id": 2,
"_source":["province", "address"]
}
]
}
在这里,我们同时请求 id 为1和2的两个文档。
我们也可以简单地写为:
GET twitter/_doc/_mget
{
"ids": ["1", "2"]
}
它和上面的做一个是一样的。使用一个命令同时获取 id 为1及2的文档。
在上面当我们写入数据时,我们有意识地把文档的 id 在命令中写了出来。如果我们不写这个 id 的话,ES 会帮我们自动生产一个 id:
POST twitter/_doc/
我可以看到右边的一个 id 像是一个随机的数值,同时我们可以看到它的一个版本信息为1。
我们也可以看出来系统所给出来的字段都是以下划线的形式给出来的,比如:_id, _shards, _index, _typed 等
修改一个文档
我们接下来看一下如何修改一个文档。在上面我们看到了可以使用 POST 的命令来修改改一个文档。通常我们使用 POST 来创建一个新的文档。在使用 POST 的时候,我们甚至不用去指定特定的 id,系统会帮我们自动生成。但是我们修改一个文档时,我们通常会使用 PUT 来进行操作,并且,我们需要指定一个特定的 id 来进行修改:
PUT twitter/_doc/1
{
"user": "GB",
"uid": 1,
"city": "北京",
"province": "北京",
"country": "中国",
"location":{
"lat":"29.084661",
"lon":"111.335210"
}
}
如上面所示,我们使用 PUT 命令来对我们的 id 为1的文档进行修改。我们也可以使用我们上面学过的 GET 来进行查询:
GET twitter/_doc/1
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "1",
"_version" : 8,
"_seq_no" : 13,
"_primary_term" : 1,
"found" : true,
"_source" : {
"user" : "GB",
"uid" : 1,
"city" : "北京",
"province" : "北京",
"country" : "中国",
"location" : {
"lat" : "29.084661",
"lon" : "111.335210"
}
}
}
显然,我们的这个文档已经被成功修改了。
我们使用 PUT 的这个方法,每次修改一个文档时,我们需要把文档的每一项都要写出来。这对于有些情况来说,并不方便,我们可以使用如下的方法来进行修改:
POST twitter/_update/1
{
"doc": {
"city": "成都",
"province": "四川"
}
}
我们可以使用如上的命令来修改我们的部分数据。同样我们可以使用 GET 来查询我们的修改是否成功:
从上面的显示中,我们可以看出来,我们的修改是成功的,虽然在我们修改时,我们只提供了部分的数据。
在关系数据库中,我们通常是对数据库进行搜索,让后才进行修改。在这种情况下,我们事先通常并不知道文档的 id。我们需要通过查询的方式来进行查询,让后进行修改。ES 也提供了相应的 REST 接口。
POST twitter/_update_by_query
{
"query": {
"match": {
"user": "GB"
}
},
"script": {
"source": "ctx._source.city = params.city;ctx._source.province = params.province;ctx._source.country = params.country",
"lang": "painless",
"params": {
"city": "上海",
"province": "上海",
"country": "中国"
}
}
}
对于那些名字是中文字段的文档来说,在 painless 语言中,直接打入中文字段名字,并不能被认可。我们可以使用如下的方式来操作:
POST edd/_update_by_query
{
"query": {
"match": {
"姓名": "张彬"
}
},
"script": {
"source": "ctx._source["签到状态"] = params["签到状态"]",
"lang": "painless",
"params" : {
"签到状态":"已签到"
}
}
}
在上面我们使用一个中括号并 escape 引号的方式来操作。
我们可以通过上面的方法搜寻 user 为 GB 的用户,并且把它的数据项修改为:
"city" : "上海",
"province": "上海",
"country": "中国"
我们也可以通过 update 接口,使用 script 的方法来进行修改。这个方法也是需要知道文档的 id:
POST twitter/_update/1
{
"script" : {
"source": "ctx._source.city=params.city",
"lang": "painless",
"params": {
"city": "长沙"
}
}
}
和前面的方法一下,我们可以使用 GET 来查询,我们的结果是否已经改变:
{
"_index" : "twitter",
"_type" : "_doc",
"_id" : "1",
"_version" : 18,
"_seq_no" : 39,
"_primary_term" : 1,
"found" : true,
"_source" : {
"uid" : 1,
"country" : "中国",
"province" : "上海",
"city" : "长沙",
"location" : {
"lon" : "111.335210",
"lat" : "29.084661"
},
"user" : "GB"
}
}
如果你涉及到多个客户端同时更新一个索引的情况,你需要阅读文章 “深刻理解文档中的 verision 及 乐观并发控制”。
我们甚至可以使用 _update 接口使用 ctx['_op'] 来达到删除一个文档的目的,比如:
POST twitter/_update/1
{
"script": {
"source": """
if(ctx._source.uid == 1) {
ctx.op = 'delete'
} else {
ctx.op = "none"
}
"""
}
}
当检测文档的 uid 是否为 1,如果为 1 的话,那么该文档将被删除,否则将不做任何事情。
UPSERT 一个文档
仅在文档事先存在的情况下,我们在前面的代码中看到的部分更新才有效。 如果具有给定 ID 的文档不存在,Elasticsearch 将返回一个错误,指出该文档丢失。 让我们了解如何使用更新 API 进行 upsert 操作。 术语 “upsert” 宽松地表示更新或插入,即更新文档(如果存在),否则,插入新文档。
doc_as_upsert 参数检查具有给定ID的文档是否已经存在,并将提供的 doc 与现有文档合并。 如果不存在具有给定 ID 的文档,则会插入具有给定文档内容的新文档。
下面的示例使用 doc_as_upsert 合并到 ID 为3的文档中,或者如果不存在则插入一个新文档:
POST /catalog/_update/3
{
"doc": {
"author": "Albert Paro",
"title": "Elasticsearch 5.0 Cookbook",
"description": "Elasticsearch 5.0 Cookbook Third Edition",
"price": "54.99"
},
"doc_as_upsert": true
}
检查一个文档是否存在
有时候我们想知道一个文档是否存在,我们可以使用如下的方法:
HEAD twitter/_doc/1
这个 HEAD 接口可以很方便地告诉我们在 twitter 的索引里是否有一 id 为1的文档:
上面的返回值表面 id 为1的文档时存在的。
删除一个文档
如果我们想删除一个文档的话,我们可以使用如下的命令:
DELETE twitter/_doc/1
在上面的命令中,我们删除了 id 为1的文档。
在关系数据库中,我们通常是对数据库进行搜索,让后才进行删除。在这种情况下,我们事先通常并不知道文档的 id。我们需要通过查询的方式来进行查询,让后进行删除。ES 也提供了相应的 REST 接口。
POST twitter/_delete_by_query
{
"query": {
"match": {
"city": "上海"
}
}
}
这样我们就把所有的 city 是上海的文档都删除了。
检查一个索引是否存在
我们可以使用如下的命令来检查一个索引是否存在:
HEAD twitter
如果 twitter 索引存在,那么上面的命令会返回:
200 - OK
否则就会返回:
{"statusCode":404,"error":"Not Found","message":"404 - Not Found"}
删除一个索引
删除一个索引 是非常直接的。我们可以直接使用如下的命令来进行删除:
DELETE twitter
当我们执行完这一条语句后,所有的在 twitter 中的所有的文档都将被删除。
批处理命令
上面我们已经了解了如何使用 REST 接口来创建一个 index,并为之创建,读取,修改,删除文档(CRUD)。因为每一次操作都是一个 REST 请求,对于大量的数据进行操作的话,这个显得比较慢。ES 创建一个批量处理的命令给我们使用。这样我们在一次的 REST 请求中,我们就可以完成很多的操作。这无疑是一个非常大的好处。下面,我们来介绍一下这个 _bulk 命令。
我们使用如下的命令来进行bulk操作:
POST _bulk
{ "index" : { "_index" : "twitter", "_id": 1} }
{"user":"双榆树-张三","message":"今儿天气不错啊,出去转转去","uid":2,"age":20,"city":"北京","province":"北京","country":"中国","address":"中国北京市海淀区","location":{"lat":"39.970718","lon":"116.325747"}}
{ "index" : { "_index" : "twitter", "_id": 2 }}
{"user":"东城区-老刘","message":"出发,下一站云南!","uid":3,"age":30,"city":"北京","province":"北京","country":"中国","address":"中国北京市东城区台基厂三条3号","location":{"lat":"39.904313","lon":"116.412754"}}
{ "index" : { "_index" : "twitter", "_id": 3} }
{"user":"东城区-李四","message":"happy birthday!","uid":4,"age":30,"city":"北京","province":"北京","country":"中国","address":"中国北京市东城区","location":{"lat":"39.893801","lon":"116.408986"}}
{ "index" : { "_index" : "twitter", "_id": 4} }
{"user":"朝阳区-老贾","message":"123,gogogo","uid":5,"age":35,"city":"北京","province":"北京","country":"中国","address":"中国北京市朝阳区建国门","location":{"lat":"39.718256","lon":"116.367910"}}
{ "index" : { "_index" : "twitter", "_id": 5} }
{"user":"朝阳区-老王","message":"Happy BirthDay My Friend!","uid":6,"age":50,"city":"北京","province":"北京","country":"中国","address":"中国北京市朝阳区国贸","location":{"lat":"39.918256","lon":"116.467910"}}
{ "index" : { "_index" : "twitter", "_id": 6} }
{"user":"虹桥-老吴","message":"好友来了都今天我生日,好友来了,什么 birthday happy 就成!","uid":7,"age":90,"city":"上海","province":"上海","country":"中国","address":"中国上海市闵行区","location":{"lat":"31.175927","lon":"121.383328"}}
在上面的命令中,我们使用了 bulk 指令来完成我们的操作。在输入命令时,我们需要特别的注意:千万不要添加除了换行以外的空格,否则会导致错误。在上面我们使用的index用来创建一个文档。为了说明问题的方便,我们在每一个文档里,特别指定了每个文档的id。当执行完我们的批处理 bulk 命令后,我们可以看到:
显然,我们的创建时成功的。因为我运行了两遍的原因,所以你看到的是 version 为2的返回结果。bulk 指令是高效的,因为一个请求就可以处理很多个操作。在实际的使用中,我们必须注意的是:一个好的起点是批量处理1000到5,000个文档,总有效负载在 5MB 到 15MB 之间。如果我们的 payload 过大,那么可能会造成请求的失败。如果你想更进一步探讨的话,你可以使用文件 accounts.json 来做实验。
如果你想查询到所有的输入的文档,我们可以使用如下的命令来进行查询:
POST twitter/_search
这是一个查询的命令,在以后的章节中,我们将再详细介绍。通过上面的指令,我们可以看到所有的已经输入的文档。
上面的结果显示,我们已经有6条生产的文档记录已经生产了。
我们可以通过使用 _count 命令来查询有多少条数据:
GET twitter/_count
上面我们已经使用了 index 来创建6条文档记录。我也可以尝试其它的命令,比如 create:
POST _bulk
{ "create" : { "_index" : "twitter", "_id": 1} }
{"user":"双榆树-张三","message":"今儿天气不错啊,出去转转去","uid":2,"age":20,"city":"北京","province":"北京","country":"中国","address":"中国北京市海淀区","location":{"lat":"39.970718","lon":"116.325747"}}
{ "index" : { "_index" : "twitter", "_id": 2 }}
{"user":"东城区-老刘","message":"出发,下一站云南!","uid":3,"age":30,"city":"北京","province":"北京","country":"中国","address":"中国北京市东城区台基厂三条3号","location":{"lat":"39.904313","lon":"116.412754"}}
{ "index" : { "_index" : "twitter", "_id": 3} }
{"user":"东城区-李四","message":"happy birthday!","uid":4,"age":30,"city":"北京","province":"北京","country":"中国","address":"中国北京市东城区","location":{"lat":"39.893801","lon":"116.408986"}}
{ "index" : { "_index" : "twitter", "_id": 4} }
{"user":"朝阳区-老贾","message":"123,gogogo","uid":5,"age":35,"city":"北京","province":"北京","country":"中国","address":"中国北京市朝阳区建国门","location":{"lat":"39.718256","lon":"116.367910"}}
{ "index" : { "_index" : "twitter", "_id": 5} }
{"user":"朝阳区-老王","message":"Happy BirthDay My Friend!","uid":6,"age":50,"city":"北京","province":"北京","country":"中国","address":"中国北京市朝阳区国贸","location":{"lat":"39.918256","lon":"116.467910"}}
{ "index" : { "_index" : "twitter", "_id": 6} }
{"user":"虹桥-老吴","message":"好友来了都今天我生日,好友来了,什么 birthday happy 就成!","uid":7,"age":90,"city":"上海","province":"上海","country":"中国","address":"中国上海市闵行区","location":{"lat":"31.175927","lon":"121.383328"}}
在上面,我们的第一个记录里,我们使用了 create 来创建第一个id为1的记录。因为之前,我们已经创建过了,所以我们可以看到如下的信息:
从上面的信息,我们可以看出来 index 和 create 的区别。index 总是可以成功,它可以覆盖之前的已经创建的文档,但是 create 则不行,如果已经有以那个 id 为名义的文档,就不会成功。
我们可以使用 delete 来删除一个已经创建好的文档:
POST _bulk
{ "delete" : { "_index" : "twitter", "_id": 1 }}
我们可以看到 id 为1的文档已经被删除了。我可以通过如下的命令来查看一下:
显然,我们已经把 id 为1的文档已经成功删除了。
我们也可以是使用 update 来进行更新一个文档。
POST _bulk
{ "update" : { "_index" : "twitter", "_id": 2 }}
{"doc": { "city": "长沙"}}
运行的结果如下:
同样,我们可以使用如下的方法来查看我们修改的结果:
我们可以清楚地看到我们已经成功地把城市 city 修改为“长沙”。
注意:通过 bulk API 为数据编制索引时,您不应在集群上进行任何查询/搜索。 这样做可能会导致严重的性能问题。
如果你对脚本编程比较熟悉的话,你可能更希望通过脚本的方法来把大量的数据通过脚本的方式来导入:
$ curl -s -H "Content-Type: application/x-ndjson" -XPOST localhost:9200/_bulk --data-binary @request_example.json
这里的 request_example.json 就是我们的 json 数据文件。我们可以做如下的实验:
下载测试数据:
wget https://github.com/liu-xiao-guo/elasticsearch-bulk-api-data/blob/master/es.json
然后在命令行中打入如下的命令:
curl -u elastic:123456 -s -H "Content-Type: application/x-ndjson" -XPOST localhost:9200/_bulk --data-binary @es.json
这里的 “elastic:123456” 是我们的 Elasticsearch 的用户名及密码,如果我们没有为我们的 Elasticsearch 设置安全,那么可以把 “-u elastic:123456” 整个去掉。正对配置有 https 的 Elasticsearch 服务器,我们可以使用如下格式的命令来进行操作:
curl --cacert /home/elastic/ca.crt -u elastic:123456 -s -H "Content-Type: application/x-ndjson" -XPOST localhost:9200/_bulk --data-binary @es.json
在上面, 我们使用 --cacert /home/elastic/ca.crt 来定义证书的地址。
等我们运行完上面的指令后,我们可以在 Kibana 中查看到我们的叫做 “bank_account” 的索引。
Open/close Index
Elasticsearch 支持索引的在线/离线模式。 使用脱机模式时,在群集上几乎没有任何开销地维护数据。 关闭索引后,将阻止读/写操作。 当您希望索引重新联机时,只需打开它即可。 但是,关闭索引会占用大量磁盘空间。 您可以通过将 cluster.indices.close.enable 的默认值从 true 更改为 false 来禁用关闭索引功能,以避免发生意外。
一旦 twitter 索引被关闭了,那么我们再访问时会出现如下的错误:
我们可以通过 _open 接口来重新打开这个 index:
Freeze/unfreeze index
冻结索引(freeze index)在群集上几乎没有开销(除了将其元数据保留在内存中),并且是只读的。 只读索引被阻止进行写操作,例如 docs-index 或 force merge。 请参阅冻结索引和取消冻结索引。
冻结索引受到限制,以限制每个节点的内存消耗。 每个节点的并发加载的冻结索引数受 search_throttled 线程池中的线程数限制,默认情况下为1。 默认情况下,即使已明确命名冻结索引,也不会针对冻结索引执行搜索请求。 这是为了防止由于误将冻结的索引作为目标而导致的意外减速。 如果要包含冻结索引做搜索,必须使用查询参数 ignore_throttled = false 来执行搜索请求。
我们可以使用如下的命令来对 twitter 索引来冻结:
POST twitter/_freeze
在执行上面的命令后,我们再对 twitter 进行搜索:
我们搜索不到任何的结果。按照我们上面所说的,我们必须加上 ignore_throttled=false 参数来进行搜索:
显然对于一个 frozen 的索引来说,我们是可以对它进行搜索的。我们可以通过如下的命令来对这个已经冻结的索引来进行解冻:
POST twitter/_unfreeze
一旦我们的索引被成功解冻,那么它就可以像我们正常的索引来进行操作了,而不用添加参数 ignore_throttled=false 来进行访问。
原文链接:Elastic 中国社区官方博客