Chinaunix首页 | 论坛 | 博客
  • 博客访问: 25351
  • 博文数量: 13
  • 博客积分: 464
  • 博客等级: 下士
  • 技术积分: 140
  • 用 户 组: 普通用户
  • 注册时间: 2010-09-19 21:58
文章分类

全部博文(13)

分类: NOSQL

2017-01-24 09:12:45

== 起步 ==

Elasticsearch是一个高度可扩展性的开源全文搜索和分析引擎,能够快速和接近实时地存储、搜索和分析大量数据,通常作为底层引擎或技术支持应用程序需要的复杂搜索需求

以下是一些适用于Elasticsearch的简单使用案例:
- 一个在线的Web应用支持客户自行搜索出售的产品,可以使用Elasticsearch存储整个产品类目和详细介绍,并为客户提供搜索功能和搜索建议
- 收集和分析日志或交易数据,了解这些数据的趋势、统计、摘要和异常,可以使用Logstash收集、汇总和处理数据,并将结果数据写入Elasticsearch,然后就能搜索和聚合任何感兴趣的信息了
- 一个价格通知平台,当某件商品的价格低于或高于设定价格时通知客户,可将所有商品价格写入Elasticsearch,利用反向搜索(过滤器)匹配商品价格和客户设定价格,一旦发现匹配即通知客户
- 一个商业智能需求,要快速分析大量数据并展现结果,可以使用Elasticsearch存储数据,使用Kibana建立自定义仪表盘展示感兴趣的数据,此外,还能使用Elasticsearch的聚合功能在存储的数据上执行复杂的商业智能查询

在该教程的后续部分,将指导完成启动Elasticsearch,执行如索引、搜索和修改数据等基础操作,在教程结束后,将会对什么是Elasticsearch以及它是如何工作的有一个良好的认识,希望在将它用于复杂搜索或数据挖掘上能有启发


== 基础概念 ==

先理解一些Elasticsearch的核心概念,将非常有利于后续的学习

【近乎实时(NRT)】

Elasticsearch是一个近乎实时的搜索平台,意思是从索引(Vt.)一个文档到该文档能被搜索到,会有一个短暂的延迟(1秒)

【集群(Cluster)】

集群是一个或多个节点的集合,它们一起存储整个数据并提供联合索引(n.),且集群上的搜索操作是跨越所有节点的

集群由一个唯一的名称标识,默认为"elasticsearch",每个节点通过此名称加入集群以成为集群的一部分,且一个节点只能加入一个集群,确保在不同的环境下不会复用相同的集群名称,否则可能会以节点加入错误的集群而告终,例如应该用logging-dev、logging-stage和logging-prod分别对开发、预发布和生产这三套集群进行命名

只有一个节点的集群也是完全有效且运行良好的,另外,也可以有多个命名唯一相互独立的集群

【节点(Node)】

每个节点都是单个服务器,作为集群的一部分参与数据存储、集群索引和执行搜索,同集群一样,节点由启动时生成的一个随机UUID当作名称标识,也可自定义节点名,必须唯一,节点名用于在集群中进行节点管理操作时指定某个节点

一个节点能通过配置集群名以加入该集群,默认的,每个节点都会加入到名为"elasticsearch"的集群中,这意为如果启动多个节点,它们又能通过网络互相发现对方,那么它们会自动组建并加入到这个名为"elasticsearch"集群中

在一个集群里,能有任意多个节点,此外,如果在网络中没有其他运行着的Elasticsearch节点,当启动一个节点时将默认形成一个名为"elasticsearch"的只有单个节点的集群

【索引(Index n.)】

每个索引(n.)都是一个有相似属性的文档的集合,例如,一个客户信息的索引,一个产品类目的索引,或一个订单数据的索引,每个索引由一个唯一的名称标识,名称必须全部小写,索引名用于在执行索引(Vt.)、搜索、修改和删除数据时指定某个索引

在集群中,可以定义任意多个索引(n.)

【类型(Type)】

在索引(n.)里,可以定义一个或多个类型,每个类型都是一个索引的逻辑种类或分区,通常,每个为文档定义的类型都有一组相同的字段,例如,将一个博客应用的所有数据存储在一个索引里,可以定义一个类型为用户数据,一个类型为文章数据,另一个类型为评论数据

【文档(Document)】

每个文档都是一个被索引的数据的基础单元,例如,每个客户是一个文档,每个产品是一个文档,每个订单是一个文档,文档是JSON格式的,这一种普遍使用的网络数据互换格式

在索引或类型中,可以存储任意多个文档,尽管一个文档物理上储存在某个索引(n.)里,但实际每个文档都逻辑上分配给索引里的某个类型里

【分片(Shards)和复本(Replicas)】

一个索引能存储大量数据,甚至超过单个节点的硬件限制,但一个占用1TB磁盘空间的亿级文档的索引,虽然可能不会装满单个节点的磁盘,但在一个节点上对这个索引执行搜索请求可能会非常慢

