开始使用Elasticsearch (1): 如何创建index,添加、删除、更新文档

05/22 15:45
阅读数 478

本文内容来自 https://blog.csdn.net/UbuntuTouch/article/details/99481016 ,有删减和文字修正。

在开始使用ES之前, 请安装好ES & Kibana. 本文仅以win10下的 ElasticSearchS 7.7 版本为例, 不涉及linux系统部分.

ES里的接口都是通过REST接口来实现的,通过HTTP的PUT/GET/POST/DELETE来实现数据的Create/Read/Update/Delete操作,并以JSON数据格式返回。

检查ES及Kibana是否运行正常

浏览器访问 http://localhost:9200 查看ES的状态:

浏览器访问 http://localhost:5601 打开Kibana操作界面(会自动跳转到http://localhost:5601/app/kibana#/home), 在欢迎界面的"Try our sample data"和"Explore on my own" 两个按钮中随便选择一个即可。

点击左下角的Dev Tools打开操作界面:

上图是操作界面,右上角的三角按钮是执行当前请求按钮,右边是执行结果显示区。

查看ES信息

就像我们之前在浏览器其中输入地址 http://localhost:9200 看到的效果一样,我们直接输入命令

GET /

执行后可以看到:

依次显示的是节点名称,集群名称, 集群id,版本号等信息。

在输入时, Kibana会自动提示可以使用的命令

创建一个索引及文档

现在我们来创建一个叫twitter的索引(index),并插入一个文档(document)。 在传统的关系型数据库(RDBMS)中, 需要使用专用的语句(DDL)来建立数据库、表,然后才能插入相应的记录。但是对于ES来说, 没有这个必要。我直接在操作区输入一下代码:

PUT twitter/_doc/1
{
  "user": "GB",
  "uid": 1,
  "city": "Beijing",
  "province": "Beijing",
  "country": "China"
}

在右边结果显示区可以看到以下信息:

我们已经成功地创建了一个叫做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,则应在上面的设置action.auto_create_index中添加其他索引名称,也可以在config/elasticsearch.yml里进行配置。

通过上面的方法写入到ES的文档,不会在搜索结果中立即显示。这是因为在ES的设计中,有一个叫做refresh的操作。它可以使更改可见以进行搜索的操作。通常会有一个refresh timer来定时完成这个操作。这个周期为1秒。这也是我们通常所说的ES可以实现秒级的搜索。当然这个timer的周期也可以在索引的设置中进行配置。如果想要写入文档立即显示在搜索结果中,我们可以用如下的方法强制让ES立即进行刷新(refresh):

PUT twitter/_doc/1?refresh=true
{
  "user": "GB",
  "uid": 1,
  "city": "Beijing",
  "province": "Beijing",
  "country": "China"
}

频繁的强制刷新操作会导致ES运行速度变慢。另外一种方式是通过设置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操作指南”。

上面的文档新增的操作, 创建了一个被叫做_doctype。自从ES 6.0以后,一个index只能有一个type。如果我们创建另外一个type的话,系统会抛出错误。在返回信息中有一个 版本(_version)的key,默认从1开始,每次更新之后都会增加1。比如我们输入:

POST twitter/_doc/1
{
  "user": "GB",
  "uid": 1,
  "city": "Shenzhen",
  "province": "Guangdong",
  "country": "China"
}

现在的版本号已经增加到4。每次执行POST或PUT操作后,版本号都会增加1。

我们还可以使用_create接口来实现新增操作:

PUT twitter/_create/1
{
  "user": "GB",
  "uid": 1,
  "city": "Shenzhen",
  "province": "Guangdong",
  "country": "China"
}

如果文档已经存在,就会返回错误:

也可以在 _doc 请求时带上参数op_type以实现与 _create 方法同样的操作:

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的值有2个: index, create

Kibana的操作界面可以直接复制出来curl的命令:

自动生成ID

上面创建新文档的操作中,我们都指定了文档的id,在实际操作中没必要这样做,反而会因为需要取判断id是否已存在而影响操作速度。ES可以自动帮我们生成一个唯一的id, 但是不能用前面的PUT请求方式, 必须使用POST。why? 因为REST中定义的POST请求是新增操作,而PUT是修改操作。e.g:

