
搞 ElasticSearch 也有几年了从最早的 2.x 一路用到 7.x、8.x中间踩过的坑比头发还多。很多人一上来就急着部署集群、调参数结果连索引和分片的关系都没搞清出了问题只能靠玄学重启。这个系列第一篇我就把 ES 的基本概念和集群内部原理掰开揉碎讲清楚给后面所有实战操作打底子。别急着敲命令先把三个基本概念焊死从检索到近实时ES 到底解决什么问题ElasticSearch 本质上是基于 Lucene 构建的分布式全文检索引擎最擅长的是从海量文本里快速找到你想要的那几条数据。它对外提供 RESTful API内部把 Lucene 的复杂性封装起来让你不用关心底层怎么分词、怎么压缩倒排索引只要往一个叫索引的地方丢 JSON 文档然后就能用一句 JSON Query DSL 查出来。我用一句大白话概括它的定位这是一个拿时间换空间、拿空间换时间都做得比较极端的搜索系统。所谓近实时Near Real-Time指的是你写入一条数据之后大约 1 秒左右就能被搜索到而不是立刻、也不是等几分钟。这 1 秒的窗口来自 Lucene 的 refresh 机制后面会细讲。适合用它的人很明确你需要全文搜索、你需要对千万级甚至亿级数据做聚合分析、或者你的业务查询条件多到关系型数据库索引撑不住。不适合用它的人也很多强事务、强一致性、复杂关联查询这种场景选 ES 基本是给自己挖坑。索引、文档、映射、分词这四个词能串起一整台机器ES 里最小的数据单元是文档Document用 JSON 表示。一个文档就像数据库表里的一行但它不要求所有文档都拥有完全相同的字段这是和关系型数据库很大的区别。文档被放进索引Index里索引是拥有相似特征的文档集合类似于数据库里的库或表的概念。映射Mapping定义了一个索引里每个字段的类型、分词器、是否索引等元信息。你写入文档时如果索引还不存在ES 会根据内容自动推断映射但这个自动推断常常不理想。比如一个字符串字段如果值长得像数字ES 可能把它映射成 long 类型后续你想做全文搜索就傻眼了。分词Analysis是全文检索的灵魂。一段文本进来后会经过字符过滤、分词器、Token Filter 三个步骤处理成一个个词项。比如ElasticSearch教程经过标准分词器会被拆成 elasticsearch 和 教程。分词器选错了查询结果就会出现明明有这条数据却搜不出来的灵异现象。选分词器这件事中文场景尤其要慎重IK、SmartCN、ICU 各有各的脾气。type 被干掉之后概念更简单了老版本 ES 里有 type 的概念一个索引下可以分多个 type类似一张表下的多个逻辑分组。后来官方发现 type 的存在让映射变得极其混乱一个索引里不同类型同名字段冲突时处理成本太高于是在 6.x 开始限制单索引单 type7.x 直接移除了 type8.x 里已经彻底看不到它了。这个变化带来的实际影响是你以前用index/type/id三段式定位文档现在变成index/_doc/id两段式。结构更清晰索引独立性更强索引之间的字段互不影响。对新手来说这其实是好事少背一个概念多省一堆坑。我在给团队做培训时经常说一句话把 ES 的索引当成数据库实例而不是表因为一个索引里的数据通常对应一个完整业务模块。你从 MySQL 迁移数据到 ES 的时候一张表往往对应一个索引而不会把整库塞进一个索引再靠 type 区分。这个心智一旦建立后面设计集群时就不会把分片数量搞乱。倒排索引ES 快的原因和查询慢的根源倒排索引怎么一回事很多人第一次听到倒排两个字就发怵其实它特别朴素。正向索引是文档到词项的映射你有一堆文档想知道哪些词在哪些文档里得逐个文档扫描。倒排索引反过来是词项到文档的映射先分词再记录每个词出现在哪些文档的哪个位置。打个比方你手上有 100 本书正向索引就是每本书的最后附一个生词表倒排索引则是把所有书里的生词汇总成一个大字典每个字词后面写着出现在第 3 本书的第 50 页、第 17 本书的第 8 页。你要搜分片这个词直接查大字典瞬间得到所有出现位置完全不用把 100 本书翻一遍。Lucene 的倒排索引还做了很多优化词项按字典序排列可二分查找、使用跳表加速多词合并、用位图存储命中集合。这也是为什么 ES 在千万级数据下的词条查询能维持在毫秒级。理解了这个机制你才能理解什么样子的查询能走索引什么样子的查询注定要全表扫描。为什么倒排索引不等于所有查询都快这里我要说一个很多教程不会讲明白的点倒排索引对等值查询和全文查询很友好但对范围查询、排序、聚合这些操作就没那么神了。Lucene 的解决方案是同时维护正排存储——通过 doc values 按列存储排序/聚合需要的字段而不是从倒排索引里现算。所以你会发现ES 里一个字段如果被用于排序或聚合一定要显式开启 doc_values默认开启。如果这个字段既要做全文搜索又要做排序ES 会同时维护倒排索引和 doc values 两份数据。磁盘占用翻倍但这笔买卖划算因为查询性能天差地别。同样的逻辑可以解释另一个常见问题为什么通配符查询、正则查询这种看起来像搜索的操作特别慢。因为它们没法高效利用倒排索引的词项字典结构本质上退化成遍历。在 ES 里写wildcard查询数据量大时能把 CPU 打满这个仗我打过好几回。能不用的正则坚决不用非用不可时加上前缀限定。分析器与文档修改理解 ES 的 immutable segmentLucene 的底层索引由多个不可变的分段segment组成段一旦生成就不能更改。文档的更新和删除不会直接改原段而是写入新段并打上删除标记后台再通过 merge 机制把小段合并成大段顺便物理清除标记。这套设计带来的结果有喜有忧。喜的是段不可变让并发读非常安全也不需要复杂的锁机制Lucene 才能做到高并发下依然稳定忧的是你在 ES 里更新一条文档底层其实是先标记旧的已删再写一条新的中间会有短暂的资源浪费。频繁更新文档的场景下segment merge 的压力特别大。refresh 也是围绕 segment 设计的。数据写入内存 buffer 后每隔 1 秒默认触发一次 refresh把 buffer 里的数据生成一个新的 segment这才让数据变得可搜索。有些读者问为什么刚写入就查询查不到十有八九是手动关闭了 refresh或者用 bulk 大批量写入后没等 refresh 就去查。ES 还提供了refreshtrue强制刷新参数但生产环境慎用写多读少时这参数能把性能打崩。集群内部原理节点、分片与路由节点角色分工别把所有鸡蛋放一个篮子里一个 ES 集群由多个节点Node组成节点是运行中的 ES 实例。节点可以按职责划分角色master 节点负责集群元数据管理和全局状态维护data 节点负责真正存储数据和执行搜索写入ingest 节点负责数据预处理管道coordinating 节点负责接收请求并路由到对应数据节点。小集群里一个节点可以同时承担多个角色比如单机部署时一个节点既是 master 也是 data。但随着集群规模变大、业务流量上来你最好把职责拆开独立的 master 节点避免因数据节点 GC 停顿导致集群失联独立的 coordinating 节点专注于请求分发防止数据和协调互相拖累。我自己踩过一个大坑曾经为了省钱让一个节点同时当 master 和 data结果某天磁盘 IO 被打满master 选举和元数据更新全部超时整个集群脑裂。从那以后我给自己定了两条铁律生产环境至少三台独立 master 候选节点data 节点和 master 职责必须分离。省的那点机器钱还不够付一次故障的加班费。分片数量为什么建索引时就要定死索引的数据分布在分片Shard上。分片是 Lucene 索引的容器ES 把一份索引拆成多个分片分散到不同节点存储。分片数量在创建索引时指定后期只能通过 split 或 shrink API 调整操作成本和风险都不低。所以这个数字必须在建索引前想清楚。为什么不能随心所欲改分片因为 ES 的文档路由规则是shard hash(routing) % number_of_primary_shards。routing 默认是文档 ID一旦分片数量变化取模基数变了之前所有数据的路由结果全部失效ES 只能重新分布全部数据。这不是改个配置就完事的小操作。分片数量该怎么定经验公式是单分片数据量控制在 30GB 到 50GB 以内比较舒服分片总数控制在节点数的 3 倍以内。但更重要的是估算你的数据增长速度和查询模式——与其精确计算不如预留 20% 余量。我这里不是让你照抄公式而是让你明白分片设计是容量规划和查询性能的综合权衡。一条写入请求到底走过了几个节点你往 ES 发一条写入请求它不是直接落到数据节点上的。以生产环境常见配置为例请求先打到 coordinating 节点coordinating 节点根据文档 ID 算哈希把请求转发给对应分片所在的主分片节点主分片写完后将操作转发给所有副本分片所有副本写成功后才向客户端返回成功。这中间的细节值得背下来。ES 的写入流程依次经过协调节点路由、主分片写入 translog、写入内存 buffer、刷写 segment、副本同步。默认情况下主分片只需要等副本写入完成就返回不是等 refresh 完成。这也是为什么写入延迟低而搜索有 1 秒延迟的原因。translog 是个经常被忽略但极其重要的设计。它相当于数据库的 WALWrite-Ahead Log所有写操作先落 translog 才会写内存 buffer节点宕机重启时靠 translog 恢复未刷盘的数据。生产环境如果对数据可靠性要求高可以把index.translog.durability设为request让每次写请求都等待 translog 刷盘代价是写入性能大幅下降。读写性能和数据安全之间永远得做取舍。主节点选举与故障转移集群怎么活下来ES 的集群发现机制叫 Zen Discovery8.x 之后默认实现改为基于协调者coordinator的 cluster formation但核心思路一致候选节点之间通过 ping 探测彼此共同维护一个集群状态版本号版本号最新的节点有机会被选成主节点。主节点不直接参与数据读写它负责分配分片、维护集群状态、处理节点上下线。一旦主节点宕机剩余候选节点会发起新一轮选举票数过半的节点成为新主节点。这里有个关键点为什么建议奇数个 master 候选节点因为 ES 需要避免脑裂一个节点必须得到超过半数的投票才能成为主节点。如果只有两个候选节点挂了一个剩下那个永远无法获得过半数选票集群就不可用了。三节点挂一个剩两个还能选出主可用性更高。故障转移是分片自动恢复的过程某个 data 节点宕机后主节点会检测到它的分片丢失在其它节点上利用冗余副本重建丢失的分片。如果宕机的是主分片所在节点副本分片会自动提升为新的主分片。这个过程不是瞬间的数据量越大恢复越慢。所以集群设计时要考虑哪些节点故障会影响整个集群——比如所有副本恰好都跟主分片在同一个节点上这个节点挂了数据就彻底没了。从部署看集群安装、启动、配置和常见坑Windows 启动 ES哪些配置必须提前改搜索热词里有windows启动elasticsearch我猜很多人是在 Windows 上第一次接触 ES。Windows 下安装 ES 其实很简单去官网下载对应版本的 zip 包解压后运行bin/elasticsearch.bat就行。但这里有一堆默认配置会让你踩坑。首先要改的是config/elasticsearch.yml里的cluster.name和node.name。ES 默认集群名是elasticsearch如果你电脑上跑过其它 ES 实例没关干净两个实例会意外加入同一个集群分片分配错乱数据直接乱套。我给所有初学者的建议是每个环境都用不同的集群名宁可多写配置文件也不要依赖默认值。其次是 JVM 堆内存。ES 的默认堆内存是 1GB对稍微像样的数据量肯定不够。修改config/jvm.options里的-Xms和-Xmx官方建议不超过物理内存的 50%且最大值不超过 32GB。为什么是 32GB因为 JVM 在堆超过 32GB 时会切换成压缩指针失效的内存模型明明内存变多了性能反而下降。Windows 上还有一个容易忽略的点ES 进程对文件句柄和线程数的要求很高Windows 默认限制比较严生产环境部署在 Windows 上需要调系统参数。如果仅仅是本地学习倒不用太纠结跑单机模式就行。单机多节点模拟集群的配置参考很多人想学集群知识却没有多台机器我推荐直接在单机上启动多个 ES 实例来模拟集群。启动前做好三件事就能避免绝大多数冲突修改node.name让每个实例不同修改http.port和transport.port避免端口冲突修改path.data和path.logs让数据和日志分开。分享一个我常用的本地三节点配置思路三个节点的cluster.name相同node.name分别为node-1、node-2、node-3http.port分别为 9200、9201、9202transport.port分别为 9300、9301、9302。启动完成后通过http://localhost:9200/_cat/nodes就能看到三个节点都加入了同一个集群。单机模拟集群最常见的坑是磁盘空间不足。三个节点的分片副本都落在同一台机器上数据膨胀非常快。建议给 node 配置node.max_local_storage_nodes或者干脆勤快一点学完就删数据别让零碎索引占了一整个磁盘。堆内存、磁盘和 ES 9.x 版本选择版本选择这件事我见过太多人纠结。ES 8.x 以后我个人的态度是新项目直接用 8.x 的最新稳定版不要再用 7.x。8.x 默认开启了安全认证虽然本地部署时有点烦需要生成密码、改 HTTPS但这套东西到了生产环境早晚要用。热搜词里出现的 9.4我还没在实际生产环境大规模验证过建议先观望等几个 patch 版本再上。磁盘选型上ES 是重度 IO 应用。机械硬盘跑 ES 的体验就是集群看起来活着一搜索就超时。SSD 基本是标配。如果条件允许把热数据和冷数据分节点存储热节点用 NVMe冷节点用 SATA SSD用索引生命周期管理ILM把旧索引自动迁移到冷节点。这套方案能省不少钱同时保证热查询的性能。关于 jvm.options我有一次血的教训某次把堆内存调到 31GB以为接近上限能获得最大性能结果集群频繁 Full GC。后来才发现是大对象分配和数据结构设计不合理堆越大 GC 停顿越恐怖。堆内存不是越大越好而是合适最好。先用默认配置跑起来再用压测数据观察 GC 状况逐步调整才是正确姿势。别把 ES 当关系型数据库也别当成 MongoDBES vs MongoDB vs MySQL定位对比有一个热搜词是MongoDB 和 ElasticSearch 框架对比这俩确实经常被放到一起讨论因为它们都存 JSON 文档。但核心定位完全不同MongoDB 是文档数据库强在灵活的数据模型和分布式事务ES 是检索引擎强在全文搜索和实时分析。MySQL 则是关系型数据库强在严格约束的事务一致性。画个直观的对比表维度MySQLMongoDBElasticSearch数据模型表结构文档型文档型事务支持强8.x 后支持多文档事务较弱全文搜索弱一般极强聚合分析一般一般极强典型场景业务主存储海量文档存储搜索与分析实际生产中我见过很多团队把 ES 当主数据库用结果死得很难看。ES 的更新是标记删除新写模式频繁更新会导致段合并压力巨大ES 也不适合做复杂事务操作它的定位是查询与分析的前端。更合理的架构是MySQL 存业务主数据通过同步工具把数据同步到 ES 建立索引查询走 ES回了主键 ID 再回 MySQL 取详情。这样各取所长。wiki.js 这类项目为什么要用 ES热词里提到wikijs 调试 elasticsearchwiki.js 是一个文档/Wiki 系统它可以用默认的数据库搜索但一旦文档量上来内置搜索的体验就很勉强。这时候很多人选择把 ES 接进去做全文搜索。wiki.js 的配置里会要求填 ES 的节点地址、索引名、认证信息配置完成后文档写入时会自动同步到 ES。这类项目用 ES 的动机很典型文档库的搜索场景要求对标题、正文、标签做全文匹配还要能处理同义词、近义词关系型数据库的 LIKE 查询根本扛不住。ES 接进去以后搜索响应时间从秒级降到几十毫秒搜索结果的关联性也好得多。但这个过程中的坑也不少。我调试过类似系统最常见的问题是版本不匹配——项目里内置的 ES 客户端版本和服务器上的 ES 版本差太多导致接口调用报错。这类集成项目一定要先确认客户端和服务端的版本兼容范围ES 8.x 的客户端连 7.x 服务端大概率有问题反之亦然。那些面试题式问题哪些真的和 ES 相关会话共享、订单过期、秒杀ES 在这些场景的位置热词里出现了一串高频面试题session 共享、订单过期、死锁、服务注册发现、秒杀、熔断降级。这些和 ES 有没有关系直观上没有直接关系但深入一层设计思路是相通的。拿 session 共享和死锁来说session 共享本质是让多个服务实例访问同一个会话状态通常在 Redis 里存 sessionES 也能存但不划算。死锁是并发控制的问题ES 的分片副本写入机制其实也在处理类似的竞争问题——它通过版本号和乐观锁控制并发更新避免了关系型数据库那种行锁死锁。如果你能理解 ES 的版本冲突机制理解数据库的死锁排查就有了一个很好的类比对象。订单过期这个场景很有意思。传统做法是定时任务扫表数据量大时要命。用 ES 可以做延时处理把订单过期时间作为字段写入索引定期用 range 查询捞出已过期订单处理。虽然不如专业的延迟队列高效但应付中小规模业务绰绰有余。不过我不建议在这种场景里硬塞 ES如果已经有 Redis 或者 RocketMQ它们做延迟队列更合适。秒杀和熔断降级就更不用说了。ES 在秒杀场景往往是被冲击的那一方——瞬间涌入的写入会把集群压垮。这时候你需要事先做好限流用 bulk 批量写入替代单条写入必要时牺牲一点实时性把 ES 降级成异步消费模型。这又回到了那个恒定的话题ES 是搜索引擎不是高并发写入数据库别把它的能力边界搞混。服务发现与 ES 集群发现的类比服务注册与发现如 Nacos、Eureka和 ES 的集群发现机制有相似之处但实现路径不同。服务发现解决的是服务实例动态上下线后调用方如何获知可用地址ES 的 master 选举解决的是多节点中谁说了算以及节点变化时元数据如何保持一致。ES 的节点通过 transport 端口互相通信各自维护一份集群状态视图。如果网络分区发生一部分节点看不到另一部分两边可能同时尝试选主这就是脑裂风险。解决办法就是之前提到的过半票机制——只有获得多数票的节点才能成为主节点少数派节点即使独立存活也无法继续提供服务。这套机制和分布式系统里的 Quorum 机制完全一脉相承理解了 ES 的脑裂防护再去学 ZooKeeper、etcd 的选主逻辑会轻松很多。从实战出发总结一点个人偏好最后分享一个这些年带团队总结出的观点学习 ES 别急着深入源码先把概念和原理图谱建起来。很多人遇到分片分配不均、集群变黄、查询变慢这些问题时毫无头绪根本原因不是操作不熟练而是不明白底层机制——不明白段不可变、不清楚路由规则、不知道主节点职责。原理上的盲区迟早会以故障的形式回来找你。我自己回头看最有价值的投入恰恰是把索引、分片、副本、节点角色、路由规则这几个基础概念吃透的时刻。那之后调参、排查、设计索引都变得有章可循。这个系列后面会继续写索引设计、查询调优、集群监控、数据同步这些实战内容但如果你还没把文章里这几个概念消化掉后面的内容会看得很吃力。建议你把本地环境搭起来创建一个三节点模拟集群把索引建起来、分片拆开、节点停一个看看集群怎么恢复。跑一遍比读十篇教程都管用。