为解决这个问题,Elasticsearch提供了把索引拆分到多个分片的能力,当创建一个索引时,可以定义想要的分片个数,每个分片都是功能齐全(fully-functional)且独立的索引,能被托管在集群的任何节点上

分片有二个主要功能:
- 能够水平拆分数据
- 能够跨分片的并行执行操作,以此提升性能和吞吐量

分片是如何分布的和文档如何被聚合回搜索请求的,这些都由Elasticsearch自行管理,完全对用户透明

失效(failures)任何时候都有可能发生,当分片或节点莫名下线或不明原因丢失时,有一个故障切换(failover)机制是非常有用和高度推荐的,为此,Elasticsearch能够为索引的分片生成一个或多个拷贝,称为分片复本

复本有二个主要功能:
- 当分片或节点失效时提供高可用性,由于这个原因,复本分片绝不会分配到跟它所拷贝的原始分片在同一个节点上
- 能够扩展搜索吞吐量,因为搜索操作能被并行地执行在所有的复本上

总结一下,每个索引都能拆分到多个分片中,还能拷贝零份(即没有复本)或多份,一旦拷贝,每个索引将有主分片(用于拷贝的原始分片)和复本分片(主分片的拷贝),分片和复本的个数能在索引被创建时定义,在索引建立后,可以随时动态调整复本个数,但不能改变分片个数

默认的,Elasticsearch里的每个索引都会被分配5个主分片和1个复本,这意为如果集群中至少有二个节点的话,每个索引将有5个主分片和5个复本分片(即1份完整拷贝)

注意:
    每个Elasticsearch分片都是一个Lucene索引,单个Lucene索引有文档个数的最大限制,为2,147,483,519约21亿,可使用 _cat/shards API监控版本大小


== 安装Elasticsearch ==

Elasticsearch需要至少Java 8,推荐使用Oracle JDK版本 1.8.0_73,Java安装因平台而异,所以这里不做详细介绍,自行查阅Oracle推荐的安装文档,在安装Elasticsearch前,请先检查Java版本,执行以下命令:

$ java -version
$ echo $JAVA_HOME

一旦Java就绪,就能安装和运行Elasticsearch了,假设已经下载5.1.2版本的tar包,使用以下命令解压:

$ tar -xvf elasticsearch-5.1.2.tar.gz

这将在当前目录下创建出若干文件和文件夹,进入bin目录:

$ cd elasticsearch-5.1.2/bin

现在,准备启动节点,即单节点集群:

$ ./elasticsearch

如果一切都好,将看到如下一连串信息:
[2017-01-20T09:58:41,802][INFO ][o.e.n.Node               ] [] initializing ...
[2017-01-20T09:58:41,911][INFO ][o.e.e.NodeEnvironment    ] [np8GqPV] using [1] data paths, mounts [[/ (/dev/sda3)]], net usable_space [12.7gb], net total_s
pace [27.3gb], spins? [possibly], types [ext4]
[2017-01-20T09:58:41,911][INFO ][o.e.e.NodeEnvironment    ] [np8GqPV] heap size [1.9gb], compressed ordinary object pointers [true]
[2017-01-20T09:58:41,913][INFO ][o.e.n.Node               ] node name [np8GqPV] derived from node ID [np8GqPVmS5eGToJAnUrAkQ]; set [node.name] to override
[2017-01-20T09:58:41,915][INFO ][o.e.n.Node               ] version[5.1.2], pid[18937], build[c8c4c16/2017-01-11T20:18:39.146Z], OS[Linux/2.6.32-431.el6.x86
_64/amd64], JVM[Oracle Corporation/Java HotSpot(TM) 64-Bit Server VM/1.8.0_74/25.74-b02]
[2017-01-20T09:58:43,059][INFO ][o.e.p.PluginsService     ] [np8GqPV] loaded module [aggs-matrix-stats]
[2017-01-20T09:58:43,059][INFO ][o.e.p.PluginsService     ] [np8GqPV] loaded module [ingest-common]
[2017-01-20T09:58:43,059][INFO ][o.e.p.PluginsService     ] [np8GqPV] loaded module [lang-expression]
[2017-01-20T09:58:43,059][INFO ][o.e.p.PluginsService     ] [np8GqPV] loaded module [lang-groovy]
[2017-01-20T09:58:43,059][INFO ][o.e.p.PluginsService     ] [np8GqPV] loaded module [lang-mustache]
[2017-01-20T09:58:43,059][INFO ][o.e.p.PluginsService     ] [np8GqPV] loaded module [lang-painless]
[2017-01-20T09:58:43,059][INFO ][o.e.p.PluginsService     ] [np8GqPV] loaded module [percolator]
[2017-01-20T09:58:43,059][INFO ][o.e.p.PluginsService     ] [np8GqPV] loaded module [reindex]
[2017-01-20T09:58:43,060][INFO ][o.e.p.PluginsService     ] [np8GqPV] loaded module [transport-netty3]
[2017-01-20T09:58:43,060][INFO ][o.e.p.PluginsService     ] [np8GqPV] loaded module [transport-netty4]
[2017-01-20T09:58:43,060][INFO ][o.e.p.PluginsService     ] [np8GqPV] no plugins loaded
[2017-01-20T09:58:46,523][INFO ][o.e.n.Node               ] initialized
[2017-01-20T09:58:46,523][INFO ][o.e.n.Node               ] [np8GqPV] starting ...
[2017-01-20T09:58:46,762][INFO ][o.e.t.TransportService   ] [np8GqPV] publish_address {127.0.0.1:9300}, bound_addresses {127.0.0.1:9300}
[2017-01-20T09:58:46,776][WARN ][o.e.b.BootstrapCheck     ] [np8GqPV] max number of threads [1024] for user [elastic] is too low, increase to at least [2048
]
[2017-01-20T09:58:46,776][WARN ][o.e.b.BootstrapCheck     ] [np8GqPV] max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [26
2144]
[2017-01-20T09:58:49,872][INFO ][o.e.c.s.ClusterService   ] [np8GqPV] new_master {np8GqPV}{np8GqPVmS5eGToJAnUrAkQ}{WI_3U36qScGo9EwQMsvr4A}{127.0.0.1}{127.0.
0.1:9300}, reason: zen-disco-elected-as-master ([0] nodes joined)
[2017-01-20T09:58:49,894][INFO ][o.e.h.HttpServer         ] [np8GqPV] publish_address {127.0.0.1:9200}, bound_addresses {127.0.0.1:9200}
[2017-01-20T09:58:49,894][INFO ][o.e.n.Node               ] [np8GqPV] started
[2017-01-20T09:58:49,908][INFO ][o.e.g.GatewayService     ] [np8GqPV] recovered [0] indices into cluster_state

