赞
踩
开始学es,总结出现的问题和解决方法。本文是在三个节点上进行安装。条件允许的话,可以在多台机器上配置es节点,如果你机器性能有限,那么可以在一台虚拟机上完成多节点的配置。
三个节点如下:A,B,C
index(索引): es里的index相当于一个数据库。
type(类型): 相当于数据库里的一个表。
document(文档)
相当于mysql中的一行(一条记录)
field(域)
相当于mysql中的一列(一个字段)
id: 唯一,相当于主键。
node:节点是es实例,一台机器可以运行多个实例,但是同一台机器上的实例在配置文件中要确保http和tcp端口不同(下面有讲)。
cluster:代表一个集群,集群中有多个节点,其中有一个会被选为主节点,这个主节点是可以通过选举产生的,主从节点是对于集群内部来说的。
shards:代表索引分片,es可以把一个完整的索引分成多个分片,这样的好处是可以把一个大的索引拆分成多个,分布到不同的节点上,构成分布式搜索。分片的数量只能在索引创建前指定,并且索引创建后不能更改。
replicas:代表索引副本,es可以设置多个索引的副本,副本的作用一是提高系统的容错性,当个某个节点某个分片损坏或丢失时可以从副本中恢复。二是提高es的查询效率,es会自动对搜索请求进行负载均衡。
es的安装依赖于jdk,所以在安装之前先安装好jdk
cluster.name: kafka_connect_test node.name: es-node1 path.data: /home/connect/esdata path.logs: /home/connect/eslogs bootstrap.memory_lock: true bootstrap.system_call_filter: false network.host: A(ip) http.port: 9200 transport.tcp.port: 9201 discovery.zen.ping.unicast.hosts: ["A", "B", "C"] discovery.zen.minimum_master_nodes: 2 #discovery.zen.ping.timeout: 40s gateway.expected_nodes: 3 gateway.expected_master_nodes: 3 gateway.expected_data_nodes: 3 gateway.recover_after_time: 5m gateway.recover_after_nodes: 2 gateway.recover_after_master_nodes: 2 gateway.recover_after_data_nodes: 2 action.destructive_requires_name: true
./elasticsearch -d
问题 1.
max file descriptors [4096] for elasticsearch process is too low, increase to at least [65536]
原因:每个进程最大同时打开文件数太小,可通过下面2个命令查看当前数量及解决方案。
ulimit -Hn
ulimit -Sn
# 在root用户下编辑/etc/security/limits.conf文件,增加如下配置:
* soft nofile 65536
* hard nofile 65536
# 用户重新登录后生效。
问题 2.
max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]
原因:虚拟内存过小导致。解决方案如下:
# 在root用户下编辑/etc/sysctl.conf文件,增加配置:
vm.max_map_count=262144
sysctl -p
# 用户重新登录后生效。
问题 3.
ERROR: bootstrap checks failed
memory locking requested for elasticsearch process but memory is not locked
原因:锁定内存失败
解决方案:
#在root用户下编辑/etc/security/limits.conf配置文件, 添加类似如下内容:
* soft memlock unlimited
* hard memlock unlimited
保存后重新登陆生效。
问题 4.
max number of threads [3818] for user [es] is too low, increase to at least [4096]
原因:最大线程个数太低。修改配置文件/etc/security/limits.conf,增加配置
* soft nproc 4096
* hard nproc 4096
curl 'localhost:9200/_cat/health?v'
curl 'localhost:9200/_cat/nodes?v'
curl 'localhost:9200/_cat/indices?v'
Elasticsearch有几个核心概念。从一开始理解这些概念会对整个学习过程有莫大的帮助。接近实时(NRT)Elasticsearch是一个接近实时的搜索平台。这意味着,从索引一个文档直到这个文档能够被搜索到有一个轻微的延迟(通常是1秒)。
- 集群(cluster) 一个集群就是由一个或多个节点组织在一起,它们共同持有你整个的数据,并一起提供索引和搜索功能。一个集群由一个唯一的名字标识,这个名字默认就是“elasticsearch”。这个名字是重要的,因为一个节点只能通过指定某个集群的名字,来加入这个集群。在产品环境中显式地设定这个名字是一个好习惯,但是使用默认值来进行测试/开发也是不错的。 - 节点(node) 一个节点是你集群中的一个服务器,作为集群的一部分,它存储你的数据,参与集群的索引和搜索功能。和集群类似,一个节点也是由一个名字来标识的,默认情况下,这个名字是一个随机的漫威漫画角色的名字,这个名字会在启动的时候赋予节点。这个名字对于管理工作来说挺重要的,因为在这个管理过程中,你会去确定网络中的哪些服务器对应于Elasticsearch集群中的哪些节点。 一个节点可以通过配置集群名称的方式来加入一个指定的集群。默认情况下,每个节点都会被安排加入到一个叫做“elasticsearch”的集群中,这意味着,如果你在你的网络中启动了若干个节点,并假定它们能够相互发现彼此,它们将会自动地形成并加入到一个叫做“elasticsearch”的集群中。 在一个集群里,只要你想,可以拥有任意多个节点。而且,如果当前你的网络中没有运行任何Elasticsearch节点,这时启动一个节点,会默认创建并加入一个叫做“elasticsearch”的集群。 - 索引(index) 一个索引就是一个拥有几分相似特征的文档的集合。比如说,你可以有一个客户数据的索引,另一个产品目录的索引,还有一个订单数据的索引。一个索引由一个名字来标识(必须全部是小写字母的),并且当我们要对对应于这个索引中的文档进行索引、搜索、更新和删除的时候,都要使用到这个名字。 在一个集群中,如果你想,可以定义任意多的索引。 - 类型(type) 在一个索引中,你可以定义一种或多种类型。一个类型是你的索引的一个逻辑上的分类/分区,其语义完全由你来定。通常,会为具有一组共同字段的文档定义一个类型。比如说,我们假设你运营一个博客平台并且将你所有的数据存储到一个索引中。在这个索引中,你可以为用户数据定义一个类型,为博客数据定义另一个类型,当然,也可以为评论数据定义另一个类型 - 文档(document) 一个文档是一个可被索引的基础信息单元。比如,你可以拥有某一个客户的文档,某一个产品的一个文档,当然,也可以拥有某个订单的一个文档。文档以JSON(Javascript Object Notation)格式来表示,而JSON是一个到处存在的互联网数据交互格式。 在一个index/type里面,只要你想,你可以存储任意多的文档。注意,尽管一个文档,物理上存在于一个索引之中,文档必须被索引/赋予一个索引的type。 - 分片和复制(shards & replicas) 一个索引可以存储超出单个结点硬件限制的大量数据。比如,一个具有10亿文档的索引占据1TB的磁盘空间,而任一节点都没有这样大的磁盘空间;或者单个节点处理搜索请求,响应太慢。 为了解决这个问题,Elasticsearch提供了将索引划分成多份的能力,这些份就叫做分片。当你创建一个索引的时候,你可以指定你想要的分片的数量。每个分片本身也是一个功能完善并且独立的“索引”,这个“索引”可以被放置到集群中的任何节点上。 分片之所以重要,主要有两方面的原因: - 允许你水平分割/扩展你的内容容量 - 允许你在分片(潜在地,位于多个节点上)之上进行分布式的、并行的操作,进而提高性能/吞吐量 至于一个分片怎样分布,它的文档怎样聚合回搜索请求,是完全由Elasticsearch管理的,对于作为用户的你来说,这些都是透明的。 在一个网络/云的环境里,失败随时都可能发生,在某个分片/节点不知怎么的就处于离线状态,或者由于任何原因消失了,这种情况下,有一个故障转移机制是非常有用并且是强烈推荐的。为此目的,Elasticsearch允许你创建分片的一份或多份拷贝,这些拷贝叫做复制分片,或者直接叫复制。 复制之所以重要,有两个主要原因: - 在分片/节点失败的情况下,提供了高可用性。因为这个原因,注意到复制分片从不与原/主要(original/primary)分片置于同一节点上是非常重要的。 - 扩展你的搜索量/吞吐量,因为搜索可以在所有的复制上并行运行 总之,每个索引可以被分成多个分片。一个索引也可以被复制0次(意思是没有复制)或多次。一旦复制了,每个索引就有了主分片(作为复制源的原来的分片)和复制分片(主分片的拷贝)之别。分片和复制的数量可以在索引创建的时候指定。在索引创建之后,你可以在任何时候动态地改变复制的数量,但是你事后不能改变分片的数量。 默认情况下,Elasticsearch中的每个索引被分片5个主分片和1个复制,这意味着,如果你的集群中至少有两个节点,你的索引将会有5个主分片和另外5个复制分片(1个完全拷贝),这样的话每个索引总共就有10个分片 这些问题搞清楚之后,我们就要进入好玩的部分了...
正如先前提到的,我们可以覆盖集群或者节点的名字。我们可以在启动Elasticsearch的时候通过命令行来指定,如下:
./elasticsearch --cluster.name my_cluster_name --node.name my_node_name
也要注意一下有http标记的那一行,它提供了有关HTTP地址(192.168.8.112)和端口(9200)的信息,通过这个地址和端口我们就可以访问我们的节点了。默认情况下,Elasticsearch使用9200来提供对其REST API的访问。如果有必要,这个端口是可以配置的。
探索你的集群
rest接口
现在我们已经有一个正常运行的节点(和集群)了,下一步就是要去理解怎样与其通信了。幸运的是,Elasticsearch提供了非常全面和强大的REST API,利用这个REST API你可以同你的集群交互。下面是利用这个API,可以做的几件事情:
- 检查你的集群、节点和索引的健康状态、和各种统计信息
- 管理你的集群、节点、索引数据和元数据
- 对你的索引进行CRUD(创建、读取、更新和删除)和搜索操作
- 执行高级的查询操作,像是分页、排序、过滤、脚本编写(scripting)
、小平面刻画(faceting)、聚合(aggregations)和许多其它操作
要检查集群健康,我们将使用_catAPI。需要事先记住的是,我们的节点HTTP的端口是9200:
curl 'localhost:9200/_cat/health?v'
相应的响应是:
epoch timestamp cluster status node.total node.data shards pri relo init unassign
1394735289 14:28:09 elasticsearch green 1 1 0 0 0 0 0
可以看到,我们集群的名字是“elasticsearch”,正常运行,并且状态是绿色。
当我们询问集群状态的时候,我们要么得到绿色、黄色或红色。绿色代表一切正常(集群功能齐全),黄色意味着所有的数据都是可用的,但是某些复制没有被分配(集群功能齐全),红色则代表因为某些原因,某些数据不可用。注意,即使是集群状态是红色的,集群仍然是部分可用的(它仍然会利用可用的分片来响应搜索请求),但是可能你需要尽快修复它,因为你有丢失的数据。
也是从上面的响应中,我们可以看到,一共有一个节点,由于里面没有数据,我们有0个分片。注意,由于我们使用默认的集群名字(elasticsearch),并且由于Elasticsearch默认使用网络多播(multicast)发现其它节点,如果你在你的网络中启动了多个节点,你就已经把她们加入到一个集群中了。在这种情形下,你可能在上面的响应中看到多个节点。
curl 'localhost:9200/_cat/nodes?v'
对应的响应是:
curl '10.37.167.204:9200/_cat/nodes?v'
host ip heap.percent ram.percent load node.role master name
mwubuntu1 127.0.1.1 8 4 0.00 d * New Goblin
curl '10.37.167.204:9200/_cat/indices?v'
响应是:
curl '10.37.167.204:9200/_cat/indices?v'
health index pri rep docs.count docs.deleted store.size pri.store.size
这个结果意味着,在我们的集群中,我们没有任何索引。
curl -XPUT 'localhost:9200/customer'
curl 'localhost:9200/_cat/indices?v'
第一个命令使用PUT创建了一个叫做“customer”的索引。我们简单地将pretty附加到调用的尾部,使其以美观的形式打印出JSON响应(如果有的话)。
响应如下:
curl -XPUT '10.37.167.204:9200/customer'
{
"acknowledged" : true
}
curl 'localhost:9200/_cat/indices?v'
health index pri rep docs.count docs.deleted storepri.store.size
yellow customer 5 1 0 0 495b 495b
第二个命令的结果告知我们,我们现在有一个叫做customer的索引,并个主分片和1份复制(都是默认值),其中包含0个文档。
你可能也注意到了这个customer索引有一个黄色健康标签。回顾我们之,黄色意味着某些复制没有(或者还未)被分配。这个索引之所以这样Elasticsearch默认为这个索引创建一份复制。由于现在我们只有一个行,那一份复制就分配不了了(为了高可用),直到当另外一个节点加集群后,才能分配。一旦那份复制在第二个节点上被复制,这个节点的就会变成绿色。
现在让我们放一些东西到customer索引中。首先要知道的是,为了索引一个文档,我们必须告诉Elasticsearch这个文档要到这个索引的哪个类型(type)下。
让我们将一个简单的客户文档索引到customer索引、“external”类型中,这个文档的ID是1,操作如下:
curl -XPUT '10.37.167.204:9200/customer/external/1' -d '
{
"name": "John Doe"
}'
响应如下:
curl -XPUT '10.37.167.204:9200/customer/external/1' -d '
{
"name": "John Doe"
}'
{
"_index" : "customer",
"_type" : "external",
"_id" : "1",
"_version" : 1,
"created" : true
}
从上面的响应中,我们可以看到,一个新的客户文档在customer索引和external类型中被成功创建。文档也有一个内部id 1, 这个id是我们在索引的时候指定的。
有一个关键点需要注意,Elasticsearch在你想将文档索引到某个索引的时候,并不强制要求这个索引被显式地创建。在前面这个例子中,如果customer索引不存在,Elasticsearch将会自动地创建这个索引。
现在,让我们把刚刚索引的文档取出来:
curl -XGET '10.37.167.204:9200/customer/external/1'
响应如下:
curl -XGET '10.37.167.204:9200/customer/external/1'
{
"_index" : "customer",
"_type" : "external",
"_id" : "1",
"_version" : 1,
"found" : true, "_source" : { "name": "John Doe" }
}
除了一个叫做found的字段来指明我们找到了一个ID为1的文档,和另外一个字段——_source——返回我们前一步中索引的完整JSON文档之外,其它的都没有什么特别之处。
curl -XDELETE '10.37.167.204:9200/customer'
curl '10.37.167.204:9200/_cat/indices?v'
响应如下:
curl -XDELETE '10.37.167.204:9200/customer'
{
"acknowledged" : true
}
curl '10.37.167.204:9200/_cat/indices?v'
health index pri rep docs.count docs.deleted store.size pri.store.size
这表明我们成功地删除了这个索引,现在我们回到了集群中空无所有的状态。
在更进一步之前,我们再细看一下一些我们学过的API命令:
curl -XPUT '10.37.167.204:9200/customer'
curl -XPUT '10.37.167.204:9200/customer/external/1' -d '
{
"name": "John Doe"
}'
curl '10.37.167.204:9200/customer/external/1'
curl -XDELETE '10.37.167.204:9200/customer'
如果我们仔细研究以上的命令,我们可以发现访问Elasticsearch中数据的一个模式。这个模式可以被总结为:
curl -<REST Verb> <Node>:<Port>/<Index>/<Type><ID>
这个REST访问模式普遍适用于所有的API命令,如果你能记住它,你就会为掌握Elasticsearch开一个好头。
修改你的数据
Elasticsearch提供了近乎实时的数据操作和搜索功能。默认情况下,从你索引/更新/删除你的数据动作开始到它出现在你的搜索结果中,大概会有1秒钟的延迟。这和其它类似SQL的平台不同,数据在一个事务完成之后就会立即可用。
索引/替换文档
我们先前看到,怎样索引一个文档。现在我们再次调用那个命令:
curl -XPUT 'localhost:9200/customer/external/1' -d '
{
"name": "John Doe"
}'
再次,以上的命令将会把这个文档索引到customer索引、external类型中,其ID是1。如果我们对一个不同(或相同)的文档应用以上的命令,Elasticsearch将会用一个新的文档来替换(重新索引)当前ID为1的那个文档。
curl -XPUT 'localhost:9200/customer/external/1' -d '
{
"name": "Jane Doe"
}'
以上的命令将ID为1的文档的name字段的值从“John Doe”改成了“Jane Doe”。如果我们使用一个不同的ID,一个新的文档将会被索引,当前已经在索引中的文档不会受到影响。
curl -XPUT 'localhost:9200/customer/external/2' -d '
{
"name": "Jane Doe"
}'
以上的命令,将会索引一个ID为2的新文档。
在索引的时候,ID部分是可选的。如果不指定,Elasticsearch将产生一个随机的ID来索引这个文档。Elasticsearch生成的ID会作为索引API调用的一部分被返回。
以下的例子展示了怎样在没有指定ID的情况下来索引一个文档:
curl -XPOST '10.37.167.204:9200/customer/external' -d '
{
"name": "Jane Doe"
}'
注意,在上面的情形中,由于我们没有指定一个ID,我们使用的是POST而不是PUT。
更新文档
除了可以索引、替换文档之外,我们也可以更新一个文档。但要注意,Elasticsearch底层并不支持原地更新。在我们想要做一次更新的时候,Elasticsearch先删除旧文档,然后在索引一个更新过的新文档。
下面的例子展示了怎样将我们ID为1的文档的name字段改成“Jane Doe”:
curl -XPOST '10.37.167.204:9200/customer/external/1/_update' -d '
{
"doc": { "name": "Jane Doe" }
}'
下面的例子展示了怎样将我们ID为1的文档的name字段改成“Jane Doe”的同时,给它加上age字段:
curl -XPOST '10.37.167.204:9200/customer/external/1/_update' -d '
{
"doc": { "name": "Jane Doe", "age": 20 }
}'
更新也可以通过使用简单的脚本来进行。这个例子使用一个脚本将age加5:
curl -XPOST '10.37.167.204:9200/customer/external/1/_update' -d '
{
"script" : "ctx._source.age += 5"
}'
在上面的例子中,ctx._source指向当前要被更新的文档。
注意,在写作本文时,更新操作只能一次应用在一个文档上。将来,Elasticsearch将提供同时更新符合指定查询条件的多个文档的功能(类似于SQL的UPDATE-WHERE语句)。
curl -XDELETE '10.37.167.204:9200/customer/external/2'
我们也能够一次删除符合某个查询条件的多个文档。以下的例子展示了如何删除名字中包含“John”的所有的客户:
curl -XDELETE '10.37.167.204:9200/customer/external/_query' -d '
{
"query": { "match": { "name": "John" } }
}'
注意,以上的URI变成了/_query,以此来表明这是一个“查询删除”API,其中删除查询标准放在请求体中,但是我们仍然使用DELETE。现在先不要担心查询语法,我们将会在本教程后面的部分中涉及。
批处理:
除了能够对单个的文档进行索引、更新和删除之外,Elasticsearch也提供了以上操作的批量处理功能,这是通过使用_bulk API实现的。这个功能之所以重要,在于它提供了非常高效的机制来尽可能快的完成多个操作,与此同时使用尽可能少的网络往返。
作为一个快速的例子,以下调用在一次bulk操作中索引了两个文档(ID 1 - John Doe and ID 2 - Jane Doe):
curl -XPOST '10.37.167.204:9200/customer/external/_bulk' -d '
{"index":{"_id":"1"}}
{"name": "John Doe" }
{"index":{"_id":"2"}}
{"name": "Jane Doe" }'
以下例子在一个bulk操作中,首先更新第一个文档(ID为1),然后删除第二个文档(ID为2):
curl -XPOST '10.37.167.204:9200/customer/external/_bulk' -d '
{"update":{"_id":"1"}}
{"doc": { "name": "John Doe becomes Jane Doe" } }
{"delete":{"_id":"2"}}'
注意上面的delete动作,由于删除动作只需要被删除文档的ID,所以并没有对应的源文档。
bulk API按顺序执行这些动作。如果其中一个动作因为某些原因失败了,将会继续处理它后面的动作。当bulk API返回时,它将提供每个动作的状态(按照同样的顺序),所以你能够看到某个动作成功与否。
{
"account_number": 0,
"balance": 16623,
"firstname": "Bradshaw",
"lastname": "Mckenzie",
"age": 29,
"gender": "F",
"address": "244 Columbus Place",
"employer": "Euron",
"email": "bradshawmckenzie@euron.com",
"city": "Hobucken",
"state": "CO"
}
我是在http://www.json-generator.com/上生成这些数据的。
你可以从下载这个样本数据集。将其解压到当前目录下,如下,将其加载到我们的集群里:
curl -XPOST '10.37.167.204:9200/bank/account/_bulk' --data-binary @accounts.json
curl 'localhost:9200/_cat/indices?v'
响应是:
curl '10.37.167.204:9200/_cat/indices?v'
health index pri rep docs.count docs.deleted store.size pri.store.size
yellow bank 5 1 1000 0 424.4kb 424.4kb
这意味着我们成功批量索引了1000个文档到银行索引中(account类型)。
现在,让我们以一些简单的搜索来开始。有两种基本的方式来运行搜索:一种是在REST请求的URI中发送搜索参数,另一种是将搜索参数发送到REST请求体中。请求体方法的表达能力更好,并且你可以使用更加可读的JSON格式来定义搜索。我们将尝试使用一次请求URI作为例子,但是教程的后面部分,我们将仅仅使用请求体方法。
搜索的REST API可以通过_search端点来访问。下面这个例子返回bank索引中的所有的文档:
curl '10.37.167.204:9200/bank/_search?q=*&pretty'
我们仔细研究一下这个查询调用。我们在bank索引中搜索(_search端点),并且q=*参数指示Elasticsearch去匹配这个索引中所有的文档。pretty参数,和以前一样,仅仅是告诉Elasticsearch返回美观的JSON结果。
以下是响应(部分列出):
curl '10.37.167.204:9200/bank/_search?q=*&pretty' { "took" : 63, "timed_out" : false, "_shards" : { "total" : 5, "successful" : 5, "failed" : 0 }, "hits" : { "total" : 1000, "max_score" : 1.0, "hits" : [ { "_index" : "bank", "_type" : "account", "_id" : "1", "_score" : 1.0, "_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"} }, { "_index" : "bank", "_type" : "account", "_id" : "6", "_score" : 1.0, "_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"} }, { "_index" : "bank", "_type" : "account",
对于这个响应,我们看到了以下的部分:
- took —— Elasticsearch执行这个搜索的耗时,以毫秒为单位
- timed_out —— 指明这个搜索是否超时
- _shards —— 指出多少个分片被搜索了,同时也指出了成功/失败的被搜索的shards的数量
- hits —— 搜索结果
- hits.total —— 能够匹配我们查询标准的文档的总数目
- hits.hits —— 真正的搜索结果数据(默认只显示前10个文档)
- _score和max_score —— 现在先忽略这些字段
使用请求体方法的等价搜索是:
curl -XPOST '10.37.167.204:9200/bank/_search' -d '
{
"query": { "match_all": {} }
}'
这里的不同之处在于,并不是向URI中传递q=*,取而代之的是,我们在_search API的请求体中POST了一个JSON格式请求体。我们将在下一部分中讨论这个JSON查询。
响应是:
curl -XPOST '10.37.167.204:9200/bank/_search' -d ' { "query": { "match_all": {} } }' { "took" : 26, "timed_out" : false, "_shards" : { "total" : 5, "successful" : 5, "failed" : 0 }, "hits" : { "total" : 1000, "max_score" : 1.0, "hits" : [ { "_index" : "bank", "_type" : "account", "_id" : "1", "_score" : 1.0, "_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"} }, { "_index" : "bank", "_type" : "account", "_id" : "6", "_score" : 1.0, "_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"} }, { "_index" : "bank", "_type" : "account", "_id" : "13",
有一点需要重点理解一下,一旦你取回了你的搜索结果,Elasticsearch就完成了使命,它不会维护任何服务器端的资源或者在你的结果中打开游标。这是和其它类似SQL的平台的一个鲜明的对比, 在那些平台上,你可以在前面先获取你查询结果的一部分,然后如果你想获取结果的剩余部分,你必须继续返回服务端去取,这个过程使用一种有状态的服务器端游标技术。
{
"query": { "match_all": {} }
}
分解以上的这个查询,其中的query部分告诉我查询的定义,match_all部分就是我们想要运行的查询的类型。match_all查询,就是简单地查询一个指定索引下的所有的文档。
除了这个query参数之外,我们也可以通过传递其它的参数来影响搜索结果。比如,下面做了一次match_all并只返回第一个文档:
curl -XPOST 'localhost:9200/bank/_search' -d '
{
"query": { "match_all": {} },
"size": 1
}'
注意,如果没有指定size的值,那么它默认就是10。
下面的例子,做了一次match_all并且返回第11到第20个文档:
curl -XPOST 'localhost:9200/bank/_search' -d '
{
"query": { "match_all": {} },
"from": 10,
"size": 10
}'
其中的from参数(0-based)从哪个文档开始,size参数指明从from参数开始,要返回多少个文档。这个特性对于搜索结果分页来说非常有帮助。注意,如果不指定from的值,它默认就是0。
下面这个例子做了一次match_all并且以账户余额降序排序,最后返前十个文档:
curl -XPOST 'localhost:9200/bank/_search' -d '
{
"query": { "match_all": {} },
"sort": { "balance": { "order": "desc" } }
}'
curl -XPOST '10.37.167.204:9200/bank/_search' -d '
{
"query": { "match_all": {} },
"_source": ["account_number", "balance"]
}'
注意到上面的例子仅仅是简化了_source字段。它仍将会返回一个叫做_source的字段,但是仅仅包含account_number和balance来年改革字段。
如果你有SQL背景,上述查询在概念上有些像SQL的SELECT FROM。
现在让我们进入到查询部分。之前,我们看到了match_all查询是怎样匹配到所有的文档的。现在我们介绍一种新的查询,叫做match查询,这可以看成是一个简单的字段搜索查询(比如对应于某个或某些特定字段的搜索)。
下面这个例子返回账户编号为20的文档:
curl -XPOST 'localhost:9200/bank/_search' -d '
{
"query": { "match": { "account_number": 20 } }
}'
下面这个例子返回地址中包含“mill”的所有账户:
curl -XPOST 'localhost:9200/bank/_search' -d '
{
"query": { "match": { "address": "mill" } }
}'
下面这个例子返回地址中包含“mill”或者包含“lane”的账户:
curl -XPOST 'localhost:9200/bank/_search' -d '
{
"query": { "match": { "address": "mill lane" } }
}'
下面这个例子是match的变体(match_phrase),它会去匹配短语“mill lane”:
curl -XPOST 'localhost:9200/bank/_search' -d '
{
"query": { "match_phrase": { "address": "mill lane" } }
}'
现在,让我们介绍一下布尔查询。布尔查询允许我们利用布尔逻辑将较小的查询组合成较大的查询。
现在这个例子组合了两个match查询,这个组合查询返回包含“mill”和“lane”的所有的账户:
curl -XPOST 'localhost:9200/bank/_search' -d '
{
"query": {
"bool": {
"must": [
{ "match": { "address": "mill" } },
{ "match": { "address": "lane" } }
]
}
}
}'
在上面的例子中,bool must语句指明了,对于一个文档,所有的查询都必须为真,这个文档才能够匹配成功。
相反的,下面的例子组合了两个match查询,它返回的是地址中包含“mill”或者“lane”的所有的账户:
curl -XPOST 'localhost:9200/bank/_search' -d '
{
"query": {
"bool": {
"should": [
{ "match": { "address": "mill" } },
{ "match": { "address": "lane" } }
]
}
}
}'
在上面的例子中,bool should语句指明,对于一个文档,查询列表中,只要有一个查询匹配,那么这个文档就被看成是匹配的。
现在这个例子组合了两个查询,它返回地址中既不包含“mill”,同时也不包含“lane”的所有的账户信息:
curl -XPOST 'localhost:9200/bank/_search' -d '
{
"query": {
"bool": {
"must_not": [
{ "match": { "address": "mill" } },
{ "match": { "address": "lane" } }
]
}
}
}'
在上面的例子中, bool must_not语句指明,对于一个文档,查询列表中的的所有查询都必须都不为真,这个文档才被认为是匹配的。
我们可以在一个bool查询里一起使用must、should、must_not。此外,我们可以将bool查询放到这样的bool语句中来模拟复杂的、多等级的布尔逻辑。
下面这个例子返回40岁以上并且不生活在ID(daho)的人的账户:
curl -XPOST 'localhost:9200/bank/_search' -d '
{
"query": {
"bool": {
"must": [
{ "match": { "age": "40" } }
],
"must_not": [
{ "match": { "state": "ID" } }
]
}
}
}'
在先前的章节中,我们跳过了文档得分的细节(搜索结果中的_score字段)。这个得分是与我们指定的搜索查询匹配程度的一个相对度量。得分越高,文档越相关,得分越低文档的相关度越低。
Elasticsearch中的所有的查询都会触发相关度得分的计算。对于那些我们不需要相关度得分的场景下,Elasticsearch以过滤器的形式提供了另一种查询功能。过滤器在概念上类似于查询,但是它们有非常快的执行速度,这种快的执行速度主要有以下两个原因
- 过滤器不会计算相关度的得分,所以它们在计算上更快一些
- 过滤器可以被缓存到内存中,这使得在重复的搜索查询上,其要比相应的查询快出许多。
为了理解过滤器,我们先来介绍“被过滤”的查询,这使得你可以将一个查询(像是match_all,match,bool等)和一个过滤器结合起来。作为一个例子,我们介绍一下范围过滤器,它允许我们通过一个区间的值来过滤文档。这通常被用在数字和日期的过滤上。
这个例子使用一个被过滤的查询,其返回值是越在20000到30000之间(闭区间)的账户。换句话说,我们想要找到越大于等于20000并且小于等于30000的账户。
curl -XPOST 'localhost:9200/bank/_search' -d ' { "query": { "filtered": { "query": { "match_all": {} }, "filter": { "range": { "balance": { "gte": 20000, "lte": 30000 } } } } } }'
分解上面的例子,被过滤的查询包含一个match_all查询(查询部分)和一个过滤器(filter部分)。我们可以在查询部分中放入其他查询,在filter部分放入其它过滤器。在上面的应用场景中,由于所有的在这个范围之内的文档都是平等的(或者说相关度都是一样的),没有一个文档比另一个文档更相关,所以这个时候使用范围过滤器就非常合适了。
通常情况下,要决定是使用过滤器还是使用查询,你就需要问自己是否需要相关度得分。如果相关度是不重要的,使用过滤器,否则使用查询。如果你有SQL背景,查询和过滤器在概念上类似于SELECT WHERE语句, although more so for filters than queries。
除了match_all, match, bool,filtered和range查询,还有很多其它类型的查uxn/过滤器,我们这里不会涉及。由于我们已经对它们的工作原理有了基本的理解,将其应用到其它类型的查询、过滤器上也不是件难事。
聚合提供了分组并统计数据的能力。理解聚合的最简单的方式是将其粗略地等同为SQL的GROUP BY和SQL聚合函数。在Elasticsearch中,你可以在一个响应中同时返回命中的数据和聚合结果。你可以使用简单的API同时运行查询和多个聚合,并以一次返回,这避免了来回的网络通信,这是非常强大和高效的。
作为开始的一个例子,我们按照state分组,按照州名的计数倒序排序:
curl -XPOST 'localhost:9200/bank/_search' -d '
{
"size": 0,
"aggs": {
"group_by_state": {
"terms": {
"field": "state"
}
}
}
}'
在SQL中,上面的聚合在概念上类似于:
SELECT COUNT(*) from bank GROUP BY state ORDER BY COUNT(*) DESC
响应(其中一部分)是:
"hits" : { "total" : 1000, "max_score" : 0.0, "hits" : [ ] }, "aggregations" : { "group_by_state" : { "buckets" : [ { "key" : "al", "doc_count" : 21 }, { "key" : "tx", "doc_count" : 17 }, { "key" : "id", "doc_count" : 15 }, { "key" : "ma", "doc_count" : 15 }, { "key" : "md", "doc_count" : 15 }, { "key" : "pa", "doc_count" : 15 }, { "key" : "dc", "doc_count" : 14 }, { "key" : "me", "doc_count" : 14 }, { "key" : "mo", "doc_count" : 14 }, { "key" : "nd", "doc_count" : 14 } ] } } }
我们可以看到AL(abama)有21个账户,TX有17个账户,ID(daho)有15个账户,依此类推。
注意我们将size设置成0,这样我们就可以只看到聚合结果了,而不会显示命中的结果。
在先前聚合的基础上,现在这个例子计算了每个州的账户的平均余额(还是按照账户数量倒序排序的前10个州):
curl -XPOST 'localhost:9200/bank/_search' -d ' { "size": 0, "aggs": { "group_by_state": { "terms": { "field": "state" }, "aggs": { "average_balance": { "avg": { "field": "balance" } } } } } }'
注意,我们把average_balance聚合嵌套在了group_by_state聚合之中。这是所有聚合的一个常用模式。你可以任意的聚合之中嵌套聚合,这样你就可以从你的数据中抽取出想要的概述。
基于前面的聚合,现在让我们按照平均余额进行排序:
curl -XPOST 'localhost:9200/bank/_search' -d ' { "size": 0, "aggs": { "group_by_state": { "terms": { "field": "state", "order": { "average_balance": "desc" } }, "aggs": { "average_balance": { "avg": { "field": "balance" } } } } } }'
下面的例子显示了如何使用年龄段(20-29,30-39,40-49)分组,然后在用性别分组,然后为每一个年龄段的每一个性别计算平均账户余额:
curl -XPOST 'localhost:9200/bank/_search' -d ' { "size": 0, "aggs": { "group_by_age": { "range": { "field": "age", "ranges": [ { "from": 20, "to": 30 }, { "from": 30, "to": 40 }, { "from": 40, "to": 50 } ] }, "aggs": { "group_by_gender": { "terms": { "field": "gender" }, "aggs": { "average_balance": { "avg": { "field": "balance" } } } } } } } }'
有很多关于聚合的细节,我们没有涉及。如果你想做更进一步的实验,http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-aggregations.html是一个非常好的起点。
总结
Elasticsearch既是一个简单的产品,也是一个复杂的产品。我们现在已经学习到了基础部分,它的一些原理,以及怎样用REST API来做一些工作。我希望这个教程已经使你对Elasticsearch是什么有了一个更好的理解,跟重要的是,能够激发你继续实验Elasticsearch的其它特性。
附:常用的rest api
#集群健康度
curl 'localhost:9200/_cluster/health?pretty'
#集群状态
curl 'localhost:9200/_cluster/stats?pretty'
#集群索引健康度
curl 'localhost:9200/_cluster/health/索引名/?pretty'
#索引统计
curl 'localhost:9200/position/_stats?pretty'
#节点信息
curl 'localhost:9200/_nodes/节点名?pretty'
#节点统计
curl 'localhost:9200/_nodes/节点名/stats?pretty'
1、kibana简介
Kibana是一个开源的分析与可视化平台,设计出来用于和Elasticsearch一起使用的。你可以用kibana搜索、查看、交互存放在Elasticsearch索引里的数据,使用各种不同的图表、表格、地图等kibana能够很轻易地展示高级数据分析与可视化
Kibana让我们理解大量数据变得很容易。它简单、基于浏览器的接口使你能快速创建和分享实时展现Elasticsearch查询变化的动态仪表盘。安装Kibana非常快,你可以在几分钟之内安装和开始探索你的Elasticsearch索引数据,不需要写任何代码,没有其他基础软件依赖。
本文只介绍Kibana如何安装使用,更多关于Kibana信息请看官网:
https://www.elastic.co/guide/en/kibana/index.html
2、kibana安装
2.1 下载
下载地址:https://www.elastic.co/downloads/kibana
备注:如果使用ElasticSearch-2.3.x,可以下载kinaba-4.5.x。linux下命令下载:
2.2 解压
解压:tar zxvf kibana-4.5.1-linux-x64.tar.gz
2.3 配置
到config/kibana.yml目录下,般修改标注的这三个参数即可。
server.port: 5601
# The host to bind the server to.
server.host: ""
# If you are running kibana behind a proxy, and want to mount it at a path,
# specify that path here. The basePath can't end in a slash.
# server.basePath: ""
# The Elasticsearch instance to use for all your queries.
elasticsearch.url: "http://" #这里是elasticsearch的访问地址
2.4 启动
到bin目录下,启动即可
./kibana //不能关闭终端
nohup ./kibana > /nohub.out & //可关闭终端,在nohup.out中查看log
2.5 访问
在浏览器中访问:http://xxxx:5601/
操作命令中文版官网
操作命令英文版官网
kibaba的增删改查
1.新建索引 PUT /testIndex?pretty 2. 查看索引 GET /_cat/indices?v 3. 新建mapping POST test_index/test_type/_mapping { "test_type": { "dynamic": false, "_all": { "enabled": false }, "properties": { "wbbh": { "type": "keyword" }, "jyxkzbh": { "type": "keyword" }, "wbmc": { "type": "text", "analyzer": "smartcn", "fields": { "raw": { "type": "keyword" }, "standard": { "type": "text", "analyzer": "standard" } } }, "zbx": { "type": "keyword" }, "zby": { "type": "keyword" }, "zby_zbx": { "type": "keyword" }, "lksj": { "type": "keyword" }, "wbdz": { "type": "text", "analyzer": "smartcn", "fields": { "raw": { "type": "keyword" }, "standard": { "type": "text", "analyzer": "standard" } } }, "cjsj": { "type": "date" }, "rksj": { "type": "date" }, "gxdwmc": { "type": "text", "analyzer": "smartcn", "fields": { "raw": { "type": "keyword" }, "standard": { "type": "text", "analyzer": "standard" } } }, "wbfzr": { "type": "text", "analyzer": "smartcn", "fields": { "raw": { "type": "keyword" }, "standard": { "type": "text", "analyzer": "standard" } } }, "dt": { "type": "keyword" }, "type": { "type": "keyword" } } } } 4.查看索引数据 GET lihao/_search { "query": { "match_all": {} } } 5. 查看索引所在服务器: GET _cat/shards/lihao?v 6.查看索引数据条数: GET lihao/_count 7. 查看索引mapping: GET lihao/_mapping 8. GET company/_mapping/employee 9. 按条件查询: GET company/_search { "query": { "match": { "name": "Lack55" } } } 10. 查看集群节点信息: GET _nodes/_all 11. es排序查找 GET <索引名>/_search { "query": { "match_all": {} }, "sort":{"<字段名>":{"order":"desc"}} } 12. 集群健康情况 GET _cluster/health 13.新建mapping: PUT bank //索引名 { "settings": { "number_of_shards": 1, //分片数 "number_of_replicas": 0 //分片副本数 }, "mappings": { "test":{ //索引类型名 "_all": { "enabled": false }, "dynamic": "strict", //不允许动态添加不符合mapping的字段,参见:https://www.elastic.co/guide/cn/elasticsearch/guide/current/dynamic-mapping.html "properties": { "account_number":{ "type":"long", "doc_values":false }, "address":{ "type":"keyword", "doc_values":false, "norms": false }, "age":{ "type": "long", "doc_values":false }, "balance": { "type": "long" }, "city": { "type": "keyword", "doc_values":false, "norms": false }, "email": { "type": "keyword", "doc_values":false, "norms": false }, "employer": { "type": "keyword", "doc_values":false, "norms": false }, "firstname": { "type": "keyword", "doc_values":false, "norms": false }, "gender": { "type": "keyword", "doc_values":false, "norms": false }, "index": { "type": "keyword", "doc_values":false, "norms": false }, "lastname": { "type": "keyword", "doc_values":false, "norms": false }, "state": { "type": "keyword", "doc_values":false, "norms": false } } } } } mapping实例2 // { "mappings":{ "properties":{ "title":{ //字段名 "type":"string", //字段类型 "store":"yes" //是否存储。yes:存储 }, "description":{ "type":"string", "index":"not_analyzed" //是否分词。 not_analyzed:不分 }, "price":{ "type":"double" }, "onSale":{ "type":"boolean" }, "type":{ "type":"integer" }, "createDate":{ "type":"date" } } } }
Settings settings = Settings.builder()
.put("cluster.name", clusterName)
.put("client.transport.sniff", true) //client.transport.sniff启动嗅探功能,这样只需要指定集群中的某一个节点(不一定是主节点),然后会加载集群中的其他节点,这样只要程序不停即使此节点宕机仍然可以连接到其他节点.
//先创建空索引库 client.admin().indices().prepareCreate("productIndex").execute().actionGet(); //put mapping: XContentBuilder mapping = jsonBuilder() .startObject() .startObject("properties") .startObject("title").field("type", "string").field("store", "yes").endObject() .startObject("description").field("type", "string").field("index", "not_analyzed").endObject() .startObject("price").field("type", "double").endObject() .startObject("onSale").field("type", "boolean").endObject() .startObject("type").field("type", "integer").endObject() .startObject("createDate").field("type", "date").endObject() .endObject() .endObject(); PutMappingRequest mappingRequest = Requests.putMappingRequest("productIndex").type("productIndex").source(mapping); client.admin().indices().putMapping(mappingRequest).actionGet();
XContentBuilder doc = jsonBuilder()
.startObject()
.field("title", "this is a title!")
.field("description", "descript what?")
.field("price", 100)
.field("onSale", true)
.field("type", 1)
.field("createDate", new Date())
.endObject();
client.prepareIndex("productIndex","productType").setSource(doc).execute().actionGet();
# id
DeleteResponse response = client.prepareDelete("twitter", "tweet", "1")
.execute()
.actionGet();
# Query
QueryBuilder query = QueryBuilders.fieldQuery("title", "query");
client.prepareDeleteByQuery("productIndex").setQuery(query).execute().actionGet();
QueryBuilder qb1 = termQuery("name", "kimchy");
QueryBuilder qb2 = boolQuery()
.must(termQuery("content", "test1"))
.must(termQuery("content", "test4"))
.mustNot(termQuery("content", "test2"))
.should(termQuery("content", "test3"));
QueryBuilder qb3 = filteredQuery(
termQuery("name.first", "shay"),
rangeFilter("age")
.from(23)
.to(54)
.includeLower(true)
.includeUpper(false)
);
其中qb1构造了一个TermQuery,对name这个字段进行项搜索,项是最小的索引片段,这个查询对应lucene本身的TermQuery。 qb2构造了一个组合查询(BoolQuery),其对应lucene本身的BooleanQuery,可以通过must、should、mustNot方法对QueryBuilder进行组合,形成多条件查询。 qb3构造了一个过滤查询,就是在TermQuery的基础上添加一个过滤条件RangeFilter,这个范围过滤器将限制查询age字段大于等于23,小于等于54的结果。
import static org.elasticsearch.common.xcontent.XContentFactory.*; BulkRequestBuilder bulkRequest = client.prepareBulk(); bulkRequest.add(client.prepareIndex("twitter", "tweet", "1") .setSource(jsonBuilder() .startObject() .field("user", "kimchy") .field("postDate", new Date()) .field("message", "trying out Elastic Search") .endObject() ) ); bulkRequest.add(client.prepareIndex("twitter", "tweet", "2") .setSource(jsonBuilder() .startObject() .field("user", "kimchy") .field("postDate", new Date()) .field("message", "another post") .endObject() ) ); BulkResponse bulkResponse = bulkRequest.execute().actionGet(); if (bulkResponse.hasFailures()) { //处理错误 }
//json数据保存在文中,代码是通过读文件的方式: **{“10190”:0,”10071”:0,”10191”:0,”10070”:0,”48”:”122186”,”type”:”122186”,”10018”:0,”10117”:0,”332”:0,”333”:0,”999”:3202,”10178”:10131,”8503”:0,”8504”:0,”10135”:”SUSP”,”31”:0,”10116”:0,”35”:”UA3202”,”10185”:0,”10184”:0,”140”:100000,”387”:0,”8538”:”P010”,”10044”:0,”10187”:0,”10043”:0,”10186”:0,”10189”:0,”10068”:[],”10188”:0,”10202”:-1,”10146”:1,”10069”:[],”10204”:0,”10203”:-1} {“10190”:0,”10071”:0,”10191”:0,”10070”:0,”48”:”122186”,”type”:”122186”,”10018”:0,”10117”:0,”332”:0,”333”:0,”999”:3202,”10178”:10131,”8503”:0,”8504”:0,”10135”:”SUSP”,”31”:0,”10116”:0,”35”:”UA3202”,”10185”:0,”10184”:0,”140”:100000,”387”:0,”8538”:”P010”,”10044”:0,”10187”:0,”10043”:0,”10186”:0,”10189”:0,”10068”:[],”10188”:0,”10202”:-1,”10146”:1,”10069”:[],”10204”:0,”10203”:-1} {“10190”:0,”10071”:0,”10191”:0,”10070”:0,”48”:”122186”,”type”:”122186”,”10018”:0,”10117”:0,”332”:0,”333”:0,”999”:3202,”10178”:10131,”8503”:0,”8504”:0,”10135”:”SUSP”,”31”:0,”10116”:0,”35”:”UA3202”,”10185”:0,”10184”:0,”140”:100000,”387”:0,”8538”:”P010”,”10044”:0,”10187”:0,”10043”:0,”10186”:0,”10189”:0,”10068”:[],”10188”:0,”10202”:-1,”10146”:1,”10069”:[],”10204”:0,”10203”:-1} ##java类 import org.elasticsearch.action.bulk.BulkRequestBuilder; import org.elasticsearch.client.transport.TransportClient; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.transport.InetSocketTransportAddress; import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.transport.client.PreBuiltTransportClient; import static org.elasticsearch.common.xcontent.XContentFactory.*; import java.io.*; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.HashMap; import java.util.Map; public class Main { public static void main(String[] args) { try { //通过setting来制定集群信息,单机就不需要制定了集群信息了,去掉即可 Settings settings = Settings.builder(). put("cluster.name", "elk_test.cluster") .put("client.transport.sniff",true).build(); //创建客户端clients InetAddress address = InetAddress.getByName("192.168.0.153"); // ip InetSocketAddress socketAddress=new InetSocketAddress(address, 9300); //socket address = ip + port TransportAddress transportAddress = new InetSocketTransportAddress(socketAddress); TransportClient client = new PreBuiltTransportClient(settings).addTransportAddress(transportAddress); File file = new File("E:/json"); FileReader reader=new FileReader(file); BufferedReader bfr=new BufferedReader(reader); String line; BulkRequestBuilder bulkRequest=client.prepareBulk(); int count=0; while((line=bfr.readLine())!=null){ bulkRequest.add(client.prepareIndex("dddddddd","article").setSource(line)); if (count%10==0) { bulkRequest.execute().actionGet(); bulkRequest=client.prepareBulk(); } count++; //System.out.println(line); } bulkRequest.execute().actionGet(); bfr.close(); reader.close(); } catch (UnknownHostException e) { e.printStackTrace(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。