当我们调用 Easysearch 的 API 写入一条数据,并在几毫秒后将其搜索出来时,一切看起来都那么自然且顺滑。但在那短暂的眨眼之间,这条数据其实刚刚经历了一场惊心动魄的“奇幻漂流”。
它是如何被安全存储的?为什么有时候刚写进去却搜不到?搜索时它是如何被瞬间定位的?
今天,我们不谈枯燥的代码,而是化身为一条 JSON 数据,亲自走一遍 Easysearch 的内部旅程。
第一站:登陆与安检(协调节点) #
我们的旅程始于一个 HTTP 请求:PUT /my_index/_doc/1。
此时,我们到达了 Easysearch 集群的“海关”——协调节点 (Coordinating Node)。实际上,集群中的任何一个节点都可以充当这个角色。
协调节点并不存储我们(除非它是主分片所在节点),它的工作是路由。它看了一眼我们的身份证号(Document ID),根据一个简单的哈希公式计算出我们的目的地:
shard_id = hash(routing) % number_of_primary_shards
“去吧,你的家在 Node-2!”协调节点大手一挥,将我们转发到了持有主分片 (Primary Shard) 的节点上。
第二站:安全的避风港(主分片写入) #
到达主分片节点后,真正的存储工作开始了。为了兼顾内存般的速度和磁盘般的安全,Easysearch 采用了一套标准的 WAL (Write-Ahead Logging) 机制。
这就像是银行柜员办业务:先在电脑上录入(内存),同时打印一张回单盖章(日志落盘),最后才会在晚上结算时把钱真正入库(数据刷盘)。
1. 录入内存(Lucene IndexWriter) #
首先,Easysearch 会将我们交给底层的 Lucene 引擎。Lucene 会解析我们的内容,校验字段类型。
- 状态:如果校验通过,我们会进入 Lucene 内存缓冲区 (In-Memory Buffer)。
- 注意:此时我们在内存里,虽然存在,但不可见(还不能被搜索),且不安全(断电即失)。
2. 签署契约(Translog 落盘) #
为了保证数据不丢失,Easysearch 紧接着会将操作追加到 Translog (事务日志) 中。
- 关键动作:默认情况下,每个写请求都会强制触发 Fsync,将 Translog 从内存刷入物理磁盘。
- 意义:这是数据持久化的**“承诺时刻”**。只有当 Translog 成功落盘,Easysearch 才会认为这次写入是成功的。即使下一秒服务器拔电,重启后也能通过重放 Translog 找回我们。
- WAL 的真谛:此时数据本身(Lucene Segment)还在内存里,没存到磁盘,但日志(Translog)已经存了。日志先于数据落盘,这就是 Write-Ahead Logging。
3. 并发复制(副本同步) #
主分片自己处理完后,会把操作打包,并发地发送给所有的 副本分片 (Replica Shards)。默认情况下,当主分片写入完成后,协调节点会向客户端返回那句悦耳的 200 OK。如果设置了waitForActiveShards参数,则只有当大多数副本都回复“收到”后,协调节点才会向客户端返回 <font style="color:rgb(15, 15, 16);">200 OK</font>。
插曲:隐形的时间(Refresh 机制) #
很多新手开发者会遇到一个困惑:“为什么我收到了 200 OK,证明 Translog 已经落盘了,但我马上搜却搜不到?”
这就是 Easysearch “近实时 (Near Real-time)” 的特性所在。
还记得刚才那个 Lucene 内存缓冲区吗?它就像一个蓄水池。默认情况下,Easysearch 每隔 1秒 会执行一次 Refresh 操作:
- 成形:将内存缓冲区中的数据,生成一个新的、不可变的 Segment (段)。
- 可见:这个 Segment 被“打开”,正式加入倒排索引链表。
- 清空:内存缓冲区被清空,准备接收下一批数据。
只有度过了这 1 秒钟(或者手动调用 _refresh),数据才从“隐形”变为“可见”。
冷知识: 如果你通过 ID 直接查询 (
GET /_doc/1),是可以立即查到的。因为 GET 操作走了“VIP 通道”,它会优先检查 Translog。但搜索 (_search) 必须等待 Refresh。
第三站:寻宝游戏(搜索流程) #
现在,我们已经安稳地躺在 Segment 中了。突然,一个搜索请求飞来:GET /_search?q=Easysearch。
这是一场典型的 分散-聚合 (Scatter-Gather) 游戏,分为两个阶段。
阶段一:Query Phase(侦察兵出动) #
协调节点再次登场。它不知道哪些文档包含 “Easysearch”,所以它向索引的所有分片(主分片或副本均可)派出“侦察兵”。
- 分散 (Scatter):每个分片独立在自己的倒排索引中查找匹配的文档。
- 筛选:每个分片计算相关性算分(Score),并选出自己分片上 Top 10 的结果。
- 返回简报:注意!分片不会返回完整的数据,只会返回 Document ID 和 分数。
协调节点收到所有分片的简报后,将它们合并、排序,最终确定全局真正的 Top 10 是哪些 ID。
阶段二:Fetch Phase(搬运工取货) #
既然确定了要哪 10 个文档,协调节点就会进行第二次精准请求。
- 聚合 (Gather):协调节点根据 ID,向持有这些文档的具体分片发送
FETCH请求(类似于“把 ID 为 1001 的完整 JSON 给我”)。 - 提取:分片从磁盘的
.fdt文件(行式存储)中读取原始的_source内容。 - 交付:完整的 JSON 数据汇聚到协调节点,拼装后返回给用户。
结语 #
从写入时的“双写保障”,到 Refresh 时的“隐形转换”,再到搜索时的“分散聚合”。一条数据在 Easysearch 中的旅程,体现了分布式系统在一致性、性能和可用性之间精妙的平衡艺术。
Easysearch 作为一个开箱即用的搜索引擎,将这些复杂的逻辑(如 Translog 的刷盘策略、分片的路由算法、Lucene 的底层交互)完美封装。
对于开发者来说,你只需要关心业务逻辑;而对于数据来说,这是一场不仅安全,而且极为高效的奇幻漂流。