可以看到,节点被命名为"np8GqPV"(每个节点都会不同),启动完成且选举自己为单节点集群的Master

如前面说的,可以指定集群名和节点名,使用以下命令启动Elasticsearch可以实现:

$ ./elasticsearch -Ecluster.name=my_cluster_name -Enode.name=my_node_name

另外,上面的输出信息还提到,通过可以访问到该节点,默认的,Elasticsearch使用端口9200提供REST API的访问,如有需要,该端口可以调整


== 探索集群 ==

【REST API】

现在,已经启动了一个节点(集群)并持续运行中,下一步了解如何与其通信,幸运的是,Elasticsearch提供了一个非常全面强大的REST API,可以使用它与节点(集群)交互,以下例举一些API能够做的事:
- 检查集群、节点和索引的健康、状态和统计信息
- 管理集群、节点和索引数据及元数据
- 在索引上执行CRUD(Create/Read/Update/Delete)和搜索操作
- 执行高级搜索操作,如分布、排序、过滤、脚本、聚合等

【集群健康】

集群健康检查能知道集群的工作状态,这里使用curl命令发起HTTP/REST调用完成此事,假设已在启动Elasticsearch的节点上打开一个Shell窗口

要检查集群健康可以使用 _cat API,命令如下:

$ curl -XGET '/_cat/health?v'

返回内容为:
epoch      timestamp cluster       status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent
1475247709 17:01:49  elasticsearch green           1         1      0   0    0    0        0             0                  -                100.0%

可以看到,集群名为"elasticsearch",正以"green"状态运行中,共有1个节点,由于还未有数据所以0个分片

集群状态有green、yellow和red三种,green意为一切良好(集群功能健全),yellow意为所有数据可用但某些复本不完整(集群功能健全),red意为因某些原因已有部分数据不可用,即使集群状态为red,它仍可以提供部分功能,例如仍能在剩余可用的分片上执行搜索请求,但是从丢失数据起就应该尽快修复

译者注:
还有一条检查集群健康的命令,如下:

$ curl -XGET '/_cluster/health?pretty'
{
  "cluster_name" : "elasticsearch",
  "status" : "yellow",
  "timed_out" : false,
  "number_of_nodes" : 1,
  "number_of_data_nodes" : 1,
  "active_primary_shards" : 5,
  "active_shards" : 5,
  "relocating_shards" : 0,
  "initializing_shards" : 0,
  "unassigned_shards" : 5,
  "delayed_unassigned_shards" : 0,
  "number_of_pending_tasks" : 0,
  "number_of_in_flight_fetch" : 0,
  "task_max_waiting_in_queue_millis" : 0,
  "active_shards_percent_as_number" : 50.0
}

使用以下命令列出集群中的节点:

$ curl -XGET '/_cat/nodes?v'

返回内容为:
ip        heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name
127.0.0.1           14          66   0    0.06    0.05     0.01 mdi       *      np8GqPV

可以看到集群当前为单节点集群,这个节点的名字为"PB2SGZY"

【列出所有索引】

以下命令可列出集群中的所有索引:

$ curl -XGET '/_cat/indices?v'

返回内容为:
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size

