全文搜索引擎之ElasticSearch

搜索引擎

数据库 流行代表 数据类型
关系型数据库 MySQL、Oracle 二维表形式的关系数据
KV存储数据库 Redis 键值对形式的数据
文档数据库 Mongodb 文档存储服务
全文搜索引擎 ElasticSearch、Solr 关键字和文档的对应关系
时序数据库 InfluxDB 专门处理带有时间序列的数据
图数据库 Neo4j 专门存储数据之间的连接关系

Lucene

Lucene 最初由Doug Cutting 开发,2000年在SourceForge 网站开源第一个版本。2001年成为Apache 软件基金会的一个子项目。Lucene 并不是一个完整的全文搜索引擎,而是一个开源的全文搜索引擎工具包,提供一个全文搜索架构,用于查询和索引。

  • 分词技术

  • 关键词检索 (倒排索引就是关键词和文档之间的对应关系)

  • 搜索排序

Doug Cutting

​ Doug Cutting 美国工程师,毕业于斯坦福大学,他是Hadoop和 Lucene 的创始人。从1997年开始,利用业余时间开发了Lucene,并于1999年正式面世,2005年成为Apache 顶级开源项目。

image-20210808182511113

分词器

​ 分词器的主要作用是将一串字符处理为一个个词项,分词器有很多种:标准分词器、空格分词器、停用词分词器、简单分词器、中文分词器等等。

​ 在创建索引的时候需要使用分词器,在进行索引查询的时候也要用到分词器,而且这两个地方要使用同一个分词器,否则可能会搜索不出结果。

IK分词器

IK分词器是一个开源的、基于Java语言开发的中文分词工具包。2006年开源第一个版本。

GoogleCode 开源项目 :http://code.google.com/p/ik-analyzer/

下载地址:http://code.google.com/p/ik-analyzer/downloads/list

倒排索引

​ Lucene 是使用经典的倒排索引的方式来达到快速检索的目的,简单点说,就是创建词项和文档ID的映射关系,在搜索时,通过类似hash 算法来快速定位到一个搜索关键词,然后读取文档ID集合,这样读取数据是非常高效快速的。

Luke 工具

Luke是用来查看倒排索引的GUI工具,方便开发和调试。

Solr

Solr是一款高性能,采用Java开发,基于Lucene的全文搜索服务器。Solr是Apache软件基金会的子项目,是ElasticSearch的另一种替代方案,不过从当前的流行程度来说,ElasticSearch要远流行于Solr,本文不再对Solr做过多说明。

Elastic Stack

​ Elastic Stack 是 Elastic 公司的核心产品,目前包括 Elasticsearch、Kibana、Beats 和 Logstash 四款产品,最初Elasticsearch、Kibana和 Logstash 三个组件合称为ELK,也就是三个开源产品的首字母,Beats 产品出来之后,将这些产品共称为 Elastic Stack。

  • Elasticsearch
  • Logstash
  • Kibana
  • Beats

ElasticSearch

Elasticsearch是一款开源、高性能的分布式搜索引擎,基于Lucene库开发。它提供了一个分布式、支持多租户的全文搜索引擎,具有HTTP Web接口和无模式JSON文档。Elasticsearch是用 Java开发的,并在Apache许可证下作为开源软件发布。

注:本文基于8.x 版本学习

发版历史

版本 时间线 说明
0.7.0 2010年 第一个版本
1.0.0 2014年
2.0.0 2015年
5.0.0 2016年
6.0.0 2017年
7.0.0 2019年
8.0.0 2022年

数据结构

ElasticSearch 是面向文档型数据库,下面是ElasticSearch 与关系型数据库RDMS 中的概念对应关系:

ElasticSearch RDMS
索引 Index 数据库
文档 Document
字段 field
映射 mapping 表结构
倒排索引 索引

注意:Type 概念在ES 7.x 版本中被废弃了。虽然ES7废弃,但还在用,Type 概念依然存在,ES8才真正的去掉了type。为什么废弃Type ?

​ 在关系型数据库中,不同的表中,包含相同的字段名是很常见的,而且它们可以做到互不干扰。在ElasticSearch中,不同的type,如果包含相同的字段名,它们是一样的,es会认为是一个字段,模糊掉不同type的概念。所以在es里边,type这个概念没必要存在,所以es7就废弃了。

分词器

倒排索引的过程就是将文档通过Analyzer分成一个一个的 term,每一个 term 都指向包含这个Term的文档集合。

分词器

分词器种类

分词器 描述
Standard Analyzer 默认分词器,按词切分,小写处理
IK Analyzer 目前功能最成熟的中文分词器
Simple Analyzer
Whitespace Analyzer 按照空格切分,不转小写
Keyword Analyzer 不分词,直接将输入当作输出
Language 提供多种常见语言的分词器

配置中文分词器

IK 中文分词器

​ ElasticSearch 中文分词器业界使用最多的是 elasticsearch-analysis-ik,它是ElasticSearch 的一个第三方插件,使用时注意版本号与ElasticSearch 保持一致。

  • 在 Elasticsearch 的安装目录的 Plugins 目录下新建 IK 文件夹,然后将下载的 IK 安装包解压到此目录下
  • 重启 ES 即生效

配置拓展词典 hotwords.dic

配置停用词典

测试分词效果

IK有两种颗粒度的拆分:

  • ik_smart: 会做最粗粒度的拆分
  • ik_max_word: 会将文本做最细粒度的拆分

注:正常情况下对中文的分词,我们应该优先选择 ik_max_word。

GET /_analyze
{
  "text":"中华人民共和国国徽",
  "analyzer":"ik_max_word"
}

分词结果

{
  "tokens" : [
    {
      "token" : "中华人民共和国",
      "start_offset" : 0,
      "end_offset" : 7,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "中华人民",
      "start_offset" : 0,
      "end_offset" : 4,
      "type" : "CN_WORD",
      "position" : 1
    },
    {
      "token" : "中华",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "CN_WORD",
      "position" : 2
    },
    {
      "token" : "华人",
      "start_offset" : 1,
      "end_offset" : 3,
      "type" : "CN_WORD",
      "position" : 3
    },
    {
      "token" : "人民共和国",
      "start_offset" : 2,
      "end_offset" : 7,
      "type" : "CN_WORD",
      "position" : 4
    },
    {
      "token" : "人民",
      "start_offset" : 2,
      "end_offset" : 4,
      "type" : "CN_WORD",
      "position" : 5
    },
    {
      "token" : "共和国",
      "start_offset" : 4,
      "end_offset" : 7,
      "type" : "CN_WORD",
      "position" : 6
    },
    {
      "token" : "共和",
      "start_offset" : 4,
      "end_offset" : 6,
      "type" : "CN_WORD",
      "position" : 7
    },
    {
      "token" : "国",
      "start_offset" : 6,
      "end_offset" : 7,
      "type" : "CN_CHAR",
      "position" : 8
    },
    {
      "token" : "国徽",
      "start_offset" : 7,
      "end_offset" : 9,
      "type" : "CN_WORD",
      "position" : 9
    }
  ]
}

ik_smart 分词

# ik_smart 分词
GET /_analyze
{
  "text":"中华人民共和国国徽",
  "analyzer":"ik_smart"
}

分词结果:

{
  "tokens" : [
    {
      "token" : "中华人民共和国",
      "start_offset" : 0,
      "end_offset" : 7,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "国徽",
      "start_offset" : 7,
      "end_offset" : 9,
      "type" : "CN_WORD",
      "position" : 1
    }
  ]
}

基本操作

9200 为客户端访问的HTTP 协议RESTful 端口。

9300 为ElasticSearch 集群内部服务器间的TCP 通信端口。

索引管理

创建索引

创建索引和在关系型数据库中创建一个数据库是一样的。

# 创建索引
PUT book
# 创建只有一个主分片,没有复制分片的索引
PUT book
{
  "settings": {
  	"number_of_shards" : 1,
  	"number_of_replicas" : 0
  }
}

可以在创建索引时指定分片数和副本个数,一个索引的分片个数一经指定就不能再次修改!副本数是可以随时修改的。

注意:索引名必须全部为小写,否则报错。

更新副本

PUT book/_settings
{
  "number_of_replicas" : 2
}

读写权限

除了设置分区和副本,还可以对索引的读写操作进行限制,下面是是三个控制读写权限的参数:

  • blocks.read_only:true
  • blocks.read:true
  • blocks.write:true

示例:

# 禁止读操作
PUT book/_settings
{
  "blocks.read": true
}

查看索引

# 查看指定索引
GET book
# 查看多个索引
GET book,car

删除索引

# 删除指定索引
DELETE book

打开与关闭

ES 中的索引可以进行打开和关闭操作,一个关闭了的索引几乎不占用系统资源。

# 打开索引
POST book/_open
# 关闭索引
POST book/_close
# 同时打开多个索引
POST book,car/_open
# 关闭以test开头的索引
POST test*/_close

收缩索引

文档管理

ES 中的文档都是JSON 数据格式。

新建文档

索引我们已经创建好了,接下来我们来创建文档,并添加数据(指定文档ID)。

