• Elasticsearch如何创建索引,添加,删除,更新文档


    准备工作

    检查 es 及 Kibana 是否运行正常

    GET /
    
    • 1

    创建索引及文档

    创建一个名为 twitter 的索引(index),并插入一个文档(document)

    PUT twitter/_doc/1
    {
     "user": "GB",
     "uid": 1,
     "city": "Beijing",
     "province": "Beijing",
     "country": "China"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在关系型数据库中,需要使用DDL语句来创建数据库和表,然后才可以插入数据, es 不是必须的。

    我们可以看到在 Kibana 右边的窗口中有下面的输出:

    创建文档相关知识点

    • 在关系型数据库中,创建表(对应es的文档)我们必须指定数据类型和长度,而es可以动态创建索引 mapping(mapping 指数据类型) 。

    • 当我们创建文档时,如果没有创建对应的mapping,那么 es会根据所输入字段的数据猜测数据类型,例如上面的 user 被被认为是 text 类型,而 uid 将被猜测为整数类型。

    • 在插入文档时,如果该文档的 ID 已经存在,那么就更新现有的文档;如果该文档从来没有存在过,那么就创建新的文档。

    • 如果更新时该文档有新的字段并且这个字段在现有的 mapping 中没有出现,那么 es会根据 schem on write 的策略来推测该字段的类型,并更新当前的 mapping 到最新的状态。

    动态 mapping 可能会导致某些字段不是我们想要的数据类型,从而导致索引请求失败

    解决方案(显式 mapping)

    明确定义索引映射

    首先我们查看索引的数据类型(相当于关系型数据库的表结构)

    GET twitter/_mapping
    
    • 1

    返回结果

    {
      "twitter" : {
        "mappings" : {
          "properties" : {
            "city" : {
              "type" : "text",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            },
            "country" : {
              "type" : "text",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            },
            "province" : {
              "type" : "text",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            },
            "uid" : {
              "type" : "long"
            },
            "user" : {
              "type" : "text",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            }
          }
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    我们拿city字段来分析

        "city" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    mulit-field 字段

    关于两个type的解释

    为一个字段的不同目的创建不同方式索引,我们定义字段 city 为 text 类型, 但是text 类型的数据在摄入的时候会分词,这样它可以实现搜索的功能。 同时,这个字段又被定义为 keyword 类型,keyword 类型可以让我们针对它进行精确匹配(比如区分大小写,空格等符号),聚合和排序。

    关于两个keyword的解释

    第一个 keyword 可以是随便定义,第二个 keyword 才是它的类型

    mulit-field应用场景

    一个字段需要同时进行搜索和聚合

    如果我们仅需要搜索或者聚合,那么我们只需要定义一种类型,可以提高数据摄入的速度,同时它也可以减少磁盘的使用。

    es的数据类型:

    • text:全文搜索字符串
    • keyword:用于精确字符串匹配和聚合
    • date 及 date_nanos:格式化为日期或数字日期的字符串
    • byte,short,integer,long:整数类型 boolean:布尔类型
    • float,double,half_float:浮点数类型 分级的类型:object 及 nested

    数据类型相关知识

    • 一般情况下,es可以很好的理解文档的结构并自动创建映射(mapping)定义。

    • 自动创建映射使用无模式(schemaless)方法快速摄取数据,无需担心字段类型。

    • 为了在索引中获得更好搜索的结果和更好性能,我们有时需要需要手动定义映射。

    微调映射带来了一些优势

    • 减少磁盘上的索引大小(禁用自定义字段的功能) 仅索引感兴趣的字段(一般加速) 用于快速搜索或实时分析(例如聚合)
    • 正确定义字段是否必须分词为多个 token 或单个 token 定义映射类型,例如地理点、suggester、向量等

    假如,我们想创建一个索引 test,并且含有 id 及 message 字段。id 字段为 keyword 类型,而 message 字段为 text 类型,那么我们可以使用如下的方法来创建:

    PUT test
    {
      "mappings": {
        "properties": {
          "id": {
            "type": "keyword"
          },
          "message": {
            "type": "text"
          }
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    我们甚至可以使用如下的 API 来追加一个新的字段 age,并且它的类型为 long 类型:

    PUT test/_mapping
    {
      "properties": {
        "age": {
          "type": "long"
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    我们可以使用如下的命令来查看索引 test 的最终 mapping:

    GET test/_mapping
    
    • 1

    上面的命令显示的结果为:

    {
      "test" : {
        "mappings" : {
          "properties" : {
            "age" : {
              "type" : "long"
            },
            "id" : {
              "type" : "keyword"
            },
            "message" : {
              "type" : "text"
            }
          }
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在上面,我们可以看出来我们已经成功地创建了一个叫做 twitter 的 index。通过这样的方法,我们可以自动创建一个 index。如果大家不喜欢自动创建一个 index,我们可以修改如下的一个设置:

    PUT _cluster/settings
    {
        "persistent": {
            "action.auto_create_index": "false" 
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在绝大多数情况下,我们并不需要这么做,除非你知道你要做什么。如果你想开启自动创建索引,你必须配置 action.auto_create_index 以允许这些创建以下索引的组件:

    PUT _cluster/settings
    {
      "persistent": {
        "action.auto_create_index": ".monitoring*,.watches,.triggered_watches,.watcher-history*,.ml*"
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    如果我们向索引中插入一条文档这个文档并不马上可以进行搜索。而是要过1s

    以为有一个 refresh timer 来定时完成refresh操作。这个周期为1秒。这就是为什么 es可以实现秒级的搜索。当然这个 timer 的周期也可以在索引的设置中进行配置。如果我们想让我们的结果马上可以对搜索可见,我们可以用如下的方法:

    PUT twitter/_doc/1?refresh=true
    {
     "user": "GB",
     "uid": 1,
     "city": "Beijing",
     "province": "Beijing",
     "country": "China"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    上面的方式可以强制使 es进行 refresh 的操作,当然这个是有代价的。频繁的进行这种操作,会使es 变慢。所以我们一般设置refresh=wait_for。这样相当于一个同步的操作,它等待下一个 refresh 周期发生完后,才返回。

    PUT twitter/_doc/1?refresh=wait_for
    {
     "user": "GB",
     "uid": 1,
     "city": "Beijing",
     "province": "Beijing",
     "country": "China"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    es为我们创建了一个被叫做 _doc 的 type。自从 es 6.0 以后,一个 index 只能有一个 type。如果我们创建另外一个 type 的话,会报错。这里发现有一个版本(_version)信息,document 第一次创建 _id 为1它会显示为 1之后如果我们更改这个 document,它的版本会每次自动增加1。

    POST twitter/_doc/1
    {
     "user": "GB",
     "uid": 1,
     "city": "Shenzhen",
     "province": "Guangdong",
     "country": "China"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    创建文档

    PUT twitter/_create/1
    {
     "user": "GB",
     "uid": 1,
     "city": "Shenzhen",
     "province": "Guangdong",
     "country": "China"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    如果文档已经存在的话,会报错

    另一种写法:在请求时带上 op_type。它可以有两种值:index 及 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"
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    curl写法

    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"
    }'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    我们可以通过如下的命令来查看被修改的文档:

    GET twitter/_doc/1
    
    • 1

    我们可以看到在右边显示了我们被修改的文档的结果。

    如果我们只想得到这个文档的 _source 部分,我们可以使用如下的命令格式:

    GET twitter/_doc/1/_source
    
    • 1

    在 es7.0 之后,在 type 最终要被废除的情况下,建议使用如下的方法来获得 _source:

    GET twitter/_source/1
    
    • 1

    自动 ID 生成

    如果我们不指定文档的 ID,创建一个新的文档,转而让 es自动帮我们生成一个 ID,这样的速度更快。在这种情况下,我们必须使用 POST,而不是 PUT,例如:

    POST my_index/_doc
    {
     "content": "this is really cool"
    }
    
    • 1
    • 2
    • 3
    • 4

    返回的结果:

    {
     "_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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在正常的情况下,当前复制组的所有分片都执行了索引操作时,es 从索引操作返回。

    设置异步复制允许我们在主分片上同步执行索引操作,在副本分片上异步执行。这样,API 调用会更快地返回响应操作。我们可以这样来进行调用:

    POST my_index/_doc?replication=async
    {
     "content": "this is really cool"
    }
    
    • 1
    • 2
    • 3
    • 4

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

    GET twitter/_doc/1/_source
    
    • 1

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

    {
     "user" : "双榆树-张三",
     "message" : "今儿天气不错啊,出去转转去",
     "uid" : 2,
     "age" : 20,
     "city" : "北京",
     "province" : "北京",
     "country" : "中国",
     "address" : "中国北京市海淀区",
     "location" : {
     "lat" : "39.970718",
     "lon" : "116.325747"
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

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

    GET twitter/_doc/1?_source=city,age,province
    
    • 1

    如果你想一次请求查找多个文档,我们可以使用 _mget 接口:

    GET _mget
    {
      "docs": [
        {
          "_index": "twitter",
          "_id": 1
        },
        {
          "_index": "twitter",
          "_id": 2
        }
      ]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    我们也可以只获得部分字段:

    GET _mget
    {
      "docs": [
        {
          "_index": "twitter",
          "_id": 1,
          "_source":["age", "city"]
        },
        {
          "_index": "twitter",
          "_id": 2,
          "_source":["province", "address"]
        }
      ]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这里,我们同时请求 id 为 1 和 2 的两个文档。

    我们也可以简单地写为:

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

    它和上面的做一个是一样的。使用一个命令同时获取 id 为 1 及 2 的文档。

    在上面当我们写入数据时,我们有意识地把文档的 id 在命令中写了出来。如果我们不写这个 id 的话,ES 会帮我们自动生产一个 id:

    POST twitter/_doc/
    
    • 1

    我可以看到右边的一个 id 像是一个随机的数值,同时我们可以看到它的一个版本信息为1。在实际的需要有大量导入数据的情况下,我们建议让系统自动帮我们生成一个 id,这样可以提高导入的速度。假如我们指定一个 id,通常 ES 会先查询这个 id 是否存在,然后在觉得是更新之前的文档还是创建一个新的文档。这里是分两步走。显然它比直接创建一个文档要慢!

    我们也可以看出来系统所给出来的字段都是以下划线的形式给出来的,比如:_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"
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    如上面所示,我们使用 PUT 命令来对我们的 id 为1的文档进行修改。我们也可以使用我们上面学过的 GET 来进行查询:

    GET twitter/_doc/1
    
    • 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"
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    显然,我们的这个文档已经被成功修改了。

    我们使用 PUT 的这个方法,每次修改一个文档时,我们需要把文档的每一项都要写出来。这对于有些情况来说,并不方便,我们可以使用如下的方法来进行修改:

    POST twitter/_update/1
    {
      "doc": {
        "city": "成都",
        "province": "四川"
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    我们可以使用如上的命令来修改我们的部分数据。同样我们可以使用 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": "中国"
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

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

    POST edd/_update_by_query
    {
      "query": {
        "match": {
          "姓名": "张彬"
        }
      },
      "script": {
        "source": "ctx._source[\"签到状态\"] = params[\"签到状态\"]",
        "lang": "painless",
        "params" : {
          "签到状态":"已签到"
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在上面我们使用一个中括号并 escape 引号的方式来操作。有关 Painless 的编程,你可以参阅文章 “Elasticsearch: Painless script 编程”。

    我们可以通过上面的方法搜寻 user 为 GB 的用户,并且把它的数据项修改为:

        "city" : "上海",
        "province": "上海",
        "country": "中国"
    
    • 1
    • 2
    • 3

    我们也可以通过 update 接口,使用 script 的方法来进行修改。这个方法也是需要知道文档的 id:

    POST twitter/_update/1
    {
      "script" : {
          "source": "ctx._source.city=params.city",
          "lang": "painless",
          "params": {
            "city": "长沙"
          }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在我们使用上面的方法更新文档时,如果当前的文档 id 不存在,那么我们甚至可以使用 upsert 属性来创建一个文档:

    POST twitter/_update/1
    {
      "script" : {
          "source": "ctx._source.city=params.city",
          "lang": "painless",
          "params": {
            "city": "长沙"
          }
      },
      "upsert": {
        "city": "长沙"
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    和前面的方法一下,我们可以使用 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"
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    如果你涉及到多个客户端同时更新一个索引的情况,你需要阅读文章 “深刻理解文档中的 verision 及 乐观并发控制”。

    我们甚至可以使用 _update 接口使用 ctx[‘_op’] 来达到删除一个文档的目的,比如:

    POST twitter/_update/1
    {
      "script": {
        "source": """
        if(ctx._source.uid == 1) {
          ctx.op = 'delete'
        } else {
          ctx.op = "none"
        }
        """
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    当检测文档的 uid 是否为 1,如果为 1 的话,那么该文档将被删除,否则将不做任何事情。

    我还可以充分使用 script 的一些高级操作,比如我们可以通过如下的方法来添加一个崭新的字段:

    POST twitter/_update/1
    {
      "script" : {
          "source": "ctx._source.newfield=4",
          "lang": "painless"
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    通过上面的操作,我们可以发现,我们新增加了一个叫做 newfield 的字段。当然我们也可以使用如下的方法来删除一个字段:

    POST twitter/_update/1
    {
      "script" : {
          "source": "ctx._source.remove(\"newfield\")",
          "lang": "painless"
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在上面的命令中,我们通过 remove 删除了刚才被创建的 newfiled 字段。我们可以通过如下的命令来进行查看:

    GET twitter/_doc/1
    
    • 1

    在这里请注意的是:一旦一个字段被创建,那么它就会存在于更新的 mapping 中。即便针对 id 为 1 的文档删除了 newfield,但是 newfield 还将继续存在于 twitter 的 mapping 中。我们可以使用如下的命令来查看 twitter 的 mapping:

    GET twitter/_mapping
    
    • 1

    这里值得注意是:对于多用户,我们可以从各个客户端同时更新,这里可能会造成更新数据的一致性问题。为了避免这种现象的出现,请阅读我的另外一篇文章 “Elasticsearch:深刻理解文档中的 verision 及乐观并发控制”。

    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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    检查一个文档是否存在
    有时候我们想知道一个文档是否存在,我们可以使用如下的方法:

    HEAD twitter/_doc/1
    
    • 1

    这个 HEAD 接口可以很方便地告诉我们在 twitter 的索引里是否有一 id 为1的文档:

    上面的返回值表面 id 为1的文档时存在的。

    删除文档

    根据ID删除文档

    DELETE twitter/_doc/1
    
    • 1

    根据条件删除

    POST twitter/_delete_by_query
    {
      "query": {
        "match": {
          "city": "上海"
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这样我们就把所有的 city 是上海的文档都删除了。

    检查一个索引是否存在

    我们可以使用如下的命令来检查一个索引是否存在:

    HEAD twitter
    
    • 1

    如果 twitter 索引存在,那么上面的命令会返回:

    200 - OK
    
    • 1

    否则就会返回:

    {"statusCode":404,"error":"Not Found","message":"404 - Not Found"}
    
    • 1

    删除索引

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

    DELETE twitter
    
    • 1

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

    批处理命令

    上面我们已经了解了如何使用 REST 接口来创建一个 index,并为之创建(Create),读取(Read),修改(Update),删除文档(Delete)(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"}}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在上面的命令中,我们使用了 bulk 指令来完成我们的操作。在输入命令时,我们需要特别的注意:千万不要添加除了换行以外的空格,否则会导致错误。在上面我们使用的 index 用来创建一个文档。为了说明问题的方便,我们在每一个文档里,特别指定了每个文档的 id。当执行完我们的批处理 bulk 命令后,我们可以看到:

    显然,我们的创建时成功的。因为我运行了两遍的原因,所以你看到的是 version 为 2 的返回结果。bulk 指令是高效的,因为一个请求就可以处理很多个操作。在实际的使用中,我们必须注意的是:一个好的起点是批量处理 1,000 到 5,000 个文档,总有效负载在 5MB 到 15MB 之间。如果我们的 payload 过大,那么可能会造成请求的失败。如果你想更进一步探讨的话,你可以使用文件 accounts.json 来做实验。更多是有数据可以在地址 加载示例数据 | Kibana 用户手册 | Elastic 进行下载。

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

    POST twitter/_search
    
    • 1

    这是一个查询的命令,在以后的章节中,我们将再详细介绍。通过上面的指令,我们可以看到所有的已经输入的文档。

    上面的结果显示,我们已经有6条生产的文档记录已经生产了。

    我们可以通过使用 _count 命令来查询有多少条数据:

    GET twitter/_count
    
    • 1

    上面我们已经使用了 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"}}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在上面,我们的第一个记录里,我们使用了 create 来创建第一个 id 为1的记录。因为之前,我们已经创建过了,所以我们可以看到如下的信息:

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

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

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

    我们可以看到 id 为1的文档已经被删除了。我可以通过如下的命令来查看一下:

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

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

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

    运行的结果如下:

    同样,我们可以使用如下的方法来查看我们修改的结果:

    我们可以清楚地看到我们已经成功地把城市 city 修改为 “长沙”。

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

    如果你对脚本编程比较熟悉的话,你可能更希望通过脚本的方法来把大量的数据通过脚本的方式来导入:

    $ curl -s -H "Content-Type: application/x-ndjson" -XPOST localhost:9200/_bulk --data-binary @request_example.json
    
    • 1

    这里的 request_example.json 就是我们的 JSON 数据文件。我们可以做如下的实验:

    下载测试数据:

    wget https://github.com/liu-xiao-guo/elasticsearch-bulk-api-data/blob/master/es.json
    
    • 1

    然后在命令行中打入如下的命令:

    curl -u elastic:123456 -s -H "Content-Type: application/x-ndjson" -XPOST localhost:9200/_bulk --data-binary @es.json
    
    • 1

    这里的 “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
    
    • 1

    在上面, 我们使用 --cacert /home/elastic/ca.crt 来定义证书的地址。

    等我们运行完上面的指令后,我们可以在 Kibana 中查看到我们的叫做 “bank_account” 的索引。

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

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

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

    关于关闭索引有很多用例:

    它可以禁用基于日期的索引(按日期存储其记录的索引)— 例如,当你将索引保留一周、一个月或一天,并且你希望保留固定数量的旧索引(即 2 个月 旧)在线和一些离线(即从 2 个月到 6 个月)。
    当你搜索集群的所有 active 索引并且不想搜索某些索引时(在这种情况下,使用 alias 是最好的解决方案,但你可以使用具有关闭索引的 alias 来实现相同的效果)。

    Freeze/unfreeze index

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

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

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

    POST twitter/_freeze
    
    • 1

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

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

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

    POST twitter/_unfreeze
    
    • 1

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

    下期更精彩

    在这篇文章中,我们详细地介绍了如果在 es 中创建索引,文档,并对他们进行更改,删除,查询的操作。在接下来的文章里,我们将重点介绍如何对 Elasticsearch 里的 index 进行搜索和分析。

  • 相关阅读:
    发现了二分查找的秘密
    android 复制粘贴监听
    javaIO流05:FileReader和Filewriter
    类型装欢,if语句里头的条件值
    22道js输出顺序问题,你能做出几道
    《opencv学习笔记》-- 形态学滤波:形态学梯度、开运算、闭运算、顶帽、黑帽
    Mysql-题目02
    手动安装nginx,ssl双证书引入。
    脑部神经系统结构模式图,大脑的神经结构示意图
    【编程之路】面试必刷TOP101:动态规划(78-82,Python实现)
  • 原文地址:https://blog.csdn.net/qq_29917503/article/details/126750617