ElasticSearch详解(篇一)

目录

前言

这篇博客主要讲解ElasticSearch,由于篇幅过长,这个博客也是分为两篇,篇一主要讲解ElasticSearch的基础概念和原理,安装部署,以及索引库和文档基本操作,希望看完这篇博客能让新人对Elasticsearch有一个直观的认识。

一,Elasticsearch概述与核心概念

什么是Elasticsearch

  • Elasticsearch是一款开源的分布式搜索引擎,基于Java开发,采用Lucene作为核心搜索库。其发展历程可追溯至2004年,当时Shay Banon基于Lucene开发了Compass,随后在2010年重写并命名为Elasticsearch

技术优势与生态系统

Elasticsearch具备显著的技术优势

  • 分布式架构:支持水平扩展,能够处理海量数据
  • RESTful接口:提供标准的HTTP API,可被任何编程语言调用
  • 高性能搜索:基于倒排索引技术,查询速度极快

Elasticsearch与Kibana、Logstash、Beats共同构成ELK技术栈,被广泛应用于日志数据分析、实时监控等领域。

elasticsearch是elastic stack的核心,负责存储、搜索、分析数据

核心概念解析

文档和字段

  • elasticsearch是面向文档(Document)存储的,可以是数据库中的一条商品数据,一个订单信息。文档数据会被序列化为json格式后存储在elasticsearch中
  • 因此,传统数据库中的一行数据就是ES中的一个JSON文档;而数据库中每行数据都包含很多列,这些列就转换为JSON文档中的字段(Field)

索引和映射

索引
  • Elasticsearch中的索引不是传统数据库中的索引,而是"表",比如商品文档、用户的文档、订单文档等,将同类型的文档集中在一起管理,成为索引。例如:
映射(mapping)
  • 传统数据库中的表会有约束信息,用来定义表的结构、字段名称、类型、主键约束等等。同样的,在elasticsearch,在创建索引(表)时,也必须有这样的约束信息,在elasticsearch中,这样的约束信息被称为mapping(映射),用于定义索引中的document的字段名称、类型等结构和约束。

  • 常见的Mapping属性包括:

  • 需要注意的是,mapping中的index(索引),指的是为这个字段创建常规索引,便于根据该字段快速查询数据,类比于mysql中的聚集索引和非聚集索引,elasticsearch中的索引概念比较混乱,这里需要理解

  • 例如下面的json文档:

    yaml 复制代码
    {
        "age": 21,
        "weight": 52.1,
        "isMarried": false,
        "info": "黑马程序员Java讲师",
        "email": "zy@itcast.cn",
        "score": [99.1, 99.5, 98.9],
        "name": {
            "firstName": "云",
            "lastName": "赵"
        }
    }

    对应的每个字段映射(Mapping):

mysql与elasticsearch

  • 学习Elasticsearch对照MySQL,pg关系型数据库会理解的更深刻,本质上可以这样理解,Elasticsearch也是一个数据库,基于倒排索引检索存储的数据,除此之外,还提供数据分析的功能,仅此而已。Elasticsearch需要掌握以下基本概念:

    MySQL/PG概念 Elasticsearch对应 说明
    Table Index 索引,即文档的集合,类似数据库的表
    Row Document 文档,一条条的数据,采用JSON格式
    Column Field 字段,JSON文档中的属性,类似数据库列
    Schema Mapping 映射,索引中 文档的字段约束信息, 字段什么名字,什么类型,需不需要倒排索引等等
    SQL DSL Elasticsearch提供的JSON风格查询语句

二,倒排索引原理与分词器

