Elasticsearch——Elasticsearch基本原理

734 阅读11分钟

摘要

本文主要介绍了 Elasticsearch(ES)的基本原理,包括其特点、基础概念、Elastic Stack 组件、容器下载与部署以及查询和聚合查询的相关内容。通过具体示例展示了如何使用 ES 进行单个查询、批量查询、分页查询以及聚合查询等操作,并提供了相关的参考资源。

1. ElasticSearch的特点

ElasticSearch是一款非常强大的、基于Lucene的开源搜索及分析引擎;它是一个实时的分布式搜索分析引擎,它能让你以前所未有的速度和规模,去探索你的数据

它被用作全文检索结构化搜索分析以及这三个功能的组合:

  • 使用 Elasticsearch 提供带有高亮片段的全文搜索,还有 search-as-you-type 和 did-you-mean 的建议。
  • 使用 Elasticsearch 将网络社交数据结合到访客日志中,为它的编辑们提供公众对于新文章的实时反馈。
  • **将地理位置查询融入全文检索中去,并且使用 more-like-this 接口去查找相关的问题和回答。
  • **使用 Elasticsearch 对1300亿行代码进行查询。

除了搜索,结合Kibana、Logstash、Beats开源产品,Elastic Stack(简称ELK)还被广泛运用在大数据近实时分析领域,包括:日志分析指标监控信息安全等。它可以帮助你探索海量结构化、非结构化数据,按需创建可视化报表,对监控数据设置报警阈值,通过使用机器学习,自动识别异常状况。ElasticSearch是基于Restful WebApi,使用Java语言开发的搜索引擎库类,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。其客户端在Java、C#、PHP、Python等许多语言中都是可用的。

1.1. Lucene搜索引擎

Lucene 可以说是当下最先进、高性能、全功能的搜索引擎库。但是 Lucene 仅仅只是一个库。为了充分发挥其功能,你需要使用 Java 并将 Lucene 直接集成到应用程序中。 您可能需要获得信息检索学位才能了解其工作原理。Lucene 非常复杂。Elasticsearch 也是使用 Java 编写的,它的内部使用 Lucene 做索引与搜索,但是它的目的是使全文检索变得简单,通过隐藏 Lucene 的复杂性,取而代之的提供一套简单一致的 RESTful API。然而,Elasticsearch 不仅仅是 Lucene,并且也不仅仅只是一个全文搜索引擎。 它可以被下面这样准确的形容:

  • 一个分布式的实时文档存储,每个字段 可以被索引与搜索。
  • 一个分布式实时分析搜索引擎。
  • 能胜任上百个服务节点的扩展,并支持 PB 级别的结构化或者非结构化数据。

1.2. ElasticSearch的主要功能及应用场景

主要功能:

  1. 海量数据的分布式存储以及集群管理,达到了服务与数据的高可用以及水平扩展;
  2. 近实时搜索,性能卓越。对结构化、全文、地理位置等类型数据的处理;
  3. 海量数据的近实时分析(聚合功能)

应用场景:

  1. 网站搜索、垂直搜索、代码搜索;
  2. 日志管理与分析、安全指标监控、应用性能监控、Web抓取舆情分析

2. ElasticSearch基础概念

  • Near Realtime(NRT) 近实时。数据提交索引后,立马就可以搜索到。
  • Cluster 集群,一个集群由一个唯一的名字标识,默认为“elasticsearch”。集群名称非常重要,具有相同集群名的节点才会组成一个集群。集群名称可以在配置文件中指定。
  • Node 节点:存储集群的数据,参与集群的索引和搜索功能。像集群有名字,节点也有自己的名称,默认在启动时会以一个随机的UUID的前七个字符作为节点的名字,你可以为其指定任意的名字。通过集群名在网络中发现同伴组成集群。一个节点也可是集群。
  • Index 索引: 一个索引是一个文档的集合(等同于solr中的集合)。每个索引有唯一的名字,通过这个名字来操作它。一个集群中可以有任意多个索引。
  • Type 类型:指在一个索引中,可以索引不同类型的文档,如用户数据、博客数据。从6.0.0 版本起已废弃,一个索引中只存放一类数据。
  • Document 文档:被索引的一条数据,索引的基本信息单元,以JSON格式来表示。
  • Shard 分片:在创建一个索引时可以指定分成多少个分片来存储。每个分片本身也是一个功能完善且独立的“索引”,可以被放置在集群的任意节点上。
  • Replication 备份: 一个分片可以有多个备份(副本)