意为集群中还没有索引(因为没有数据)

【创建一个索引】

创建一个名为"customer"的索引,使用curl命令的PUT方法,添加pretty参数是为了能竖排打印返回的JSON内容

$ curl -XPUT '/customer?pretty'

返回内容如下:
{
  "acknowledged" : true,
  "shards_acknowledged" : true
}

再次列出索引:

$ curl -XGET '/_cat/indices?v'

返回内容如下:
health status index    uuid                   pri rep docs.count docs.deleted store.size pri.store.size
yellow open   customer 95SQ4TSUT7mWBT7VNHH67A   5   1          0            0       260b           260b

可以看出,现在有了1个名为"customer"的索引,默认有5个主分片和1份复本,但无(0个)文档在索引内

还看到,当前"customer"索引的健康状态为"yellow",这意为部分数据复本尚不完整,由于为了高可能用性,Elasticsearch默认要为索引创建一份复本,但此刻集群中只有一个节点在运行,复本分片又不能与主分片在同一节点上,所以这份复本当前不能创建,直到以后有另一个节点加入到集群中,一旦复本在第二个节点上创建,索引的健康状态就会转成"green"

译者注:
可以指定索引的分片和复本数,如下:

$ curl -XPUT '/customer?pretty' -d '{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 2
  }
}'
设置该索引为3个分片,2个复本,即3主6备

【索引(vt.)和查询文档】

写入文档到索引时,为了能索引(vt.)文档,必须告诉Elasticsearch该文档定位到索引的哪个类型上

以下命令,索引(vt.)一个文档到"customer"索引,类型为"external",文档ID为1

$ curl -XPUT '/customer/external/1?pretty' -d '{"name": "John Doe"}'

返回内容如下:
{
  "_index" : "customer",
  "_type" : "external",
  "_id" : "1",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "created" : true
}

可以看到,一个文档成功创建在索引customer的类型external里,并在索引(vt.)该文档时指定了内部ID为1

需要指出的是,Elasticsearch并不需要在索引(vt.)文档前显式的创建索引,在索引(vt.)文档时,如果索引尚不存在,Elasticsearch会自动创建

用以下命令检索刚才索引(vt.)的文档:

$ curl -XGET '/customer/external/1?pretty'  

返回内容如下:
{
  "_index" : "customer",
  "_type" : "external",
  "_id" : "1",
  "_version" : 1,
  "found" : true,
  "_source" : {
    "name" : "John Doe"
  }
}

字段"found"为true,说明按照给定的"index/type/id"找到了一个文档,字段"_source"返回该文档的完整JSON

【删除索引】

用以下命令删除之前创建的索引:

$ curl -XDELETE '/customer?pretty'        

返回内容如下:
{ "acknowledged" : true }

再次列出索引:
$ curl -XGET '/_cat/indices?v'

返回内容如下:
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size

已无任何索引,说明之前的删除成功


== 修改数据 ==

Elasticsearch提供近乎实时的数据修改和搜索功能,默认的,从index/update/delete数据到这些数据变化反应在搜索结果上大约有1秒钟延迟(数据刷新间隔),这与事务完成就能立即看到数据变化的数据库有较大差别

【索引(vt.)或替换文档】

再次执行之前的文档索引(vt.)命令:

$ curl -XPUT '/customer/external/1?pretty' -d '{"name": "John Doe"}'

将再次把文档索引(vt.)在索引customer的类型external上,其ID为1,如果继续对"/customer/external/1"索引(vt.)文档,Elasticsearch将用新文档替换(reindex)现有的这个ID为1的文档,如下:

$ curl -XPUT '/customer/external/1?pretty' -d '{"name": "Jane Eyre"}'

执行后,修改了ID为1的文档,另一方面,如使用不同的ID,即会索引(vt.)一个新文档,已存在索引中的文档都会保持原样,如下:

$ curl -XPUT '/customer/external/2?pretty' -d '{"name": "Jane Eyre"}'

执行后,会在索引customer的类型external上索引(vt.)一个ID为2的新文档

在索引(vt.)时,ID是可以省略的,如果不给出ID,Elasticsearch将生成一个随机ID索引(vt.)文档,该随机ID可以在返回的信息中获得,如下:

$ curl -XPOST '/customer/external?pretty' -d '{"name": "John Eyre"}'

当不指定ID时,需要使用POST方法,而不是之前的PUT方法

返回内容为:
{
  "_index" : "customer",
  "_type" : "external",
  "_id" : "AVm6kxIj-K3q0APeGgab",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "created" : true
}

【更新文档】

除了能索引(vt.)和替换,还能更新文档,但Elasticsearch并不是“原位”更新,而是先删除旧文档,然后立即索引(vt.)新文档

下例演示如何用"Jane Doe"更新ID为1的文档的字段name:

$ curl -XPOST '/customer/external/1/_update?pretty' -d '{ "doc": { "name": "John Doe" } }'

