ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Elasticsearch从倒排索引到分片副本:原理、架构与选型

Elasticsearch从倒排索引到分片副本:原理、架构与选型

简介:《Elasticsearch 演讲PPT》面向希望系统了解 Elasticsearch 的技术人员、架构师及团队内部分享者,围绕全文检索、实时搜索与数据分析场景,梳理从基础概念到应用实践的知识框架,适合初学者建立术语体系,也便于有经验的技术人员整理分享材料。演示内容从 Lucene 基础、分布式架构与 RESTful 接口讲起,依次覆盖集群、节点、索引、文档、分片和副本等核心概念,并延伸到字段数据类型、Mapping 映射、索引模板与别名等数据管理机制。讲解中还结合倒排索引原理、中文分词器选择、TF-IDF 打分,以及 ES 与 MySQL 在检索响应时间上的对比,帮助读者理解其性能优势与适用边界。资源包共 1 个 pptx 文件,约 3.21MB,结构紧凑,适合直接用于技术分享、培训课件或自学提纲;已有 223 人学习,可作为快速建立 ES 知识地图、准备演讲与团队交流的参考材料,对准备内部分享或培训课件的读者较友好。

1. 到底什么是 Elasticsearch:从「搜三个字,页面转到人事不省」说起

几乎每个做过查询功能的人都干过同一件事:接到「加个搜索框」的需求,第一反应是打开数据库写一句LIKE '%关键词%',按下回车,然后看着页面转圈。几万条数据时它还挺听话,几十万条开始喘,上百万条就足够让人起身去泡杯茶。

Elasticsearch(圈内一般直接叫 ES)就是来解决这个尴尬的。它是一个基于 Lucene 构建的开源分布式全文搜索引擎,用 Java 写成,对外用 RESTful 接口说话,能在多台机器上协同工作,做到近实时的存储、搜索与分析。通俗地说,它不是「一个更大的数据库」,而是一套把「找词」这件事做到极致的检索与分析平台——你喂给它一堆 JSON 文档,它回你毫秒级的答案。搞懂它,你才真正搞懂了今天大部分搜索框背后那台发动机。

2. 倒排索引凭什么这么快:把「逐本翻书」换成「先查目录」

一次查询从敲下回车到结果返回,中间隔着一段很短的旅程。这段旅程的长短,取决于一件很朴素的事:系统是「挨个翻」还是「查目录」。这一章就把这条链路拆开看,看完你会明白为什么同样是搜一个词,有的系统要三分钟,有的只要几十毫秒。

2.1 正排与倒排:同一批数据,两种查找姿势

假设手上有两篇文档:

  • 文档 1:某信息技术公司
  • 文档 2:某环境咨询公司

如果数据是按「文档 → 文档里有哪些词」的方式存的,这叫正排。要判断某篇文档里有没有「信息」这个词,只能把每篇文档的词表读一遍,读完再比对。数据量越大,要读的越多,耗时几乎是线性往上涨的。用数据库的模糊匹配去搜一个词,走的差不多就是这个路子:前面的%让索引失效,只能一条条扫。

倒排索引把关系颠倒过来,存的是「词 → 哪些文档里有它」。这时候再搜「信息」,系统不用碰任何一篇原文,直接去词典里找到「信息」这个词,取出它后面挂着的一串文档编号,答案就出来了。类比一下:一本厚厚的工具书,如果书末没有索引,你想找某个名词只能一页页翻;如果书末有名词索引,写着「某某 P123、P456」,翻两页就到位。倒排索引就是给整个文档集合配了一本这样的书末索引。

它通常由两部分构成:单词词典记录所有出现过的词,倒排列表记录每个词出现在哪些文档里、出现几次、在什么位置。搜索「信息」这个动作,在内部就是「查词典拿列表 → 多个词的列表求交集 → 按编号取回文档」。这里有个关键细节:倒排列表一般按文档编号有序存放,两个有序列表求交集,代价跟命中多少条相关,而跟整个集合有多少条几乎无关。所以在十亿级的数据里搜一个冷门词,依然是几十毫秒的事。

注意:这是一条简化过的链路。真实的实现里还有词典的压缩结构、跳表、列表压缩等一堆优化,但方向就是这个方向。