3. Elastic Stack组件简介

3.1. ES相关组件

Beats + Logstash + ElasticSearch + Kibana

如下是我从官方博客中找到图,这张图展示了ELK生态以及基于ELK的场景(最上方)。

3.1.1. Beats组件

Beats是一个面向轻量型采集器的平台,这些采集器可以从边缘机器向Logstash、ElasticSearch发送数据,它是由Go语言进行开发的,运行效率方面比较快。从下图中可以看出,不同Beats的套件是针对不同的数据源。

3.1.2. Logstash

Logstash是动态数据收集管道,拥有可扩展的插件生态系统,支持从不同来源采集数据,转换数据,并将数据发送到不同的存储库中。其能够与ElasticSearch产生强大的协同作用,后被Elastic公司在2013年收购。它具有如下特性:1. 实时解析和转换数据;2. 可扩展,具有200多个插件;3. 可靠性、安全性。Logstash会通过持久化队列来保证至少将运行中的事件送达一次,同时将数据进行传输加密;4. 监控;

3.1.3. ElasticSearch

ElasticSearch对数据进行搜索、分析和存储,其是基于JSON的分布式搜索和分析引擎,专门为实现水平可扩展性、高可靠性和管理便捷性而设计的。它的实现原理主要分为以下几个步骤:

  1. 首先用户将数据提交到ElasticSearch数据库中;
  2. 再通过分词控制器将对应的语句分词;
  3. 将分词结果及其权重一并存入,以备用户在搜索数据时,根据权重将结果排名和打分,将返回结果呈现给用户;

3.1.4. Kibana

Kibana实现数据可视化,其作用就是在ElasticSearch中进行民航。Kibana能够以图表的形式呈现数据,并且具有可扩展的用户界面,可以全方位的配置和管理ElasticSearch。Kibana最早的时候是基于Logstash创建的工具,后被Elastic公司在2013年收购。Kibana可以提供各种可视化的图表;可以通过机器学习的技术,对异常情况进行检测,用于提前发现可疑问题;

3.2. ES实践项目

基本的日志系统

增加数据源,和使用MQ

3.2.1. Metric收集和APM性能监控

3.2.2. 多数据中心方案

通过冗余实现数据高可用

两个数据采集中心(比如采集两个工厂的数据),采集数据后的汇聚

数据分散,跨集群的搜索

4. ES 容器下载与部署

docker pull elasticsearch:7.12.1

docker run -d \
	--name elasticsearch \
    -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
    -e "discovery.type=single-node" \
    -v es-data:/usr/share/elasticsearch/data \
    -v es-plugins:/usr/share/elasticsearch/plugins \
    --privileged \
    --restart=always
    -p 9200:9200 \
    -p 9300:9300 \
elasticsearch:7.12.1

在浏览器中输入:http://ip:9200 即可看到elasticsearch的响应结果:

docker pull kibana:7.12.1

docker run -d \
--name kibana \
-e ELASTICSEARCH_HOSTS=http://es:9200 \
-p 5601:5601  \
kibana:7.12.1

此时,在浏览器输入地址访问:http://ip:5601,即可看到结果

5. 查询和聚合查询

5.1. 单个查询

PUT /customer/_doc/1
{
  "name": "John Doe"
}

查询刚才插入的文档

GET /customer/_doc/1

5.2. 批量查询

数据是index为bank,accounts.json 下载地址:📎accounts.rar (如果你无法下载,也可以clone ES的官方仓库,然后进入/docs/src/test/resources/accounts.json目录获取)

curl -H "Content-Type: application/json" -XPOST "localhost:9200/bank/_bulk?pretty&refresh" --data-binary "@/opt/accounts.json"

match_all表示查询所有的数据, sort即按照什么字段排序

GET /bank/_search
{
  "query": { "match_all": {} },
  "sort": [
    { "account_number": "asc" }
  ]
}

5.3. 分页查询(from+size)

本质上就是from和size两个字段

GET /bank/_search
{
  "query": { "match_all": {} },
  "sort": [
    { "account_number": "asc" }
  ],
  "from": 10,
  "size": 10
}

结果

5.3.1. 指定字段查询:match

如果要在字段中搜索特定字词,可以使用match; 如下语句将查询address 字段中包含 mill 或者 lane的数据

GET /bank/_search
{
  "query": { "match": { "address": "mill lane" } }
}

结果

(由于ES底层是按照分词索引的,所以上述查询结果是address 字段中包含 mill 或者 lane的数据)