返回内容如下:
{
  "_index" : "customer",
  "_type" : "external",
  "_id" : "1",
  "_version" : 2,
  "result" : "updated",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  }
}

下例演示在更新ID1文档name字段的同时增加age字段:

$ curl -XPOST '/customer/external/1/_update?pretty' -d '{ "doc": { "name": "John Doe", "age": 20 } }'

返回内容为:
{
  "_index" : "customer",
  "_type" : "external",
  "_id" : "1",
  "_version" : 3,
  "result" : "updated",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  }
}

此外,更新还能执行简单的脚本,下例演示用脚本对字段age值加5:

$ curl -XPOST '/customer/external/1/_update?pretty' -d '{ "script": "ctx._source.age += 5" }'

其中,"ctx._source"可引用当前被更新的源文档

返回内容为:
{
  "_index" : "customer",
  "_type" : "external",
  "_id" : "1",
  "_version" : 4,
  "result" : "updated",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  }
}

可以看到,每次更新后,字段"_version"的值都会增加

来看看文档ID1现在的样子:

$ curl -XGET '/customer/external/1?pretty'

返回内容如下:
{
  "_index" : "customer",
  "_type" : "external",
  "_id" : "1",
  "_version" : 4,
  "found" : true,
  "_source" : {
    "name" : "John Doe",
    "age" : 25
  }
}

注意:
    update只能一次更新一个文档,在未来,Elasticsearch或许会提供通过查询更新多个文档的功能,类似SQL的update-where语句

【删除文档】

删除文档方法很简单明了,下例演示如何删除之前的索引customer上的文档ID2:

$ curl -XDELETE '/customer/external/2?pretty'

返回内容如下:
{
  "found" : true,
  "_index" : "customer",
  "_type" : "external",
  "_id" : "2",
  "_version" : 2,
  "result" : "deleted",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  }
}

另外,若要删除某个索引里的所有文档,可直接删除该索引,例如:

$ curl -XDELETE '/customer?pretty'

返回内容如下:
{
  "acknowledged" : true
}

注意:
    Elasticsearch没有删除类型的概念

【批量处理】

Elasticsearch支持使用 _bulk API批量执行索引(vt.)、更新和删除文档的能力,该功能为Elasticsearch在最少网络往返交互下,提供了非常高效的多次操作机制

如下例,在一次批量处理中完成二个文档的索引(vt.),ID1为John,ID2为Jane:

$ curl -XPOST '/customer/external/_bulk?pretty' -d '
{"index": {"_id":"1"}}
{"name": "John Doe"}
{"index": {"_id":"2"}}
{"name": "Jane Doe"}
'

注意:
    一定要有换行

返回内容为:
{
  "took" : 48,
  "errors" : false,
  "items" : [
    {
      "index" : {
        "_index" : "customer",
        "_type" : "external",
        "_id" : "1",
        "_version" : 1,
        "result" : "created",
        "_shards" : {
          "total" : 2,
          "successful" : 1,
          "failed" : 0
        },
        "created" : true,
        "status" : 201
      }
    },
    {
      "index" : {
        "_index" : "customer",
        "_type" : "external",
        "_id" : "2",
        "_version" : 1,
        "result" : "created",
        "_shards" : {
          "total" : 2,
          "successful" : 1,
          "failed" : 0
        },
        "created" : true,
        "status" : 201
      }
    }
  ]
}

再如下例,在一次批量处理中,更新文档ID1并删除文档ID2:

$ curl -XPOST '/customer/external/_bulk?pretty' -d '
{"update": {"_id":"1"}}
{"doc": { "name": "John Doe becomes Jane Doe" } }
{"delete": {"_id":"2"}}
'

返回内容为:
{
  "took" : 25,
  "errors" : false,
  "items" : [
    {
      "update" : {
        "_index" : "customer",
        "_type" : "external",
        "_id" : "1",
        "_version" : 2,
        "result" : "updated",
        "_shards" : {
          "total" : 2,
          "successful" : 1,
          "failed" : 0
        },
        "status" : 200
      }
    },
    {
      "delete" : {
        "found" : true,
        "_index" : "customer",
        "_type" : "external",
        "_id" : "2",
        "_version" : 2,
        "result" : "deleted",
        "_shards" : {
          "total" : 2,
          "successful" : 1,
          "failed" : 0
        },
        "status" : 200
      }
    }
  ]
}

批量处理不会因其中的某个操作失败就终止,无论该操作是何原因失败了,都会继续处理后续的操作,在批量处理的返回消息中,当有操作失败时,字段"errors"会为true,字段"items"则包含所有操作的执行状态,顺序与它们被执行的顺序相同,可用于检查哪步出错

如下例,在一次批量处理中,更新文档ID2和ID3,但由于文档ID2已不存在,操作会失败

$ curl -XPOST '/customer/external/_bulk?pretty' -d '
{"update": {"_id":"2"}}
{"doc": { "name": "Jane Eyre" } }
{"update": {"_id":"3"}}
{"doc": { "name": "John Xi" } }
'

