Shihanmax's blog

< Back

Elasticsearch初探

Elasticsearch

Elasticsearch是一个高伸缩的开源全文搜索和分析引擎,它可以快速地、近实时的存储,搜索和分析大规模的数据。一般被用作底层引擎/技术,为具有复杂搜索功能和要求的应用提供强有力的支撑。

使用场景:

  • 电商网站,商品的检索
  • 日志或交易数据的统计、汇报、总结
  • 在百万或十亿条数据中查找特定的问题

基本概念:

近实时 NRT

ES平台接近实时,从文档索引到可搜索的时间非常短暂。

集群 Cluster

集群是一个或多个节点(Server)的集合,它们联合起来保存所有的数据,并且可以在任何一个节点上进行索引和搜索操作。集群由唯一的名称标识,默认是elasticsearch。一个节点只能属于一个集群。你也可以有多个集群,每个集群都有一个唯一的名字。

节点 Node

一台独立的服务器就是一个节点,是集群的一部分,参与集群的索引与检索。同样地,节点拥有一个唯一的名字,默认是一个随机的UUID,当然也可以定制。节点名称很重要,可以帮助区分集群服务器和节点的对应关系。

节点通过配置集群名称之后,就可以加入指定的集群,默认情况下节点都会加入到默认集群elasticsearch。

索引 Index

索引是具有相似特点的文档集合,如客户数据,产品目录等。

索引由名称(只能使用小写字母)来标识,该名称用于对文档的索引、搜索、更新和删除等操作。单个集群中,可以定制任意数量的索引。

文档 Document

文档是可以被索引的基本单位,如用一个文档保存客户的数据,或单个商品的数据。文档用JSON表示,在索引中可以存储大量文档。

分片和副本 Shards & Replicas

一个索引可能存储海量数据,有可能超过单个节点的硬盘容量,为了解决这个问题,ES提供了分片功能,即索引细分。即创建索引时,可以定义分片数,每个分片具备索引的全部功能,可以存放在集群中的任何一个节点上。

分片很重要,原因如下:

  • 允许水平分割/缩放内容量
  • 允许并行地分发操作到多个节点的分片上,从而提高性能和吞吐量

分片分发机制,以及在检索中如何汇总到搜索响应的过程完全由ES管理,并且是透明的。

在网络/云环境中,故障可能会发生,此时,分片会非常有用,强烈建议使用故障转移机制,以防止节点脱机。ES允许将索引的分片复制多份,即副本。

副本的重要性:

  • 在分片/节点故障时,提高可用性(注意,副本与原始/主分片不能分配在同一个节点上)
  • 允许扩展搜索量,可对所有副本执行搜索

每个索引可以分为多个分片。每个索引也可以被复制零次(意味着没有副本)或多次。一旦复制,每个索引将具有主分片(原始分片)和副分片(主分片的副本)。可以在创建索引时根据索引定义分片和副本的数量。创建索引后,您可以随时动态更改副本数,但不能更改分片数。

默认情况下,每个索引都会被分配5个主分片和1一个复制分片,这意味着如果你的集群中有两个节点,你的索引将会有5个主分片和5个复制分片,总共有10个分片。

每个分片是一个Lucene index,一个Lucene index中可以有很多的文档,截至 LUCENE-5843,最多2147483519(= Integer.MAX_VALUE - 128) 个文档。可以使用 _cat/shards api监视分片大小。

关于全文检索,Lucene和倒排索引

ES初探

安装、启动

安装过程参考ES官网,注意ES依赖Java8环境。

启动方法:

1
2
cd elasticsearch-5.4.1
./bin/elasticsearch

REST API

ES提供了非常强大的REST API与集群进行通讯。

API能力:

  • 检查集群、节点、索引的运行状态和统计信息
  • 管理集群、节点、索引和元数据
  • 执行CRUD,针对索引进行搜索
  • 执行高级操作,如分页、排序、过滤、脚本、聚合等

查看集群、节点、索引

1
2
3
4
5
6
7
8
GET /_cat/health?v
	# 查看节点健康

GET /_cat/nodes?v
	# 查看集群节点列表

GET /_cat/indices?v
	# 查看所有索引,如果集群中只有一个节点,则此索引的健康值为yellow(副本未分配)

索引的创建和删除

1
2
3
4
5
PUT /bank?pretty
	# 创建名为testindex的索引

DELETE /bank?pretty
	# 删除testindex索引

文档的创建、修改和删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
PUT /bank/account/1?pretty
{
	"name": "John Doe"
}
	# 索引一篇文档,类型为sometype,ID为1(此ID也可不指定)索引

PUT /bank/account/1?pretty
{
	"name": "Maxan"
}
	# 如果对已存在的文档修改内容后,再次执行索引,ES会使用一个新文档取代旧文档(重建索引)

POST /bank/account?pretty
{
	"name": "Tom"
}
	# 没有指定ID,使用POST方法取代PUT

POST /bank/account/1/_update?pretty
{
	"doc": { "name": "Maxan", "age": 24}
}
	# 对id为1的文档执行更新,ES会删除旧文档,索引新文档

DELETE /bank/account/1?pretty
	# 删除ID为1的文档

批处理

1
2
3
4
5
6
7
8
9
10
POST /bank/account/_bulk?pretty
{"index": {"_id": "1"}}
{"name": "Tom hanks"}
{"index": {"_id": "2"}}
{"name": "Jack"}
{"update": {"_id": "1"}}
{"doc": {"name": "Hello World my name is ES"}}
{"delete": {"_id": "2"}}

	# 上面的请求批量索引了两篇文档,注意:最后要有空行

查询

1
2
3
4
5
6
7
8
9
10
11
12
GET /_search?q=*&sort=account_number:asc&pretty
	# 在所有索引下查询文档(方式1:通过REST request URI 方式发送查询参数)
GET /bank/_search?q=*&sort=account_number:asc&pretty
	#在bank下查询文档
GET /bank/_search
{
  "query": { "match_all": {} },
  "sort": [
		{ "account_number":"asc" }
  ]
}
	# 使用方式二查询(通过REST request body)

返回结果解释

  • took: 查询时间(ms)
  • time_out: 是否超时
  • _shards: 查询了多少分片
  • hits: 查询结果
  • hits.total: 符合我们查询条件的文档总数
  • hits.hits: 实际查询结果数组(默认为前10个文档)
  • hits.sort: 对结果进行排序的键(如果没提供,则默认使用_score进行排序)
  • hits._score:
  • max_score:

查询语言介绍(Query DSL)

match_all

1
2
3
4
5
6
7
8
9
10
GET /bank/_search
{
  "query": { "match_all": {} }
}
	# "query": 指定查询定义
	# "match_all": 查询类型
	# "size": 返回结果数
	# "from": 起始位置
	# "sort": {"age" : { "order": "desc"}}  # 按age降序排序
	# "_source": {"name", "age"}

match

1
2
3
4
5
GET /bank/_search
{
  "query": { "match": { "address": "mail Goodson" } }
}
	# "match": 指针对特定字段或一组字段进行搜索,上例返回address中包含"mail"或"Goodson"的文档

match_phase

1
2
3
4
5
GET /bank/_search
{
  "query": { "match_phrase": { "address": "mail Goodson" } }
}
	# 返回包含"main Goodson"短语的文档

bool query

must

1
2
3
4
5
6
7
8
9
10
11
12
GET /bank/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "address": "mill" } },
        { "match": { "address": "lane" } }
      ]
    }
  }
}
	# 查询address中既包含"mail"又包含"lane"的文档

should

1
2
3
4
5
6
7
8
9
10
11
12
GET /bank/_search
{
  "query": {
    "bool": {
      "should": [
        { "match": { "address": "mill" } },
        { "match": { "address": "lane" } }
      ]
    }
  }
}
	# should条件中只要有一条满足就可返回

must_not

1
2
3
4
5
6
7
8
9
10
11
12
GET /bank/_search
{
  "query": {
    "bool": {
      "must_not": [
        { "match": { "address": "mill" } },
        { "match": { "address": "lane" } }
      ]
    }
  }
}
	# must_not,所有条件都不应该满足

filter

查询结果中_score字段代表查询分数,是一个数值,表示匹配度,越高说明越匹配。

布尔查询支持filter子句,它允许使用查询语句限制其它子句的匹配结果,同时不会计算文档的得分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
GET /bank/_search
{
  "query": {
    "bool": {
      "must": { "match_all": {} },
      "filter": {
        "range": {
          "balance": {
            "gte": 20000,
            "lte": 30000
          }
        }
      }
    }
  }
}
	# 查询余额balance在20000到30000之间的结果

执行聚合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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"
              }
            }
          }
        }
      }
    }
  }
}
	# 按年龄段分组,按性别分组,最终得到每个年龄段的男女平均账户余额

2018/11/23 16:40:32

参考:[https://github.com/13428282016/elasticsearch-CN/wiki/es-gettting-started]