2.2 分词:倒排索引的第一道工序,也是中文搜索最容易翻车的地方

倒排索引的前提是「先有词」。而中文没有天然的空格分隔,一句话怎么切成词,直接决定了它能被怎么搜到。还是刚才那两篇文档,换两种切法,结果完全不同:

分词策略文档 1 切出来的词条文档 2 切出来的词条搜「信息」的命中
细粒度切分某 / 信息 / 技术 / 公司某 / 环境 / 咨询 / 公司命中文档 1
整串当成一个词某信息技术公司某环境咨询公司一条都不命中

第二种切法并不是瞎编,很多人配置分词的第一个跟头就栽在这:字段用错了类型(比如该用全文类型的地方用了精确类型),或者分词器压根没装,结果建索引时整句话被当成一个词条存进去,查询时再按词去搜,自然对不上。这就是那种「数据明明在,就是搜不出来」的玄学问题。

常见的中文分词器有 IK、ansj、jieba、hanLP 等,各有取舍。IK 生态最广,提供粗粒度和细粒度两种切法,适合大多数通用场景;ansj 在人名识别和新词识别上更下功夫,语料里专有名词、人名多的时候更有优势;其余几个也各有拥趸。选型没有标准答案,取决于你的数据长什么样——一份满是企业全称的名录,和一份满是用户昵称的评论,最优的切法本来就不同。

还有一个高频踩坑点:建索引时用的分词器和查询时用的分词器必须对得上。有的系统给字段配了细粒度分词建索引,查询时却用了粗粒度,或者反过来,结果就是「分词切出来的词条」和「查询拆出来的词条」永远碰不上面。字段级别的analyzer和search_analyzer就是干这个的,配错一个,前面所有的优化都白搭。

2.3 TF-IDF:命中了之后,凭什么它排第一

搜「信息」,可能一下命中八千篇文档。谁排前面?这就轮到打分算法上场了。

TF-IDF 是信息检索里最经典的一套思路,拆开看就两个量:

  • TF(词频):某个词在这篇文档里出现了多少次。出现得越多,说明这篇文档越可能就是在讲这个词。
  • IDF(逆文档频率):某个词在整个文档集合里有多罕见。越罕见的词,区分度越高,权重越大。

两者相乘,就是这个词对这篇文档的重要程度。举个直观的例子:「信息」这个词在某篇文档里出现了 5 次,在另一篇里一次没出现,前者的得分自然更高。但「公司」这个词如果在几乎每篇文档里都出现,它的 IDF 就会低得可怜——因为它区分不出任何东西。这也解释了为什么搜索系统几乎从不把「的」「是」「公司」这类高频词当回事,它们的信息量接近于零。

除了 TF-IDF,成熟的搜索系统还会叠加别的信号来决定顺序,比如基于链接关系的分析方法,用来判断一篇内容被多少其他地方引用、权威程度如何。如今的 ES 默认使用的是一种由 TF-IDF 改良而来的相似度算法(BM25),它对词频的增益做了饱和处理——一个词出现 10 次和出现 100 次,得分差距不会夸张到 10 倍,更符合人的直觉。但不管怎么改良,底层逻辑没变:先靠倒排索引找得快,再靠打分算法排得准,这是两件不同的事,很多人把它们混成一件事。

2.4 一次模糊匹配的性能对照:为什么数据量越大,差距越夸张

原始演讲资料里给过一组对照,测的是模糊匹配的响应时间,只列趋势,不必纠结具体数字背后的机器配置:

数据规模传统数据库模糊查询ES 查询
10 万条0.2 秒0.008 秒
100 万条2.2 秒0.013 秒
1000 万条193.1 秒0.028 秒
1 亿条未能完成测试0.066 秒
10 亿条——0.1 秒以内

看这张表要抓的不是「快了多少倍」,而是两条曲线的形状。传统数据库那条是往右上角猛蹿的,因为模糊匹配让索引失效,数据翻十倍,扫描量就翻十倍;ES 那条几乎是贴着地面的一条平线,因为查询代价主要取决于「命中多少个文档」,而不是「总共有多少个文档」。数据越多,这个差距越离谱——百万级还能忍,千万级就是几分钟和几十毫秒的区别。