返回内容如下:
{
  "took" : 16,
  "errors" : true,
  "items" : [
    {
      "update" : {
        "_index" : "customer",
        "_type" : "external",
        "_id" : "2",
        "status" : 404,
        "error" : {
          "type" : "document_missing_exception",
          "reason" : "[external][2]: document missing",
          "index_uuid" : "BcFWzdrzQK6hQmzTPt45fA",
          "shard" : "2",
          "index" : "customer"
        }
      }
    },
    {
      "update" : {
        "_index" : "customer",
        "_type" : "external",
        "_id" : "3",
        "_version" : 2,
        "result" : "updated",
        "_shards" : {
          "total" : 2,
          "successful" : 1,
          "failed" : 0
        },
        "status" : 200
      }
    }
  ]
}

可以看到,尽管文档ID2操作失败,但Elasticsearch并没有停下,文档ID3仍被更新了


== 探索数据 ==

【导入数据】

假设有如下格式的.json文件(accounts.json):

{"index": {"_id": "1"}}
{"account_number": 1, "balance": 39225, "firstname": "Amber", "lastname": "Duke", "age": 32, "gender": "M", "address": "880 Holmes Lane", "employer": "Pyrami", "email": "amberduke@pyrami.com", "city": "Brogan", "state": "IL"}
{"index": {"_id": "6"}}
{"account_number": 6, "balance": 5686, "firstname": "Hattie", "lastname": "Bond", "age": 36, "gender": "M", "address": "671 Bristol Street", "employer": "Netagy", "email": "hattiebond@netagy.com", "city": "Dante", "state": "TN"}
{"index": {"_id": "13"}}
{"account_number": 13, "balance": 32838, "firstname": "Nanette", "lastname": "Bates", "age": 28, "gender": "F", "address": "789 Madison Street", "employer": "Quility", "email": "nanettebates@quility.com", "city": "Nogal", "state": "VA"}

使用以下命令将数据导入集群中:
$ curl -XPOST '127.0.0.1:9200/bank/account/_bulk?pretty&refresh' --data-binary "@accounts.json"

查看索引:
$ curl -XGET '127.0.0.1:9200/_cat/indices?v'

返回内容如下:
health status index uuid                   pri rep docs.count docs.deleted store.size pri.store.size
yellow open   bank  l7sSYV2cQXmu6_4rJWVIww   5   1       1000            0    128.6kb        128.6kb

这表示已成功批量索引(vt.)了1000个文档到索引"bank",在类型account下

【搜索数据】

有二种方法运行搜索:一是通过REST请求的URI传递搜索参数,二是通过RESET请求的body,body方法可用更易读的JSON格式定义搜索,这里,演示一个RUI方法的例子,在余下的教程里将只使用body方法

下例使用搜索API返回索引bank中的所有文档:

$ curl -XGET '127.0.0.1:9200/bank/_search?q=*&sort=account_number:asc&pretty'

解释一下该URI,"/bank/_search"表示对索引bank进行搜索,参数"q=*"表示匹配索引中的所有文档,参数"sort=account_number:asc"表示按字段account_number升序排列结果集

返回内容如下(部分):
{
  "took" : 28,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 3,
    "max_score" : null,
    "hits" : [
      {
        "_index" : "bank",
        "_type" : "account",
        "_id" : "1",
        "_score" : null,
        "_source" : {
          "account_number" : 1,
          "balance" : 39225,
          "firstname" : "Amber",
          "lastname" : "Duke",
          "age" : 32,
          "gender" : "M",
          "address" : "880 Holmes Lane",
          "employer" : "Pyrami",
          "email" : "amberduke@pyrami.com",
          "city" : "Brogan",
          "state" : "IL"
        },
        "sort" : [
          1
        ]
      },
      {
        "_index" : "bank",
        "_type" : "account",
        "_id" : "6",
        "_score" : null,
        "_source" : {
          "account_number" : 6,
          "balance" : 5686,
          "firstname" : "Hattie",
          "lastname" : "Bond",
          "age" : 36,
          "gender" : "M",
          "address" : "671 Bristol Street",
          "employer" : "Netagy",
          "email" : "hattiebond@netagy.com",
          "city" : "Dante",
          "state" : "TN"
        },
        "sort" : [
          6
        ]
      },
      ...
    ]
  }
}

其中:
- took,Elasticsearch执行搜索花费的毫秒数
- timed_out,执行该搜索是否超时
- _shards,搜索了多少分片,以及成功和失败的分片个数
- hits,列出搜索结果
- hits.total,有多少文档匹配搜索条件
- hits.hits,以数组方法列出结果集,默认只返回前10个文档
- sort,该文档中排序字段的值

以上例搜索转换成body方法:

$ curl -XGET '127.0.0.1:9200/bank/_search?pretty' -d '{
  "query": {
    "match_all": {}
  },
  "sort": [
    { "account_number": "asc" }
  ]
}'