POST /book/_create/1001

{
    "title": "钢铁是怎样炼成的",
    "author": "列夫托尔斯泰",
    "price": 49.9,
    "createTime": "20210101T160000.000Z",
    "updateTime": "20220107T160000.000Z"
}

注:我们也可以在创建时不指定ID,没有指定ID时会自动生成一个随机的文档ID:

# 创建文档:不指定ID
POST /book/_doc
...

查看文档

# 根据ID查看文档
GET shopping/_doc/1001
# 查看指定索引下的全部文档数据
GET shopping/_search

注:

/shopping/_doc/1001 依次是索引名、类型名、文档ID。因为ES废弃了type 概念,系统中只保留了一个默认的文档类型:_doc

更新文档

# 全量替换更新
PUT http://127.0.0.1:9200/shopping/_doc/1001
{
    "title":"小米手机",
    "categoary":"小米",
    "price":2999
}
# 部分字段更新
POST http://127.0.0.1:9200/shopping/_update/1001
{
    "doc": {
        "title": "小米手机2"
    }
}

条件更新

删除文档

复制文档

将一个索引中的所有文档复制到另一个索引中,但目标索引不会复制源索引的配置信息,如分片数、副本数等。

POST _reindex
{
	"source":{"index":"blog"},
	"dest":{"index":"blog_news"}
}

映射管理

有两种映射选项:

  • 自动映射
  • 手动映射

自动映射

​ 自动映射也叫动态映射,让 Elasticsearch 自动检测数据类型并为您创建映射。动态映射可帮助您快速入门,但由于自动字段类型推断,可能会为您的特定用例产生次优结果。

ES自动推测字段类型的规则:

JSON格式的数据 自动推测的字段类型
null 没有字段被添加
true or false boolean
浮点类型数字 float
数字 long
JSON对象 object
数组 array,类型由数组中第一个非空值决定
文本 有可能是date、double、long、text、keyword中的一个

手动映射

​ 手动映射也叫静态映射,静态映射是在创建索引时手动指定的,和SQL中的建立表结构类似。相比动态映射,静态映射可以添加更加详细、精准的配置信息。

示例:

# 创建索引并静态映射
PUT my_index
{
  "mappings": {
    "properties" : {
        "author" : {
          "type" : "text"
        },
        "createTime" : {
          "type" : "date",
          "format" : "basic_date_time"
        },
        "id" : {
          "type" : "keyword"
        },
        "price" : {
          "type" : "double"
        }
      }
  }
}

字段类型

一级分类 二级分类 具体类型
核心字段 字符串 text、 keyword
数值 byte、short、integer、long
float、double、half_float 、scaaled_float
日期 date
布尔 boolean
二进制 binary
范围 range
复合类型 对象 object
数组 array
嵌套 nested
地理类型 地理坐标 geo_point
地理图型 geo_shape
特殊类型 IP类型 ip
范围类型 completion
令牌计数类型 token_count
附件类型 attachment
抽取类型 percolator

常用的数据类型有:

range 的使用场景一般为网页表单中的时间范围选择、年龄范围选择等,支持存储一个范围。

geo_point 用于记录一个地理位置,经纬度

geo_shape 用于记录一块区域

token_count 用于统计字符串分词后的词项个数

元字段

元字段是映射中描述文档本身的字段,属于ES的内置字段,命名以下划线开头。如:_index , _id , _version, _source , _type 等。

字段 描述
_id 它是document的唯一标识。可以手动指定,也可以自动生成。如果不指定的话,ES会自动为我们生成一个长20个字符的id,ES会保证集群中的生成的doc id不会发生冲突。
_index 索引名称
_version _version是doc的版本号,可以用来做并发控制,当一个doc被创建时它的_version是1,之后对它的每一次修改,都会使这个版本号+1
_source 文档的原始数据,通过这个字段可以定制我们想要返回字段。比如说一个doc中存在100个字段,但是可能前端并不是真的需要这100个字段,于是我们使用_source去除一些字段。
_all 首先它也是一个元数据,当我们往ES中插入一条document时。ES会自动的将这个doc中的多个field的值串联成一个字符串,然后用这个作为_all字段的值并建立索引。当用户发起检索却没有指定从哪个字段查询时,默认就会在这个_all中进行匹配。

查看映射

# 查看索引的映射
GET book/_mapping

映射参数

# 创建索引并静态映射
PUT my_index
{
  "mappings": {
    "properties" : {
        "author" : {
          "type" : "text"
          "analyzer":"ik_max_word"
          "search_analyzer":"ik_max_word"
        }
      }
  }
}

analyzer

​ analyzer 参数用于指定文本字段的分词器,对索引和查询都有效。

search_analyzer

​ 大多数情况下索引和查询的分词器应该是相同的,但有时候也需要指定不同的分词器,可以使用 search_analyzer 参数将查询的分词器覆盖。

enabled

​ ES默认会索引所有的字段,而有些字段只需要存储,没有查询或者聚合的需求,这种情况下可以使用enabled 参数来控制。enabled 设置为false,ES会跳过字段内容,该字段只能从_source 中获取,但是不可以被搜索。

format

​ format参数是用来格式化日期的。

系统状态

# 查看所有节点
GET /_cat/nodes
# 查看健康状况
GET /_cat/health?v
# 查看主节点
GET /_cat/master?v
# 查看所有索引
GET /_cat/indices?v

高级搜索

全文搜索 match

查询全部

# 查询全部
GET /book/_search

# 查询全部,同上
GET /book/_search
{
    "query":{
        "match_all":{}
    }
}

match query

match query 会对查询语句进行分词,分词后查询语句中的任何一个词项被匹配,文档就会被搜索到。如果想

GET articles/_search
{
  "query": {
    "match": {
      "description": "iPhone 15 Pro" // 在 description 字段中搜索关键词
    }
  }
}

适用场景:文章内容搜索、商品标题/描述搜索

match_phrase

​ match_phrase 一般用于模糊查询。

​ 在 Elasticsearch 中,match_phrasematch 是两种常用的查询方式,它们的核心区别在于 对词项顺序和位置的要求。以下是详细对比和使用场景分析:

核心区别

特性 match 查询 match_phrase 查询
词项顺序 不要求顺序(只要包含所有词项即可) 必须严格按指定顺序出现
词项间隔 允许任意间隔 要求连续出现(默认无间隔)
适用字段类型 适用于 text 类型字段 适用于 text 类型字段,但需精确匹配短语
相关性评分 根据词频和逆文档频率(TF-IDF)计算得分 需要连续匹配,通常得分更高

示例:

GET products/_search
{
  "query": {
    "match_phrase": {
      "description": "iPhone 15 Pro" // 必须按顺序连续出现
    }
  }
}

匹配结果可能包括

  • “iPhone 15 Pro Max…”
  • “Latest iPhone 15 Pro model…”

不匹配:”Pro version of iPhone 15…”

多字段匹配

Multi-match Query(多字段匹配)

GET products/_search
{
  "query": {
    "multi_match": {
      "query": "手机",
      "fields": ["name", "description"] // 同时搜索 name 和 description 字段
    }
  }
}

精确查询 term

​ 全文搜索在执行查询之前会分析查询字符串,词项搜索时对倒排索引中存储的词项进行精确操作。词项级别的查询通常用于结构化数据,如数字、日期和枚举类型。

特别注意:term 过滤器要求字段是 keyword 类型。

GET orders/_search
{
  "query": {
    "term": {
      "surveyId": "b0051f52"   // 精确匹配 surveyId 字段为 b0051f52 值的记录(字段必须为 keyword 类型)
    }
  }
}

注意:term 查询不会分析输入值,需确保字段是 keyword 类型

term query
terms query
range query
prefix query
wildcard query
regexp query
fuzzy query
ids query

范围查询 range

Range Query(范围查询)

支持的操作符

gt(大于)
gte(大于等于)
lt(小于)
lte(小于等于)

数值范围查询

GET products/_search
{
  "query": {
    "range": {
      "price": {  // 字段名称
        "gte": 100,
        "lte": 500,
        "format": "double" // 明确数值类型
      }
    }
  }
}

适用场景:筛选价格在 100 到 500 元之间的商品。

日期范围查询

GET logs/_search
{
  "query": {
    "range": {
      "@timestamp": { // 日期字段
        "gte": "now-7d/d", // 过去7天(含当天)
        "lt": "now/d"      // 不含当天
      }
    }
  }
}

日期格式支持

  • 相对时间:now-1h(1小时前)、now+1d(1小时后)
  • 绝对时间:2024-02-20T00:00:00Z
  • 格式化字符串:"yyyy-MM-dd HH:mm:ss"

复合查询 bool

Bool Query(布尔组合查询)

语法:

{
  "query": {
    "bool": {
      "must": [],     // 必须匹配的条件(影响相关性评分)
      "should": [],   // 可选匹配的条件(提升评分,若未设置 `minimum_should_match` 可不生效)
      "must_not": [], // 必须不匹配的条件
      "filter": []    // 过滤条件(不计算评分,可缓存结果)
    }
  }
}