POST my_index/_doc
{
  "content": "this is really cool"
}

返回结果:

ES已经自动帮我们分配了一个ID。

查看文档

GET twitter/_doc/1

如果文档不存在:

获取部分信息

如果我们只对 source 的内容感兴趣,可以使用:

GET twitter/_doc/1/_source

这样我们直接得到了 source 的信息:

上面的写法已经不推荐, 建议使用新的写法: GET twitter/_doc/1?_source

我们也可以只获取source的部分字段:

GET twitter/_doc/1?_source=user,uid,city

使用_mget接口请求多个文档

首先我们在twitter新增一个id为2的文档:

PUT twitter/_doc/2
{
  "user": "Aben",
  "uid": 1,
  "city": "Guangzhou",
  "province": "Guangdong",
  "country": "China"
}

方法1: 输入命令:

GET _mget
{
  "docs": [
    {
      "_index": "twitter",
      "_id": 1
    },
    {
      "_index": "twitter",
      "_id": 2
    }
  ]
}

也可以只获得部分字段:


GET _mget
{
  "docs": [
    {
      "_index": "twitter",
      "_id": 1,
      "_source":["uid","city"]
    },
    {
      "_index": "twitter",
      "_id": 2,
      "_source":["uid","province"]
    }
  ]
}

方法2:

使用命令:

GET twitter/_doc/_mget
{
  "ids": ["1", "2"]
}

注意:此方法在7.7版本下会警告:

#! Deprecation: [types removal] Specifying types in multi get requests is deprecated.

修改文档

方法1: PUT + doc 全数据更新

按照REST的要求,PUT操作用来修改数据。

PUT twitter/_doc/1
{
  "user": "Aben2",
  "uid": 1,
  "city": "Hefei",
  "province": "Anhui",
  "country": "China",
  "location":{
     "lat":"29.084661",
     "lon":"111.335210"
 }
}

查询数据:

注意: 使用PUT的_doc方法操作, 必须把所有的项目都传入, 否则会丢失数据

方法2: POST + update 部分数据更新

POST twitter/_update/1
{
  "doc":{
    "city":"Chengdu",
    "province":"Sichuan"
  }
}

执行后GET一下看看数据是否更新。

按条件查询批量更新

在关系型数据库中,我们通常是对数据库进行搜索,然后才进行修改。在这种情况下,通常事先并不知道文档的id,需要通过查询的方式来进行查询,然后再修改。ES也提供了相应的REST接口。

POST twitter/_update_by_query
{
  "query": {
    "match": {
      "user": "Aben2"
    }
  },
  "script": {
    "source": "ctx._source.city = params.city;ctx._source.province = params.province;ctx._source.country = params.country",
    "lang": "painless",
    "params": {
      "city": "上海",
      "province": "上海",
      "country": "中国"
    }
  }
}

上面这段代码, 会搜索'user'='Aben2'的文档, 并按照script.source的更新操作, 把source.params的数据更新到文档。

我们前面设置的是id=1的文档符合条件, 现在去看看文档信息:

对于那些名字是中文字段的文档来说,在painless语言中,直接打入中文字段名字,并不能被认可。我们可以使用如下的方式来操作:

POST edd/_update_by_query
{
  "query": {
    "match": {
      "姓名": "张彬"
    }
  },
  "script": {
    "source": "ctx._source["签到状态"] = params["签到状态"]",
    "lang": "painless",
    "params" : {
      "签到状态":"已签到"
    }
  }
}

UPSERT一个文档

我们前面给的update数据的操作,如果指定ID的文档不存在,ES会返回错误: 'document missing'。 术语upsert表示更新或插入,即如果存在则更新文档,否则插入新文档,这类似于mysql中的replace。

doc_as_upsert参数检查具有给定ID的文档是否已经存在,并将提供的doc与现有文档合并。 如果不存在具有给定ID的文档,则会插入具有给定文档内容的新文档。

下面的示例使用doc_as_upsert合并到ID为3的文档中,或者如果不存在则插入一个新文档:

POST /twitter/_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是上海的文档都删除了。