Elasticsearch是一次性返回所有结果集文档的,不再维持任何服务端(server-side)资源或在结果上打开游标等,不像其他数据平台,每次返回结果集的一部分,当要剩余数据时,通过有状态的服务端游标,继续返回结果集中文档,直至全部文档返回结束

【查询语言介绍】

Elasticsearch提供JSON格式的查询语言,称为Query DSL,来看以下查询:

$ curl -XGET '127.0.0.1:9200/bank/_search?pretty' -d '{
 "query": { "match_all": {} }
}'

"query"部分是查询定义,"match_all"部分是要执行的查询类型,意为在指定的索引上搜索所有文档

除了query,还能传递其他参数去影响搜索结果,来看以下查询:

$ curl -XGET '127.0.0.1:9200/bank/_search?pretty' -d '{
 "query": { "match_all": {} },
 "size": 1
}'

"size"用来指定返回多少个文档,若不指定,默认为10

下例查询,返回第11到第20个文档:

$ curl -XGET '127.0.0.1:9200/bank/_search?pretty' -d '{
 "query": { "match_all": {} },
 "from": 10,
 "size": 10
}'

"from"(从0起算)指定从第几个文档开始,"size"指定从from开始返回多少个文档,该功能在实现搜索结果分页时很有用,"from"默认为0

在上一节,使用以下命令,按字段account_number升序返回10个文档:

$ curl -XGET '127.0.0.1:9200/bank/_search?pretty' -d '{
 "query": { "match_all": {} },
 "sort": [ { "account_number": "asc" } ]
}'

如果排序字段只有一个,该命令也可以写成如下格式:

$ curl -XGET '127.0.0.1:9200/bank/_search?pretty' -d '{
 "query": { "match_all": {} },
 "sort": { "account_number": {"order": "asc" } }
}'

相对"asc",可以使用"desc"降序排序结果集

【数据搜索】

默认情况下,文档的完整JSON(所有字段)都会被返回,如果不需要返回整个源文档,可以使用"_source"参数指定需要返回的字段

下例演示返回"account_number"和"balance"二个字段:

$ curl -XGET '127.0.0.1:9200/bank/_search?pretty' -d '{
 "query": { "match_all": {} },
 "_source": ["account_number", "balance"]
}'

在返回的结果集的"_source"中,将只有每个文档的"account_number"和"balance"字段

以上,都是使用"match_all"方法匹配所有文档,现在,介绍一个新的"match"方法,下例返回字段account_number的值为20的文档:

$ curl -XGET '127.0.0.1:9200/bank/_search?pretty' -d '{
 "query": { "match": {"account_number": 20} }
}'

下例返回所有字段address中包含"mill"的文档:

$ curl -XGET '127.0.0.1:9200/bank/_search?pretty' -d '{
 "query": { "match": {"address": "mill"} }
}'

译者注:
1. Elasticsearch不区分大小写,mill、MILL和Mill都是一样的
2. 由于Elasticsearch是全文搜索,所以如果有name为"mill anything"的文档也会被返回,因为匹配了"mill"这个单词,但是如果name为"millanything"就不匹配了,匹配的是单词,不是字符

下例返回所有字段address中包含"mill"或"lane"的文档:

$ curl -XGET '127.0.0.1:9200/bank/_search?pretty' -d '{
 "query": { "match": {"address": "mill lane"} }
}'

注意:
    因为Elasticsearch是全文搜索

使用match方法的变体"match_phrase",来返回字段address中包含字符"mill lane"的文档:

$ curl -XGET '127.0.0.1:9200/bank/_search?pretty' -d '{
 "query": { "match_phrase": {"address": "mill lane"} }
}'

现在,介绍下bool查询,其可以使用布尔逻辑组合多个查询条件

下例演示组合二个match查询,返回字段address中既有字符"mill"又有"lane"的文档:

$ curl -XGET '127.0.0.1:9200/bank/_search?pretty' -d '{
  "query": {
    "bool": {
      "must": [
        { "match": {"address": "mill"} },
        { "match": {"address": "lane"} }
      ]
    }
  }
}'

"bool"."must"指定所有查询条件必须全都为true才是匹配的文档

下例演示组合二个match查询,返回字段address中有字符"mill"或有"lane"的文档:

$ curl -XGET '127.0.0.1:9200/bank/_search?pretty' -d '{
  "query": {
    "bool": {
      "should": [
        { "match": {"address": "mill"} },
        { "match": {"address": "lane"} }
      ]
    }
  }
}'

"bool"."should"指定文档只要满足查询条件中的任意一个即可匹配

下例演示组合二个match查询,返回字段address中既无字符"mill"又无"lane"的文档:

$ curl -XGET '127.0.0.1:9200/bank/_search?pretty' -d '{
  "query": {
    "bool": {
      "must_not": [
        { "match": {"address": "mill"} },
        { "match": {"address": "lane"} }
      ]
    }
  }
}'

"bool"."must_not"指定所有查询条件都不为true的文档才匹配

