开始使用Elasticsearch (1)

1,738 阅读17分钟

在今天的这篇文章中,我们来主要介绍一下如何使用REST接口来对Elasticsearch进行操作。为了完成这项工作,我们必须完成如下的步骤:

  1. 安装 Elasticsearch。请参阅文章“如何在Linux,MacOS及Windows上进行安装Elasticsearch”。把Elasticsearch运行起来。
  2. 安装Kiban。请参阅文章“如何在Linux及MacOS上安装Elastic栈中的Kibana”。把Kibana运行起来。
  3. 熟悉有关于Elastic栈的一些最基本的概念。请参阅文章“Elasticsarch中的一些重要概念:cluster, node, shards及replica”。这些概念对我们如下的练习有非常多的帮助。

有了上面最基本的一些安装及概念,我们就很容进行下面的讲解了。在如下所展示的所有的scripts可在地址 github.com/liu-xiao-gu… 找到。

什么是JSON?

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999的一个子集。在Elasticsearch中,所以的数据都是以JSON的格式来进行表述的。这个和其它的有些数据库,比如 Solr,它支持更多格式的数据,比如xml, csv等。

我们来看一下一个简单的JSON格式的数据表达:

{
  "name" : "Elastic",
  "location" : {
    "state" : "CA",
    "zipcode" : 94123
  }
}

这个看起来非常简单直接。如果大家熟悉 Javascript的话,你会发现它和Javascript里的Object非常地相似。

什么是REST接口?

相信很多做过微服务架构的开发者来说,你们可能对REST接口再熟悉不过了。REST即表述性状态传递(英文:Representational State Transfer,简称REST)是Roy Fielding博士在2000年他的博士论文中提出来的一种软件架构风格。REST是一种规范。即参数通过封装后进行传递,响应也是返回的一个封装对象。一个REST的接口就像如下的接口:

example.com/customers/1…

我们可以通过:

HTTP GET
HTTP POST
HTTP PUT
HTTP DELETE
HTTP PATCH

来对数据进行增加(Create),查询(Read),更新(Update)及删除(Delete)。也就是我们通常说是的CRUD。

Elasticsearch里的接口都是通过REST接口来实现的。我们在一下的章节里来重点介绍一下是如果使用REST接口来实现对数据的操作及查询的。

检查Elasticsearch及Kibana是否运行正常

我们首先在我们的浏览器中输入如下地址:http://localhost:9200。查看一下我们的输出:

如果你能看到如上的信息输出,表明我们的Elasticsearch是处于一个正常运行的状态。

同时,我们在浏览器中输入地址:http://localhost:5601。在浏览器中,我们查看输出的信息:

上面显示了Kibana的界面。由于Kibana的功能有很多。我们在今天的培训中就不一一介绍了。我们着重使用在上面显示的 “Dev Tools”菜单里的功能。当我们点击它的时候,我们可以看到如下的界面。

当我们执行命令时,我们必须点击左边窗口里的那个绿色的播放按钮。命令所执行显示的结果将在右边展示。在接下的所有练习中,我们都将使用这样的操作来进行。

查看Elasticsearch信息

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

GET /

我们就可以看到如下的信息:

在这里我们可以看到Elasticsearch的版本信息及我们正在使用的Elasticsearch的Cluster名称等信息。

我们发现当我们打入一个命令时,Kibana会帮我们自动地显示可以输入的选择项,它具有autocomplete的功能。这个对我们打入我们所需要的命令非常用用。我们有时候不需要记那么多。

创建一个索引及文档

我们接下来创建一个叫做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" 
    }
}

详细设置请参阅链接

通常对一个通过上面方法写入到Elasticsearch的文档,在默认的情况下并不马上可以进行搜索。这是因为在Elasticsearch的设计中,有一个叫做refresh的操作。它可以帮在Lucene里的离散的Segments进行合并,并使新进入的文档变为搜索可见。通常会有一个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"
}

它也创建了一个被叫做_doc的type。自从Elasticsearch 6.0以后,一个index只能有一个type。如果我们创建另外一个type的话,系统会告诉我们是错误的。这里我们也会发现有一个版本信息,它显示的是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"
}

如果文档已经存在的话,我们会收到一个错误的信息:

我们必须指出的是,如果你是在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

这样我们可以直接得到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的两个文档。

在上面当我们写入数据时,我们有意识地把文档的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
{
  "script": {
    "source": "ctx._source.city = params.city;ctx._source.province = params.province;ctx._source.country = params.country",
    "lang": "painless",
    "params": {
      "city": "上海",
      "province": "上海",
      "country": "中国"
    },
    "query": {
      "match": {
        "user": "GB"
      }
    }
  }
}

对于那些名字是中文字段的文档来说,在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"
  }
}

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

删除一个index

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

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的返回结果。如果你想查询到所有的输入的文档,我们可以使用如下的命令来进行查询:

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修改为“长沙”。

Open/close Index

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

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

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

总结

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

如果你想了解更多关于Elastic Stack相关的知识,请参阅我们的官方网站:www.elastic.co/guide/index…

下一步

接下来,我们可以学习教程: