一、前言
- 什么是Elastisearch :
- Elasticsearch 是一个分布式、可扩展、实时的搜索与数据分析引擎
二、基础入门
1、 基础知识
- 什么是 Lucene
- 一个搜索引擎库
- Elasticsearch 也是使用 Java 编写的,它的内部使用 Lucene 做索引与搜索,但是它的目的是使全文检索变得简单, 通过隐藏 Lucene 的复杂性,取而代之的提供一套简单一致的 RESTful API
1.1、 安装并运行
- 自己捣鼓捣鼓
1.2、 和 ES 的交互
- 通过RESTful 风格的 API。自己捣鼓捣鼓
1.3、 面向文档
- 数据通常不是简单的键值对,通常包含更加复杂的结果
- 如果采用关系型数据库的行和列,相当于把一个对象进行拆分,放入到一个非常巨大的表格,
- 为了适应表结构,你必须设法将这个对象扁平化-----通常一个字段对应一列------而且每次查询时又需要将其重新构造为对象。
- 在 Elasticsearch 中将对象转化为 JSON 后构建索引要比在一个扁平的表结构中要简单的多。
1.4、 上手
- 自己捣鼓捣鼓
1.5、 索引员工文档
- 存储数据到 Elasticsearch 的行为叫做
索引
,但在索引一个文档之前,需要确定将文档存储在哪里 - 一个 Elasticsearch 集群可以 包含多个 索引 ,相应的每个索引可以包含多个 类型
对于员工目录,我们将做如下操作:
- 每个员工索引一个文档,文档包含该员工的所有信息。
- 每个文档都将是
employee
类型 。 - 该类型位于 索引
megacorp
内。 - 该索引保存在我们的 Elasticsearch 集群中。
实践中这非常简单(尽管看起来有很多步骤),我们可以通过一条命令完成所有这些动作:
PUT /megacorp/employee/1
{
"first_name" : "John",
"last_name" : "Smith",
"age" : 25,
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}
注意,路径 /megacorp/employee/1
包含了三部分的信息:
-
megacorp 索引名称
-
employee 类型名称
PUT /megacorp/employee/2
{
"first_name" : "Jane",
"last_name" : "Smith",
"age" : 32,
"about" : "I like to collect rock albums",
"interests": [ "music" ]
}
PUT /megacorp/employee/3
{
"first_name" : "Douglas",
"last_name" : "Fir",
"age" : 35,
"about": "I like to build cabinets",
"interests": [ "forestry" ]
}
1.6、检索文档
GET /megacorp/employee/1
{
"_index" : "megacorp",
"_type" : "employee",
"_id" : "1",
"_version" : 1,
"found" : true,
"_source" : {
"first_name" : "John",
"last_name" : "Smith",
"age" : 25,
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}
}
1.7、 轻量级检索
- 请求所有的雇员
GET /megacorp/employee/_search
- 尝试下搜索姓氏为
Smith
的雇员
GET /megacorp/employee/_search?q=last_name:Smith
1.8、 表达式搜索
GET /megacorp/employee/_search
{
"query" : {
"match" : {
"last_name" : "Smith"
}
}
}
- 返回结果与之前的查询一样,但还是可以看到有一些变化。其中之一是,不再使用 query-string 参数,而是一个请求体替代。这个请求使用 JSON 构造,并使用了一个
match
查询(属于查询类型之一,后面将继续介绍)。
1.9、 更为复杂的操作
- 过滤器
GET /megacorp/employee/_search
{
"query" : {
"bool": {
"must": {
"match" : {
"last_name" : "smith" (1)
}
},
"filter": {
"range" : {
"age" : { "gt" : 30 } (2)
}
}
}
}
}
1.10、 全文检索
- 搜索下所有喜欢攀岩(rock climbing)的员工:
GET /megacorp/employee/_search
{
"query" : {
"match" : {
"about" : "rock climbing"
}
}
}
显然我们依旧使用之前的 match
查询在about
属性上搜索 ``rock climbing'' 。得到两个匹配的文档:
{
...
"hits": {
"total": 2,
"max_score": 0.16273327,
"hits": [
{
...
"_score": 0.16273327, (1)
"_source": {
"first_name": "John",
"last_name": "Smith",
"age": 25,
"about": "I love to go rock climbing",
"interests": [ "sports", "music" ]
}
},
{
...
"_score": 0.016878016, (1)
"_source": {
"first_name": "Jane",
"last_name": "Smith",
"age": 32,
"about": "I like to collect rock albums",
"interests": [ "music" ]
}
}
]
}
}
- 相关性得分
-
Elasticsearch 默认按照相关性得分排序,即每个文档跟查询的匹配程度。第一个最高得分的结果很明显:John Smith 的
about
属性清楚地写着 ``rock climbing'' 。 -
但为什么 Jane Smith 也作为结果返回了呢?原因是她的
about
属性里提到了rock'' 。因为只有
rock'' 而没有 ``climbing'' ,所以她的相关性得分低于 John 的。
-
1.11、 短语搜索
GET /megacorp/employee/_search
{
"query" : {
"match_phrase" : {
"about" : "rock climbing"
}
}
}
毫无悬念,返回结果仅有 John Smith 的文档。
{
...
"hits": {
"total": 1,
"max_score": 0.23013961,
"hits": [
{
...
"_score": 0.23013961,
"_source": {
"first_name": "John",
"last_name": "Smith",
"age": 25,
"about": "I love to go rock climbing",
"interests": [ "sports", "music" ]
}
}
]
}
}
1.12、 高亮
GET /megacorp/employee/_search
{
"query" : {
"match_phrase" : {
"about" : "rock climbing"
}
},
"highlight": {
"fields" : {
"about" : {}
}
}
}
- 当执行该查询时,返回结果与之前一样,与此同时结果中还多了一个叫做
highlight
的部分。这个部分包含了about
属性匹配的文本片段,并以 HTML 标签<em></em>
封装:
{
...
"hits": {
"total": 1,
"max_score": 0.23013961,
"hits": [
{
...
"_score": 0.23013961,
"_source": {
"first_name": "John",
"last_name": "Smith",
"age": 25,
"about": "I love to go rock climbing",
"interests": [ "sports", "music" ]
},
"highlight": {
"about": [
"I love to go <em>rock</em> <em>climbing</em>" (1)
]
}
}
]
}
}
1.13、 分析
-
Elasticsearch 有一个功能叫聚合(aggregations),允许我们基于数据生成一些精细的分析结果。聚合与 SQL 中的
GROUP BY
类似但更强大。 -
挖掘出员工中最受欢迎的兴趣爱好:
GET /megacorp/employee/_search
{
"aggs": {
"all_interests": {
"terms": { "field": "interests" }
}
}
}
- 暂时忽略掉语法,直接看看结果:
{
...
"hits": { ... },
"aggregations": {
"all_interests": {
"buckets": [
{
"key": "music",
"doc_count": 2
},
{
"key": "forestry",
"doc_count": 1
},
{
"key": "sports",
"doc_count": 1
}
]
}
}
}
-
可以看到,两位员工对音乐感兴趣,一位对林业感兴趣,一位对运动感兴趣。这些聚合的结果数据并非预先统计,而是根据匹配当前查询的文档即时生成的
-
如果想知道叫 Smith 的员工中最受欢迎的兴趣爱好,可以直接构造一个组合查询:
GET /megacorp/employee/_search
{
"query": {
"match": {
"last_name": "smith"
}
},
"aggs": {
"all_interests": {
"terms": {
"field": "interests"
}
}
}
}
all_interests
聚合已经变为只包含匹配查询的文档:
...
"all_interests": {
"buckets": [
{
"key": "music",
"doc_count": 2
},
{
"key": "sports",
"doc_count": 1
}
]
}
聚合还支持分级汇总 。比如,查询特定兴趣爱好员工的平均年龄:
GET /megacorp/employee/_search
{
"aggs" : {
"all_interests" : {
"terms" : { "field" : "interests" },
"aggs" : {
"avg_age" : {
"avg" : { "field" : "age" }
}
}
}
}
}
- 得到的聚合结果有点儿复杂,但理解起来还是很简单的:
...
"all_interests": {
"buckets": [
{
"key": "music",
"doc_count": 2,
"avg_age": {
"value": 28.5
}
},
{
"key": "forestry",
"doc_count": 1,
"avg_age": {
"value": 35
}
},
{
"key": "sports",
"doc_count": 1,
"avg_age": {
"value": 25
}
}
]
}
- 输出基本是第一次聚合的加强版。
1.14、 教程结束语
1.15、 分布式特性
-
分配文档到不同的容器 或 分片 中,文档可以储存在一个或多个节点中
-
按集群节点来均衡分配这些分片,从而对索引和搜索过程进行负载均衡
-
复制每个分片以支持数据冗余,从而防止硬件故障导致的数据丢失
-
将集群中任一节点的请求路由到存有相关数据的节点
-
集群扩容时无缝整合新节点,重新分配分片以便从离群节点恢复
2、ES 集群的原理
- 垂直扩容:不添加新的机器,通过改善当前机器的性能,实现扩容
- 水平扩容:添加新的机器实现扩容
2.1、 空集群
- 如果我们启动了一个单独的节点,里面不包含任何的数据和索引,那么当前集群就是包含空节点的集群。
-
节点
-
集群是由一个或者多个拥有相同
cluster.name
配置的节点组成,共同承担数据和负载的压力 -
当有节点加入集群中或者从集群中移除节点时,集群将会重新平均分布所有的数据
-
-
主节点
-
当一个节点被选举成为
主节点
时, 它将负责管理集群范围内的所有变更,例如增加、删除索引,或者增加、删除节点等 -
主节点并不需要涉及到文档级别的变更和搜索等操作,即使流量的增加它也不会成为瓶颈。
-
2.2、 集群健康
- Elasticsearch 的集群监控信息中包含了许多的统计数据,其中最为重要的一项就是 集群健康 , 它在
status
字段中展示为green
、yellow
或者red
。
GET /_cluster/health
2.3、 索引
-
索引实际上是指向一个或者多个物理 分片 的 逻辑命名空间 。
-
分片
-
一个 分片 是一个底层的 工作单元 ,它仅保存了全部数据中的一部分
-
一个分片是一个 Lucene 的实例, 本身就是一个完整的搜索引擎
-
一个分片可以是 主 分片或者 副本 分片
-
我们的文档被存储和索引到分片内,但是应用程序是直接与索引而不是与分片进行交互
-
-
Elasticsearch 是利用分片将数据分发到集群内各处的。
分片是数据的容器,文档保存在分片内,分片又被分配到集群内的各个节点里。
-
在索引建立的时候就已经
确定了主分片数
,但是副本分片数可以随时修改。
-
创建一个 blogs 的索引,默认会有五个主分片,设置成为3个主分片和 1 份副本
-
我们的集群现在是拥有一个索引的单节点集群,所有3个主分片都被分配在
Node 1
。
- 如果我们现在查看
集群健康
,我们将看到如下内容:
{
"cluster_name": "elasticsearch",
"status": "yellow", (1)
"timed_out": false,
"number_of_nodes": 1,
"number_of_data_nodes": 1,
"active_primary_shards": 3,
"active_shards": 3,
"relocating_shards": 0,
"initializing_shards": 0,
"unassigned_shards": 3, (2)
"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
}
- 集群
status
值为yellow
。 - 没有被分配到任何节点的副本数。
集群的健康状况为 yellow
则表示全部 主 分片都正常运行(集群可以正常服务所有请求),但是 副本 分片没有全部处在正常状态。
2.4、 集群模式
- 另启一个节点
- 当第二个节点加入到集群后,3个 副本分片 将会分配到这个节点上——每个主分片对应一个副本分片。 这意味着当集群内任何一个节点出现问题时,我们的数据都完好无损。
2.5、 水平扩容
- 分片是一个功能完整的搜索引擎,它拥有使用一个节点上的所有资源的能力。
- 我们这个拥有6个分片(3个主分片和3个副本分片)的索引可以最大扩容到6个节点,每个节点上存在一个分片,并且每个分片拥有所在节点的全部资源。
2.6、 应对故障
- 如果我们关闭第一个节点
- 此时集群的状态将会为
yellow
未完待续