disgare 的博客
首页
博客
分类
标签
首页
博客
分类
标签
  • 网络

    • 计算机网络学习笔记
    • 网络安全相关
    • 域名和子网掩码
    • CORS 跨域资源共享
    • DNS、HTTP 与 HTTPS
    • Server-Sent Events (SSE)
    • WebSocket 长连接
  • 计算机基础

    • 操作系统 IO 相关知识
    • 操作系统学习笔记
    • 程序的机器级表示
    • 音频文件基础
    • 正则表达式相关概念
    • ffmpeg 的安装以及实现音频切分功能
    • Hex 和 Base64 编码
    • XML 的使用
  • 数据结构与算法

    • 动态规划算法学习笔记
    • 基于比较的排序算法的最坏情况下的最优下界为什么是O(nlogn)
    • 集合与数据结构学习笔记
    • 面试常见算法总结
    • 算法导论第二部分排序学习笔记
    • 算法导论第一部分学习笔记
  • Java

    • 对象之间的映射与转换
    • 反射学习笔记
    • 泛型相关概念
    • 关于 boolean 类型的坑
    • 如何使用 lambda 表达式实现排序
    • CompletableFuture 相关用法
    • CompletableFuture 源码浅要阅读
    • FutureTask 源码阅读
    • Guava 常用 API
    • Guava 源码阅读:Multimap 相关
    • Jackson 的各种使用
    • Java 的 Excel 相关操作
    • java 的常见性能问题分析以及出现场景
    • java 基础知识
    • JAVA 枚举的基础和原理
    • Java 图片文件上传下载处理
    • Java 序列化
    • Java 异常
    • Java 语法糖
    • Java 中关于字符串处理的常用方法
    • Java 中强、软、弱、虚引用
    • JAVA 注解小结
    • Java Http 访问框架
    • Java Stream 的使用
    • Java8 新特性
    • netty 学习笔记
    • Scanner 的各种用法
    • Servlet 学习笔记
    • String、StringBuffer、StringBuilder 学习笔记
  • JVM

    • 虚拟机执行子系统
    • JVM 自动内存管理
    • Linux 中 JVM 常用工具以及常见问题解决思路
  • Linux

    • crontab 表达式
    • Linux 常见命令
    • Linux 文件系统
  • 中间件

    • 关于定时任务原理
    • 详解 kafka
    • ES 搜索引擎
      • ES 原理
        • 倒排索引
        • 分词器
        • 复杂查询
        • 分页查询原理和限制
        • 文档储存
        • ES JSON 文档的结构以及索引数据结构
        • 分片和副本
        • 段(Segment)
      • ES 的查询
        • 通过 API 查询
        • 通过 JSON 查询
      • 额外问题
        • ES master 选举机制
        • 更新删除、新增文档的过程
        • ES 写入数据流程
    • flink 提交流程
    • Grape-RAG
    • Hadoop 基础原理
  • 多线程

    • 多线程基础学习笔记
    • 简单了解并发集合
    • 如何手写单例
    • 深入理解 java 多线程安全
    • 生产者消费者问题
    • 线程池作用、用法以及原理
    • AQS 组件
    • ThreadLocal 原理以及使用
  • 非关系型数据库

    • Redis 集群
    • Redis 数据结构、对象与数据库
    • Redis 学习笔记
  • 关系型数据库

    • B+ 树的插入、删除和数据页分裂机制
    • MySQL 的 binglog、redolog、undolog
    • MySQL 的记录存储结构、存储引擎与 Buffer Pool
    • MySQL 基本的特性
    • MySQL 开发规范
    • MySQL 事务与锁与 MVCC
    • MySQL 数据类型、字符集相关内容
    • MySQL 索引与索引优化
    • PostgreSQL 更新数据时 HOT优化
    • PostgreSQL 相关用法
  • Python

    • Python 基础语法
    • Python 学习
  • Spring 项目

    • Lombok 的常用注解
    • maven 小结
    • MyBatis 框架的使用
    • MyBatis 重要知识点总结
    • MybatisPlus 的使用
    • Spring 框架基础使用
    • Spring 事务相关
    • Spring IOC 的原理及源码
    • Spring AOP 的使用和原理
    • SpringBoot 的原理
    • SpringBoot 基础使用
    • SpringWeb 重要知识点
  • 分布式

    • 初步了解 docker
    • 从 ACID 到 BASE 事务处理的实现
    • 访问远程服务
    • 分布式 id
    • 分布式缓存相关问题
    • 分布式集群理论和分布式事务协议
    • 分布式架构的观测
    • 分布式一致性算法
    • 负载均衡 Load Balancing
    • 关于分布式系统 RPC 中高可用功能的实现
    • 集群间数据同步的目的
    • 三高问题下的系统优化
    • 数据库分库分表
    • 详解 Spring Cloud
    • Dubbo 基础概念
    • Gossip 协议
    • nginx 学习笔记
    • Protobuf 通信协议
    • Zookeeper 基础学习
  • 架构设计

    • 参数校验与异常处理
    • 抽象方法与设计模式
    • 代码整洁之道
    • 权限系统设计
    • 用低内存处理大量数据
    • 设计模式——策略模式
    • 设计模式——过滤器模式在 Spring 中的实践
    • 状态模式
    • 统一结果返回
    • 为什么要打日志?怎么打日志?打什么日志?
    • 运维监控常见指标含义
    • 资深研发进阶
    • DDD 架构学习笔记
    • Java 常用的规则引擎
    • MVC 架构学习笔记
  • AI

    • 如何编写 Prompt
    • Agent 工程架构
    • LLM 相关内容
    • NLP 相关知识
    • vibe coding 最佳实践
    • windows 下 ollama 迁移到 D 盘
  • 开发工具

    • 如何画时序图、流程图、状态流转图
    • excel 关于 =vlookup 的用法
    • git 的学习以及使用
    • IDEA 插件推荐
    • IDEA 常用快捷键以及调试
    • Shell 脚本
    • swagger 的使用
  • 前端

    • 简单了解前端页面开发
    • 伪静态是什么
    • GitHub Pages 部署教程
    • Vercel 部署教程
    • vue-admin-template 简单使用
    • VuePress 博客搭建指南
  • 项目

    • 面试刷题网——技术方案
    • 影视资源聚合站——技术方案
  • 问题记录

    • 定时任务单线程消费 redis 中数据导致消费能力不足
    • 提供可传递的易受攻击的依赖项
    • Liteflow 在 SpringBoot 启动时无法注入组件问题 couldn‘t find chain with the id[THEN(NodeComponent)]
  • 金融

    • 股票分析——关于电力
    • 股票技术面——量价关系
    • 股票技术面——盘口
    • 股票技术面——基础
    • 基础的金融知识
    • 基金与股票
    • 韭菜的自我总结
    • 聊聊价值投资
  • 其他

    • 程序员职场工作需要注意什么
    • 创业全链路SOP:从灵光一现到系统化增长的实战指南
    • 观罗翔讲刑法随笔
    • 价格和价值
    • 立直麻将牌效益理论
    • 梅花易数学习笔记
    • 压力管理