但也别只盯着读的那一列。ES 为了这份读性能,付出了写入更重、存储更多、运维更复杂的代价。它把一份数据切开放到多台机器上,还要额外存副本,这些都不是免费的。看到「快 7000 倍」就冲动地把主库换成 ES,是另一个更贵的坑,后面会专门讲。

3. 集群、节点、索引、分片、副本:一套「图书馆」式的组织架构

搞懂了「快在哪」,接下来要搞懂「怎么摆」。ES 的术语听起来唬人,其实全都可以映射到一个图书馆的日常运作上:一个馆装不下就开分馆,一套书印成几册再各复印一份,读者多了就分流。理清这套骨架,你才知道建索引时那几个数字到底在调什么。

3.1 集群与节点:一个馆装不下,就开分馆

单台服务器跑一个 ES 进程,这叫一个节点,本身就能对外提供服务。但数据一多、或者要求「一台机器挂了服务不能停」,就得让多台机器连起来协同工作,这些相互合作的服务器集合叫集群。

集群里的节点分工不一样。有负责管理元数据的(比如索引建没建、分片放在哪台机器上),有专门存数据、扛查询的。类比一下:总馆长不负责搬书,他只决定哪一批书放哪个分馆、哪个书架;真正找书的是各馆的馆员。集群靠一个共同的集群名字来认亲,节点启动时发现彼此、加入同一个集群,这个过程是自动的。

这里有个容易被忽略的点:不是节点越多就越快。节点之间要通信,查询要分发到多个分片再汇总,协调的活儿本身有成本。三五台机器能扛住的量,硬堆到三十台,收益可能还不如把分片数调对。

3.2 索引与文档:它像表,但只是「像」

索引是一堆结构相似的文档的集合,很多资料直接说「相当于数据库里的表」;文档是能被索引的最小信息单元,一条 JSON 数据,相当于表里的一行。这个类比能帮你快速上手,但它的边界比想象中窄。

第一个差别:ES 没有强制的表结构约束。字段类型可以预先定义,也可以让它自动猜,猜出来的结果常常不是你想要的。第二个差别:文档是不可变的,所谓「更新一条文档」,底层是把旧的那条标记删除、再写一条新的进去。所以频繁修改同一条数据的场景,用 ES 会很别扭。第三个差别:索引之间基本没有 join 的概念,你没法像写 SQL 那样随手把两张表连起来查,需要提前把数据「拍平」成一份宽文档。

提示:索引名只能用英文小写,不能有大写字母,这条限制劝退过不少第一次建索引的人。

3.3 分片与副本:切开来算得快,复制一份才不怕丢

一个索引如果只存在一台机器的一个目录里,数据量一旦超过单机磁盘,就彻底没辙了。分片解决的就是这个问题:索引被水平切成若干份,每一份是一个独立的、完整的底层索引结构,可以放在不同机器上。查询时多个分片并行干活,最后把结果汇总。切开来,既是扩容,也是提速。

副本是分片的拷贝,一个分片可以有零个或多个副本。它的作用有两个:一是容错,某台机器挂了,副本顶上,数据不丢、服务不断;二是分担读请求,同一份数据有多个拷贝,读的人多时可以分流到不同拷贝上去。

类比一下:一套三册的百科全书,读者多的时候,你把它拆成三册分别放在三个阅览室,找起来更快(分片);再给每一册各复印一份放到别的馆,万一某个馆漏水,书还在,而且两个人同时要查同一册也不用排队(副本)。

这里藏着 ES 建索引时最贵的一个决定:主分片数在索引创建时就定死了,之后改不了。想改,只能新建一个索引再把数据搬过去。副本数倒是随时可以调。所以第一次建索引时按「未来两三年的数据量」来估主分片数,是必须做的一次功课;随手写个默认值,将来就是一场彻夜的数据迁移。

3.4 五个概念的层级关系

概念图书馆里的对应物一句话说明建好后能否改
集群整个图书馆系统多台服务器协同工作的集合名称固定,节点可增删
节点单个分馆集群里的一台服务器可随时加入或退出
索引某一类藏书的目录相似文档的集合可删可建,字段类型难改
分片一套书拆成的几册索引的水平切分,可分布到不同节点主分片数创建后不可改
副本每册书的复印件分片的拷贝,用于容错和分担读随时可调
文档一本书一条 JSON 数据可增删改,改即重建