示例:

GET users/_search
{
  "query": {
    "bool": {
      "must": [ // 必须满足的条件
        { "match": { "age": { "gte": 18 } } },
        { "term": { "status": "active" } }
      ],
      "should": [ // 可选条件(至少满足一个)
        { "match": { "city": "北京" } },
        { "match": { "city": "上海" } }
      ],
      "minimum_should_match": 1 // should 至少匹配1个
    }
  }
}

分页

根据分页的大小,ES 提供了不同的分页策略:

方案 适用场景 实时性 性能 资源消耗
from+size 小规模分页(前 100 页) 实时 低(深度分页差)
search_after 实时连续分页 实时
Scroll API 大数据导出(离线处理) 非实时
PIT +search_after 长时间稳定分页 近实时

小规模分页

GET /book/_search
{
    "from":0,
    "size":10
}
  • from 表示第几页,从0开始

  • size 表示一页显示多少条文档

深度分页问题

​ Elasticsearch(ES)的深度分页问题是指在处理大规模数据时,使用传统的 fromsize 参数进行分页(如查询第 1000 页之后的数据)会导致性能急剧下降甚至内存溢出。以下是问题的根源、解决方案及示例:

深度分页问题的根源

ES 的分页机制基于以下流程:

  1. 分片查询 :每个分片独立执行查询,并返回 from + size 条结果到协调节点。
  2. 全局排序与合并 :协调节点从所有分片的结果中合并并排序,最终返回 size 条数据。

问题表现

  • 内存压力 :当 from 值很大时(如 from=10000),每个分片需要加载并传输大量数据到协调节点,导致内存和网络开销剧增。
  • 性能瓶颈 :分片数量越多,协调节点合并数据的成本越高,查询延迟显著增加。

例如,查询 from=10000, size=10 时,如果有 5 个分片,每个分片需返回 10010 条数据,协调节点需要处理 5*10010=50050 条记录,最终取最后 10 条。

深度分页的解决方案

  • search_after
  • Scroll API

search_after

原理 :基于唯一且有序的字段(如时间戳或文档 ID)作为游标,逐页获取数据,避免使用 from

优点

  • 无深度限制,性能稳定。
  • 适合实时分页(如用户连续翻页)。

注:Search After 的方式不支持指定页数,只能一页一页的往下翻。

示例:

// 第一次搜索
POST users/_search
{
    "size": 10,  
    "query": {
        "match_all": {}
    },
    "sort": [
        {"age": "desc"} ,
        {"_id": "asc"}  
    ]
}

// 此时返回的文档中有一个 sort 值 "sort" : [13, "4dR-IHcB71-f4JZcrL2z"]

//  之后的每一次搜索都需要用到上一次搜索结果的最后一个文档的 sort 值
POST users/_search
{
    "size": 10,
    "query": {
        "match_all": {}
    },
    "search_after": [    //  上一次搜索结果的最后一个文档的 sort 值放在这里
    	13, "4dR-IHcB71-f4JZcrL2z"
    ], 
    "sort": [
        {"age": "desc"} ,
        {"_id": "asc"}    
    ]
}

Scroll API

创建一个滚动上下文(Scroll Context),在固定的数据快照上分批拉取数据。

适用场景 :

  • 大数据量导出(如备份、离线分析);
  • 需要遍历索引中的所有文档。

优点

  • 高效导出数据 :通过分批拉取(如每次 1000 条),避免一次性加载全部数据。
  • 固定数据快照 :初始化 Scroll 时,数据状态被冻结,后续新增/修改的数据不会影响结果。

缺点

  • 非实时性 :快照数据不包含初始化后的变更(如新增、更新或删除的文档)。
  • 资源消耗高 :滚动上下文需占用内存和文件句柄,长时间未关闭可能导致性能问题。
  • 需手动管理生命周期:需显式调用 DELETE /_search/scroll 清理上下文,否则会堆积资源。

示例:

// 第一次查询前,先建立快照,快照存在时间为 5 分钟,一般不要太长
POST /users/_search?scroll=5m
{
    "size": 10,
    "query": {
        "match_all" : {}
    }
}
// 返回的结果中会有一个 _scroll_id

//  查询
POST /_search/scroll
{
    "scroll" : "1m", //  快照的生存时间,这里是 1 分钟
    "scroll_id" : "xxx==" // 上一次的 _scroll_id 值
}
# 每次的查询结果都会返回一个 _scroll_id,供下一次查询使用

// 关闭 Scroll
DELETE /_search/scroll
{
  "scroll_id": "FGluY2x1ZGVfZnJvbV9maWxlPzA..."
}

PIT + search_after

原理 :结合 PIT(Point in Time,时间点快照)和 search_after,在长时间查询中保持数据一致性。

优点

  • 支持实时数据变更(相对 Scroll API 更灵活)。
  • 适合需要长时间保持分页状态的场景。

示例

// 创建 PIT
POST /my_index/_pit?keep_alive=1m

// 使用 PIT 分页查询
POST /_search
{
  "size": 10,
  "pit": {
    "id": "PIT_ID",
    "keep_alive": "1m"
  },
  "sort": [
    { "timestamp": "desc" },
    { "_id": "asc" }
  ],
  "search_after": [1638316800000, "doc_id_123"],
  "query": { ... }
}

分页优化策略

  • 避免深度分页 :业务设计时尽量减少对深度分页的需求。
  • 选择合适工具 :
    • 实时分页:search_after + 唯一排序字段。
    • 数据导出:Scroll API 或 PIT。
    • 长时间查询:PIT + search_after

分页优化策略

  1. 限制最大分页深度
    业务层限制用户可查询的最大页数(如最多查询前 100 页)。
  2. 引导用户使用过滤条件
    通过搜索、时间范围或分类筛选缩小结果集,减少分页需求。
  3. 客户端分页
    在客户端缓存部分数据,减少对 ES 的请求压力。

排序

​ ES 默认使用算分进行排序,我们可以使用 sort-processor 来指定排序规则;可以对某个字段进行排序,最好只对数字型日期型字段排序。

GET products/_search
{
  "sort": [
    { "price": { "order": "asc" } } // 按价格升序排序
  ]
}

相关度评分

相关性越高排序越靠前,而这个排序的依据就是_score

最小评分过滤查询

ES 提供了基于最小评分的过滤机制,通过评分过滤可以筛选出最相关的文档,排除相关性较低的数据。

GET /book/_search
{
	"min_score":0.6
}

嵌套数组查询

在 Elasticsearch 中,要查询嵌套数组 anQuestions 中包含特定 quDwId 的文档,可以通过以下两种方式实现:

  • 直接使用 term 查询(推荐)
  • 使用 nested 查询(需明确嵌套结构)

原始文档数据:

"anQuestions": [
            {
              "anRadio": {
                "optionDwId": "72d7e96d-993e-463a-afe9-f97ef04e23e5"
              },
              "quAnScore": 0,
              "quDwId": "3889be76-49e3-4cd3-917f-42b73d67478c",
              "quType": "RADIO"
            }
      ]

一、直接使用 term 查询(推荐)

如果 quDwId 字段是 keyword 类型(或 text 类型的 .keyword 子字段),可以直接通过嵌套路径查询:

GET your_index/_search
{
  "query": {
    "term": {
      "anQuestions.quDwId": "3889be76-49e3-4cd3-917f-42b73d67478c" // 精确匹配
    }
  }
}

二、使用 nested 查询(需明确嵌套结构)

如果 anQuestionsnested 类型(即数组中的对象是独立对象),需要显式指定嵌套路径:

GET your_index/_search
{
  "query": {
    "nested": {
      "path": "anQuestions", // 嵌套路径
      "query": {
        "term": {
          "anQuestions.quDwId": "3889be76-49e3-4cd3-917f-42b73d67478c"
        }
      }
    }
  }
}

注意事项

字段类型要求:如果 quDwId是 text 类型,需改用.keyword 子字段(前提是映射中定义了 keyword子字段):

"anQuestions.quDwId.keyword": "3889be76-49e3-4cd3-917f-42b73d67478c"

如果未显式定义 nested 类型,Elasticsearch 会默认将数组扁平化存储,可能导致误匹配。

存在字段过滤

GET users/_search
{
  "query": {
    "exists": {
      "field": "phone_number" // 筛选包含 phone_number 字段的文档
    }
  }
}

查询性能优化

优先使用 filter 替代 query