2023-10-18
中间件
目录

ES 搜索引擎

# ES 原理

ElasticSearch 是面向文档的非关系型数据库,ElasticSearch 集群由多个节点(Node)组成,每个节点是一个 Elasticsearch 实例。节点中可以包含多个索引,每个索引中可以包含多个分片(表),每个类型下又包含多个文档(行)。其实可以把 es 理解成多个倒排索引的聚合,它的底层原理涉及多个方面,包括分布式架构、倒排索引、分片和副本、文档存储和检索等

# 倒排索引

倒排索引是 ElasticSearch 实现高效搜索的核心技术。倒排索引将文档中的每个词映射到包含该词的文档列表,从而允许快速查找包含特定词的文档

  • 正向索引:文档 ID 到文档内容的映射
  • 倒排索引:单词到文档 ID 的映射

例如,假设有两个文档:

  • 文档1:ElasticSearch is cool
  • 文档2:ElasticSearch is fast

倒排索引可能如下所示:

  • ElasticSearch: [文档1/位置1, 文档2/位置1]
  • is: [文档1/位置2, 文档2/位置2]
  • cool: [文档1/位置3]
  • fast: [文档2/位置3]

如果需要建立索引的数据过多,倒排索引可能会构建一个 b+ 树形式的索引,同时对每个字段的文档数过多的话,也会使用树状数据结构来做优化

在 pg 中构建数组、json 型数据的索引时也会使用到倒排索引的,可以简单理解成,倒排就是根据文档中的字段快速找到该文档的方式