4. 数据类型与 Mapping:为什么字段类型要在建索引前想清楚

前面讲的都是「怎么找得快」,这一章讲「怎么存才对」。字段类型选错,是那种当时毫无感觉、三个月后才爆发的错误——数据进了几千万条,才发现某个字段被识别成了文本,改类型要重建整个索引。这种后悔药,最好提前吃。

4.1 基础类型与复杂类型:ES 里到底有哪些「格子」

ES 的字段类型大致可以分成四类:

基础类型是主力。字符串分两种,这点特别重要:精确类型用于不分词的完整匹配(编号、状态、标签),全文类型用于需要分词的搜索(标题、正文、备注)。数值类型有一整套,从单字节的到双精度浮点的都有,还有一个专门的缩放浮点类型,用整数加缩放因子的方式存小数,适合金额这种需要精确计算的场景。此外还有日期、布尔、二进制,以及区间类型(比如表示「某个数值落在某个范围内」)。

复杂类型用来表达嵌套结构。数组类型其实不需要单独声明,任何字段默认都能存多个值;对象类型用于嵌套的 JSON 结构;嵌套类型则是为了解决对象数组在查询时的「跨对象误匹配」问题——这个坑很隐蔽,普通对象数组在底层会被拍平成一组平行字段,查「A 是 1 且 B 是 2」时可能命中一条 A 和 B 来自不同对象的记录。

特定类型是为专门场景准备的:地理坐标、IP 地址、自动补全、父子关系等。

选型时最常出错的是字符串的两种类型。记住一条判断标准:这个字段需要被「搜」还是被「精确匹配」。商品标题要搜,用全文;订单号要精确匹配,用精确类型。把订单号设成全文类型,搜「1001」时会连「10010」「21001」一起捞出来,业务方第二天就会来找你。

4.2 Mapping 映射:预先定义,还是让系统自己猜

Mapping 就是字段类型和相关属性的定义。建索引时你可以显式写上,也可以什么都不写,让系统看到第一条数据时自动推断。

自动推断听起来省事,但它是典型的前期舒服、后期要命。带前导零的编号(比如00123)很可能被当成文本,日期如果格式不常见也可能识别不对,字符串默认常常被同时映射成「可分词 + 精确」两个子字段,索引体积直接翻倍。更麻烦的是,一旦某个字段被推断成文本,你想按数值范围去筛,就没法用了。

显式 Mapping 的写法很直白:字段名、类型、要不要索引、用什么分词器。前面提到的分词器配置,也在这儿落地。要记住的是,大部分字段类型一旦确定就不能直接修改,唯一的正规路子是新建一个索引、把老数据重新灌一遍。所以在数据量还小的时候把 Mapping 定好,比什么都划算。

4.3 索引模板与索引别名:给未来的自己留一颗后悔药

索引模板是给「还没建出来的索引」准备的。你写一条规则,比如「凡是名字以 order- 开头的索引,都自动套用这套分片数、这套字段映射、这套分词器」,之后新建索引时不用再重复交代一遍。按时间分索引的场景(每月一个、每天一个)几乎是靠模板撑起来的,手写必错。

索引别名则是一层指向。别名可以指向一个索引,也可以指向多个索引,还可以带过滤条件。它最大的价值是让业务代码不认死某个具体的索引名。举例来说,你要重建索引,正常的操作是:建新索引 → 灌数据 → 把别名从老索引切到新索引 → 删老索引。整个过程业务代码一行没改,用户毫无感知。没有别名,这一步就得停机改配置。

4.4 一段能看懂的配置示例

下面这段是给一个订单类索引准备的模板,用命令行方式提交,注释写在每行前面:

# 注册一个索引模板:凡是名字以 order- 开头的索引,都自动套用下面的设置和映射 curl -X PUT "http://localhost:9200/_index_template/order_tpl" \ -H 'Content-Type: application/json' -d ' { "index_patterns": ["order-*"], "template": { "settings": { "number_of_shards": 3, "number_of_replicas": 1 }, "mappings": { "properties": { "order_no": { "type": "keyword" }, "remark": { "type": "text", "analyzer": "ik_max_word" }, "amount": { "type": "scaled_float", "scaling_factor": 100 }, "created_at": { "type": "date" } } } } }'

