全文搜索引擎之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 顶级开源项目。

分词器
分词器的主要作用是将一串字符处理为一个个词项,分词器有很多种:标准分词器、空格分词器、停用词分词器、简单分词器、中文分词器等等。
在创建索引的时候需要使用分词器,在进行索引查询的时候也要用到分词器,而且这两个地方要使用同一个分词器,否则可能会搜索不出结果。
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_phrase
和 match
是两种常用的查询方式,它们的核心区别在于 对词项顺序和位置的要求。以下是详细对比和使用场景分析:
核心区别
特性 | 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)的深度分页问题是指在处理大规模数据时,使用传统的 from
和 size
参数进行分页(如查询第 1000 页之后的数据)会导致性能急剧下降甚至内存溢出。以下是问题的根源、解决方案及示例:
深度分页问题的根源
ES 的分页机制基于以下流程:
- 分片查询 :每个分片独立执行查询,并返回
from + size
条结果到协调节点。 - 全局排序与合并 :协调节点从所有分片的结果中合并并排序,最终返回
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
。
- 实时分页:
分页优化策略
- 限制最大分页深度
业务层限制用户可查询的最大页数(如最多查询前 100 页)。 - 引导用户使用过滤条件
通过搜索、时间范围或分类筛选缩小结果集,减少分页需求。 - 客户端分页
在客户端缓存部分数据,减少对 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
查询(需明确嵌套结构)
如果 anQuestions
是 nested
类型(即数组中的对象是独立对象),需要显式指定嵌套路径:
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
提升性能(如分页、聚合前的过滤)。
filter
和 query
有什么区别?
- **
query
**:计算文档相关性得分(_score
),用于排序(如搜索关键词)。 - **
filter
**:仅判断是否匹配,不计算得分,结果可缓存,性能更高。
特性 | 说明 |
---|---|
相关性无关 | filter 仅判断文档是否满足条件,不计算匹配度得分(所有匹配文档的 _score 为 1) |
结果可缓存 | 过滤结果会被缓存,重复查询时性能更高 |
布尔逻辑支持 | 支持 must 、should 、must_not 组合 |
高效执行 | 无评分计算,适合大规模数据过滤 |
将 range
包裹在 bool
的 filter
子句中,强制进入 过滤上下文(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 ,以列式格式存储数据,用于排序、聚合和脚本计算。
作用 :
- 高效支持
sort
、aggs
,用于排序、聚合和脚本计算等操作; - 减少磁盘 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
(完整词项)。 - 数值字段 :
integer
、float
、double
等。 - 日期字段 :
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 树(数值与日期类型)
作用 :针对数值(如 integer
、double
)和日期字段,使用 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
truststore
和 keystore
的性质是一样的,都是存放 key 的一个仓库,区别在于 truststore
里存放的是只包含公钥的数字证书,代表了可以信任的证书,而 keystore
是包含私钥的。
修改用户名密码
在elasticsearch 的bin目录下执行:
# 手动设置密码:
./elasticsearch-reset-password -u elastic -i
# 生成随机密码:
./elasticsearch-reset-password -u elastic
kibana_system
索引文件
在Elasticsearch中,每个索引都由一组文件组成,用于存储索引的数据和元数据。以下是一些常见的Elasticsearch索引文件:
.cfs
文件:Compound File Set,包含了合并后的索引段数据,以减少文件数量和提高读取性能。.si
文件:Segments Info,包含了索引段的元数据信息,如段名称、版本、文档数等。.fnm
文件:Field Names,包含了字段名称的映射关系。.fdt
文件:Field Data,包含了文档字段的实际数据。.fdx
文件:Field Index,包含了字段数据的位置信息,用于快速查找和访问。.tim
文件:Term Index,包含了词项的位置信息,用于倒排索引的查找和文档匹配。.tip
文件:Term Info,包含了词项的元数据信息,如频率、位置和偏移量等。.dvd
文件:Deleted Documents,包含了被删除文档的标记信息,用于垃圾回收和段合并。.del
文件:Deletes,包含了被删除文档的标记信息,用于文档级别的删除。.docvalues
文件:存储了字段的doc values,用于聚合、排序和快速字段访问。
需要注意的是,上述文件列表只是一些常见的索引文件,具体的文件结构和命名规则可能会因Elasticsearch的版本、配置和使用场景而有所不同。此外,随着Elasticsearch的版本更新和新功能的引入,可能会有新的索引文件出现。
Lucene 语法
1.直接使用单词:
周
2.多个单词,可以用逗号或者空格隔开
周,数学
3.可以指定字段:值
来查询,例如page: 18
、content:"sport"
author:吴
4.通配符查询
- ?匹配单个字符
- *匹配0或多个字符
muscle?
hi*er
*er
5.模糊搜索
~
:在一个单词后面加上启用模糊搜索,可以搜到一些拼写错误的单词`能匹配到错误的单词frist
例如`first
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的方式:
- Spring Data ElasticSearch(Spring提供)
- RestHighLevelClient (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 和 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 构建日志处理平台,提供无侵入、集中式的远程日志采集和检索系统

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)
参考:
官网文档
记录:
2022-10-01 第一次修订
2023-05-09 完善