倒排索引当然不只是存文档 id 这么简单。还包括:词频(TF,Term Frequency)、偏移量(offset)、位置(Posting)等信息

# 分词器

分词器不是 ES 的主要知识点,但是我还是在这里额外说明一下。ES 是存放文档、构建文档的索引的搜索引擎,为了构建索引,要将文档按一定的格式切分开

在创建索引的时候需要用到分词器,在使用字符串搜索的时候也会用到分词器,并且这两个地方要使用同一个分词器,否则可能会搜索不出来结果。一般 ES 会使用默认会使用标准分词器(StandardAnalyzer 默认会使用标准分词器),我输入一段话,分词结果如下:

image-2026-02-28-16-04-59.png

因此实际应用中一般是使用构建真正对文本进行分词处理的 TokenStream 分词处理器,这回将句子按照一个 token 一个 token 切分开,这又引入了一个概念嵌入。这么切分不仅可以正确的按照语义分词,甚至配合上距离计算技术可以让计算机理解每个词与其他词的相似度。因此 ES 也可以做 RAG 向量数据库

# 复杂查询

我们在使用 es 的时候,也知道 es 支持一些复杂查询,这些复杂查询组合里多个查询条件,那么倒排索引如何通过使用布尔逻辑(AND、OR、NOT)组合多个查询条件来查找数据的呢?

首先,Elasticsearch 将用户的查询解析成多个子查询,并确定它们之间的逻辑关系。对于每个子查询,Elasticsearch 会在倒排索引中查找对应的文档列表。根据布尔逻辑,Elasticsearch 对这些文档列表进行交集、并集或差集操作,最终得到满足所有条件的文档列表。es 敢这么做得益于它高效的查询速度以及将整个索引分片的设计原理。注意,协调节点只发一次请求到每个分片,每个分片内部自行处理所有子查询,但是这些子查询是先分开查然后再拼接结果的

当然取交集 ES 内部也有一定的优化,举例来说需要对这两个倒排索引求交集,也就是同时包含高并发和架构的网页才是符合搜索要求的结果,最终的交集结果应该是只有一篇网页,即 docID 为2的满足要求

列表求交集最简单的实现就是双层 for 循环,但是这种算法的时间复杂度是O(n^2),一个改进的算法是拉链法,es 将网页列表先按照 docID 的编号进行排序,同时遍历两个链表,如果其中一个链表当前指向的元素小于另一个链表当前指向的元素,那么这个链表就继续向前遍历,如果出现第一个链表遍历到自己的最后一个元素,才和第二个链表的第一个元素相同。就是用双指针算法来优化查询性能。那么第一个链表能不能跳过前面那些元素呢?这个问题我们想到可以用跳表来实现

我们百度就是依靠这种思想来构建的,除此之外可能还有一些排序算法,比如 PageRank 算法(主要思想为若网页 A 链接到网页 B,相当于 A 给 B 投了一票,投票的权重取决于 A 自身的重要性,一个网页的 PageRank 值由所有指向它的网页的贡献之和决定)

所以 es 里面复杂的关联查询尽量别用,一旦用了性能都不太好。最好是先在 Java 系统里就完成关联,将关联好的数据直接写入 ES 中

# 分页查询原理和限制

这里还有一些 ES 的分页需要额外说明一下,关于浅分页(前 1000 条以内),我们可以用以下语句查询

GET /your_index/_search
{
  "from": 0,   // 起始位置
  "size": 10,  // 每页数量
  "query": { ... }
}
1
2
3
4
5
6

Elasticsearch 索引文档的工作过程为协调节点(Coordinating Node)向所有分片广播查询。每个分片返回前 from + size 条结果的元数据(文档 ID 和排序值),协调节点合并所有分片的结果,排序后返回 [from, from + size) 区间内的文档,最后通过 _id 回查(Fetch Phase)获取完整文档数据(文档也会被分到不同的分片上进行存储,不同的分片都有自己的副本,因此协调节点还需要再访问一次所有分片拿到真正的文档信息)

这就导致在查询数量很多的情况下,性能很差,每个节点都需要查询 from + size 条结果 ,同时 es 会默认只能查询一万条数据,就是在单次查询中,from + size 必须小于等于1万。因为分页查询是个经常使用到的功能,所以这个限制还是很可以会被触发的