逐项说清楚:index_patterns决定这条模板管哪些索引,用的是通配符匹配;number_of_shards是主分片数,创建后不能改,要按未来的数据量估;number_of_replicas是每个分片的副本数,随时可调;order_no用了精确类型,因为它要的是「一字不差地匹配」;remark用了全文类型并指定中文分词器,因为它要的是「搜得到」;amount用了缩放浮点,把小数乘 100 存成整数,避免浮点误差;created_at声明成日期,这样范围查询和按时间排序才走得动。

接着给索引挂一个别名,让业务只认这一个名字:

# 把别名 order_search 指向当前的 order-2024 索引 # 将来重建索引时,只需要把这条 add 换成指向新索引,业务代码无需改动 curl -X POST "http://localhost:9200/_aliases" \ -H 'Content-Type: application/json' -d ' { "actions": [ { "add": { "index": "order-2024", "alias": "order_search" } } ] }'

两个要点:一是别名要指向「当前正在服务的那一个」,切换时先把新索引的数据备齐,再一次性把别名挪过去,中间不留空窗;二是如果用了带过滤条件的别名,同一份数据可以给不同业务看到不同的子集,这在多租户场景里非常好用。中文分词器需要先装好对应插件,否则上面那段会因为找不到分词器直接报错——这也是部署时最常见的第一个错误。

5. 六个最容易搞混的概念:索引不是表,分片也不是副本

学 ES 的时候,最大的障碍往往不是难,而是「几个词长得太像」。下面这几组,是我见过被问得最多、也最容易在方案评审会上吵起来的。

5.1 索引 vs 数据库表:像,但不能照着表的思路用

易混点:几乎所有入门材料都说「索引相当于表」,于是有人把原来那套表结构原封不动搬过来,一列一列照抄。

为什么混:这个类比在「存一类结构相似的数据」这件事上确实成立,理解到这个层面已经够用了,所以大家就停在这儿了。

怎么区分:表有强 schema、有事务、有外键、能 join;索引没有这些。索引里的一条文档是自包含的 JSON,你查一条订单,想把下单人的昵称也带出来,正确做法是在写入时就把昵称冗余进去,而不是查询时再去连另一张「表」。另外,索引的文档不可变,更新等于重建,这一点决定了它不适合高频修改的场景。把它当「表」来建模,早晚要在关联查询上撞墙。

5.2 分片 vs 副本:一个是「切开」,一个是「抄一份」

易混点:都跟「一份数据变成好几份」有关,看监控面板时一片数字,分不清哪个涨了会怎样。

为什么混:两者经常同时出现,一个索引有 3 个主分片、每个 1 个副本,界面上就是 6 个分片,看起来像同一件事的两个说法。

怎么区分:分片是把数据切开,每片装的是不同的数据,合起来才是全部;副本是把分片复制,每份装的是相同的数据。分片数影响的是「一份数据能被切成几块并行处理」,副本数影响的是「同一块数据有几个备份、能扛几次机器故障」。所以主分片数建完就改不了,副本数随时可调。机器加多了想提升吞吐,调的是副本;数据涨了装不下,那才轮到分片,而且只能重建。

5.3 Mapping vs 索引模板 vs 索引别名:三个都带「索引」,各管一段

易混点:三个词都跟索引有关,配置的时候常常被写在同一个文件里,看起来很像是同一层东西。

为什么混:它们确实经常一起出现,模板里就包含了 Mapping,模板里也能顺带定义别名。

怎么区分:Mapping 管的是「这个索引里的字段长什么样」;索引模板管的是「将来新建的索引自动长成什么样」;索引别名管的是「对外暴露的那个名字指向谁」。一个是字段级定义,一个是创建时的自动化规则,一个是查询时的间接层。出问题时看现象就能定位:字段类型不对是 Mapping 的事;新索引没套上配置是模板没匹配上;切了索引之后查询报错,多半是别名没挪。

5.4 精确类型 vs 全文类型:同一句话,两种存法

易混点:都是字符串,都存文本,看起来只是「类型名字不同」。

为什么混:很多字段两种都能存,建索引时不报错,所以没人深究,直到某天搜索行为变得莫名其妙。

