ElasticSearch笔记
ElasticSearch.jpg前言
Elasticsearch 是一个开源的搜索引擎,建立在一个全文搜索引擎库 Apache Lucene™ 基础之上。 Lucene 可以说是当下最先进、高性能、全功能的搜索引擎库--无论是开源还是私有。
但是 Lucene 仅仅只是一个库。为了充分发挥其功能,你需要使用 Java 并将 Lucene 直接集成到应用程序中。 更糟糕的是,您可能需要获得信息检索学位才能了解其工作原理。Lucene 非常 复杂。
Elasticsearch 也是使用 Java 编写的,它的内部使用 Lucene 做索引与搜索,但是它的目的是使全文检索变得简单, 通过隐藏 Lucene 的复杂性,取而代之的提供一套简单一致的 RESTful API。
然而,Elasticsearch 不仅仅是 Lucene,并且也不仅仅只是一个全文搜索引擎。 它可以被下面这样准确的形容:
- 一个分布式的实时文档存储,每个字段 可以被索引与搜索
- 一个分布式实时分析搜索引擎
- 能胜任上百个服务节点的扩展,并支持 PB 级别的结构化或者非结构化数据
Elasticsearch 将所有的功能打包成一个单独的服务,这样你可以通过程序与它提供的简单的 RESTful API 进行通信, 可以使用自己喜欢的编程语言充当 web 客户端,甚至可以使用命令行(去充当这个客户端)。
就 Elasticsearch 而言,起步很简单。对于初学者来说,它预设了一些适当的默认值,并隐藏了复杂的搜索理论知识。 它 开箱即用 。只需最少的理解,你很快就能具有生产力
本文基于ElasticSearch 2.2,参考自https://www.elastic.co/guide/en/elasticsearch/reference/2.2/getting-started.html
基础知识
-
Near RealTime
Elastic是一种实时搜索引擎 -
Cluster
集群是各个节点的集合,保存了所有的数据并提供了数据检索能力。- 默认集群的名称是elasticsearch
- 集群名称不可重复
- 每个集群至少有一个节点
-
Node
多个节点构成了集群,集群的数据存储和数据检索也是依赖于节点来完成的,节点的名称是一个随机值,当然你也可以自己修改咯- 节点默认加入elasticsearch集群
- 一个集群的拥有的节点数量不做限制
- 如果网络上没有一个节点,那么此时新建一个节点将默认加入到elasticsearch集群中
-
Index
索引是document的集合,通过索引你可以查询,更新,删除document- 索引的名称必须全部是小写
- 集群中你可以定义多个index
-
Type
Type可以理解为Java中的类,Document就是通过该类创建出来的实例 -
Document
Document是可以被检索到的最小数据单元集合- Document数据全部是JSON格式
- Document必须指定其对应type
-
Shards
数据分片,提供了index数据量无限增大的能力。出现数据分片的原因:单个node容不下某个index中所有的数据。 -
Replicas
既然有了数据分片,也就意味着数据不在同一个物理节点上存储,那么某次查询就会可能出现某个数据分片所在的节点挂掉的情况,此时的解决方案就是进行数据复制- 默认情况下,elasicsearch有5个数据分片和1次复制,这就意味着如果集群中只有两个nodeA和B,加入nodeA保存着源数据,那么经过一个数据复制之后,nodeB中也会存在一份相同的数据
启动ElasticSearch
在bin目录下执行
./elasticsearch
启动时修改集群名称和节点名称
./elasticsearch --cluster.name fanyank_cluster --node.name fanyank_node1
探索ElasticSearch
-
执行健康检查
curl "localhost:9200/_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 1541906922 11:28:42 elasticsearch green 1 1 0 0 0 0 0 0 - 100.0%
- status: green(正常),yellow(集群整体可用,但某些复制节点有问题),red(集群不可用)
-
查询所有节点
curl "localhost:9200/_cat/nodes?v"
执行成功之后可以看到
host ip heap.percent ram.percent load node.role master name 127.0.0.1 127.0.0.1 4 81 -1.00 d * Nightshade
-
查询所有index
curl "localhost:9200/_cat/indices?v"
执行成功之后可以看到
health status index pri rep docs.count docs.deleted store.size pri.store.size
意味着我们目前还没有建立任何索引
-
建立index
建立名称为customer的indexcurl -XPUT 'localhost:9200/customer?pretty' curl 'localhost:9200/_cat/indices?v'
执行之后可以看到
health status index pri rep docs.count docs.deleted store.size pri.store.size yellow open customer 5 1 0 0 650b 650b
我们可以得知customer索引现在有5个数据分片和1次复制,包含了0个文档。另外,health状态为yellow,这是因为我们现在只有一个node,而复制操作至少需要两个node,所以replica是连接不上的,所以状态为yellow
-
插入document
在插入数据前,必须指定数据的type。现在我们在customer索引中插入一个类型为external的数据,并且数据的ID为1
插入的JSON数据{"name":"fanyank"}
curl -XPUT 'localhost:9200/customer/external/1?pretty' -d '{"name" : "fanyank"}'
执行之后可以看到
{ "_index" : "customer", "_type" : "external", "_id" : "2", "_version" : 1, "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "created" : true }
插入数据时注意,elasticsearch不会检查索引是否存在,如果发现索引不存在,那么elasticsearch会自动创建索引并插入,所以执行插入时一定要检查索引名称是否拼写正确。
我们在插入数据时也可以不指定ID,这样elasticsearch会为我们生成一个唯一的hashcode,注意此时使用的http请求是POST
curl -XPOST 'localhost:9200/customer/external?pretty' -d '{"name" : "fanyank"}'
执行之后返回如下
{ "_index" : "customer", "_type" : "external", "_id" : "AWcBDGmpUlH0QZg74qVp", "_version" : 1, "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "created" : true }
-
更新数据
更新数据和插入数据一样,如果发现id已经存在,那么elasticsearch就会执行更新操作
elasticsearch执行更新操作的本质是删除已有数据然后新插入一条数据-
方法一
curl -XPUT 'localhost:9200/customer/external/1?pretty' -d '{"name":"Big Bang"}'
-
方法二
curl -XPOST 'localhost:9200/customer/external/1/_update?pretty' -d '{"doc":{"name":"Nathan James"}}'
-
方法三
在external中添加age字段curl -XPOST 'localhost:9200/customer/external/1/_update?pretty' -d '{"doc":{"name":"Nathan James","age":20}}'
-
方法四
使用scriptcurl -XPOST 'localhost:9200/customer/external/1/_update?pretty' -d ' { "script" : "ctx._source.age += 5" }'
注意方法二,方法三请求是POST请求,而且请求体包含doc字段
-
-
查询数据
查询我们刚刚插入的id为1的数据curl 'localhost:9200/customer/external/1?pretty'
执行之后返回如下
{ "_index" : "customer", "_type" : "external", "_id" : "2", "_version" : 4, "found" : true, "_source" : { "name" : "fanyank" } }
-
删除index
curl -XDELETE 'localhost:9200/customer?pretty' curl 'localhost:9200/_cat/indices?v'
-
删除document
curl -XDELETE 'localhost:9200/customer/external/2?pretty'
-
批处理
批处理可以在一次请求中对多个document完成插入,更新,删除操作
如下示例在一次请求中插入了一条数据{"name":"Alice"}
,更新一条数据{"name":"tom","age",16}
,删除一条数据{"_id":"1"}
curl -XPOST 'localhost:9200/customer/external/_bulk?pretty' -d ' {"index":{"_id":"3"}} {"name":"Alice"} {"update":{"_id":"2"}} {"doc":{"name:"tom","age":"16"}} {"delete":{"_id":"1"}} '
这个不成功,稍后再试一下
搜索API详解
-
搜索某个索引的全部数据
curl 'localhost:9200/bank/_search?q=*&pretty'
或者
curl 'localhost:9200/bank/_search?pretty' -d ' { "query" : {"match_all":{}} } '
-
限制返回的结果数量为100,默认限制的是10个
curl 'localhost:9200/bank/_search?pretty' -d ' { "query" : {"match_all":{}}, "size": 100 } '
-
分页
返回下标为10往后的10个数据curl 'localhost:9200/bank/_search?pretty' -d ' { "query" : {"match_all":{}}, "from": 10, "size": 10 } '
-
排序
按照余额倒序排列curl 'localhost:9200/bank/_search?pretty' -d ' { "query" : {"match_all":{}}, "sort": { "balance": { "order": "desc" } } } '
-
限制返回的字段
限制返回的字段只有account_number和balancecurl -XPOST 'localhost:9200/bank/_search?pretty' -d ' { "query": { "match_all": {} }, "_source": ["account_number", "balance"] }'
-
条件查询
查询account_id为10010的账户curl -XPOST 'localhost:9200/bank/_search?pretty' -d ' { "query": { "match": {"account_id":"10010"} } }'
-
与查询
查询address为test,且account_id为10010的账户curl -XPOST 'localhost:9200/bank/_search?pretty' -d ' { "query": { "bool": { "must": [ {"match": {"account_id":"10010"}}, {"match": {"address": "test"}} ] } } }'
-
或查询
查询account_id为10010或account_id为10011的账户curl -XPOST 'localhost:9200/bank/_search?pretty' -d ' { "query": { "bool": { "should": [ {"match": {"account_id":"10010"}}, {"match": {"account_id": "10011"}} ] } } }'
-
非查询
查询account_id不为10010和10011的账户curl -XPOST 'localhost:9200/bank/_search?pretty' -d ' { "query": { "bool": { "must_not": [ {"match": {"account_id":"10010"}}, {"match": {"account_id": "10011"}} ] } } }'
-
组合查询(与或非)
查询account_id不为10011,且address为test的账户curl -XPOST 'localhost:9200/bank/_search?pretty' -d ' { "query": { "bool": { "must_not": [ {"match": {"account_id": "10011"}} ], "must": [ {"match": {"addrss": "test"}} ] } } }'
-
过滤器查询
查询所有数据,并且过滤出ts在2017-11-14T13:08:40.000Z - 2017-12-14T13:08:40.000Z
之间的数据curl -XPOST 'localhost:9200/bank/_search?pretty' -d ' { "query": { "bool": { "must": [ {"match_all": {} ], "filter": { "range": { "ts": { "gte": 2017-11-14T13:08:40.000Z "lte": 2017-12-14T13:08:40.000Z } } } } } }'
-
聚合查询
-
avg
查询学生的平均成绩{ "aggs": { "avg_grade": { "avg": { "field": "grade" } } } }
查询结果如下
{ ... "aggregations": { "avg_grade": { "value": 75 } } }
-
count
查询学生数量{ "size": 0, "aggs": { "student_count": { "cardinality": { "field": "id" } } } }
查询结果如下:
{ ... "aggregations": { "student_count": { "value": 100 } } }
-
stats
统计学生成绩{ "size": 0, "aggs": { "student_grade_stats": { "extended_stats": { "field": "grade" } } } }
查询结果如下:
{ ... "aggregations": { "grade_stats": { "count": 9, "min": 72, "max": 99, "avg": 86, "sum": 774, "sum_of_squares": 67028, "variance": 51.55555555555556, "std_deviation": 7.180219742846005, "std_deviation_bounds": { "upper": 100.36043948569201, "lower": 71.63956051430799 } } } }
-
bucket
把学生按照性别进行分组{ "size": 0, "aggs": { "genders": { "terms": { "field": "gender" } } } }
执行之后结果如下:
{ ... "aggregations" : { "genders" : { "doc_count_error_upper_bound": 0, "sum_other_doc_count": 0, "buckets" : [ { "key" : "male", "doc_count" : 10 }, { "key" : "female", "doc_count" : 10 }, ] } } }
-
嵌套查询
分别计算男生和女生的数学成绩的平均分{ "aggs": { "groupby_gender": { "terms": { "field": "gender" }, "aggs": { "avg_math_score": { "avg": { "field": "math" } } } } } }
查询结果如下
{ ... "aggregations": { "groupby_gender": { "doc_count_error_upper_bound": 3, "sum_other_doc_count": 304, "buckets": [ { "doc_count": 10, "avg_math_score": { "doc_count_error_upper_bound": 0, "sum_other_doc_count": 0, "buckets": [ { "doc_count": 1, "key": 87 } ] }, "key": "male" }, { "doc_count": 9, "avg_math_score": { "doc_count_error_upper_bound": 0, "sum_other_doc_count": 0, "buckets": [ { "doc_count": 1, "key": 91 } ] }, "key": "female" } ] } } }
某次搜索结果如下,借此说明各个字段的含义
{ "took": 63, "timed_out": false, "_shards": { "total": 5, "successful": 5, "failed": 0 }, "hits": { "total": 1000, "max_score": 1, "hits": [ { "_index": "bank", "_type": "account", "_id": "1", "_score": 1, "_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, "_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" } } ] } }
数据字典:
- took: 查询花费的毫秒数
- time_out: 查询是否超时
- _shards: 查询所涉及到的数据分片的数量,成功数量和失败数量
- hits: 查询结果
- hits_.total: 返回的结果总数
- hits.hits: 返回的结果,以数组形式返回
- _score: 与查询参数匹配的程度
- max_score: 最大匹配程度
-
网友评论