还能在bool查询里组合使用"must"、"should"和"must_not",下例演示返回字段age为40且字段state不包含"ID"的文档:

$ curl -XGET '127.0.0.1:9200/bank/_search?pretty' -d '{
  "query": {
    "bool": {
      "must": [
        { "match": {"age": 40} }
      ],
      "must_not": [
        { "match": {"state": "ID"} }
      ]
    }
  }
}'

【数据过滤】

在之前的章节,有意忽略了一个小细节,搜索结果集中的"_score"字段,"score"是一个数值型数据表示某个文档与指定的查询要求的匹配度,其值越高,文档的与查询要求的匹配度越大,其值越低,匹配度就越小

但查询并不总要生成score,尤其只是对文档集进行“过滤”时,Elasticsearch会检测出这些场景,自动优化查询的执行过程以免去计算无用的score

上一节介绍的bool查询也支持"filter",在不影响score计算逻辑的情况下,允许使用一个查询限定与其他条件匹配的文档,为了举例,介绍下range查询,其能够按数据值范围过滤文档,通常用于数值或日期过滤

下例,使用bool查询返回所有字段balance值在20000至30000的文档:

$ curl -XGET '127.0.0.1:9200/bank/_search?pretty' -d '{
  "query": {
    "bool": {
      "must": { "match_all": {} },
      "filter": {
        "range": {
          "balance": { "gte": 20000, "lte": 30000 }
        }
      }
    }
  }
}'

bool查询包含一个"match_all"(query部分)和一个"range"(filter部分),只有符合filter条件的文档才会去匹配query并计算score

【数据聚合】

聚合提供对数据分组计算并提取统计值的能力,类似SQL中的group by和count()、sum()和avg()等聚合函数,在Elasticsearch里,可以在执行查询返回结果集的同时返回对这些结果集聚合计算的统计结果

首先,以字段state分组统计文档个数,并以统计值倒序(默认)返回10个(默认)结果:

$ curl -XGET '127.0.0.1:9200/bank/_search?pretty' -d '{
  "size": 0,
  "aggs": {
    "group_by_state": {
      "terms": {
        "field": "state.keyword"
      }
    }
  }
}'

类似于SQL的group by语句:

SELECT state, COUNT(*) FROM bank GROUP BY state ORDER BY COUNT(*) DESC;

返回内容如下:
{
  "took": 29,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits" : {
    "total" : 1000,
    "max_score" : 0.0,
    "hits" : [ ]
  },
  "aggregations" : {
    "group_by_state" : {
      "doc_count_error_upper_bound": 20,
      "sum_other_doc_count": 770,
      "buckets" : [ {
        "key" : "ID",
        "doc_count" : 27
      }, {
        "key" : "TX",
        "doc_count" : 27
      }, {
        "key" : "AL",
        "doc_count" : 25
      }, ... ]
    }
  }
}

可以看到,有27个文档的state字段为"ID",27个为"TX",25个为"AL",等等

注意:
    因为设置了 "size": 0 ,所以不会返回搜索命中的文档结果集,只会返回聚合统计的结果集

基于上面的聚合,统计每个state的balance字段的平均值:

$ curl -XGET '127.0.0.1:9200/bank/_search?pretty' -d '{
  "size": 0,
  "aggs": {
    "group_by_state": {
      "terms": {
        "field": "state.keyword"
      },
      "aggs": {
        "average_balance": {
          "avg": {
            "field": "balance"
          }
        }
      }
    }
  }
}'

该例在group_by_state聚合中嵌入了average_balance聚合,这是取得多项统计数据的常用方法

基于上面的聚合,按balance字段的平均值倒序返回:

$ curl -XGET '127.0.0.1:9200/bank/_search?pretty' -d '{
  "size": 0,
  "aggs": {
    "group_by_state": {
      "terms": {
        "field": "state.keyword",
        "order": {
          "average_balance": "desc"
        }
      },
      "aggs": {
        "average_balance": {
          "avg": {
            "field": "balance"
          }
        }
      }
    }
  }
}'

下例演示如何先以字段age自定义分桶(20-29/30-39/40-49),再按字段gender分组,最终计算出字段balance的平均值:

$ curl -XGET '127.0.0.1:9200/bank/_search?pretty' -d '{
  "size": 0,
  "aggs": {
    "group_by_state": {
      "range": {
        "field": "age",
        "ranges": [
          { "from": 20, "to": 30 },
          { "from": 30, "to": 40 },
          { "from": 40, "to": 50 }
        ]
      },
      "aggs": {
        "group_by_gender": {
          "terms": {
            "field": "gender.keyword"
          },
          "aggs": {
            "average_balance": {
              "avg": {
                "field": "balance"
              }
            }
          }
        }
      }
    }
  }
}'

【总结】

Elasticsearch是一个既简单又复杂的产品,至此介绍了一些通过REST API使用它的基础方法,希望该教程能较好的给予对Elasticsearch的初步了解,也希望能激起继续学习的兴趣


阅读(608) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~