怎么区分:精确类型不分词,整串原样存,适合编号、状态、标签、枚举值,查询时必须一模一样;全文类型会分词,切成一个个词条,适合标题、正文、备注,查询时按词去匹配。判断标准就一句:这个字段是给人「搜」的,还是给程序「对」的。给程序对的用了全文类型,会捞出一堆不该有的结果;给人搜的用了精确类型,用户少打一个字就搜不到。

5.5 搜索引擎 vs 数据库:不是替代,是分工

易混点:既然 ES 查询快这么多,那为什么还要数据库?直接把数据都塞进 ES 不就行了。

为什么混:前面那张性能对照表太有冲击力,容易让人产生「新的一定全面更好」的错觉。

怎么区分:数据库保证的是「这条数据一定在、一定对、改完立刻生效」,也就是事务和一致性;ES 保证的是「找得快、排得准、扛得住大并发读」。前者是账本,后者是目录。目录丢了可以重建,账本丢了就是事故。所以常见的架构是:数据库当唯一可信源,ES 当检索层,写入时同步过去,查询时从 ES 拿结果,需要强一致的操作用主键回数据库确认。把 ES 当主库用,是在拿目录当账本。

5.6 分词器 vs 相似度算法:一个决定「能不能搜到」,一个决定「谁排前面」

易混点:都在搜索链路上,出了问题都表现为「搜出来的结果不对」。

为什么混:用户反馈「搜不到」和「搜出来顺序怪」,在现象上离得很近。

怎么区分:分词器在写入和查询两端工作,决定的是词条切分,它出问题表现为完全搜不到或者搜出一堆不相关的;相似度算法只在打分阶段起作用,它出问题表现为该排前面的排后面了。排查顺序也该是这个顺序:先确认分词对得上,再去看打分。分词都没对上就调打分参数,纯属白费力气。

6. 什么时候该上,什么时候别硬上:一次取舍的复盘

说个虚构的场景。某团队做企业名录检索,数据量在千万级,用户在搜索框里输入一个公司名的片段,要求秒级返回。最初用的是数据库模糊匹配,百万级还能忍,到千万级直接几分钟起步,页面只能挂个转圈动画。换成 ES 之后,同样的查询稳定在几十毫秒。他们的数据有个特点:每天批量导入一次,白天只读不写,读远远多于写。这个特点,恰好是 ES 最舒服的姿势。

反过来,如果数据是一天改几十万次的订单状态,每次改动都要立刻对其他系统可见,还要保证「扣了库存就一定不能重复扣」,那就别把 ES 当主存储。它没有跨文档事务,更新是「删了重写」,近实时也意味着写入到可见之间有一小段延迟。这种场景的正确姿势是主库照旧,ES 只做检索层。

判断的边界可以归纳成几个问题:

你的场景建议原因
全文检索、模糊匹配、多条件组合筛选用它倒排索引的主场,数据越大优势越明显
日志、指标类的海量写入与聚合分析用它天然分布式,按时间分索引后查询和清理都方便
需要事务、强一致、频繁更新的核心账务别当主库无跨文档事务,更新成本高
复杂的多表关联查询慎用没有 join,只能靠写入时冗余宽表
数据量很小、查询简单未必值得引入一套分布式系统,运维成本远大于收益

我自己有过一次教训:一个内部小工具,总共几万条数据,查询条件就一个主键,我图省事接了 ES,结果为了它多维护一套集群、多写一套同步逻辑、多排查一类「数据不一致」的问题,收益几乎为零。从那以后,每次有人提「上 ES」,我都会强制先问三个问题:数据量真的到瓶颈了吗?读写比例是什么样?这份数据丢了能不能重建?三个问题里有一个答不上来,就先别动手。

这份 Elasticsearch 演讲资料把这些东西用图串成了一条线——从倒排索引的单词词典和倒排列表,到分词策略的选择,到 TF-IDF 怎么打分,再到集群、分片、副本、Mapping、模板、别名怎么摆,最后落在应用场景和常见提问上。自己零散搜十篇帖子拼出来的认知,和顺着一条线走一遍,密度是不一样的。拿去给团队做一次内部分享,或者自己过一遍打底,都合适。

希望帮到你。

本文还有配套的精品资源,点击获取

返回列表