因此深分页时,我们应该使用 searchAfter 方法,根据某种规则排序,然后 searchAfter 内填上一页最后一条的排序值

GET /your_index/_search
{
  "size": 10,
  "query": { ... },
  "sort": [
    {"timestamp": "desc"},  // 必须包含唯一性字段(如 _id)
    {"_id": "asc"}
  ],
  "search_after": [ "2023-01-01T00:00:00", "doc_id_123" ] // 上一页最后一条的排序值
}
1
2
3
4
5
6
7
8
9
10

使用排序字段的唯一性组合(如时间戳 + _id)作为分页锚点。es 在每次查询基于上一页最后一条的排序值(search_after)定位下一页的起始位置(当然每个分片都会做这个处理,分片提交给协调节点的数据就大大减少了)。仅获取当前页的 size 条数据,无需全局排序,也就是做了一个滑动窗口的查询

es 还有一种支持深分页的机制,就是滚动分页,原理为首次查询创建快照(snapshot),后续请求基于该快照读取,类似数据库游标,冻结查询时的索引状态。这种方式内存消耗比较高,适合超大数据集导出,同时上下文过期前必须显式清除(否则资源泄漏)

// 初始化Scroll(保持1分钟,必须要设置时间)
POST /index/_search?scroll=1m
{
  "size": 100,
  "query": {...}
}

