Elasticsearch学习

发布时间:2026/7/17 4:30:06
Elasticsearch学习 一、ES的简单了解一直面ElasticsearchElasticsearch通常简写为ES是一个开源的分布式搜索和分析引擎它被设计用于快速、实时地搜索和分析大规模数据。以下是对Elasticsearch的简单了解分布式搜索引擎Elasticsearch是一个分布式系统它可以在多个节点上运行允许你存储和搜索大量的数据。这使得它非常适合处理日志、文档、地理空间数据等各种类型的信息。文本搜索Elasticsearch是一个强大的文本搜索引擎它可以高效地搜索和匹配文本数据。它支持全文搜索、模糊搜索、多字段搜索等功能使得用户可以轻松地构建搜索引擎、推荐系统和数据分析应用。实时性Elasticsearch支持实时搜索这意味着当你添加、更新或删除文档时你可以立即在搜索结果中看到变化这对于监控、日志分析和实时报告非常有用。多种数据类型支持Elasticsearch不仅支持文本数据搜索还支持地理空间数据、数值数据、日期和时间数据等多种数据类型的搜索和分析。弹性和可扩展性Elasticsearch是弹性的你可以根据需要添加或删除节点以适应不断增长的数据和负载。它还具有自动分片和复制机制以确保数据的高可用性和可扩展性。RESTful APIElasticsearch提供了一个易于使用的RESTful API使得与其交互变得简单。你可以使用HTTP请求来执行各种操作例如索引文档、执行搜索查询、管理索引和节点等。生态系统Elasticsearch是Elastic公司的一个产品它是ELKElasticsearch、Logstash和Kibana堆栈的一部分用于日志收集、分析和可视化。此外有丰富的插件和工具可以扩展Elasticsearch的功能。二、基本概念回顾当使用Elasticsearch时有一些基本概念是很重要的下面是一些ES基本概念的回顾索引Index概念索引是Elasticsearch中的一个逻辑容器用于组织和存储相关的文档数据。它类似于关系数据库中的表但更灵活。理解可以将索引看作是一个大型数据集的容器用于存储相似类型的数据。例如你可以创建一个名为products的索引用于存储产品信息。文档Document概念文档是存储在索引中的基本数据单元通常以JSON格式表示。每个文档代表了一个独立的数据记录。理解文档类似于关系数据库中的一行记录。例如在products索引中每个文档可以代表一个产品包括产品的名称、描述、价格等信息。字段Field概念字段是文档中的数据项它们包含了文档的具体信息。每个字段都有一个名称和一个对应的值。理解在一个文档中每个字段代表了一个属性或特征例如一个产品文档可以包含字段如“productName”、“productDescription”、“price”等每个字段存储相关信息。综合起来可以将这些概念视为组织和存储数据的层次结构一个索引可以包含多个文档这些文档代表了不同类型的数据。每个文档包含多个字段每个字段存储文档的具体数据。这种层次结构的弹性和灵活性使得Elasticsearch非常适合处理大规模、异构数据并且具有强大的搜索和分析功能。通过理解这些概念你可以更好地组织、检索和分析你的数据二映射Mapping 定义了一个索引中每个字段的数据类型、索引方式以及查询规则相当于 Elasticsearch 的表结构Schema。如果你有 MySQL 基础可以这样类比MySQLElasticsearchDatabaseIndexTableES 7 已取消 Type可理解为 IndexColumnField表结构DDLMappingRowDocument例如MySQL 中建表CREATE TABLE user ( id BIGINT, name VARCHAR(100), age INT, birthday DATE );对应到 ES大概就是PUT user_index { mappings: { properties: { id: { type: long }, name: { type: text }, age: { type: integer }, birthday: { type: date } } } }这里的Mapping就是在告诉 ESid 是 longname 是 textage 是 integerbirthday 是 dateMapping 最容易考的几个点① text 和 keyword 有什么区别text会分词keyword是整体一个是全文搜索一个是精确查询② Mapping 可以自动生成吗可以。ES 默认Dynamic Mapping。例如第一次{ age:18 }ES 自动age:long第二次{ name:Tom }自动name:text但是不建议这么做因为如果由text-long 会冲突报错。③ Mapping 能修改吗text 改 keywork是不行的因为底层的倒排索引已经建好了改意味着Segment重建一般都是重建索引Mapping 是 Elasticsearch 中对索引结构的定义类似于关系型数据库中的表结构Schema。它不仅定义字段的数据类型如 text、keyword、integer、date 等还决定字段是否建立索引、采用什么分词器、是否支持排序和聚合等。不同的 Mapping 会决定底层采用不同的数据结构例如 text 字段建立倒排索引数值和日期字段采用 BKD Tree排序和聚合依赖 Doc Values。因此 Mapping 不仅影响数据存储方式也直接影响查询性能和功能。由于底层索引结构已经生成大部分字段类型创建后不能直接修改通常需要重新创建索引并进行 Reindex。映射Mapping是Elasticsearch中的一个重要概念它用于定义索引中文档的结构和字段的属性。映射指定了每个字段的数据类型、如何分析文本、是否存储原始数据等信息。以下是关于映射的详细介绍字段的数据类型映射确定了每个字段的数据类型例如文本、数值、日期、布尔值等。指定正确的数据类型有助于Elasticsearch正确地索引和搜索数据。分析器Analyzer对于文本字段映射可以指定使用哪种分析器来处理文本数据。分析器决定了如何将文本拆分成词条以及如何处理这些词条例如小写化、删除停用词等。存储选项映射可以指定是否将字段的原始值存储在索引中。存储原始值可以提高检索性能但会占用更多的存储空间。多值字段映射允许你指定字段是否可以包含多个值这对于数组或多选字段非常有用。日期格式对于日期字段映射可以定义日期的格式以确保正确的日期解析和排序。自定义字段属性你还可以在映射中定义自定义字段属性例如字段的权重、是否可搜索、是否可排序等。嵌套对象映射允许你在文档中包含嵌套对象这些对象可以具有自己的字段和映射。映射的正确定义对于Elasticsearch的性能和数据质量非常重要。它确保了索引中的文档被正确地存储和检索并允许执行高级的搜索和分析操作。通常映射可以自动创建但在需要更精细的控制时也可以手动定义映射。Lucene 负责“单机搜索能力”ES 负责“把多个 Lucene 组成一个可用的分布式搜索服务”。可以按照职责拆分Elasticsearch | -------------------------------- | | 分布式系统能力 搜索引擎能力 | | | | Cluster管理 Lucene Shard分配 倒排索引 副本复制 Segment 节点通信 BKD Tree REST API FST DSL解析 BM25 权限 translog 查询执行一、Lucene 负责什么Lucene 是搜索核心。1. 索引构建比如文档{ title:Java技术, price:100 }Lucene负责text字段分词Java 技术建立倒排索引Java | doc1 技术 | doc12. 倒排索引这是 Lucene 最核心能力。例如10亿篇文章。搜索Java不是扫描。而是Java | Posting List | docId列表快速找到文档。3. 数值索引例如{ price:100 }Lucene使用BKD Tree支持range查询数值term查询geo查询例如price 100~2004. 相关性计算例如搜索iphone哪个文档排前面Lucene计算TF-IDF老BM25现在得到doc1 score10.2 doc2 score8.55. Segment管理Lucene索引不是一个大文件。而是Index | -- Segment1 | -- Segment2 | -- Segment3每个 Segment类似一个不可变的小索引。Lucene负责创建 Segment查询 SegmentMerge Segment6. 文件存储Lucene定义自己的文件格式例如.tim .tip .doc .pos .dvd .dvm管理写入压缩读取ES的查询链路你前面已经把 ES 搜索链路基本串起来了Mapping | Analyzer 完整的一个分词 | Tokenizer 负责把词语分开 | Token Filter 负责把分开的词语做后处理比如转小写、去掉无意义词同义词等等 | 倒排索引 | 召回 | BM25 | 排序同义词就在Token Filter 这一层。你现在问的已经不是“会不会用 ES”而是在理解搜索引擎内部机制了。Segment段是 Lucene 最核心的数据组织单位。简单理解Segment 是 Lucene 生成的一份不可变的小型索引。一个 Lucene Index 不是一个完整的大文件而是由多个 Segment 组成。你可以把它理解成MySQL一个表里面有很多页PageLucene一个索引里面有很多 SegmentSegment段是 Lucene 最核心的数据组织单位。简单理解Segment 是 Lucene 生成的一份不可变的小型索引。一个 Lucene Index 不是一个完整的大文件而是由多个 Segment 组成。你可以把它理解成MySQL一个表里面有很多页PageLucene一个索引里面有很多 Segment1. 为什么需要 Segment因为搜索引擎面对两个矛盾写入要求希望快速写入顺序写磁盘不频繁修改已有数据结构查询要求希望快速查找有完整倒排索引有BKD Tree如果每写一条数据就修改整个索引新增一条数据 | 修改倒排索引 | 修改BKD Tree | 磁盘随机写性能会很差。所以 Lucene 设计数据先积累然后批量生成一个新的 Segment。2. Segment生成过程例如写入数据{ id:1, title:Java开发 }流程写入 | v 内存缓冲区 | refresh | v 生成Segment生成Segment_1 倒排索引 BKD Tree DocValues Stored Fields继续写Segment_1 已有 新增数据 ↓ Segment_2于是Index ------------- | Segment_1 | ------------- | Segment_2 | ------------- | Segment_3 | -------------3. Segment里面有什么一个 Segment 里面包含很多 Lucene 数据结构① 倒排索引负责文本搜索例如文档doc1: Java Spring doc2: Java Elasticsearch倒排Java doc1 doc2 Spring doc1 Elasticsearch doc2② BKD Tree负责数值、地理查询。例如price 100 location within 5km③ Stored Fields保存原始文档。例如搜索命中doc1返回{ title:Java Spring }这个数据来自 Stored Fields。④ DocValues用于排序聚合例如按照价格排序 统计品牌数量4. Segment为什么叫不可变这是 Lucene 一个非常重要的设计。Segment生成后不能修改。例如已有Segment_1 doc1 doc2 doc3现在修改doc2doc2: Java 改成 Python不会直接改Segment_1。而是Segment_1 doc2 标记删除 Segment_2 新的doc2为什么因为如果允许修改需要维护倒排索引BKD TreeDocValues代价巨大。5. 那Segment越来越多怎么办比如持续写入Segment_1 Segment_2 ... Segment_1000查询需要查1000个索引。性能下降。所以 Lucene 有Segment Merge后台合并Segment_1 Segment_2 Segment_3 | v Segment_big合并过程重新构建倒排索引构建BKD Tree清理删除数据Segment Merge 是 Lucene 自动执行的不需要业务代码手动触发。但是它不是“每生成一个 Segment 马上合并”而是由 Lucene 的Merge Policy合并策略和后台Merge Scheduler合并调度器控制。1. Segment Merge 的整体流程假设持续写入写入数据 | v Segment_1 继续写 | v Segment_2 继续写 | v Segment_3此时Lucene Index Segment_1 Segment_2 Segment_3后台线程发现这些 Segment 太多了需要合并。于是Segment_1 Segment_2 Segment_3 | v Segment_4生成新的大 Segment。旧 Segment 删除。2. 谁决定什么时候合并两个核心组件① Merge Policy合并策略负责决定哪些 Segment 应该合并Lucene 默认TieredMergePolicy它主要考虑Segment数量Segment大小Segment之间大小差异例如现在Segment A 10MB Segment B 12MB Segment C 15MB Segment D 500MB可能优先ABC而不是CD因为小 Segment 合并收益更高。② Merge Scheduler负责什么时候执行合并用几个线程执行例如后台Merge Thread | | 读取多个Segment | | 生成新Segment3. Merge 是不是实时发生不是。例如大量写入Segment_1 Segment_2 Segment_3 ... Segment_100可能短时间存在。原因Merge 本身也消耗资源CPUIO磁盘空间如果疯狂merge写入性能会下降。所以 Lucene 会控制节奏。4. Merge 会影响查询吗会但是影响可控。例如正在查询Segment_1 Segment_2 Segment_3后台开始mergeSegment_1 Segment_2 生成 Segment_4查询不会被阻塞。因为Segment是不可变的。旧查询继续读Segment_1 Segment_2新查询可能读Segment_4 Segment_35. Merge过程会不会丢数据不会。因为流程旧Segment 读取数据 ↓ 生成新Segment ↓ 校验成功 ↓ 替换引用 ↓ 删除旧Segment不是先删除再生成。6. ES里面可以控制吗可以。例如查看merge情况GET _cat/segments查看segment数量大小手动触发ES提供POST index/_forcemerge例如POST my_index/_forcemerge?max_num_segments1强制合并。但是生产环境慎用。因为merge非常耗资源。KD TreeK-Dimensional TreeK维树是一种用于多维空间数据检索的数据结构主要解决在多个维度的数据中如何快速查找附近的点、范围内的点。它和你前面问的BKD Tree有直接关系BKD Tree 可以理解为 KD Tree 面向磁盘、大数据场景的工程化改进版本。1. KD Tree解决什么问题假设有二维数据城市坐标 北京 (116,39) 上海 (121,31) 广州 (113,23) 深圳 (114,22)如果我要查询距离点 (115,30) 最近的城市暴力遍历所有点北京 上海 广州 深圳计算距离。复杂度O(N)数据多了很慢。KD Tree把空间组织起来。查询不用访问所有点。2. KD Tree的核心思想一句话不断选择一个维度把空间一分为二。例如二维维度X轴 Y轴假设数据y | A | | -----------x | |第一次按照 X 轴切x 100 x 100分左右。然后下一层按照 Y 轴切。再下一层继续 X。所以维度轮流切。3. KD Tree结构长什么样例如二维数据(3,6) (17,15) (13,15) (6,12) (9,1)构建第一层按 X(9,1) / \ x9 x9第二层按 Y(9,1) / \ (6,12) (17,15)类似二叉树。4. KD Tree查询怎么做假设找距离(10,10)最近的点先走因为当前节点(9,1)比较x10 9所以优先走右边。但是不能只走右边。为什么因为左边可能也有更近的点。所以需要判断另一边有没有可能更优。这就是剪枝Pruning核心如果另一边空间距离已经不可能更近直接舍弃。否则继续搜索。5. KD Tree为什么适合多维因为它利用空间划分。例如二维全部空间 ---------------- 左半区 | 右半区 继续切 上 下最终每个节点代表一个空间区域。查询时排除大量区域。6. KD Tree的问题KD Tree 理论很好。但是实际大规模数据有问题。BKD树 就是非点状的而是一个点存很多数据为什么KD树要做成二叉的这样查询效率应该比多叉的慢吧这是一个非常好的问题。你的直觉是对的如果只从“树的高度”来看二叉树确实比多叉树更深单次查找路径更长。但是 KD Tree 选择二叉不是因为二叉一定最快而是因为它解决的是多维空间划分问题二叉划分更符合它的数学和工程需求。ES 查询快 ≠ BKD Tree 比 BTree 快。实际上ES 快的原因绝大部分不是 BKD Tree而是倒排索引Inverted Index。BKD Tree 只是负责数值、日期、地理位置等字段的查询。如果按照对 ES 查询性能的贡献排序大概可以理解成倒排索引70%~80% ↓ Lucene Segment 不可变设计 ↓ 缓存Query Cache、Request Cache、OS Page Cache ↓ 分布式并行查询多个 Shard ↓ BKD Tree数值/范围查询所以很多人学 ES一直在研究 BKD Tree其实真正让 ES 牛的是倒排索引。全文检索能力真正强的是 Lucene而不是 Elasticsearch。ES 的全文检索能力本质上就是 Lucene 提供的。Lucene 解决的是如何搜索ES 解决的是如何在生产环境大规模使用搜索。举个例子假设你只有一台机器。100 万篇文档。用 LuceneIndexWriter ↓ 建立倒排索引 ↓ IndexSearcher ↓ 查询查询速度和 ES几乎没有区别。为什么因为ES 底层最后也是IndexSearcher.search(...)也是 Lucene 在查。那为什么大家不用 Lucene而用 ES因为 Lucene 太底层了。Lucene 更像JDBCES 更像MyBatis或者Netty和Spring Boot不是一个层级。Lucene 只有搜索库你自己要负责建立索引 打开索引 关闭索引 更新索引 Merge Commit Searcher 缓存 线程池全部自己写。例如Directory directory FSDirectory.open(...); IndexWriter writer ... writer.addDocument(...) writer.commit() DirectoryReader.open(...)都是代码。