倒排索引工作机制

  1. 讲倒排索引之前,先回顾一下mysql、pg等传统数据库的正向索引,什么是正向索引,简单来说,就是根据关键词检索数据时,依次将数据库表中的记录去匹配查询关键词条件,重点是看记录中包不包含关键词 ,将包含关键词的记录一条一条检索出来,下面这个图是一个比较经典的正向索引查新数据的过程。

  2. 与传统数据库的正向索引不同,Elasticsearch采用倒排索引技术,倒排索引有两个非常重要的概念:

    概念 说明
    文档(Document) Elasticsearch存储的数据,用来搜索的数据,其中的每一条数据就是一个文档。例如一个网页、一个商品信息,类比传统数据库中的Row
    词条(Term) 对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条。例如:我是中国人,就可以分为:我、是、中国人、中国、国人这样的几个词条
  3. 倒排索引的核心思想是对文档内容进行分词,对词条创建索引,并记录词条所在文档的ID。查询时先根据词条查询到文档ID,再获取完整文档。注意,这里所说的索引不是指mysql、pg等传统数据库地索引,索引在Elasticsearch中,是类似于传统数据库的表的概念,可以这样简单理解,正向索引就是存放文档(document)的表,倒排索引是正向索引中拆分出来的词语表。

  4. 倒排索引的搜索流程如下(以搜索"华为手机"为例),如图所示。Elasticsearch先将用户输入的查询条件进行分词,然后在词语倒排索引中进行查找,由于词条有索引(注意理解,这个索引指的是系统为倒排索引内部的"词条字典"(Term Dictionary)这个组成部分本身又建立了一层高效的索引结构(通常是 Term Index)),所以查询效率也高。

  5. 虽然要先查询倒排索引,再查询倒排索引,但是无论是词条、还是文档id都建立了索引,查询速度非常快!无需全表扫描

正向和倒排的优缺点

索引 优点 缺点
正向索引 可以给多个字段创建索引, - 根据索引字段搜索、排序速度非常快 根据非索引字段,或者索引字段中的部分词条查找时,只能全表扫描
倒排索引 - 根据词条搜索、模糊搜索时,速度非常快 - 只能给词条创建索引,而不是字段, - 无法根据字段做排序

IK中文分词器

  1. 由于中文分词的复杂性,需要专门的中文分词器。IK分词器采用正向迭代最细粒度切分算法,提供两种分词模式
  • ik_smart:智能切分,粗粒度
  • ik_max_word:最细切分,细粒度
  1. 我们在调用Elasticsearch Restfull接口时,可以指定分词器的工作模式。

    yaml 复制代码
    POST /_analyze
    {
      "analyzer": "ik_max_word",
      "text": "传智播客开设大学,真的泰裤辣!"
    }
  2. 如果我们想要扩展IK分词器分词的基本词汇,IK分词器提供了扩展词汇的功能,我们只需要创建一个自定义的新增词语文件,然后在IK分词器配置文件中关联一下即可。

  3. 在IKAnalyzer.cfg.xml配置文件内容添加:

    xml 复制代码
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
    <properties>
            <comment>IK Analyzer 扩展配置</comment>
            <!--用户可以在这里配置自己的扩展字典 *** 添加扩展词典-->
            <entry key="ext_dict">ext.dic</entry>
    </properties>

三,索引库操作

  1. 创建索引库, 请求方式:PUT,请求参数:mapping映射

    yaml 复制代码
    PUT /索引库名称
    {
      "mappings": {
        "properties": {
          "字段名":{
            "type": "text",
            "analyzer": "ik_smart"
          },
          "字段名2":{
            "type": "keyword",
            "index": "false"
          },
          "字段名3":{
            "properties": {
              "子字段": {
                "type": "keyword"
              }
            }
          },
          // ...略
        }
      }
    }
  2. 查询索引库:GET /索引库名

  3. 删除索引库:DELETE /索引库名

  4. 修改索引库

    • 倒排索引结构虽然不复杂,但是一旦数据结构改变(比如改变了分词器),就需要重新创建倒排索引,这简直是灾难。因此索引库一旦创建,无法修改mapping。

    • 虽然无法修改mapping中已有的字段,但是却允许添加新的字段到mapping中,因为不会对倒排索引产生影响。因此修改索引库能做的就是向索引库中添加新字段,或者更新索引库的基础属性。

      yaml 复制代码
      PUT /索引库名/_mapping
      {
        "properties": {
          "新字段名":{
            "type": "integer"
          }
        }
      }

四,文档操作

新增文档

  • 语法如下:

    yaml 复制代码
    POST /索引库名/_doc/文档id
    {
        "字段1": "值1",
        "字段2": "值2",
        "字段3": {
            "子属性1": "值3",
            "子属性2": "值4"
        },
    }

    示例:

查询文档

  • 语法如下:

    yaml 复制代码
    GET /{索引库名称}/_doc/{id}

    示例:

删除文档

删除使用DELETE请求,同样,需要根据id进行删除:

yaml 复制代码
DELETE /{索引库名}/_doc/id值

修改文档

修改文档也有两种方式:

  • 全量修改:直接覆盖原来的文档
  • 局部修改:修改文档中的部分字段

全量修改

  • 全量修改是覆盖原来的文档,本质上是删除原来的文档,然后新增一个相同id的文档,如果id不存在,也会新增一个新的文档。

  • 语法如下:

    yaml 复制代码
    PUT /{索引库名}/_doc/文档id
    {
        "字段1": "值1",
        "字段2": "值2",
        // ... 略
    }

局部修改

  • 局部修改是只修改指定id匹配的文档中的部分字段

  • 语法:

    yaml 复制代码
    POST /{索引库名}/_update/文档id
    {
        "doc": {
             "字段名": "新的值",
        }
    }

批处理

  • Elasticsearch中允许通过一次请求中携带多次文档操作,也就是批量处理,语法格式如下:

    yaml 复制代码
    POST _bulk
    { "index" : { "_index" : "test", "_id" : "1" } }
    { "field1" : "value1" }
    { "delete" : { "_index" : "test", "_id" : "2" } }
    { "create" : { "_index" : "test", "_id" : "3" } }
    { "field1" : "value3" }
    { "update" : {"_id" : "1", "_index" : "test"} }
    { "doc" : {"field2" : "value2"} }
  • 其中,index表示新增操作。

五,DSL查询

  1. 在实际开发中,Elasticsearch的查询远比简单的文档ID精确查询要复杂得多。就像传统数据库业务中不能仅依赖主键查询一样,我们需要处理各种复杂的查询场景。这时就需要使用Elasticsearch的查询语言DSL来构建复杂的查询语句,其功能类似于关系型数据库中的SQL语句。
  2. Elasticsearch提供了基于JSON的DSL(Domain Specific Language)语句以JSON格式来定义查询条件。

基本结构

Elasticsearch的查询可以分为两大类:

  • 叶子查询: 简单查询,或者叫单条件查询,一般是在特定的字段里查询特定值。
  • 复合查询:逻辑组合多个叶子查询,一般实际开发中多以复合查询为主。

DSL查询语句结构是固定的,如下:

yaml 复制代码
GET /{索引库名}/_search
{
  "query": {
    "查询类型": {
      // .. 查询条件
    }
  }
}

Elasticsearch返回的查询结果也是固定的,各字段含义如下,其中检索的原始数据在_source字段中,Elasticsearch默认的返回数量是10条文档。

叶子查询

  1. 叶子查询是单条件的简单查询,叶子查询常用的有三大类,每一大类又有多个具体的类型。

全文检索查询

  1. 会对用户输入内容进行分词,然后去倒排索引库中匹配,将查询出来的文档整体计算与输入内容的匹配度,最后根据匹配度排序返回结果。

  2. match查询:在单个字段上进行全文检索,语法

    yaml 复制代码
    GET /{索引库名}/_search
    {
      "query": {
        "match": {
          "字段名": "搜索条件"
        }
      }
    }
  3. multi_match查询:允许同时在多个字段上进行全文检索,但参与查询的字段越多,性能通常越差。

    yaml 复制代码
    GET /{索引库名}/_search
    {
      "query": {
        "multi_match": {
          "query": "搜索条件",
          "fields": ["字段1", "字段2"]
        }
      }
    }

精确查询

  1. 不会对搜索条件分词,而是直接精确匹配。推荐用于查找 keyword、数值、日期、布尔等类型字段。如果查询字段是一个分词字段(text类型),Elasticsearch不会将输入内容分词,而是将输入内容整体与拆分 的词条索引库(表)匹配,进而查询出文档

  2. term查询:根据词条进行精确匹配。

    yaml 复制代码
    GET /{索引库名}/_search
    {
      "query": {
        "term": {
          "字段名": {
            "value": "输入词条"
          }
        }
      }
    }
  3. range查询:根据数值或日期范围进行查询。

    yaml 复制代码
    GET /{索引库名}/_search
    {
      "query": {
        "range": {
          "字段名": {
            "gte": {最小值},
            "lte": {最大值}
          }
        }
      }
    }
  4. ids查询: 根据id数组查询文档