5.3.2. 查询段落匹配:match_phrase

如果我们希望查询的条件是 address字段中包含 “mill lane”,则可以使用match_phrase

GET /bank/_search
{
  "query": { "match_phrase": { "address": "mill lane" } }
}

结果

5.3.3. 多条件查询: bool

如果要构造更复杂的查询,可以使用bool查询来组合多个查询条件。

例如,以下请求在bank索引中搜索40岁客户的帐户,但不包括居住在爱达荷州(ID)的任何人

GET /bank/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "age": "40" } }
      ],
      "must_not": [
        { "match": { "state": "ID" } }
      ]
    }
  }
}

结果

must, should, must_notfilter 都是bool查询的子句。那么filter和上述query子句有啥区别呢?

GET your_index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "field1": "value1"
          }
        },
        {
          "range": {
            "field2": {
              "gte": 10,
              "lte": 20
            }
          }
        }
      ],
      "should": [
        {
          "term": {
            "field3": "optional_value1"
          }
        },
        {
          "term": {
            "field4": "optional_value2"
          }
        }
      ],
      "must_not": [
        {
          "term": {
            "field5": "excluded_value"
          }
        }
      ],
      "filter": [
        {
          "term": {
            "field6": "filtered_value"
          }
        }
      ]
    }
  }
}

解释:

  1. must:文档必须匹配的条件,类似于 SQL 的 AND。例如:
    • field1 必须包含 value1
    • field2 必须在 [10, 20] 范围内。
  1. should:非必须条件,匹配的条件越多,相关性得分(_score)越高。至少有一个条件匹配时,文档会更相关。
    • field3field4 中匹配一个即可加分。
  1. must_not:文档不能包含的条件,类似于 SQL 的 NOT。
    • 排除 field5 等于 excluded_value 的文档。
  1. filter:过滤条件,类似于 must,但不影响相关性评分(_score),性能较高。
    • field6 必须为 filtered_value

5.3.4. 查询条件:query or filter

先看下如下查询, 在bool查询的子句中同时具备query/must 和 filter

GET /bank/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "state": "ND"
          }
        }
      ],
      "filter": [
        {
          "term": {
            "age": "40"
          }
        },
        {
          "range": {
            "balance": {
              "gte": 20000,
              "lte": 30000
            }
          }
        }
      ]
    }
  }
}

结果

两者都可以写查询条件,而且语法也类似。区别在于,query 上下文的条件是用来给文档打分的,匹配越好 _score 越高;filter 的条件只产生两种结果:符合与不符合,后者被过滤掉

所以,我们进一步看只包含filter的查询

GET /bank/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "term": {
            "age": "40"
          }
        },
        {
          "range": {
            "balance": {
              "gte": 20000,
              "lte": 30000
            }
          }
        }
      ]
    }
  }
}

结果,显然无_score

5.4. 聚合查询:Aggregation

我们知道SQL中有group by,在ES中它叫Aggregation,即聚合运算。

5.4.1. 简单聚合

比如我们希望计算出account每个州的统计数量, 使用aggs关键字对state字段聚合,被聚合的字段无需对分词统计,所以使用state.keyword对整个字段统计

GET /bank/_search
{
  "size": 0,
  "aggs": {
    "group_by_state": {
      "terms": {
        "field": "state.keyword"
      }
    }
  }
}

结果

因为无需返回条件的具体数据, 所以设置size=0,返回hits为空。

doc_count表示bucket中每个州的数据条数。

5.4.2. 嵌套聚合

ES还可以处理个聚合条件的嵌套。

比如承接上个例子, 计算每个州的平均结余。涉及到的就是在对state分组的基础上,嵌套计算avg(balance):

GET /bank/_search
{
  "size": 0,
  "aggs": {
    "group_by_state": {
      "terms": {
        "field": "state.keyword"
      },
      "aggs": {
        "average_balance": {
          "avg": {
            "field": "balance"
          }
        }
      }
    }
  }
}

结果

5.4.3. 对聚合结果排序

可以通过在aggs中对嵌套聚合的结果进行排序

比如承接上个例子, 对嵌套计算出的avg(balance),这里是average_balance,进行排序

GET /bank/_search
{
  "size": 0,
  "aggs": {
    "group_by_state": {
      "terms": {
        "field": "state.keyword",
        "order": {
          "average_balance": "desc"
        }
      },
      "aggs": {
        "average_balance": {
          "avg": {
            "field": "balance"
          }
        }
      }
    }
  }
}

结果

博文参考