Elasticsearch是一个开源的高可用全文本搜索和分析引擎,它可以快速(接近实时)的存、搜索和分析大量数据。elasticsearch常被用作后台技术引擎,用来增强应用复杂的搜索能力。
以下为几个elasticsearch的使用案例:
● 电商网站需要满足用户搜索售卖产品的需求,可以利用elasticsearch存储所有的商品目录、商品详情并提供搜索及建议。
● 用于收集日志及交易数据并且从这批数据中分析挖掘出趋势、报表、总结、异常等信息。在这种案例下,可以利用logstash收集、聚合、清洗数据然后通过logstash把数据输出到elasticsearch。只要数据存入elasticsearch就可以运行搜索和聚合语句去挖掘任何你感兴趣的信息。
● 你可以利用elasticsearch搭建一个价格提醒平台,允许对价格敏感的用户指定规则(如:我对某一个电子元件感兴趣,如果在下个月内不管是哪个店铺中该元件的价格降到x一下,就通知我。。。),在这种情况下你可以抓取供应商的价格然后把数据放入elasticsearch并利用elasticsearch的过滤器功能将用户的查询进行匹配,一旦出现匹配就把通知推送给用户。
● 如果你有分析和BI方面的需求,想快速的进行调查、分析、可视化、向海量数据随机的提问题(ad-hoc)。在这种情况下,可以利用elasticsearch存储数据然后用kibana对重要的数据指标进行可视化并建立仪表盘。此外,可以利用elasticsearch的聚合功能对数据进行更加复杂的BI级别查询。
在本教程的其余部分中,将学习如何使Elasticsearch启动并运行,对elasticsearch进行简单的了解并执行像索引,搜索和修改数据的基本操作。 在本教程的最后,将会对Elasticsearch是什么以及它的工作原理有一个很好的了解,并希望能够启发您如何使用它来构建复杂的搜索应用程序或从数据中挖掘情报。
基本概念
准实时
elasticsearch是一个准实时平台,这就意味着elasticsearch从开始索引数据到数据可以被搜索到有轻微的延时。
集群
集群是一个活多个节点的集合,集群保存全量的数据并且提供跨节点的索引和搜索的功能。一个集群有一个唯一的名称,默认的名称为“elasticsearch”。节点启动时只有通过指定集群的名称才能加入集群,因此集群的名称非常重要。
在不同的开发环境中不要使用相同的集群名称,否则可能造成节点加入错误集群的情况。可以使用像logging-dev,logging-sit,logging-pre,logging-prd来分别表示开发环境、测试环境、预生产环境和生产环境的es集群。
需要注意,一个集群下有只有一个节点是允许的。
节点
节点是集群的组成部分,可以理解为一个单独的服务器,参与存储数据和集群索引和搜索数据。一个节点也有一个唯一的名称,默认在节点启动的时候把UUID作为节点名称,该节点名称可以通过配置文件进行指定。名称对于日常的集群管理十分重要,可以帮助确定网络中的哪些服务器对应于Elasticsearch群集中的哪些节点。
节点可以通过制定集群的名称加入特定的集群,默认情况下,节点启动会加入集群名为“elasticsearch”的集群。如果在相同的网络下启动多个节点,假如所有的节点可以互相发现,则他们会自动组织建立一个集群,集群名称为“elasticsearch”。
index
index是具有相似特征文档的集合
index名称必须是小写
一个集群可以创建任意多的index
type
es6.0弃用该功能
index下的逻辑分类
document(文档)
索引的最小数据单元
以json的格式表示文档内容
虽然存储在索引中,存储时必须指定type
shard
一个shard具有索引的所有功能
可以横向扩展容量
增加操作性能和吞吐量
只能在索引创建之前进行设置
一个shard是一个标准的lucene索引,一个lucene索引的文档数量最大
不超过2,147,483,519 (= Integer.MAX_VALUE - 128)
primary shard && replica shard
当shard可以设置副本时,则分为primary shard和replica shard
replica shard是primary shard的副本
提高查询的性能和查询的吞吐量(查询可以并行的在所有副本上执行)
索引创建完成后,可以动态设置副本数量
安装
https://www.elastic.co/guide/en/elasticsearch/reference/6.1/_installation.html
集群探索
现在我们的节点(集群)已经启动了,下一步需要做的就是和集群进行交流。幸运的是,Elasticsearch提供了非常全面和强大的REST API,您可以使用与您的集群进行交互。可以使用API来完成的几件事情如下:
- 检查您的群集,节点和索引健康状态,和统计信息
- 管理您的群集,节点以及索引数据和元数据
- 在你的索引上执行CRUD(创建,读取,更新和删除)和搜索等操作
- 执行高级搜索操作,如分页,排序,过滤,scripting, faceting, 聚合等等
集群健康
让我们从基本的集群健康检查开始,通过这个检查,我们可以观察我们的集群是如何工作的(正在做什么)。我们将使用 curl 这个工具来进行一系列操作。当然,你可以使用任何其它的支持 HTTP/REST 调用的工具。假定我们依然在刚启动的节点上工作,打开一个新的 Shell .
我们将用到_cat API来进行集群健康状态检查。
GET /_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 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1514554800 | 21:40:00 | elasticsearch | green | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | - | 100.0% |
我们可以看到我们的名称为 elasticsearch 的集群正在工作,并且状态为green.每当我们检查集群运行状态时,会得三种值分别表示不同的健康状态: green、 red、 yellow.
其中,
green: 表示集群所有节点都在正常的工作;
yellow:表示集群节点中的数据都是可访问的(集群正常工作),但是有一部分副本无法分配
red:意味着集群某些节点中的数据因为某种原因导致不可用。
注意,尽管集群运行状态是red, 集群也是可以部分工作的,它会在可用的分片中执行搜索请求。但你需要尽快解决存在的问题,因为这个时候你的数据可能丢失了或者存在其它问题。
从上述响应中,我们还可以发现,此时我们一共有一个节点,0个分片,因为我们的集群中还未存储数据。注意,因为我们使用集群的默认名称 elasticsearch 并且,elasticsearch在默认情况下使用多路广播来发现同一网段下其它节点,所以你可能意外的启动一些其它的节点,这些节点自动的加入到了你的集群中。在这种情况下,你所获得的响应中可能不止一个节点。
我们可以通过如下方式获取集群中的节点列表:
GET /_cat/nodes?v
响应结果为:
ip | heap.percent | ram.percent | cpu | load_1m | load_5m | load_15m | node.role | master | name |
---|---|---|---|---|---|---|---|---|---|
127.0.0.1 | 30 | 69 | 13 | mdi | * | ix7fQY1 |
我们可以发现,我们的集群中只有一个节点,名称为”ix7fQY1”
列出所有索引
通过以下命令可以查看索引概览:
GET /_cat/indices?v
得到的响应为:
health | status | index | uuid | pri | rep | docs.count | docs.deleted | store.size | pri.store.size |
---|
这意味着集群中还没有索引。
创建一个索引
现在,让我们创建一个名为“customer”的索引,并重新列出所有的索引:
PUT /customer?pretty
GET /_cat/indices?v
第一条命令,我们使用PUT 方法创建了一个名称为”customer”的索引,并且使用一个pretty参数告诉集群让它返回一个格式美观的JSON响应。
命令得到的响应:
health | status | index | uuid | pri | rep | docs.count | docs.deleted | store.size | pri.store.size |
---|---|---|---|---|---|---|---|---|---|
yellow | open | customer | WRAPhoRyTOSK2-_ZvjkyWw | 5 | 1 | 0 | 0 | 1.1kb | 1.1kb |
第二条命令的响应告诉我们,现在我们的集群中有一个名为 “customer”的索引,该索引有5个原始分片,一个副本。
你可能注意到了,此索引的health状态标识为yellow,这说明这个索引的副本没有被分配。因为我们目前只有一个节点,为了保证高可用性,副本是不会被分配到和它原始分片相同的节点上的。等到有新的节点加入到集群中后,副本会再分配。那时再查看索引状态的话,yellow就会变为green.
索引并查询文档
现在,向customer索引中添加一些内容。我们将向customer索引中索引一个简单的消费者文档,文档的id设置为1,如:
PUT /customer/doc/1?pretty
{
"name": "John Doe"
}
elasticsearch响应为:
{
"_index": "customer",
"_type": "doc",
"_id": "1",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1
}
从上述的响应中我们可以看出,一个新的客户文档已经被索引到了customer索引中,并且该文档中的id为1,这是在索引时存储语句中指定的。
需要注意的是,elasticsearch 并不用在索引一个文档之前首先显式的创建索引,在刚才的例子中,elasticsearch会自动的创建customer索引,如果它不存在的话。
现在,让我们检索一下我们刚刚索引的文件:
GET /customer/doc/1?pretty
响应为:
{
"_index": "customer",
"_type": "doc",
"_id": "1",
"_version": 1,
"found": true,
"_source": {
"name": "John Doe"
}
}
我们发现多了一个found字段,该字段值表明找到了id为1的文档。_source字段的内容为我们之前索引的文档内容。
删除一个索引
现在删除刚才创建的索引,并查看索引列表
DELETE /customer?pretty
GET /_cat/indices?v
返回响应:
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
这意味着已经成功删除了刚才创建的索引。现在集群已经恢复到没有创建索引之前的状态。
现在回顾一下之前用到的api:
PUT /customer
PUT /customer/doc/1
{
"name": "John Doe"
}
GET /customer/doc/1
DELETE /customer
如果我们仔细研究了上面的命令,我们可以清楚地看到访问Elasticsearch数据的模式。该模式可以被概括如下:
<REST Verb> /<Index>/<Type>/<ID>
这种模式在 elasticsearch REST API中是非常普遍的,记住它,对于你掌握其它命令非常有帮助。
修改数据
Elasticsearch提供准实时数据的操作和搜索功能。一般情况,从操作数据(索引、删除、更新)到数据可以体现到搜索结果中要经历1秒钟。在这一点上跟其他的平台有着很大的区别(如:数据库在事务操作完成后可以立即生效)
上文已经讲过索引一个文档,现在重新操作一遍:
PUT /customer/doc/1?pretty
{
"name": "John Doe"
}
同样的,上述命令向customer索引中索引数据并且指定id为1。如果再次运行上述命令但是提交的数据不同(也可以是相同的数据),elasticsearch将把原来id为1的文档替换为当前的最新文档:
PUT /customer/doc/1?pretty
{
"name": "Jane Doe"
}
上述命令把文档的name字段值从”John Doe” 变换为 “Jane Doe”。如果指定一个不同的ID,则索引中将会增加一个新的文档,之前索引中存在的文档将不受影响。
PUT /customer/doc/2?pretty
{
"name": "Jane Doe"
}
上述指令索引一个ID为2的新文档。
在索引文档的时候ID的指定是可选的,在未指定的情况下elasticsearch会自动指定一个随机的ID号。实际的ID号,例如elasticsearch生成的,或者我们指定的ID,会作为索引API调用的一部分被返回。
如下命令是不指定ID 号,而是让elasticsearch自动生成ID:
POST /customer/doc?pretty
{
"name": "Jane Doe"
}
注意,我们使用了POST 来代替 PUT。
更新文档
除了索引和替换文档,也可以更新文档。需要注意的是,更新操作不会在原来的文档上进行而是删除旧的文档然后把新的文档索引进去。在进行更新操作的时候,如果更新的字段值没有变化则会造成更新失败。
向索引customer索引一个文档,ID:1
PUT /customer/doc/1?pretty
{
"name": "Jane Doe"
}
响应内容为:
{
"_index": "customer",
"_type": "doc",
"_id": "1",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1
}
利用更新api:
POST /customer/doc/1/_update?pretty
{
"doc": { "name": "Fang Shuzhi", "age": 20 }
}
得到的响应:
{
"_index": "customer",
"_type": "doc",
"_id": "1",
"_version": 2,
"result": "updated",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 1,
"_primary_term": 1
}
如果更新的内容与当前文档内容相一致:
POST /customer/doc/1/_update?pretty
{
"doc": { "name": "Fang Shuzhi"}
}
得到的响应内容为:
{
"_index": "customer",
"_type": "doc",
"_id": "1",
"_version": 2,
"result": "noop",
"_shards": {
"total": 0,
"successful": 0,
"failed": 0
}
}
可以看到result的值为noop,表示内容并未更新,因此版本号也不会增加。
更新操作也可以通过简单的脚本语言进行,如下的更新语句是把age字段每次增加5:
POST /customer/doc/1/_update?pretty
{
"script" : "ctx._source.age += 5"
}
得到的响应是:
{
"_index": "customer",
"_type": "doc",
"_id": "1",
"_version": 3,
"result": "updated",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 2,
"_primary_term": 1
}
通过响应发现版本已更新,更新后的doc内容为:
{
"_index": "customer",
"_type": "doc",
"_id": "1",
"_version": 3,
"found": true,
"_source": {
"name": "Fang Shuzhi",
"age": 25
}
}
上述请求中ctx._source代表要更新文档的source字段。
Elasticsearch同时也提供向sql一样通过一个查询条件批量的更新数据。
删除文档
可以通过指定id的方式对文档直接删除,如:
DELETE /customer/doc/1?pretty
可以利用delete_by_query的方式通过查询条件批量的删除数据,但是最高效的批量删除数据的方式是直接删除索引。
通过条件删除数据的方式不建议使用,有的elasticsearch版本不支持delete_by_query,可以通过查询数据然后指定删除文档的方式批量删除。可以用batch的方式进行批量操作,以减少网络消耗。
批处理
除了对单独的文档提供增删改查操作,elasticsearch还可以通过_bulk方式对上述功能提供批量操作,这种方式可以提供高效的机制处理大量的操作请求,从而达到更小的网络消耗更快的速度。
POST /customer/doc/_bulk?pretty
{"index":{"_id":"1"}}
{"name": "John Doe" }
{"index":{"_id":"2"}}
{"name": "Jane Doe" }
表示一次批处理建立两个文档
POST /customer/doc/_bulk?pretty
{"update":{"_id":"1"}}
{"doc": { "name": "John Doe becomes Jane Doe" } }
{"delete":{"_id":"2"}}
该批处理表示更新id为1的文档,删除id为2的文档
注意在删除语句中没有紧跟文档的信息。
BULK操作不会因为其中一个操作的失败而造成整个bulk操作的时候,当bulk操作执行完毕后会返回响应信息,该信息包含每个操作的执行情况,返回操作顺序为开始执行时候的顺序相同。
探索数据
search API
搜索分为两种形式:
- rest request URI
GET /bank/_search?q=*&sort=account_number:asc&pretty
- rest request body
GET /bank/_search
{
"query": { "match_all": {} },
"sort": [
{ "account_number": "asc" }
]
}
查询语言介绍
Elasticsearch提供json风格的DSL语言。
GET /bank/_search
{
"query": { "match_all": {} }
}
query字段指定查询条件,其中“match_all”表示匹配bank索引下的所有文档。
GET /bank/_search
{
"query": { "match_all": {} },
"from": 10,
"size": 10
}
form表示从查询结果的那个地方开始
size表示返回数据的条数
GET /bank/_search
{
"query": { "match_all": {} },
"sort": { "balance": { "order": "desc" } }
}
表示查询结果以balance字段降序排列,没有指定size字段默认取10。
搜索语句默认会返回数据所有的字段,可以通过语句使结果只返回特定的字段值:
GET /bank/_search
{
"query": { "match_all": {} },
"_source": ["account_number", "balance"]
}
上述语句返回的_source字段中,只有 “account_number”, “balance”两个字段。类似于sql语句的SELECT FROM语法。
match query
match搜索是一种基础的字段搜索语句:
GET /bank/_search
{
"query": { "match": { "account_number": 20 } }
}
该搜索语句表示搜索account_number字段等于20的数据。
GET /bank/_search
{
"query": { "match": { "address": "mill" } }
}
表示搜索address字段中包含“mill”词条的数据
bool query
该类型的查询可以用逻辑组合的方式来联合查询
GET /bank/_search
{
"query": {
"bool": {
"must": [
{ "match": { "address": "mill" } },
{ "match": { "address": "lane" } }
]
}
}
}
该查询语句由两个match query组成,bool must 表示一个文档要同时满足这两个查询(两个查询结果都为true)才算文档符合查询结果。
GET /bank/_search
{
"query": {
"bool": {
"should": [
{ "match": { "address": "mill" } },
{ "match": { "address": "lane" } }
]
}
}
}
该bool should,由两个match query组成,一个文档只要满足一个match query就表示满足查询。
GET /bank/_search
{
"query": {
"bool": {
"must_not": [
{ "match": { "address": "mill" } },
{ "match": { "address": "lane" } }
]
}
}
}
同理bool must_not 查询表示两个查询均不满足才表示一个文档符合该查询。
bool查询可以把must、should、must_not进行组合放入语句,同样bool查询可以放入另一个bool查询中,这样可以组成更复杂的查询语句:
GET /bank/_search
{
"query": {
"bool": {
"must": [
{ "match": { "age": "40" } }
],
"must_not": [
{ "match": { "state": "ID" } }
]
}
}
}
表示查询年龄为40岁但是住所不在ID州的人。
过滤查询
返回数据中的score字段表示数据与查询的相关性评分,评分越高表示相关性越大。并不是所有的查询都会有评分操作,elasticsearch会对查询语句进行检测和优化,避免进行无用的想关心评分。过滤查询一般不需要评分。
过滤查询语句,是过滤其他查询的结果集:
GET /bank/_search
{
"query": {
"bool": {
"must": { "match_all": {} },
"filter": {
"range": {
"balance": {
"gte": 20000,
"lte": 30000
}
}
}
}
}
}
上述bool查询包含query和filter两个部分。
聚合查询
聚合操作类似于SQL的GROUP BY语句,聚合查询的返回结果包含查询语句的结果和聚合语句的结果两部分,例:
GET /bank/_search
{
"size": 0,
"aggs": {
"group_by_state": {
"terms": {
"field": "state.keyword"
}
}
}
}
上述语句等价于sql的:
SELECT state, COUNT(*) FROM bank GROUP BY state ORDER BY COUNT(*) DESC
结果集为:
{
"took": 29,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped" : 0,
"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
}, {
"key" : "MD",
"doc_count" : 25
}, {
"key" : "TN",
"doc_count" : 23
}, {
"key" : "MA",
"doc_count" : 21
}, {
"key" : "NC",
"doc_count" : 21
}, {
"key" : "ND",
"doc_count" : 21
}, {
"key" : "ME",
"doc_count" : 20
}, {
"key" : "MO",
"doc_count" : 20
} ]
}
}
}
size指定为0表示不返回搜索结果,返回的聚合数据默认显示10条,按照count进行排序。
GET /bank/_search
{
"size": 0,
"aggs": {
"group_by_state": {
"terms": {
"field": "state.keyword",
"order": {
"average_balance": "desc"
}
},
"aggs": {
"average_balance": {
"avg": {
"field": "balance"
}
}
}
}
}
}
上述语句指定聚合返回结果的排序方式,并且算出每一个聚类的平均收入值。
GET /bank/_search
{
"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.keyword"
},
"aggs": {
"average_balance": {
"avg": {
"field": "balance"
}
}
}
}
}
}
}
}
上述语句表示先以年龄聚合然后以性别聚合最后计算平均输入。
更多的聚合教程:https://www.elastic.co/guide/en/elasticsearch/reference/6.1/search-aggregations.html