复合查询

  1. 复合查询是一个或多个叶子查询的组合,通过逻辑运算符来构建复杂查询,也称为bool查询

  2. bool查询支持的逻辑运算有:

    运算符 说明
    must 必须匹配每个子查询,类似"与"
    should 满足其中一个子查询即可,类似"或"
    must_not 必须不匹配,不参与算分,类似"非"
    filter 必须匹配,不参与算分
  3. 需要解释下这个算分的过程,如果一个DSL复合查询语句,有must、should、must_not、filter四种运算符,Elasticsearch会先执行must_not、filter这两种不算分的运算,将不满足条件的记录过滤掉,然后执行must匹配,将过滤后的文档集中验证must条件,这个过程时算分的,最后再对满足should条件的文档进行相关性加分,大概是这样的算分过程。

  4. 需要注意的是,算分是基于文档与查询条件的匹配程度,而不是与输入词语的简单匹配

  5. 一个经典的复合查询语句如下:

    yaml 复制代码
    GET /items/_search
    {
      "query": {
        "bool": {
          "must": [
            {"match": {"name": "手机"}}
          ],
          "should": [
            {"term": {"brand": { "value": "vivo" }}},
            {"term": {"brand": { "value": "小米" }}}
          ],
          "must_not": [
            {"range": {"price": {"gte": 2500}}}
          ],
          "filter": [
            {"range": {"price": {"lte": 1000}}}
          ]
        }
      }
    }

排序

  1. elasticsearch默认是根据相关度算分(_score)来排序,但是也支持自定义方式对搜索结果排序。不过分词字段无法排序,能参与排序字段类型有:keyword类型、数值类型、地理坐标类型、日期类型等。

  2. 支持多字段排序,语法如下:

    yaml 复制代码
    GET /indexName/_search
    {
      "query": {
        "match_all": {}
      },
      "sort": [
        {
          "FIELD": "desc"  // 排序字段和排序方式ASC、DESC
        }
      ]
    }

分页

  1. elasticsearch 默认情况下只返回top10的数据。而如果要查询更多数据就需要修改分页参数了。

  2. 基础分页:elasticsearch中通过修改from、size参数来控制要返回的分页结果:

    yaml 复制代码
    GET /items/_search
    {
      "query": {
        "match_all": {}
      },
      "from": 0, // 分页开始的位置,默认为0
      "size": 10,  // 每页文档数量,默认10
      "sort": [
        {
          "price": {
            "order": "desc"
          }
        }
      ]
    }
  3. 深度分页 ,跟传统数据库一样,elasticsearch 也存在深度分页问题,讲深度分页之前,先说下elasticsearch 的数据存储方式,跟传统数据库一样,如果文档数量过大,elasticsearch 会将所有文档存储在多个分片(实例)上,如果有排序分页的查询需求,比如 from 990 size 10,传统分页需要将每个分片的文档,先排序,再取前1000个文档到本地,再进行排序,然后向后数到990,将接下来的10条数据返回结果。

  4. 因此,如果当查询分页深度较大时,汇总数据过多,对内存和CPU会产生非常大的压力,因此elasticsearch会禁止from+ size 超过10000的请求

  5. 为了解决这个问题,elasticsearch提供了search after这个解决方案,分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。

  6. 还是这个from 990 size 10这个分页需求,search after 会分批将小批量(size)抽取到本地分片,进行排序,然后将排序最后一个的数据作为标签,向各个分片发出查询请求,检索值大于(或者小于)这个标签的数据(再来size),循环这个过程,直到定位到分页的位置

高亮显示

  1. elasticsearch可以将关键词加上HTML标签,以帮助前端对关键词进行渲染或者高亮显示。

  2. 基本语法如下 :

    yaml 复制代码
    GET /{索引库名}/_search
    {
      "query": {
        "match": {
          "搜索字段": "搜索关键字"
        }
      },
      "highlight": {
        "fields": {
          "高亮字段名称": {
            "pre_tags": "<em>",
            "post_tags": "</em>"
          }
        }
      }
    }
  3. 示例如下:

数据聚合

  • 类比于传统数据库的聚合函数、分组等数据分析功能,elasticsearch也可以极其方便的实现对数据的统计、分析、运算。
  • 聚合运算常用的主要有两类,分组(桶聚合)和函数运算(度量聚合)。

Bucket桶聚合

  • 例如我们要统计所有商品中共有哪些商品分类,其实就是以分类(category)字段对数据分组。category值一样的放在同一组,属于Bucket聚合中的Term聚合

    yaml 复制代码
    GET /items/_search
    {
      "size": 0,  # 设置size为0,就是结果中就不包含文档,只包含聚合结果
      "aggs": {
        "category_agg": {   # 给聚合起个名字
          "terms": { # 聚合实际类型
            "field": "category",
            "size": 20 # 只返回20条聚合结果
          }
        }
      }
    }
  • 下面是返回的结果

带条件的Bucket桶聚合

  • 默认情况下,Bucket聚合是对索引库的所有文档做聚合,我们可以通过query添加查询条件,elasticsearch会将满足条件的文档筛选出来,然后进一步做聚合操作。

    yaml 复制代码
    GET /items/_search
    {
      "query": {
        "bool": {
          "filter": [
            {
              "term": {
                "category": "手机"
              }
            },
            {
              "range": {
                "price": {
                  "gte": 300000
                }
              }
            }
          ]
        }
      }, 
      "size": 0, 
      "aggs": {
        "brand_agg": {
          "terms": {
            "field": "brand",
            "size": 20
          }
        }
      }
    }
  • 查询返回的结果如下:

    yaml 复制代码
    {
      "took" : 2,
      "timed_out" : false,
      "hits" : {
        "total" : {
          "value" : 13,
          "relation" : "eq"
        },
        "max_score" : null,
        "hits" : [ ]
      },
      "aggregations" : {
        "brand_agg" : {
          "doc_count_error_upper_bound" : 0,
          "sum_other_doc_count" : 0,
          "buckets" : [
            {
              "key" : "华为",
              "doc_count" : 7
            },
            {
              "key" : "Apple",
              "doc_count" : 5
            },
            {
              "key" : "小米",
              "doc_count" : 1
            }
          ]
        }
      }
    }

Metric聚合(度量聚合)

  • 度量聚合是桶聚合后,对每个桶内的文档做分析统计,返回结果。

    yaml 复制代码
    GET /items/_search
    {
      "query": {
        "bool": {
          "filter": [
            {
              "term": {
                "category": "手机"
              }
            },
            {
              "range": {
                "price": {
                  "gte": 300000
                }
              }
            }
          ]
        }
      }, 
      "size": 0, 
      "aggs": {
        "brand_agg": {
          "terms": {
            "field": "brand",
            "size": 20
          },
          "aggs": { # 聚合的内部新加了一个aggs参数。这个聚合就是brand_agg的子聚合,会对brand_agg形成的每个桶中的文档分别统计
            "stats_meric": { # 自己定义的聚合名称
              "stats": {
                "field": "price"
              }
            }
          }
        }
      }
    }
  • 下面是返回结果 的说明

  • 此外,我们可以定义聚合返回的顺序

相关推荐
大哥,带带弟弟3 小时前
ES错误记录
elasticsearch·kubernetes
一只小青团5 小时前
Hadoop之HDFS
大数据·hadoop·分布式
ITVV5 小时前
hadoop-3.4.1 单机伪部署
大数据·linux·hadoop
小杜谈数5 小时前
企业BI建议--数据治理平台
大数据
谅望者6 小时前
数据分析笔记07:Python编程语言介绍
大数据·数据库·笔记·python·数据挖掘·数据分析
中国国际健康产业博览会9 小时前
2026第35届中国国际健康产业博览会探索健康与科技的完美结合!
大数据·人工智能
一念一花一世界9 小时前
Jenkins VS GitLab CI/CD VS Arbess,CI/CD工具选型指南
ci/cd·gitlab·jenkins·arbess
Lansonli10 小时前
大数据Spark(七十三):Transformation转换算子glom和foldByKey使用案例
大数据·分布式·spark
中电金信10 小时前
2025新加坡金融科技节:看AI驱动的金融转型策略与“中国方案”
大数据·人工智能·金融