​ 在 Elasticsearch 中,默认情况下,所有查询都会计算相关性评分(_score。但在某些场景下(如纯过滤),可以通过特定语法避免计算评分以提高性能。

GET orders/_search
{
  "query": {
    "range": {
      "order_date": {
        "gte": "2024-01-01"
      }
    }
  }
}

​ 如上面的查询,该查询将 range 放在顶层 query 中,属于 查询上下文(Query Context),Elasticsearch 会为匹配的文档计算相关性评分(_score)。

当不需要相关性排序时,用 filter 提升性能(如分页、聚合前的过滤)。

filterquery 有什么区别?

  • **query**:计算文档相关性得分(_score),用于排序(如搜索关键词)。
  • **filter**:仅判断是否匹配,不计算得分,结果可缓存,性能更高。
特性 说明
相关性无关 filter 仅判断文档是否满足条件,不计算匹配度得分(所有匹配文档的 _score 为 1)
结果可缓存 过滤结果会被缓存,重复查询时性能更高
布尔逻辑支持 支持 mustshouldmust_not 组合
高效执行 无评分计算,适合大规模数据过滤

range 包裹在 boolfilter 子句中,强制进入 过滤上下文(Filter Context),此时不计算评分:

//单条件过滤
GET orders/_search
{
  "query": {
    "bool": {
      "filter": [ 
        { "term": { "status": "active" } }
      ]
    }
  }
}
//多条件过滤(隐式 AND 逻辑)
GET products/_search
{
  "query": {
    "bool": {
      "filter": [  // 必须包裹在 bool 的 filter 数组中
        { "term": { "status": "active" } },
        { "range": { "price": { "gte": 100 } } },
        { "terms": { "category": ["electronics", "books"] } }
      ]
    }
  }
}

示例

Term Filter(精确匹配)

GET products/_search
{
  "query": {
    "bool": {
      "filter": [
        { "term": { "category": "electronics" } } // 精确匹配 category 字段
      ]
    }
  }
}

Range Filter(范围查询)

//数值范围查询
GET orders/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "range": {
            "price": {
              "gte": 100,  // 大于等于
              "lte": 500,  // 小于等于
              "format": "double"
            }
          }
        }
      ]
    }
  }
}
//时间范围查询
GET /oss_dwsurvey_answer_index/_search
{
    "query": {
        "bool": {
            "filter": [
                {
                    "range": {
                        "answerCommon.anTime.endAnDate": {
                            "gte": "2025-01-01T00:00:00+08:00",
                            "lte": "2025-12-31T23:59:59.999+08:00",
                            "format": "strict_date_optional_time"
                        }
                    }
                }
            ]
        }
    }
}

Exists Filter(字段存在性检查)

GET users/_search
{
  "query": {
    "bool": {
      "filter": [
        { "exists": { "field": "phone_number" } } // 筛选包含 phone_number 字段的文档
      ]
    }
  }
}

Bool Filter(组合过滤)

GET logs/_search
{
  "query": {
    "bool": {
      "filter": [
        { "term": { "level": "ERROR" } },
        { "range": { "@timestamp": { "gte": "now-1h" } } }
      ]
    }
  }
}

查询缓存

利用缓存机制

Elasticsearch 会自动缓存频繁使用的 filter 结果。

通过 request_cache: true显式启用缓存:

GET products/_search?request_cache=true

避免 _source 高开销操作

禁用 _source 返回以减少网络传输:

GET products/_search
{
  "_source": false,
  "query": { ... }
}

字段类型优化

对需要过滤的字段使用 keyword(精确匹配)或 date(时间范围)类型。

聚合分析

Elasticsearch 的聚合分析(Aggregations)是其核心功能之一,能够对海量数据进行复杂的统计计算、分组分析和可视化处理。