删除一个index

删除一个index是非常直接的。我们可以直接使用如下的命令来进行删除:

DELETE twitter

当我们执行完这一条语句后,所有的在twitter中的文档都被删除。

批处理命令

上面我们已经了解了如何使用REST接口来创建一个index,并为之创建,读取,修改,删除文档(CRUD)。因为每一次操作都是一个REST请求,对于大量的数据进行操作的话,这个显然比较慢。ES提供了一个批量处理的命令。这样我们通过一次REST请求,就可以完成很多的操作。这无疑是一个非常大的好处。下面,我们来介绍一下这个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命令后,我们可以看到:

显然,我们的创建是成功的。而且上面的命令还可以执行多次,相当于UPSERT的(插入/更新)批量操作而不会出现因为数据已存在而插入失败。在实际的使用中,我们必须注意的是:一个好的起点是批量处理1000到5,000个文档,总有效负载在5MB到15MB之间。如果我们的payload过大,那么可能会造成请求的失败。如果你想更进一步探讨的话,你可以使用文件 accounts.json 来做实验。

如果你想查询到所有的输入的文档,我们可以使用如下的命令来进行查询:

POST twitter/_search

也可以使用 GET 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的记录。因为之前已经创建过了,所以我们可以看到如下的信息:

从上面的信息,我们可以看出来indexcreate的区别。index总是可以成功,它可以覆盖之前的已经创建的文档,但是create则不行,如果已经有以那个id为名义的文档,就不会成功。

我们可以使用delete来删除一个已经创建好的文档:

POST _bulk
{ "delete" : { "_index" : "twitter", "_id": 1 }}

现在我们来查询一下id=1的文档:

GET twitter/_doc/1

显然,我们已经把id为1的文档成功删除了。

我们也可以使用update来更新一个文档。

POST _bulk
{ "update" : { "_index" : "twitter", "_id": 2 }}
{"doc": { "city": "长沙2020"}}

我们现在来查看一下数据:

注意:通过bulk API为数据编制索引时,您不应在集群上进行任何查询/搜索。 这样做可能会导致严重的性能问题。

Open/Close Index 打开/关闭索引

ES支持索引的在线/离线模式。 使用离线模式时,在群集上几乎没有任何开销地维护数据。 关闭索引后,将阻止读/写操作。 当您希望索引重新联机时,只需打开它即可。 但是,关闭索引会占用大量磁盘空间。 您可以通过将cluster.indices.close.enable的默认值从true更改为false来禁用关闭索引功能,以避免发生意外。

POST twitter/_close

一旦twitter索引被关闭了,那么我们再访问时会出现如下的错误:

我们可以通过_open接口来重新打开这个index:

POST twitter/_open

Freeze/Unfreeze index 冻结/解冻索引

冻结索引(freeze index)在群集上几乎没有开销(除了将其元数据保留在内存中),并且是只读的。 只读索引被阻止进行写操作,例如docs-indexforce merge。 请参阅冻结索引取消冻结索引

冻结索引受到限制,以限制每个节点的内存消耗。 每个节点的并发加载的冻结索引数受search_throttled线程池中的线程数限制,默认情况下为1。 默认情况下,即使已明确命名冻结索引,也不会针对冻结索引执行搜索请求。 这是为了防止由于误将冻结的索引作为目标而导致的意外减速。 如果要包含冻结索引做搜索,必须使用查询参数ignore_throttled=false来执行搜索请求。

我们可以使用如下的命令来对twitter索引来冻结:

POST twitter/_freeze

在执行上面的命令后,我们再对twitter进行搜索:

我们搜索不到任何的结果。按照我们上面所说的,我们必须加上ignore_throttled=false参数来进行搜索:

显然对于一个frozen的索引来说,我们是可以对它进行搜索的。我们可以通过如下的命令来对这个已经冻结的索引来进行解冻:

POST twitter/_unfreeze

一旦我们的索引被成功解冻,那么它就可以像我们正常的索引来进行操作了,而不用添加参数 ignore_throttled=false 来进行访问。



总结

执行当前代码块, 可以使用快捷键: Ctrl + Enter

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
在线直播报名
返回顶部
顶部