260401笔记
重定向和请求转发
重定向是服务器返回 3xx 让浏览器重新访问新 URL(地址栏会改变,需二次请求),而请求转发只在同一服务器内部把同一 request 转给其他资源处理(一次请求,地址栏不变)。
Elasticsearch相关
Elasticsearch:擅长“模糊”和“相关性”搜索。例如:搜索“苹果手机”,它不仅能找到包含“苹果”和“手机”的商品,还能通过算法智能地判断出“iPhone”、“iOS设备”的相关性,并按照相关度高低返回结果。这是因为它底层使用了倒排索引 这种神奇的数据结构。
倒排索引(Inverted Index)
一句话:把“文档→关键词”反过来,变成“关键词→文档”,用来做快速搜索。
1. 它是什么?
正常存储是:
- 文档1:我喜欢苹果
- 文档2:苹果很好吃
倒排索引就是反过来:
- 苹果 → 文档1、文档2
- 喜欢 → 文档1
- 好吃 → 文档2
这样搜“苹果”,直接就能拿到所有包含它的文档,不用全文扫一遍。
2. 核心结构
一般包含两部分:
- 词典(Term Dictionary)
所有关键词的集合:苹果、喜欢、好吃…… - 倒排表(Posting List)
每个词对应的文档ID列表,还会存:- 出现位置
- 出现次数
- 文档权重(用于排序)
3. 为什么要用它?
- 搜索极快:不用遍历全文,直接查词→取文档
- 支持多词检索:苹果 AND 好吃 → 取交集
- 支持排序:按词频、TF-IDF 打分排结果
4. 简单类比
就像书后面的索引页:
- 不是一页页翻找“苹果”
- 而是看索引:苹果 → 第10、25、30页
5. 典型应用
- Elasticsearch / Lucene 核心
- 搜索引擎(百度、Google)
- 数据库全文索引
- 日志检索系统
elasticsearch中text在检索时会分词,而keyword不会执行分词
DocValues 是 Elasticsearch(基于 Lucene)核心的列式存储正排索引,专为解决排序、聚合、脚本访问字段值等分析型查询性能而生。
一、为什么需要 DocValues?
ES 默认核心是倒排索引(Term → Doc):
- 优势:全文检索极快(查词找文档)
- 劣势:分析操作极慢(按文档取值、排序、分组)
DocValues 就是为了弥补这个缺陷:
- 结构:文档 → 字段值(正排/列式存储)
- 作用:让 ES 能快速按文档ID读取字段值,支撑排序、聚合
二、核心原理与特性
1. 存储结构
- 列式存储:同一字段所有值连续存放
- 索引时构建:写入文档时同步生成,不可变
- 磁盘持久化:存在磁盘,靠 OS PageCache 自动缓存
- 不占 JVM 堆:避免 OOM、GC 压力
2. 与倒排索引对比
| 特性 | 倒排索引 (Inverted Index) | DocValues |
|---|---|---|
| 映射关系 | 词项 → 文档ID | 文档ID → 字段值 |
| 擅长 | 全文搜索、过滤 | 排序、聚合、脚本取值 |
| 存储 | 行式、词频为主 | 列式、完整原值 |
| 内存 | 堆内 | 堆外(OS Cache) |
3. 支持的字段类型
- ✅ 默认开启:
keyword,numeric,date,boolean,ip,geo_point等 - ❌ 不支持:
text/annotated_text(分词字段)
三、主要应用场景
DocValues 是以下功能的性能基石:
- 排序 (Sort)
1
2# 按价格降序:直接读取 price 列
"sort": [{ "price": { "order": "desc" } }] - 聚合 (Aggregation)
terms,sum,avg,min/max,stats,percentiles等
- 脚本访问字段值
1
2
3"script": {
"source": "doc['price'].value * 0.8"
} - 部分过滤与查询优化
四、默认行为与配置
1. 默认状态
- 所有可分析字段默认
doc_values: true - 代价:额外磁盘占用(约 10%~25%)、轻微索引变慢
2. 禁用 DocValues(节省空间)
只用于全文检索、永不排序/聚合的字段可关闭:
1 | "mappings": { |
- 后果:无法排序、聚合、脚本访问此字段
3. Doc-Value-Only 字段
只存 DocValues、不建倒排索引(节省空间):
1 | "log_time": { |
五、DocValues vs FieldData(历史对比)
早期 ES 用 FieldData(纯内存),现已被 DocValues 全面替代:
| 特性 | FieldData(旧) | DocValues(现) |
|---|---|---|
| 构建时机 | 查询时(Query Time) | 索引时(Index Time) |
| 存储位置 | JVM 堆内存 | 磁盘 + OS Cache |
| 内存风险 | 极易 OOM、GC 卡顿 | 稳定、无堆压力 |
| 冷启动 | 首次查询极慢 | 无感知、直接读盘 |
| 适用 | 已废弃 | 现代 ES 默认标准 |
六、性能与优化要点
- 空间换时间:默认开启最稳妥
- 只在确定不需要时关闭:日志类只检索不聚合的字段可关
- 高基数字段(如 UUID):DocValues 占用较大,谨慎开启
- OS 内存充足:DocValues 会被自动缓存,性能接近纯内存
- 避免在 text 上聚合:必须用
.keyword子字段
总结
DocValues = ES 的分析引擎心脏
- 列式正排索引,解决倒排索引在排序、聚合上的短板
- 索引时构建、磁盘存储、OS 缓存:稳定、高效、不占 JVM 堆
- 默认开启:绝大多数场景保持默认即可
- 优化原则:不需要排序/聚合就关闭,否则保持开启
停顿词过滤是文本分析过程中的一个步骤,它会移除像“的”、“了”、“a”、“the”、“and”这类极其常见但本身缺乏实际含义的词语,目的是为了提升搜索效率、减少索引体积并改善搜索结果的相关性。
Elasticsearch不支持传统数据库(如MySQL)中的ACID事务
它仅能保证单个文档操作的原子性,无法保证跨多个文档操作的原子性、一致性、隔离性和持久性。
Elasticsearch (ES) 的设计目标主要是为了分布式搜索和数据分析,而非事务性数据处理。因此,它在事务支持方面有明确的限制:
🔍 单个文档的原子性:ES确实保证对单个文档的创建、更新或删除操作是原子的。这意味着一个文档的写操作要么完全成功,要么完全失败,不会出现中间状态。这是通过其版本控制机制(早期使用_version,后来改用if_seq_no和if_primary_term)实现的。
⚠️ 不支持多文档事务:ES不支持跨多个文档或跨多个索引的ACID事务。无法保证一批对多个文档的操作要么全部成功,要么全部失败。如果你需要同时更新多个文档,并且要求严格的原子性,ES本身无法直接满足。常见的批量操作API(如_bulk)虽然能减少网络开销,但即使其中部分操作失败,已成功的操作也不会回滚。
Elasticsearch通过不修改旧数据,而是增加新数据的方式来实现更新,具体是利用新增Segment(段) 和 标记删除(.del) 机制。
不变性(Immutable)
这就像用笔记本记账:写错时不会用橡皮擦掉,而是直接划掉旧账(标记删除),在最新一页写上更正后的新账(写入新Segment)。
- 为什么不变性(Immutable)是优势?
倒排索引一旦创建就不可修改,这带来了两大好处:
无需锁机制:读取操作永远不需要等待写入操作,也不会被锁阻塞,极大地提升了并发性能。
缓存友好:系统可以放心地将Segment缓存到内存中,因为知道它永远不会改变,缓存命中率极高。
2. 更新的具体步骤:
当你需要更新一个文档的内容时(比如修改某个商品的价格),Elasticsearch并不会去原来的倒排索引里查找并修改这个词条。它会按顺序执行以下操作:
触发新写入:将整个更新后的文档作为一个新文档,进行一次新的写入操作。
存入新Segment:这个新文档会进入内存缓冲区,随后通过Refresh操作,被写入一个全新的Segment中。这个新Segment立刻就可被搜索。
标记旧文档失效:在原有的、包含旧版本文档的Segment中,打上一个“已删除”(.del)的标记。查询时,系统会知道这个版本的文档已经无效。
3. 删除如何工作?
删除其实就是一种特殊的更新。ES不会立即从物理上删除数据,而是在其所在Segment的.del文件中将其标记为已删除。在后续搜索中,它就会被过滤掉。
- 清理机制:Segment合并(Merge)
随着更新和删除操作越来越多,会产生大量带有删除标记的旧Segment,导致存储效率下降和搜索变慢(需要查询的Segment太多)。
ES会在后台自动执行Merge任务。
这个过程会将多个小的Segment(包括那些有删除标记的)合并成一个新的、更大的Segment。
在合并时,那些被标记为删除的文档才会被真正地从物理上丢弃,从而释放磁盘空间。
总结一下:
不变性通过“只增不改”的策略来维护。更新 = 写入新数据 + 标记旧数据失效。后台的合并操作(Merge)是最终清理磁盘空间、保持系统高效运行的关键。
Elasticsearch 中的字典树(Trie)是一种用于高效处理字符串检索的树形数据结构,特别擅长前缀匹配和快速查找,它是实现自动补全(Auto-complete)等搜索建议功能的核心基石。
Refresh(刷新)
Refresh(刷新)是Elasticsearch将内存中的数据生成一个新的、可被搜索的段(Segment) 的过程,这是其实现近实时搜索的核心机制。
详细解析
你可以把Refresh理解为一个给新数据“贴标签”并“上架”的动作,让它能被顾客(搜索请求)找到。
- 核心流程:
当你写入一个新文档时,它首先被存放到内存缓冲区(In-memory buffer)里。
默认情况下,Elasticsearch每秒会执行一次Refresh操作。这个操作会:
将内存缓冲区里的所有数据清空。
将这些数据写入到文件系统缓存(Filesystem cache)中,形成一个新的段(Segment)。
这个新的段一旦生成,其中的文档就立刻可以被搜索到了。这就是为什么Elasticsearch的搜索是“近实时”(Near Real-Time)的——从写入到可搜索,仅有约1秒的延迟。
2. 为什么需要这个机制?
性能考量:如果每次写入一条数据都直接写到磁盘才能搜索,速度会极其缓慢,I/O开销巨大。而写到文件系统缓存(内存)则非常快。
平衡实时性与吞吐量:每秒一次的频率,在数据可搜索的“实时性”和系统的“写入吞吐量”之间做了一个很好的平衡。频率太高会产生大量小段,影响合并和搜索性能;频率太低则数据延迟太久。
3. 重要注意事项:
并非持久化:Refresh只是让数据变得可搜索,但并没有保证数据已经安全持久化到磁盘。数据在Refresh后位于文件系统缓存中,如果此时服务器断电,这部分数据仍然会丢失。
持久化由Flush负责:保证数据安全落地到磁盘,是另一个叫 Flush 的机制负责的,它依赖事务日志(Translog)并且默认30分钟执行一次或在Translog达到一定大小时触发。
4. 如何手动控制?
正因为Refresh有性能开销,在一些特定场景下我们可以调整它:
手动刷新:POST /my_index/_refresh。在测试或特定需要立即让数据可搜的场景下使用。
暂时关闭自动刷新:在需要大批量导入数据时,可以暂时将索引的refresh_interval设置为-1,导入完成后再恢复,可以极大提升写入速度。
海量大文件写入
在Elasticsearch中
使用批量(Bulk)API,并结合调整刷新频率、合理分片等策略,来实现海量数据的高效写入。
详细解析
海量数据写入Elasticsearch的核心思路是减少请求次数、降低磁盘I/O压力、充分利用集群资源。具体方法如下:
使用Bulk API:这是最重要的手段。不要单条写入,而是将数百或数千条请求组合成一个大的Bulk请求一次性提交。这极大地减少了网络往返开销和创建索引的开销。
调整刷新间隔(Refresh Interval):默认情况下,Elasticsearch每秒刷新一次索引(refresh),使新数据对搜索可见。但这个操作很耗资源。对于海量写入场景,可以暂时将refresh_interval设置为-1(在写入期间完全禁用刷新),或在写入期间设置为一个较大的值(如30s)。写入完成后恢复原设置。
禁用刷新器(在Bulk时):在进行大型批量导入时,可以在Bulk请求中直接设置”refresh”: “false”,避免每次请求后都触发刷新。
合理设置分片数(Number of Shards):分片是数据存储和并行化的基本单位。分片数量需要根据数据总量和硬件资源提前规划好。
分片过少:无法利用所有节点资源,无法实现水平扩展,单个分片过大影响性能。
分片过多:会增加集群元数据管理的开销,影响性能。分片数建议与集群的节点数成倍数关系,并预留未来扩展的空间。
避免巨大的单个文档:单个文档过大(比如超过10MB)会严重影响索引和网络传输性能。如果可能,将大文档拆分为逻辑上更小的文档。
使用官方客户端:使用Elasticsearch官方的Java、Python等高级客户端,它们内置了Bulk处理器,能自动积攒请求、按数量或大小批量发送,并提供了重试机制,非常方便。
监控和错误处理:海量写入时,部分请求失败是常见的。务必对Bulk API的响应结果进行解析,处理失败的项目(例如记录日志并重试),而不是简单地忽略。
地理索引
Elasticsearch 使用 GeoHash 或 BKD 树将二维的地理坐标编码成一维的字符串或数字进行存储,通过这种特殊的空间索引结构来实现高效的地理位置查询。
详细解析如下:
想象一下给整个地球铺上一张巨大的网格纸,ES 的地理索引核心就是如何用一套巧妙的编码给每个网格命名:
- 核心索引结构:BKD Tree
ES 底层实际使用的是 BKD 树 (Block KD-Tree),这是一种专门为多维数据(包括地理坐标这样的二维数据)设计的高效磁盘索引结构
它就像是给地图做了一套精密的“经纬度象限编码”,把二维的坐标点转化为一棵高效的二叉树进行存储和检索
相比于传统的倒排索引处理文本,BKD 树特别适合处理数值范围查询,包括地理坐标的范围
2. 地理编码的魔法:GeoHash(早期原理)
虽然底层是 BKD,但理解 GeoHash 能帮你明白原理(现在默认用 BKD,但 GeoHash 仍可选)
编码过程:把一个坐标(如[39.9, 116.4])通过特定算法转换成一串类似“wx4g0”的字符串
巧妙之处:这个字符串的前缀表示一个大的区域,字符串越长,表示的区域越精确。拥有相同前缀的GeoHash码,意味着它们在地理位置上是相邻的!
3. 索引构建过程:
当一个地理点(如{“location”: [116.4, 39.9]})被索引时:
坐标处理:ES 识别geo_point类型字段
内部编码:默认通过 BKD 树结构,将该二维点坐标转化为一种适于高效查询的内部格式(本质上是将二维问题通过空间填充曲线转化为一维问题)
存储入列:与其他字段一样,被压缩存储到索引结构中
4. 如何支持高效查询?
正是基于这种索引,ES才能实现令人惊叹的地理查询:
地理边界框查询(Bounding Box):快速找出落在某个矩形区域内的点
地理距离查询(Distance):“找出我周围1公里内的所有奶茶店” – 基于索引快速筛选出大致区域内的点,再精确计算距离
地理多边形查询(Polygon):即使复杂的多边形区域,也能利用索引高效过滤
5. 性能优化技巧
预处理:有时在索引前先计算好GeoHash前缀作为过滤条件
精度控制:根据业务需求选择合适的地理精度,不需要厘米级的可以降低精度提升性能
结合其他索引:如将地理索引和传统倒排索引结合,实现“附近的中餐馆”这种复合查询
ES 处理地图的本质是用数学的智慧把二维空间压扁到一维来索引,再通过高效的树形结构快速筛选候选集,最后进行精确计算。这让我们能用近乎实时的方式查附近的人、叫外卖、分析区域数据,背后全是索引的功劳。