聚合类型

  • Bucket Aggregations(桶聚合):将文档分组到多个”桶”中(类似 SQL 的 GROUP BY

    示例:按地区、时间范围、数值区间分组

  • Metric Aggregations(指标聚合):对每个桶内的文档进行数值计算,

    示例:计算平均值、总和、最大值、百分位数

桶聚合 Bucket

基础桶聚合

示例数据:

[
  { "product": "iPhone", "price": 999, "sales": 150, "date": "2024-02-01" },
  { "product": "MacBook", "price": 1999, "sales": 80, "date": "2024-02-01" },
  { "product": "AirPods", "price": 299, "sales": 300, "date": "2024-02-01" }
]

按产品分类统计:

GET sales/_search
{
  "size": 0,
  "aggs": {
    "products": {
      "terms": { "field": "product.keyword" }, // 按 product 字段分组
      "aggs": {
        "total_sales": { "sum": { "field": "sales" } }, // 计算总销量
        "avg_price": { "avg": { "field": "price" } }    // 计算平均价格
      }
    }
  }
}

返回结果:

{
  "aggregations": {
    "products": {
      "buckets": [
        {
          "key": "iPhone",
          "doc_count": 1,
          "total_sales": { "value": 150 },
          "avg_price": { "value": 999 }
        },
        {
          "key": "MacBook",
          "doc_count": 1,
          "total_sales": { "value": 80 },
          "avg_price": { "value": 1999 }
        }
      ]
    }
  }
}

指标聚合 Metric

指标聚合:对文档中的数值型字段进行数学计算(如求和、平均值、百分位数等)。

与桶聚合的区别:

  • 桶聚合:将文档分组(如按地区、时间分组)

  • 指标聚合:对每个桶内的文档进行数值计算

常用指标聚合类型

  • 求和
  • 求平均值
  • 最大值、最小值
  • 百分位
GET sales/_search
{
  "size": 0,
  "aggs": {
    "total_sales": {
      "sum": { "field": "amount" } // 计算 amount 字段的总和
    }
  }
}

GET logs/_search
{
  "size": 0,
  "aggs": {
    "avg_latency": {
      "avg": { "field": "latency_ms" } // 计算平均延迟
    }
  }
}

// 统计
GET products/_search
{
  "size": 0,
  "aggs": {
    "price_stats": {
      "stats": { // 同时返回 max/min/avg/sum
        "field": "price"
      }
    }
  }
}

拓展统计

GET metrics/_search
{
  "size": 0,
  "aggs": {
    "metric_stats": {
      "extended_stats": {
        "field": "temperature"
      }
    }
  }
}

输出

{
  "count": 1000,
  "min": 20.5,
  "max": 35.8,
  "avg": 28.3,
  "sum": 28300,
  "sum_of_squares": 805600,
  "variance": 3.2,
  "std_deviation": 1.8
}

分组统计(Terms + Metrics)

GET orders/_search
{
  "size": 0,
  "aggs": {
    "by_region": {
      "terms": { "field": "region.keyword" }, // 按地区分组
      "aggs": {
        "total_sales": { "sum": { "field": "amount" } },
        "avg_order": { "avg": { "field": "amount" } }
      }
    }
  }
}

高级用法

复合聚合

管道聚合

高级进阶

数据存储

​ Elasticsearch 的存储机制结合了多种技术,包括倒排索引列式存储(Doc Values)行存储(_source) 等。这些技术共同支撑了 ES 的高性能搜索、聚合分析和灵活的数据管理能力。以下详细讲解这些核心概念:

当一条数据写入Elasticsearch(ES)后,数据会被保存为以下几个主要部分:

  • 原始数据(_source )
  • 列存储(doc_values)
  • 倒排索引 Index
  • 元数据 Metadata

原始数据( _source )

  • 作用 :存储原始文档的完整JSON内容,用于检索、更新或部分字段提取。
  • 内容 :默认情况下,ES会将整个文档存储在 _source 字段中,每个文档的 _source 字段是一个完整的 JSON 对象,按文档 ID 存储。但 不会对 _source 本身建立倒排索引

配置方式

在索引的 Mapping 中设置

PUT /my_index
{
  "mappings": {
    "_source": {
      "enabled": false
    },
    "properties": {
      "field1": { "type": "keyword" },
      "field2": { "type": "text" }
    }
  }
}

列存储(doc_values)

列存储 :ES 默认对所有字段(除 text 类型外)生成 Doc Values ,以列式格式存储数据,用于排序、聚合和脚本计算。

作用 :

  • 高效支持 sortaggs ,用于排序、聚合和脚本计算等操作;
  • 减少磁盘 I/O(仅读取需要的字段)。

存储方式

  • 结构 :按字段分列存储,每个字段的值按文档 ID 顺序排列。
  • 示例 (对字段 user_id 的 Doc Values):
user_id: [1001, 1002, 1003, ...]

优缺点分析

优点 :

  • 聚合和排序性能高;
  • 支持大数据量分析。

缺点 :

  • 增加写入时的存储开销(需额外生成列数据);
  • text 类型字段默认不支持 Doc Values(需显式启用 fielddata,但可能引发内存问题)。

配置方式

为每个字段单独设置 "doc_values": false

PUT /my_index
{
  "mappings": {
    "properties": {
      "field1": {
        "type": "keyword",
        "doc_values": false
      },
      "field2": {
        "type": "text",
        "doc_values": false
      }
    }
  }
}

注意事项

如果字段类型为 text,默认不会生成 Doc Values,但会启用 fielddata(内存中的正排索引)。禁用 fielddata 的方式如下:

"field2": {
  "type": "text",
  "fielddata": false
}

倒排索引(Inverted Index)

​ 倒排索引是ES 的核心检索结构,将文档内容拆分为词条(Term),建立“词条 → 文档列表”的映射关系。

作用 :

  • 支持全文搜索(如 match 查询);
  • 加速等值查询(如 term 查询)。

存储方式

  • 结构 :包含词条字典 和 倒排列表

    • 词条字典 :所有唯一词条的有序列表(如 ["apple", "banana", ...]);
    • 倒排列表 :每个词条对应的文档 ID 列表(如 apple → [1, 3, 5])。
  • 示例 (字段 message 的倒排索引):

    message: {
      "apple": [1, 3],
      "banana": [2, 5],
      "fruit": [1, 2, 3, 5]
    }
  • 作用 :用于快速全文检索,将词项(Term)映射到包含它的文档列表。

  • 内容 :所有被标记为 index: true 的字段(默认)都会被写入倒排索引。例如:

    • text 类型字段会被分词后建立索引。
    • keyword 类型字段会以完整字符串形式索引。
    • 数值、日期等字段会被索引,但可能使用其他结构(如BKD树)辅助范围查询。

被倒排索引的数据

以下字段会被写入倒排索引(需满足 index: true):

  • 文本字段text(分词后)、keyword(完整词项)。
  • 数值字段integerfloatdouble 等。
  • 日期字段date
  • 布尔字段boolean
  • 复杂类型nested 对象、geo_shape 等(可能结合其他索引结构)。

不被倒排索引的数据

以下数据不会被写入倒排索引:

  • 禁用索引的字段 :显式设置 "index": false 的字段。
  • _source 字段 :存储原始文档,但本身不参与倒排索引。
  • 元数据字段 :如 _version_seq_no 等(除非显式配置)。
  • 二进制字段binary 类型默认不索引。

配置方式

PUT /my_index
{
  "mappings": {
    "properties": {
      "content": {
        "type": "text",          // 会被分词并倒排索引
        "index": true           // 默认值,可省略
      },
      "raw_data": {
        "type": "keyword",
        "index": false         // 不会被倒排索引,但会存储在 _source 中
      }
    }
  }
}

其他存储

BKD 树(数值与日期类型)

作用 :针对数值(如 integerdouble)和日期字段,使用 BKD 树 (一种空间高效的数据结构)加速范围查询(如 range 查询)。

特点 :

  • 类似数据库的 B+ 树,但更适合高维数据;
  • 支持快速定位数值范围内的文档。

词向量(Term Vectors)

作用 :存储分词后的词频、位置、偏移量等信息(需显式启用)。

场景 :

  • 高亮显示(Highlighting);
  • 文本相似度计算。

元数据(Metadata)

内容 :包括文档ID(_id)、索引名、时间戳(@timestamp)等。

索引情况 :

  • _id 默认会被索引,可通过 "_id": { "index": false } 禁用。
  • 其他元数据(如 _routing)需显式配置是否索引。

字段单独存储 store

定义

  • store: true :将字段的值单独存储到磁盘上,不依赖 _source 字段。
  • 默认情况下,字段值不会单独存储(即 store: false),而是通过 _source 字段统一管理。

_source 的区别

_source :

  • 存储整个文档的原始 JSON 数据;
  • 查询时可以通过 _source 提取特定字段值;
  • 占用存储空间较大,但灵活性高。

store: true

  • 只存储指定字段的值;
  • 查询时可以通过 stored_fields API 直接获取字段值;
  • 占用存储空间较小,但需要显式启用

示例1:

如果某些字段经常被查询,且不需要返回整个文档内容,可以启用 store: true,从而避免从 _source 中解析数据。

PUT /logs
{
  "mappings": {
    "properties": {
      "timestamp": {
        "type": "date",
        "store": true
      },
      "log_level": {
        "type": "keyword",
        "store": true
      },
      "message": {
        "type": "text"
      }
    }
  }
}

示例2:

如果禁用了 _source(设置 "_source": { "enabled": false }),但仍然需要访问某些字段值,则必须启用 store: true

PUT /logs
{
  "mappings": {
    "_source": {
      "enabled": false
    },
    "properties": {
      "id": {
        "type": "keyword",
        "store": true
      },
      "status": {
        "type": "keyword",
        "store": true
      }
    }
  }
}

分片和副本

数据分片

​ 一个索引可以存储超过单个节点硬件限制的大量数据,比如一个具有10亿文档的索引占据10TB的磁盘空间,而任一节点都没有这样大的磁盘空间,为了解决单节点存储空间上限问题,ES 提供了将索引划分为多份的能力,也就是数据分片。分片允许你水平拓展磁盘空间,允许在分片上进行分布式的、并行的操作,从而提高性能和吞吐量。

副本

​ 任一服务节点都有可能宕机,ES 提供故障转移机制,为主分片创建一份或多份拷贝,保证高可用。

总结:

  • Elasticsearch 允许在创建索引时指定任意数量的主分片(默认为 1,最大可配置为 1024)。
  • 单机环境下,所有主分片会分布在同一个节点上运行。
  • 主分片和副本分片不能共存于同一节点,这是 Elasticsearch 的强制规则。
  • 单机环境下副本分片无法分配,需通过调整 number_of_replicas: 0 避免警告。
  • 索引创建之后,可以在任意时候动态改变副本分片数量,但却不能改变主分片数量
  • 每个索引可以有多个主分片,多个副本分片。

最佳实践建议

单机环境的分片规划

  • 小数据量(< 50GB):使用默认的 1 个主分片。
  • 中大数据量:按 每分片 10GB~50GB 的容量规划主分片数量。
  • 例如:单机存储 200GB 数据,可设置 4 个主分片(每个分片约 50GB)。

以下命令可创建一个含 4 个主分片的索引:

PUT /my_index
{
  "settings": {
    "index.number_of_shards": 4
  }
}

如果是单机环境,建议将 number_of_replicas 设置为 0(单机默认副本数为 1,会触发警告):

PUT /my_index/_settings
{
  "index.number_of_replicas": 0
}

集群

备份恢复

​ 可以使用Elasticsearch的内置工具或第三方工具来导出索引数据为Lucene格式的索引文件。以下是两种常见的方法:

Elasticsearch内置工具:

  • Elasticsearch提供了elasticsearch-dump插件,它是一个用于索引数据导入和导出的工具,支持将索引数据导出为Lucene格式的索引文件。
  • 首先,确保已安装和配置了elasticsearch-dump插件。
  • 使用以下命令导出索引数据为Lucene格式的索引文件:
    elasticdump --input=http://localhost:9200/my_index --output=my_index_dump --type=data
    其中,http://localhost:9200/my_index 是源索引的URL,my_index_dump 是导出的目标目录。
  • 执行命令后,elasticsearch-dump将导出索引数据为Lucene格式的索引文件。

​ 需要注意的是,导出的Lucene索引文件是一个静态快照,它只反映导出时的索引状态。如果索引数据在导出后发生了变化,你需要重新导出索引文件以获取最新的数据。

另外,使用Lucene工具(如Luke)可以打开和分析导出的Lucene索引文件,以查看其中的文档、字段和术语等信息。

# 安装
npm install elasticdump -g
# 备份
elasticdump --input=http://127.0.0.1:9200/book --output=E:/Desktop/book.json

导出结果:

{
    "_index":"book",
    "_type":"_doc",
    "_id":"1001",
    "_score":1,
    "_source":{
        "_class":"com.jackpot.es.entity.Book",
        "id":"1001",
        "title":"钢铁是怎样炼成的",
        "author":"列夫托尔斯泰",
        "price":49.9,
        "createTime":"20210101T160000.000Z",
        "updateTime":"20220107T160000.000Z"
    }
}
{
    "_index":"book",
    "_type":"_doc",
    "_id":"1002",
    "_score":1,
    "_source":{
        "_class":"com.jackpot.es.entity.Book",
        "id":"1002",
        "title":"凤凰架构",
        "author":"周志明",
        "price":49.9,
        "createTime":"20210102T160000.000Z",
        "updateTime":"20220101T160000.000Z"
    }
}
...

常用操作如下:

# 备份
elasticdump --input=http://192.168.1.2:9200/test --output=/opt/esdump/test.json
# 支持星号匹配,以test开头的所有索引数据导出到test.json文件
elasticdump --input=http://192.168.1.2:9200/test* --output=/opt/esdump/test.json

# 还原,不需要指定索引自动插入
elasticdump --input=/opt/esdump/test.json --output=http://192.168.1.3:9200
# 还原
elasticdump --input=/opt/esdump/test.json --output=http://192.168.1.3:9200/test

# 迁移
elasticdump --input=http://192.168.1.2:9200/test --output=http:/192.168.1.3:9200/test
# 带账号密码的数据迁移
elasticdump --input=http://username:password@192.168.1.2:9200/test --output=http://username@password@192.168.1.3:9200/test
 
# 导出Mapping信息  
elasticdump --ignore-errors=true  --scrollTime=120m  --bulk=true --input=http://10.10.20.164:9200/xmonitor-2015.04.29   --output=http://192.168.100.72:9200/xmonitor-prd-2015.04.29  --type=mapping
 
# 根据查询条件导出
elasticdump --ignore-errors=true  --scrollTime=120m  --bulk=true --input=http://10.245.161.31:9200/ams_data  --output=/usr/output_datas.json --type=data 
--searchBody="{\"query\":{\"term\":{\"username\": \"admin\"}}}"
elasticdump --input=http://172.16.0.39:9200/companydatabase --output=http://172.16.0.20:9200/companydatabase --type=settings
elasticdump --input=http://172.16.0.39:9200/companydatabase --output=http://172.16.0.20:9200/companydatabase --type=mapping
elasticdump --input=http://172.16.0.39:9200/companydatabase --output=http://172.16.0.20:9200/companydatabase --type=data

​ 注意:如果想完整迁移,应该先将索引的settings迁移,如果直接迁移mapping或者data将失去原有集群中索引的配置信息如分片数量和副本数量等,当然也可以直接在目标集群中将索引创建完毕后再同步mapping与data。

安全认证

​ XPack 是 Elastic Stack 的扩展功能,提供安全性、报警、监视、报告、机器学习和其他功能。ES 7.0+ 之后默认情况下 ES 都会安装 Xpack,不用再单独安装。

Xpack 主要配置说明

# 默认为 true,启用节点上 ES 的 xpack 安全功能,相当于总开关
xpack.security.enabled: true

xpack.security.enrollment.enabled: true

# 用于开启 https
xpack.security.http.ssl:
  # 是否开启 https
  enabled: false
  # full:验证所提供的证书是否由受信任的权威机构(CA)签名,并验证服务器的主机名(或IP地址)是否与证书中识别的名称匹配
  # certificate:验证所提供的证书是否由受信任的机构(CA)签名,但不执行任何主机名验证
  # none:不执行服务器证书的验证
  verification_mode: certificate
  # 密钥存放的位置
  keystore.path: certs/http.p12
  # 信任文件存放位置
  truststore.path: certs/http.p12

# 集群节点间的加密和相互认证功能,配置内容同 xpack.security.http.ssl
xpack.security.transport.ssl:
  enabled: true
  verification_mode: certificate
  keystore.path: certs/transport.p12
  truststore.path: certs/transport.p12
# 仅使用当前节点创建新集群
# 其他节点以后仍然可以加入集群
cluster.initial_master_nodes: ["DESKTOP-LENNVGU"]

# 允许任何地方的 HTTP API 连接
# 连接是加密的则需要用户认证
http.host: 0.0.0.0

# 允许其他节点从任何地方加入集群
# 连接是加密和相互认证的
#transport.host: 0.0.0.0

关于 keystroe 和 truststore

  • keystore:一个放 key 的库,key 就是公钥、私钥、数字签名等组成的一个信息。
  • truststore:放信任的证书的一个 store

truststorekeystore 的性质是一样的,都是存放 key 的一个仓库,区别在于 truststore 里存放的是只包含公钥的数字证书,代表了可以信任的证书,而 keystore 是包含私钥的。

修改用户名密码

在elasticsearch 的bin目录下执行:

# 手动设置密码:
./elasticsearch-reset-password -u elastic -i
# 生成随机密码:
./elasticsearch-reset-password -u elastic

kibana_system

索引文件

在Elasticsearch中,每个索引都由一组文件组成,用于存储索引的数据和元数据。以下是一些常见的Elasticsearch索引文件:

  1. .cfs 文件:Compound File Set,包含了合并后的索引段数据,以减少文件数量和提高读取性能。

  2. .si 文件:Segments Info,包含了索引段的元数据信息,如段名称、版本、文档数等。

  3. .fnm 文件:Field Names,包含了字段名称的映射关系。

  4. .fdt 文件:Field Data,包含了文档字段的实际数据。

  5. .fdx 文件:Field Index,包含了字段数据的位置信息,用于快速查找和访问。

  6. .tim 文件:Term Index,包含了词项的位置信息,用于倒排索引的查找和文档匹配。

  7. .tip 文件:Term Info,包含了词项的元数据信息,如频率、位置和偏移量等。

  8. .dvd 文件:Deleted Documents,包含了被删除文档的标记信息,用于垃圾回收和段合并。

  9. .del 文件:Deletes,包含了被删除文档的标记信息,用于文档级别的删除。

  10. .docvalues 文件:存储了字段的doc values,用于聚合、排序和快速字段访问。

​ 需要注意的是,上述文件列表只是一些常见的索引文件,具体的文件结构和命名规则可能会因Elasticsearch的版本、配置和使用场景而有所不同。此外,随着Elasticsearch的版本更新和新功能的引入,可能会有新的索引文件出现。

Lucene 语法

1.直接使用单词:

2.多个单词,可以用逗号或者空格隔开

周,数学

3.可以指定字段:值来查询,例如page: 18content:"sport"

author:吴

4.通配符查询

  • ?匹配单个字符
  • *匹配0或多个字符
muscle?
hi*er
*er

5.模糊搜索

~:在一个单词后面加上启用模糊搜索,可以搜到一些拼写错误的单词
例如`first
`能匹配到错误的单词frist

first~

可以在~后面添加模糊系数,例如first~0.8,模糊系数[0-1],越靠近1表示越相近,默认模糊系数为0.5。

6.近似查询

在短语后面加上~,可以搜到被隔开或顺序不同的单词
"life movement"~2表示life和movement之间可以隔开2两个词

"life movement"~2

7.范围查询

  • page: [2 TO 8]
  • page: {2 TO 8}
    []表示端点数值包含在范围内,{}表示端点不包含在范围内

搜索第2到第8页,包含两端点page: [2 TO 8]

page: [2 TO 8]
page: {2 TO 8}

8.优先级查询

如果单词的匹配度很高,一个文档中或者一个字段中可以匹配多次,那么可以提升该词的相关度。使用符号^提高相关度。

默认为1,可以为0~1之间的浮点数,来降低优先级

keep^2 cheng

9.逻辑操作

  • AND:逻辑与,也可以用&&代替
  • OR:逻辑或,也可以使用||代替
  • NOT:逻辑非,也可以使用!代替
  • +:必须包含
  • -:不能包含

示例:

muscle AND easy    //muscle和easy必须同时存在
muscle NOT easy    //muscle存在easy不存在
muscle OR easy     //muscle或easy存在
+life -lies        //必须包含life,不包含lies

10.括号分组

可以使用小括号对子句进行分组,构造更复杂的查询逻辑

chenqionghe OR (生命 AND 运动)
content:(+chenqionghe +"muscle")   //也可以在字段中使用小括号分组

11.转义特殊符号

+ - && || ! ( ) { } [ ] ^ " ~ * ? : \,这些字符需要转义
例如\(1\+1\)\:2用来查询 (1+1):2

向量搜索

语义重新排序 re-ranking

Java API

Java技术栈有两种操作ES的方式:

RestHighLevelClient 依赖:

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>

Spring Data ElasticSearch 依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

本文基于ES官方提供的 RestHighLevelClient 进行讲解。

Repository文档操作

​ Spring Data 的强大之处,就在于你不用写任何DAO处理,自动根据方法名或类的信息进行CRUD操作。只要你定义一个接口,然后继承Repository提供的一些子接口,就能具备各种基本的CRUD功能。

​ 只需要定义接口,然后继承它就OK了。

@Repository
public interface BookDao extends ElasticsearchRepository<Book, String>{

}

新增文档

批量新增文档

修改文档

查询

基本查询

自定义查询

高级查询

​ 虽然基本查询和自定义方法已经很强大了,但是如果是复杂查询(模糊、通配符、词条查询等)就显得力不从心了。此时,我们只能使用原生查询。

@Test
public void testQuery(){
    // 词条查询
    MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("title", "小米");
    // 执行查询
    Iterable<Item> items = this.itemRepository.search(queryBuilder);
    items.forEach(System.out::println);
}

​ 分页

​ 排序

​ 聚合

映射

集群

查看集群健康状态

GET /_cat/health?v&pretty

Logstash

Logstash 是免费且开源的服务器端数据处理管道,能够从多个来源采集数据,转换数据,然后将数据输出到你指定的存储库中。

注:Logstash使用 Java 和 Ruby 语言编写并开源。是基于JVM来实现的。

功能特性

​ Logstash 能够动态地采集、转换和传输数据,不受格式或复杂度的影响。利用 Grok 从非结构化数据中派生出结构,从 IP 地址解码出地理坐标,匿名化或排除敏感字段,并简化整体处理过程。

采集数据

​ 数据往往以各种各样的形式,或分散或集中地存在于很多系统中。Logstash 支持各种输入选择,可以同时从众多常用来源捕捉事件。能够以连续的流式传输方式,轻松地从您的日志、指标、Web 应用、数据存储以及各种 AWS 服务采集数据。

实时解析和转换数据

Logstash 能够动态地转换和解析数据,不受格式或复杂度的影响:

  • 利用 Grok 从非结构化数据中派生出结构
  • 从 IP 地址破译出地理坐标
  • 将 PII 数据匿名化,完全排除敏感字段
  • 简化整体处理,不受数据源、格式或架构的影响。

输出到存储库

​ 尽管 Elasticsearch 是我们的首选输出方向,能够为我们的搜索和分析带来无限可能,但它并非唯一选择。 Logstash 提供了众多输出选择,您可以将数据发送到所需的位置。

自定义管道

​ Logstash 采用可插拔框架,社区提供上百种插件。您可以将不同的输入选择、筛选器和输出选择混合搭配,让它们在管道中和谐地运行。如果社区中的插件都不满足自己的需求,Logstash 提供了非常方便的插件开发 API 和插件生成器,开发者可以自定义构建自己的Logstash 插件。

实践

使用 Logstash 对 CSV 文件进行解析并将内容采集到 Elasticsearch 中。

Beats 采集器

​ 轻量级数据采集器 Beats 是一个免费且开源的平台,集合了多种单一用途的轻量级数据采集器,它们从成百上千或成千上万台机器和系统向 Logstash 或 Elasticsearch 发送数据。

Beats 包含的主要组件有:

组件 描述
Filebeat 针对日志文件的轻量级采集器
Packetbeat 针对网络数据包的采集
Winlogbeat windows 事件日志的采集
Metricbeat 指标的采集
Heartbeat 运行时间的采集
Auditbeat 审计数据的采集
Functionbeat 使用无服务基础架构发送云数据

beats项目的设计初衷是为了采集各类的数据,所以beats抽象出了一个libbeat库,基于libbeat我们可以快速的开发实现一个采集的工具,除了filebeat,还有像metricbeat、packetbeat等官方的项目也是在beats工程中。

注:elastic beats 使用 Go 和 JS 语言编写并开源。

filebeat

​ Filebeat 是一个轻量级的传输器,用于转发和集中日志数据。Filebeat 安装在您的服务器上作为代理,它会监控您指定的日志文件或位置,收集日志事件,并将它们转发到 Elasticsearch 或 Logstash 进行索引。

工作原理

​ Filebeat 的工作原理如下:启动 Filebeat 时,它会启动一个或多个输入 Input,这些输入会在您为日志数据指定的位置中查找。对于 Filebeat 找到的每个日志,Filebeat 都会启动一个收割机 Harvester。每个收割机都会读取单个日志以获取新内容,并将新日志数据发送到 libbeat,libbeat 会聚合事件并将聚合数据发送到您为 Filebeat 配置的输出。

filebeat

filebeat 和 Logstash 都可以收集日志,两者有何区别?

Logstash 功能更强大,支持复杂的数据处理和转换,但资源消耗较高。Filebeat 更轻量,适合作为日志收集的“搬运工”。

目前有两种采集日志的方式:

  • filebeat 和 Logstash 都使用;
  • 使用轻量级采集器 Filebeat 取代 Logstash ;

filebeat基于go语言开发,无其他任何依赖。

​ Filebeat 内置有多种模块(apache、nginx、kafka、mongodb、redis、mysql 等等),可针对常见格式的日志大大简化收集、解析和可视化过程,只需一条命令即可。

输入 Input

​ Input 负责管理收割机 Harvester 并查找所有要读取的源。如果输入类型为日志,则输入会查找驱动器上与定义的 glob 路径匹配的所有文件,并为每个文件启动一个收割机。每个输入都在其自己的 Go 协程中运行。

以下示例将 Filebeat 配置为从所有与指定 glob 模式匹配的日志文件中收集行:

filebeat.inputs:
- type: filestream
  paths:
    - /var/log/*.log
    - /var/path2/*.log

收割机 Harvester

​ 收割机负责读取单个文件的内容。收割机逐行读取每个文件,并将内容发送到输出。每个文件启动一个收割机。收割机负责打开和关闭文件,这意味着收割机运行时文件描述符一直保持打开状态。

​ 如果在收割过程中删除或重命名文件,Filebeat 将继续读取文件。这会产生副作用,即磁盘上的空间会被保留,直到收割机关闭。

Filebeat 如何保持文件的状态?

​ Filebeat 保持每个文件的状态,并经常将状态刷新到注册表 registry 文件中的磁盘。状态用于记住收割机harvester 读取的最后一个偏移量 offset,并确保发送所有日志行。如果无法访问输出(例如 Elasticsearch 或 Logstash),Filebeat 会跟踪发送的最后一行,并在输出再次可用时继续读取文件。在 Filebeat 运行时,每个输入的状态信息也会保存在内存中。重新启动 Filebeat 时,注册表文件中的数据用于重建状态,Filebeat 会在最后一个已知位置继续每个收割机。

​ 对于每个输入,Filebeat 都会保留它找到的每个文件的状态。由于文件可以重命名或移动,因此文件名和路径不足以识别文件。对于每个文件,Filebeat 都会存储唯一标识符以检测文件是否以前被收割过。

​ 问题:如果您的用例涉及每天创建大量新文件,您可能会发现注册表文件变得太大。有关可以设置以解决此问题的配置选项的详细信息,请参阅注册表文件太大。

Filebeat 如何确保至少一次交付?

​ Filebeat 保证事件至少交付一次到配置的输出,并且不会丢失数据。Filebeat 能够实现此行为,因为它将每个事件的交付状态存储在注册表文件中。

​ 在定义的输出被阻止且尚未确认所有事件的情况下,Filebeat 将继续尝试发送事件,直到输出确认已收到事件。如果 Filebeat 在发送事件的过程中关闭,它不会等待输出确认所有事件后再关闭。在 Filebeat 关闭之前发送到输出但未确认的任何事件都会在 Filebeat 重新启动时再次发送。这可确保每个事件至少发送一次,但最终可能会有重复的事件发送到输出。您可以通过设置 shutdown_timeout 选项将 Filebeat 配置为在关闭前等待特定时间。

管道过载保护

​ 当将数据发送到 Logstash 或 Elasticsearch 时,Filebeat 使用背压敏感协议,以应对更多的数据量。如果 Logstash 正在忙于处理数据,则会告诉 Filebeat 减慢读取速度。一旦拥堵得到解决,Filebeat 就会恢复到原来的步伐并继续传输数据。

启动 filebeat

./filebeat -c /path/to/root/config/filebeat.yml -e

配置

输入配置

filestream 类型

从7.14版本开始提供 filestream类型的采集组件。相交于旧的log组件,主要有以下优势:

  • 性能更好:主要是修改了registry文件的结构:支持增量写registry文件;支持元素合并防止无限膨胀
  • 边界问题处理得更好:支持基于output状态维护采集任务的close.on_state_change.*
  • 提供更多功能:支持多种file_identity用于区分采集文件(而不仅依赖inode+deviceId)

建议在新项目中使用 filestream

filebeat.inputs:
- type: filestream
  id: my-filestream-id
  paths:
    - /var/log/messages
    - /var/log/*.log
  fields:
    app_id: query_engine_12

id 是必填项

添加自定义信息 fields

​ fields 是可选的,通过fields 可以向输出添加自定义信息,例如,您可以添加可用于过滤日志数据的字段。字段可以是标量值、数组、字典或这些的任何嵌套组合。默认情况下,您在此处指定的字段将分组到输出文档中的字段子字典下。要将自定义字段存储为顶级字段,请将 fields_under_root 选项设置为 true。

输出配置

常见的输出有:

  • Elasticsearch
  • Kafka
  • Logstash

Elasticsearch

输出到Elasticsearch:

output.elasticsearch:
  hosts: ["https://myEShost:9200"]
  username: "filebeat_writer"
  password: "YOUR_PASSWORD"

api key 权限类型

output.elasticsearch:
  hosts: ["https://myEShost:9200"]
  api_key: "ZCV7VnwBgnX0T19fN8Qe:KnR6yE41RrSowb0kQ0HWoA"

PKI 证书身份验证:

output.elasticsearch:
  hosts: ["https://myEShost:9200"]
  ssl.certificate: "/etc/pki/client/cert.pem"
  ssl.key: "/etc/pki/client/cert.key"

查看Elasticsearch,有一个默认的索引名字 filebeat-%{[beat.version]}-%{+yyyy.MM.dd}

kafka

输出到kafka:

output.kafka:
  # initial brokers for reading cluster metadata
  hosts: ["kafka1:9092", "kafka2:9092", "kafka3:9092"]
  username: 
  password: 
  # message topic selection + partitioning
  topic: '%{[fields.log_topic]}'
  partition.round_robin:
    reachable_only: false

  required_acks: 1
  compression: gzip
  max_message_bytes: 1000000

Logstash

输出到 Logstash

output.logstash:
  hosts: ["127.0.0.1:5044"]

启动

# 测试一下输出
filebeat test output
# 启动
filebeat.exe -e -c filebeat.yml

多行支持

​ Filebeat 收集的文件可能包含跨多行文本的消息。例如,多行消息在包含 Java 堆栈跟踪的文件中很常见。为了正确处理这些多行事件,您需要在 filebeat.yml 文件中配置多行设置,以指定哪些行属于单个事件。

官方文档【多行支持】

parsers:
- multiline:
    type: pattern
    pattern: '^\['   # 指定要匹配的正则表达式模式。
    negate: true      # 定义模式是否为否定模式,默认为 false。
    match: after   

注:以上是文件流类型的配置,和日志类型的不一样。

示例:

Java 堆栈跟踪由多行组成,每行在初始行之后以空格开头,如下例所示:

Exception in thread "main" java.lang.NullPointerException
        at com.example.myproject.Book.getTitle(Book.java:16)
        at com.example.myproject.Author.getBookTitles(Author.java:25)
        at com.example.myproject.Bootstrap.main(Bootstrap.java:14)

要在 Filebeat 中将这些行合并为单个事件,请对 filestream 使用以下多行配置:

parsers:
- multiline:
    type: pattern
    pattern: '^[[:space:]]'
    negate: false
    match: after

再看一个稍复杂的例子

Exception in thread "main" java.lang.IllegalStateException: A book has a null property
       at com.example.myproject.Author.getBookIds(Author.java:38)
       at com.example.myproject.Bootstrap.main(Bootstrap.java:14)
Caused by: java.lang.NullPointerException
       at com.example.myproject.Book.getId(Book.java:22)
       at com.example.myproject.Author.getBookIds(Author.java:35)
       ... 1 more

filebeat配置:

parsers:
- multiline:
    type: pattern
    pattern: '^[[:space:]]+(at|\.{3})[[:space:]]+\b|^Caused by:'
    negate: false
    match: after

处理器

​ 虽然不像 Logstash 那样强大和强大,但 Filebeat 可以在将数据转发到您选择的目标之前对日志数据应用基本处理和数据增强功能。 你可以解码 JSON 字符串,删除特定字段,添加各种元数据。

Java应用后台日志数据:

2025-03-03 22:39:00 [main] INFO  com.anji.captcha.service.impl.BlockPuzzleCaptchaServiceImpl - 初始化缓存...
2025-03-03 22:39:01 [main] INFO  io.lettuce.core.EpollProvider - Starting without optional epoll library

配置文件:

processors:
  - drop_fields:
  # 去除多余的系统字段
      fields: ["agent","log","input","ecs","host",]
  - script:
      lang: javascript
      id: my_filter
      tag: enable
      source: >
        function process(event) {
            var str= event.Get("message");
            var time =str.split(" ").slice(0,2).join(" ");
            event.Put("start_time",time);
        }
  - timestamp:
      field: start_time
      #timezone: Asia/Shanghai
      layouts:
        - '2006-01-02 15:04:05'
        - '2006-01-02 15:04:05.999'
      test:
        - '2019-06-22 16:33:51'
  - drop_fields:
      fields: [start_time]
add_cloud_metadata
add_locale
decode_json_fields
drop_event
drop_fields
include_fields
add_kubernetes_metadata
add_docker_metadata

通用配置

注册表存储根路径

# 注册表的根路径。如果使用相对路径,则认为它是相对于数据路径的。默认值为 ${path.data}/registry。
filebeat.registry.path: registry

注册表文件权限

​ 要应用于注册表数据文件的权限掩码。默认值为 0600。permissions 选项必须是以八进制表示法表示的有效 Unix 样式文件权限掩码。在 Go 中,八进制表示法中的数字必须以 0 开头。允许的最宽松掩码是 0640。如果通过此设置指定了更高的权限掩码,则它将受到 umask 0027 的约束。

示例:

  • 0640:授予文件所有者读写权限,授予与文件关联的组成员读取权限。

  • 0600:授予文件所有者读写权限,其他所有成员无权访问。

filebeat.registry.file_permissions: 0600

刷入磁盘间隔

控制何时将注册表项写入磁盘 (刷新) 的超时值。当未写入的更新超过此值时,它会触发对磁盘的写入。当 registry.flush 设置为 0 时,在成功发布每批事件后,注册表将写入磁盘。默认值为 1s。

registry.flush: 1

服务关闭等待时间

​ Filebeat 在关闭时等待发布者完成发送事件的时间长度,然后 Filebeat 才会关闭。默认情况下,此选项处于禁用状态,Filebeat 不会等待发布者完成发送事件后再关闭。这意味着,在 Filebeat 关闭前发送到输出但未确认的任何事件,在您重新启动 Filebeat 时都会再次发送。这是 Filebeat 为确保至少一次交付所设计的机制。

您可以配置 shutdown_timeout 选项来指定 Filebeat 在关闭前等待发布者完成发送事件的最长时间。如果在达到 shutdown_timeout 之前确认了所有事件,Filebeat 将关闭。

此选项没有推荐的设置,因为确定 shutdown_timeout 的正确值在很大程度上取决于 Filebeat 运行的环境和输出的当前状态。

filebeat.shutdown_timeout: 5s

配置动态更新

你可以指定一个外部的配置文件,filebeat 会自动检测并更新:

filebeat.config.inputs:
  enabled: true
  path: configs/*.yml
  reload.enabled: true
  reload.period: 10s

系统配置

filebeat.yml 配置文件:

path.home: /usr/share/beat
path.config: /etc/beat
path.data: /var/lib/beat
path.logs: /var/log/

Kibana

Kibana 的进程是一个 NodeJs 服务,不能单独运行,必须依赖ES服务。

Kibana 默认访问地址:http://localhost:5601

kibana 在配置文件中要配置连接的elasticsearch的用户名密码

elasticsearch.username: "kibana_system"
elasticsearch.password: "123456"

注:这里的连接用户不能是elasticsearch 服务的超级管理员 elastic,必须使用普通账号,默认会提供一个专门为 kibana 服务提供的账号 kibana_system。

APM 系统

​ Elastic APM 是一款基于 Elastic 技术栈的免费及开放的性能监控系统。用于实时监控软件服务和应用程序的各项性能指标,如:请求访问的各项指标、访问耗时、数据库查询、缓存调用、外部 HTTP 请求等。便于开发人员快速排查和修复各种性能问题。

​ Elastic APM 由 4 个组件组成:APM 代理、APM 服务端、Elasticsearch 和 Kibana。

APM Agent

​ 为通过与应用服务相同的语言编写的开源类库。通过按照其他应用程序服务一样安装代理。安装完成之后,即可通过代理收集各项应用程序指标和运行时异常。收集的数据将在本地缓存较短时间后发送至 APM Server。

支持的语言主要包括:Go、Java、.NET、Node.js、Python、Ruby、JavaScript。

APM Server

​ APM Server 是一款收集 APM Agent 数据、可独立部署的开源应用程序。APM Server 使得代理变得轻量级、防范安全风险、并提高了 Elastic 技术栈的跨语言能力。APM Server 将通过APM Agent 收集的数据进行验证、处理后存入对应的 Elasticsearch 索引。经过几秒种后,就可以通过 Kibana APM 应用观测到可视化后的应用程序性能数据了。

最佳实践

QQ音乐

​ 使用ELK 构建日志处理平台,提供无侵入、集中式的远程日志采集和检索系统

image-20230116225858548

Filebeat 作为日志采集和传送器。Filebeat监视服务日志文件并将日志数据发送到Kafka。

Kafka 在Filebeat和Logstash之间做解耦。

Logstash 解析多种日志格式并发送给下游。

ElasticSearch 存储Logstash处理后的数据,并建立索引以便快速检索。

Kibana 是一个基于ElasticSearch查看日志的系统,可以使用查询语法来搜索日志,在查询时制定时间和日期范围或使用正则表达式来查找匹配的字符串。

测试用例

kibana dev

GET /book/_search
{
  "query": {
    "match_all": {}
  }
}
# 
GET /book/_search
{
  "query": {
    "match_all": {}
  }
}
# ik_max_word 分词
GET /_analyze
{
  "text":"中华人民共和国国徽",
  "analyzer":"ik_max_word"
}
# ik_smart 分词
GET /_analyze
{
  "text":"中华人民共和国国徽",
  "analyzer":"ik_smart"
}
# 删除索引
DELETE test2
# 查看指定索引
GET /test1
# 查看集群中所有的索引
GET _all
# 打开索引
POST /test1/_open
# 关闭索引
POST /test1/_close
# 查询索引中的全部文档数据
GET /test1/_search
# 禁止读操作
PUT test1/_settings
{
  "blocks.read": false
}

# 根据ID查看文档
GET shopping/_doc/1001
# 查看指定索引下的全部文档数据
GET shopping/_search

# 查看索引的映射
GET book/_mapping
# 创建索引并静态映射
PUT my_index
{
  "mappings": {
    "properties" : {
        "author" : {
          "type" : "text"
        },
        "createTime" : {
          "type" : "date",
          "format" : "basic_date_time"
        },
        "id" : {
          "type" : "keyword"
        },
        "price" : {
          "type" : "double"
        }
      }
  }
}

README

作者:银法王

微信公众号:米开的网络私房菜

版权声明:本文遵循知识共享许可协议3.0(CC 协议): 署名-非商业性使用-相同方式共享 (by-nc-sa)

参考:

官网文档

FileBeat 文档

优雅应对故障:QQ音乐怎么做高可用架构体系?

elasticdump 文档

记录:

 2022-10-01 第一次修订

 2023-05-09 完善


全文搜索引擎之ElasticSearch
http://jackpot-lang.online/2022/10/08/数据库/全文搜索引擎之Elasticsearch/
作者
Jackpot
发布于
2022年10月8日
许可协议