// 后续获取(使用返回的_scroll_id)
POST /_search/scroll
{
  "scroll": "1m",
  "scroll_id": "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4..."
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 文档储存

一个索引中,包含多个文档

文档:之前说 elasticsearch 是面向文档的,那么就意味着索引和搜索数据的最小单位是文档,每个文档是一个 JSON 对象,elasticsearch 中,文档有几个重要属性

  • 文档是层次型的,一个文档中包含自文档,简单来说文档其实就是个 JSON 对象
  • 灵活的结构,文档不依赖预先定义的模式,我们知道关系型数据库中,要提前定义字段才能使用,在 elasticsearch 中,对于字段是非常灵活的,有时候,我们可以忽略该字段,或者动态的添加一个新的字段。这是 JSON 的特性
  • 尽管我们可以随意的新增或者忽略某个字段,但是,每个字段的类型非常重要,比如一个年龄字段类型,可以是字符串也可以是整形。因为 elasticsearch 会保存字段和类型之间的映射及其他的设置。这种映射具体到每个映射的每种类型,这也是为什么在 elasticsearch 中,类型有时候也称为映射类型

映射信息被存储在索引的元数据中,这些元数据用于描述索引的结构和字段类型,elasticsearch 中的索引是一个非常大的文档集合,索引存储了映射类型和倒排索引

这里也贴一下字段类型长什么样子,以下是文本类型(Text)

     {
       "mappings": {
         "properties": {
           "title": {
             "type": "text"
           }
         }
       }
     }
1
2
3
4
5
6
7
8
9

# ES JSON 文档的结构以及索引数据结构

我们在上面的例子中,使用了两个单词当成文档,但是 ElasticSearch 里存放的都是 json,json 是怎么作为文档存放在 ES 中的呢

假设我们有一个 JSON 文档如下:

{
  "title": "ElasticSearch教程",
  "author": "张三",
  "content": "ElasticSearch是一个强大的全文搜索和分析引擎。",
  "tags": ["ElasticSearch", "搜索", "分析"],
  "date": "2023-10-01"
}
1
2
3
4
5
6
7

ElasticSearch 首先会解析 JSON 文档,将其转换为内部数据结构。每个字段(如title、author、content、tags、date)都会被单独处理。随后会对每个字段的值进行分词,对于文本字段(如 title、author、content 的值),ElasticSearch 会使用分词器(Analyzer)将文本拆分成一个个词(Token)

例如,对于 content 字段,可能会得到以下这些单词:

["Elasticsearch", "是", "一个", "强大", "的", "全文", "搜索", "和", "分析", "引擎"]
1

倒排索引的核心是一个映射表,记录了每个词(Term)出现的文档 ID 及其位置信息,对于上面的单词,倒排索引可能如下所示(记录单词信息、文档 ID、在文档中的位置、属于哪个字段等信息):

Term Document ID Position Field Name
ElasticSearch 1 1 content
是 1 2 content
一个 1 3 content
强大 1 4 content
的 1 5 content
全文 1 6 content
搜索 1 7 content
和 1 8 content
分析 1 9 content
引擎 1 10 content

对于非文本字段(如 date),ElasticSearch 会根据字段类型进行适当的处理。例如,对于 date 字段,ElasticSearch 会将其转换为标准的日期格式,并可能生成一些额外的索引条目,以便进行日期范围查询

注意,上面的 case 只是为了帮助理解,倒排索引在 ElasticSearch 中会类似下面这样组织信息:Term -> {Field Name -> [ (Document ID, Position) ]},真实存储的时候,倒排利用 FST (有限状态转换器)数据结构存放数据

FST 更契合倒排索引的查询模式和压缩需求(文档中的字太多了),通过共享前缀压缩词典。我们一般来说存放上面表中的数据只要一个 map 就行了,但是 map 的性能好吗?显然不好,我们需要一个更流弊的数据结构存储,比如要存下面的几条数据 在这里插入图片描述 FST 最终存放的数据如下 在这里插入图片描述

# 分片和副本

ElasticSearch 在后台把每个索引划分成多个分片,每份分片可以在集群中的不同服务器间迁移

  • 分片(Shard):将索引分成多个分片,每个分片是一个独立的 Lucene 索引。分片可以分布在不同的节点上,从而实现负载均衡。文档的数据结构和存储逻辑主要是在分片中
  • 副本(Replica):每个分片可以有多个副本,副本用于提高可用性和读取性能。如果某个分片所在的节点发生故障,副本可以接管请求

这就类似 kafka,每个机器上可能有一定数量的分片和副本

一个集群至少有一个节点,而一个节点就是一个 elasricsearch 进程,节点可以有多个索引,es 默认会有个5个分片(primary shard,又称主分片) 构成的,每一个主分片会有一个副本(replica shard,又称复制分片)

下图是一个有3个节点的集群,可以看到主分片和对应的复制分片都不会在同一个节点内,这样有利于某个节点挂掉了,数据也不至于丢失。同时,es 在插入和查询的时候,也会像 redis 的去中心化集群一样,先找到对应的主分片,再进行对应的操作。这样做有利于数据的分流和查询速度

在这里插入图片描述 实际上,一个分片是一个 Lucene(Lucene 可以理解成包含了映射、存放文档功能、倒排索引、索引字段格式的数据库),一个包含倒排索引的文件目录,倒排索引的结构使得 elasticsearch 在不扫描全部文档的情况下,就能告诉你哪些文档包含特定的关键字

ElasticSearch 是基于 Lucene 的,那么为什么不是直接使用 Lucene 呢?

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

举个例子,我们请求 ES 时,ES 会解析语句,如何第一个收到请求的节点作为协调节点协调集群内部分布式操作。协调节点并非独立的物理节点类型,而是任何节点都可以临时扮演的角色,它会请求 ES 集群中每个分片的数据,然后将结果聚合

# 段(Segment)

段是 Lucene 的基本单位,每个段是一个不可变的倒排索引。段包含了一部分文档的数据,以及相应的倒排索引。段是物理存储的最小单位,可以被独立地读取和搜索

请注意,这里的概念有点绕,一个索引并非指一个 Lucene 索引,也不是一个倒排索引

  • 1,一个索引可以被划分为多个分片,里面保存的是该索引一部分文档的子集
  • 2,每个分片本身就是一个完整的 Lucene 索引,每个分片分布在不同的节点上,这样做实现了负载均衡、数据切分(让搜索更快)和故障恢复
  • 3,每个索引有很多的主分片和从分片,Elasticsearch 尽量将主分片(primary shards)分布在集群中的不同节点上,而不会将所有主分片放在同一台机器上。有可能会出现3个主分片在 A 机器上,剩下2个分片在 B 机器上这种情况
  • 4,每个分片由多个段组成,段是不可变的,一旦创建就不会改变,一个段是一个倒排索引的一小部分,每个段包含部分文档的倒排索引、正向索引、词向量、每个词在文档中的位置信息等数据结构
  • 5,新的文档会被添加到新的段中,旧的段不会被修改
  • 6,会定期进行段合并(Segment Merging),将多个小段合并成一个大段,以减少段的数量,提高查询性能
  • 7,文档根据路由规则(默认 _id 哈希)散列到不同主分片上 ES 结构图示例

# ES 的查询

熟悉 es 的同学都知道 es 一般有两种查询方式

1,在 java 中构建查询对象,调用 es 提供的 api 做查询 2,使用 json 调用接口做查询

查询语句无非是将足够的信息丢给数据库,但是它却和 SQL 不一样有自己独立的查询方式

# 通过 API 查询

模糊查询

BoolQueryBuilder boolBuilder = QueryBuilders.boolQuery();

//Elasticsearch 中文会把汉字分词,“王大”会匹配到like“王”和like“大”,要在字段后面接keyword
boolBuilder.must(QueryBuilders.wildcardQuery("userName.keyword","*王大*"));
1
2
3
4

等于、不等于

BoolQueryBuilder boolBuilder = QueryBuilders.boolQuery();
//等于  must
boolBuilder.must(QueryBuilders.termQuery("age","30"));
//不等于  mustNot
boolBuilder.mustNot(QueryBuilders.termQuery("sex","1"));
1
2
3
4
5

大于、小于

BoolQueryBuilder boolBuilder = QueryBuilders.boolQuery();
//大于
boolBuilder.must(QueryBuilders.rangeQuery("createTime").gt(1609430400000));
//小于
boolBuilder.must(QueryBuilders.rangeQuery("createTime").lt(1672502400000));
1
2
3
4
5

es 也是有层级的,下面演示 and 、or 同时使用

BoolQueryBuilder boolBuilder = QueryBuilders.boolQuery();
 
boolBuilder.must(QueryBuilders.termQuery("a",1));
 
QueryBuilder queryBuilder1 = QueryBuilders.boolQuery()
                    .must(QueryBuilders.termQuery("b", 2))
                    .must(QueryBuilders.termQuery("c", 3))
                    .mustNot(QueryBuilders.termQuery("d", 4));
 
QueryBuilder queryBuilder2 = QueryBuilders.boolQuery()
                    .must(QueryBuilders.termQuery("e", 4))
                    .must(QueryBuilders.termQuery("f", 5));
 
QueryBuilder queryBuilder = QueryBuilders.boolQuery()
                    .should(queryBuilder1)
                    .should(queryBuilder2);
 
boolBuilder.must(queryBuilder);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

等同与这个 sql

select * from user where a=1 and ((b=2 and c=3 and d !=4) or (e=4 and f=5))
1

# 通过 JSON 查询

我们需要先知道 es 的地址,然后看看他存在哪些索引

-- 访问所有索引信息
curl -X GET http://localhost:9200/_cat/indices?v
-- 添加索引
curl -X PUT http://localhost:9200/demo
-- 访问索引
curl -X GET http://localhost:9200/demo
-- 删除索引
curl -X DELETE http://localhost:9200/demo
-- 给索引添加文档数据
curl -X POST http://localhost:9200/demo/_doc -d "{\"name\": \"hello\"}" -H "content-type: application/json; charset=UTF-8"
1
2
3
4
5
6
7
8
9
10

接下来聊一下他的 SQL,我们需要在索引后面加 /_search 来访问他的数据,get 和 post 都可以来查询

curl -X GET http://localhost:9200/demo/_search?q=name:wang
curl -X GET http://localhost:9200/demo/_search -d "{请求体}" -H "content-type: application/json; charset=UTF-8"
1
2

相信这个简单的查询大家都可以看出是查 name 字段等于 wang 的数据,接下来我们把他扩充一下。下面的 from 参数表示从第几条数据开始;size 参数表示获取多少条数据;query 表示查询,match 表示匹配,里面填写要匹配的字段值

{
	"query": {
		"match": {
			"name": "wang"
		},
		"from": 0,
		"size": 1
	}
}
1
2
3
4
5
6
7
8
9

是不是很简单,我们加个速,多条件查询如下

{
    "query": {
        "bool": {
            "must": [
                {
                    "match": {
                        "name": "wang"
                    }
                },
                {
                    "match": {
                        "age": 10
                    }
                }
            ],
            "should": [
                {
                    "match": {
                        "name": "andy"
                    }
                }
            ]
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

query 参数表示查询,bool 参数表示查询的条件,里面填写要查询的条件;must 参数表示多个条件要同时成立,里面填写条件列表,相当于 AND 的意思,而 should 表示 OR,上面的 must 和 should 是同级的

match 和 bool 有一些区别,当在 match 子句中仅使用一个条件查询时,则没有区别,当想要组合多个条件时,bool 子句很有用

范围查询,查询年龄大于等于18,并且小于等于50的数据,include_lower 代表包含下界,include_upper 代表包含上界。如果有多个条件的话,范围查询只需要在 bool 条件下添加 filter 参数即可,参数里面可以填写多个过滤条件

{
    "query": {
        "range": {
            "age": {
                "include_lower": true,
                "include_upper": true,
                "from": 18,
                "to": 50
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

# 额外问题

# ES master 选举机制

先说一下为什么还要分主从节点,前文说,es 集群是一个机器中有多个分片,可能是主分片也可能是从分片,按这个理解每台 es 机器都是主节点了,就算某台机器挂掉,也可以负载均衡到其他机器上获取数据,为什么还需要选出一个主节点呢?

Master 节点集群的管理者负责:

  • 集群元数据管理:维护索引结构、分片分布、节点列表等全局信息
  • 分片分配决策:当节点加入 / 离开时,决定如何重新分配分片(如主分片挂掉后,选择哪个副本分片升级为主分片)
  • 协调节点通信:处理节点间的状态同步(如心跳检测、版本冲突解决)

因此对集群来说主分片是必要的,接下来说下选举过程

只有标记为候选节点且处于健康状态的节点才能参与选举。每个候选节点启动后,会向其他候选节点发送我要参选的请求,每个选举周期有一个唯一的 Term 编号(类似选举轮次,思想来源于 paxos 协议),每次选举 Term 会递增。每个候选节点优先投票给 Term 更大的节点,如果 Term 相同,优先投票给节点 ID 更小的节点(节点 ID 是启动时生成的唯一标识)

如果对某个节点的投票数达到一定的值(选举中一半以上的集群,如果小于该值会出现脑裂问题),那这个节点就是 master,会向所有节点发送我是主节点的广播,否则(超时或者没达到票数)重新选举

# 更新删除、新增文档的过程

删除和更新也都是写操作,但是 Elasticsearch 中的文档是不可变的,因此不能被删除或者改动以展示其变更(根本原因是底层 lucene 的 segment 段文件不可更新删除)

磁盘上的每个段都有一个相应的 .del 文件。当删除请求发送后,文档并没有真的被删除,而是在 .del 文件中被标记为删除。该文档依然能匹配查询,但是会在结果中被过滤掉

当段合并时,在.del 文件中被标记为删除的文档将不会被写入新段

在新的文档被创建时,Elasticsearch 会为该文档指定一个版本号,当执行更新 时,旧版本的文档在.del 文件中被标记为删除,新版本的文档被索引到一个新段,旧版本的文档依然能匹配查询,但是会在结果中被过滤掉(就是把更新操作拆成了新增和删除)

在变更命令执行时,所有文档变更首先被写入 translog,默认每次写入请求后都会异步同步到磁盘,节点重启时,通过重放 translog 恢复未持久化的数据

# ES 写入数据流程

1,乐观并发控制(核心机制):es 有个版本号校验功能,每次更新时版本号+1。es 会在请求到达时立即校验当前文档版本号和请求版本号,若不一致则拒绝操作,这么做是为了解决数据一致性问题。如果正常校验通过会再写入数据后写入日志,这里写入数据是写入内存 buffer,写入日志是写入 translog,log 会再返回客户端前刷盘 在这里插入图片描述 2,es 一般是集群部署的,因此写操作一定会同步到集群中,我们可以配置写操作一致性级别,可配置的写入确认策略:

## 控制一致性级别
wait_for_active_shards: "all"  ## 需所有副本确认(强一致)
wait_for_active_shards: 1       ## 仅主分片确认(弱一致)
wait_for_active_shards: quorum:## 大多数分片确认(默认)
1
2
3
4

es 写从分片操作比较简单,就是主分片写完后会告知从分片,和 MySQL 类似

#ES
最后更新: 2/28/2026, 11:31:29 AM
详解 kafka
flink 提交流程

← 详解 kafka flink 提交流程→

最近更新
01
vibe coding 最佳实践
02-24
02
立直麻将牌效益理论
02-23
03
伪静态是什么
02-08
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式