本文将深度解析第 3 部分所述控制平面架构在 Elasticsearch 中的实现,展示如何使用 Elasticsearch percolator 构建该架构。本文还概述了在生产环境中实现确定性、受治理的策略引擎所采用的模式。
从架构到实现
第 3 部分介绍了控制平面架构:将反向匹配作为查找原语、使用策略文档分离匹配与操作,以及通过级联转换将多个策略组合成单一执行计划。本文将通过实际操作介绍驱动策略查找的 Elasticsearch 核心功能:percolator 查询。
percolator 与治理场景天然契合,因为它正是以控制平面所需的方式反转了搜索方向。本文将逐步讲解实现过程:先清晰说明 percolator 的作用及其重要性,再介绍索引设计、策略存储、查询时评估和多策略组合。
常规搜索的工作原理
在电子商务系统中,您可能拥有数十万甚至数百万个产品文档,其中包含 title、category 和 price 等字段。当用户搜索匹配文档时,实际上是在让 Elasticsearch 将用户的搜索字符串与这些产品文档中存储的一个或多个字段进行比较。作为 Elasticsearch 的默认分析器,standard analyzer(标准分析器)会将文本转为小写,并将其拆分为词元。搜索 “oranges” 会匹配 “Oranges”,因为分析器会执行小写化处理。使用包含词干提取功能的语言感知分析器时,它也会匹配 “orange”,因为这两种形式都会归约到相同的词干。例如,以下 match 查询会返回 “title” 字段中包含 “orange” 或 “oranges” 的文档。
因此,对于上述查询,Elasticsearch 会返回 title 字段与 “oranges” 匹配的产品文档,结果可能包括 “Orange Fruit Spread”“Orange Juice”“Juicy oranges”“Orange Marmalade” 等。需要记住的关键点是:Elasticsearch 通常用于将搜索字符串与文档进行比较,并返回与该搜索字符串匹配的文档。

治理问题:搜索产品前先找到相关策略
如第 1 部分至第 3 部分所述,受治理的搜索系统不会将用户的搜索字符串直接发送到产品目录。它会先检查是否有策略适用于该搜索字符串。
一位商品经理决定,当有人精确搜索 “oranges” 时,结果应限制在 Oranges 类别中,从而排除 orange juice、orange marmalade 和 orange soda。这一业务决策会存储为一项策略。当用户输入 “oranges” 时,控制平面需要找到该策略,读取其指令,并相应修改针对产品目录的搜索。为此,控制平面需要确定哪些已存储的策略与该搜索字符串相关。
企业部署中可能有数百甚至数千项此类策略。使用 if/else 逻辑逐一检查这些策略,正是第 2 部分所述的应用层反模式。我们需要一种方法,将所有策略存储在一个索引中,并即时找到与给定搜索字符串匹配的策略。这正是 percolator 发挥作用的地方。
反转方向:percolator
我们之前提到,在常规搜索中,Elasticsearch 通常用于将搜索字符串与文档进行比较,并返回包含该搜索字符串的文档。
percolator 会反转这一过程。使用 percolator 时,您会拥有一个索引,其中每个文档都存储一个查询模式。随后,系统会将传入的搜索字符串与这些已存储的查询进行比对,以确定哪些已存储的查询模式被触发。

在治理场景中,“已存储的查询模式”就是策略。每项策略都包含一个模式,用于描述它应匹配哪类搜索字符串。例如,搜索字符串是精确匹配 “oranges”,还是包含 “olive oil”?传入字符串是用户的搜索文本,它会在查询时到达,并需要与所有已存储的策略模式进行比对。相关 PRISM 视频的 4:09 处对此进行了介绍。
逐步解析:搜索 “oranges” 如何找到对应策略
策略
一位商品经理编写了一项策略,用于在用户仅搜索 “oranges” 且不包含任何其他词时触发匹配。percolator 匹配后,文档的其余部分会包含控制平面用于构建产品查询的规则;在本例中,其中一条规则是将结果限制(过滤)到 Fruits 类别。
percolator 字段包含用于定义该策略何时触发的模式。在这种情况下,它会匹配短语 "START oranges END"。rule_type 和 rule_args 字段定义该策略触发时应执行的操作。START 和 END 令牌是边界标记,我们稍后会对此进行说明。
您可以在 PRISM Studio UI 中查看策略的创建方式,详情请参阅相关 PRISM 视频的 2:52 处。
用户发起搜索
购物者在搜索栏中输入 “oranges”。
控制平面检查是否存在匹配策略
在搜索产品目录之前,控制平面会拦截用户搜索字符串,用边界标记将其包裹起来,并将其发送到 percolator:
字符串 "START oranges END" 会与所有已存储的策略模式进行比对。Elasticsearch 会在内部针对此字符串运行已存储的策略模式,并返回匹配项。这就是 percolator 的运行机制。系统将用户的搜索字符串与所有已存储的策略模式进行匹配,并返回匹配项。无需 if/else 语句链。无需顺序评估。匹配由索引处理。
控制平面应用策略
控制平面读取匹配策略的操作。上述策略指示控制平面将结果限制为水果类别。控制平面按如下方式构建针对产品目录的最终 Elasticsearch 查询:
用户搜索的是 “oranges”。产品目录收到一个受 Fruits 类别约束的 “oranges” 查询。由于这一约束,Orange Juice、Orange Marmalade 和 Orange Soda 会被排除。
为什么 “orange marmalade” 不会触发 oranges 策略
假设另一位用户搜索 “orange marmalade”。控制平面会包裹该字符串并执行 percolator 匹配:"START orange marmalade END"。oranges 策略的模式是 match_phrase: "START oranges END"。oranges 策略不匹配,因此不会应用该策略,结果也不会限制在 Fruits 类别中。
这就是 START 和 END 边界标记的作用。没有这些标记时,匹配 “oranges” 一词的策略可能会被 “orange marmalade” 这样的查询意外触发。通过使用 START 和 END 包裹用户的搜索字符串,并在策略模式中包含这些标记,我们可以确保该策略仅在 “oranges” 是完整搜索字符串且不包含其他词时触发。这同时符合购物者和商品经理的意图。
第二项策略:基于词干化字段的 “olive oil”
并非每项策略都需要精确字符串匹配。“olive oil” 策略会在词干化字段上匹配,因此即使存在轻微词形变化,也会触发:
此策略的模式会匹配 query.stemmed,而不是 query。当用户的搜索字符串到达时,它会同时存储在 query 字段(精确文本)和 query.stemmed 字段中(使用词干提取分析器进行分析,该分析器会将单词归约到词干,因此 “olives” 和 “olive” 会归约为相同词干,“oils” 和 “oil” 也是如此)。该策略的模式会与字符串的词干化版本进行比对,因此即使存在轻微词形变化,也会触发。
START 和 END 边界标记同样适用于词干化字段,确保该策略仅在 “olive oil” 是完整搜索字符串时触发,而不会在它作为较长搜索字符串的一部分出现时触发。
本文其余部分将介绍让该方案可用于生产环境的实现细节:支持两种匹配模式的索引映射、高亮如何驱动短语移除和已消耗短语跟踪,以及多个冲突策略如何组合成单一执行计划。
策略索引映射
策略索引需要一个 percolator 字段来保存已存储的查询模式,还需要一个文本字段,其结构与传入搜索字符串保持一致,供 percolator 匹配。为便于理解,以下映射经过简化。生产部署更加复杂,会使用自定义分析器来处理边界标记、可变模式匹配(例如识别 “under $4” 包含货币值)以及其他类型的分析。
该索引命名为 policies,因为每个文档都代表第 2 部分中定义的一项完整受治理策略,其中包括匹配条件、操作、优先级和元数据。rule_type 和 rule_args 字段包含策略的操作组件,其中的指令将由控制平面用来组合查询,并针对产品目录执行该查询。
query 字段是 percolator 用于匹配的字符串。它有两个变体:精确版本和词干化版本。当用户的搜索字符串到达时,它会被放入临时内存索引中的这个字段。匹配 query 的策略会看到精确字符串;匹配 query.stemmed 的策略会看到词干化版本。
结合高亮、筛选和排序进行 percolator 匹配
上述简单示例展示的是最简 percolation 请求。在实际应用中,控制平面会添加高亮、过滤已禁用策略,并按优先级排序:
高亮配置使用 "query" 作为字段键,并在 matched_fields 中包含 "query.stemmed"。这会告诉 Elasticsearch 的 unified highlighter 在父 query 字段上返回高亮,同时在确定要高亮哪些词元时,也考虑来自 query.stemmed 子字段的匹配项。这样一来,基于词干化字段匹配的策略仍能在原始文本上生成准确的高亮片段;控制平面需要这些片段来执行短语移除和已消耗短语跟踪。
enabled: true 筛选器可确保跳过已禁用的策略。基于优先级的 sort 可确保优先级较高的策略先返回,使控制平面能够按照正确顺序处理它们,以执行级联转换。highlight 字段是最重要的新增内容;它能准确告诉我们用户搜索字符串中的哪些词触发了每次匹配。
“olive oil” 搜索的响应可能如下所示:
高亮为何重要
请注意响应中的高亮:"<em>START olive oil END</em>"。Elasticsearch 正在准确告诉我们,用户搜索字符串中的哪些词导致了策略匹配。这并不是为了美观。高亮元数据会驱动两个关键的下游行为:
短语移除。有些策略需要在构建产品目录查询之前,从搜索字符串中移除匹配文本。例如,匹配 “cheap” 的策略会移除该词,并将其转换为价格过滤器。高亮会准确标识搜索字符串中与策略匹配的区间,因此系统知道要移除哪些内容。
已消耗短语跟踪。如第 3 部分所述,当多个策略匹配同一搜索字符串时,优先级较高的策略可能会移除优先级较低的策略也匹配到的词。通过将每项策略的高亮与当前(不断演变的)搜索字符串进行比较,系统可以检测到某个短语已被消耗,从而跳过优先级较低的策略。这样可以防止重复处理,并确保行为具有确定性。
您可以在这篇文章中详细了解高亮的工作原理。
从 percolator 匹配到执行计划
percolator 会返回一组匹配的策略。但如第 3 部分所述,查找只完成了一半。另一半是将这些匹配项组合成一个连贯的执行计划。下面以一个具体查询为例说明。
示例:圣诞活动期间的 “Cheap chocolate”
假设系统有两个有效策略:“Cheap chocolate” 策略(优先级 210)和 “Christmas chocolates” 策略(优先级 300),这两个策略均已在第 3 部分中详细介绍。
第 1 步:执行 percolator 匹配。用户搜索 “cheap chocolate”。控制平面将搜索字符串包装为 "START cheap chocolate END",并将其发送到 percolator。有两项策略匹配:“Cheap chocolate” 策略的模式匹配短语 “cheap chocolate”;“Christmas chocolates” 策略的模式则通过词干化字段匹配 “chocolate”。
第 2 步:按优先级排序。percolator 返回两个策略,并按优先级降序排序。系统会先处理 “Christmas chocolates” 策略(300),再处理 “Cheap chocolate” 策略(210)。
第 3 步:应用级联转换。这就是第 3 部分中的 initial state → [Policy A] → state' → [Policy B] → state'' → execution plan 模型。
“Christmas chocolates” 策略(优先级 300)首先应用:
- 添加类别硬性过滤器:“Christmas foods and drinks”、“Christmas sweets”。
- 添加价格过滤器:低于 $7。
- 添加类别软性提升:“Advent calendars” (3x)。
接下来,“Cheap chocolate” 策略(优先级 210)会应用于修改后的状态:
- 尝试添加硬性类别过滤器:“Chocolates”、“Milk chocolates”;但 Christmas 策略已使用
on_conflict: override设置该字段,因此 Cheap chocolate 类别会被丢弃。 - 尝试添加价格过滤器:$2,圣诞节政策将价格设置为
on_conflict: restrict,而 $2 比 $7 更严格,因此 $2 获胜。 - 从搜索字符串中移除 “cheap”。
第 4 步:构建 Elasticsearch 查询。控制平面将执行计划组装为针对产品目录的单个 Elasticsearch 查询:
原始搜索字符串是 “cheap chocolate”。到达产品目录的查询是一个受治理且具备意图感知能力的检索计划:“cheap” 一词已被消耗并转换为价格约束,结果限制在圣诞季节性类别中,Advent calendar 产品获得排名提升,价格上限则采用较低优先级策略中更严格的值。每一次转换都是确定性的、可追溯且可解释的。
如需快速了解这些乘数如何与 BM25 基础分数相互作用,请参阅相关 PRISM 视频的 8:45 处,其中简要讨论了乘法提升(multiplicative boosts)。
为何具备扩展性
由于这种不对称性,percolator 在此用例中非常高效:企业电子商务系统可能拥有数百万个产品,但只有数百或数千项治理策略。percolator 会将一个传入搜索字符串与那组已存储的策略模式进行比对,而不是扫描完整产品目录。开销与策略数量成正比;同时,Elasticsearch 会应用内部优化(例如从已存储的查询模式中索引词项、对布尔逻辑进行短路处理),以保持快速匹配。
添加新策略只是为一个新文档建立索引。禁用策略只是更新一个字段。无需修改代码,无需部署,无需重启。
从查找到受控检索
percolator 提供了快速反向匹配原语,让第 3 部分介绍的控制平面架构在规模化场景中切实可行。策略是一种数据,会被存储和索引,并能与传入的搜索字符串进行高效匹配。控制平面通过第 3 部分所述的级联转换和逐字段冲突解决,将匹配策略组合成受治理的执行计划。随后,检索引擎会针对产品目录执行该受治理的执行计划。
由此形成的系统可让商品经理在不改动应用程序代码的情况下创建新策略,针对代表性查询进行测试,将其推广到生产环境,并立即看到效果。percolator 让策略查找更快速;控制平面让策略组合具有确定性;受治理的工作流则让整个流程更加安全可靠。
本系列内容预告
本系列的下一篇文章会将受治理的控制平面拓展到新领域。文中将介绍一种多层搜索架构,说明如何在保持稳定分页和分面的同时,编排严格检索、宽松检索和语义检索。
将受治理的电子商务搜索付诸实践
本文介绍的基于 percolator 的控制平面,从索引映射和边界标记,到由高亮驱动的短语跟踪和级联策略组合,均由 Elastic Services Engineering 构建,是我们可复用电子商务搜索加速器的一部分。本文展示的每个查询示例和策略结构,均来自一个已针对企业级产品目录完成验证的实际运行系统。
如果您希望在 Elasticsearch 上实现一个受治理、由策略驱动的控制平面,Elastic Services 可以帮助您更快达成目标。请联系 Elastic Professional Services。
加入讨论
对搜索治理、检索策略或电子商务搜索架构有疑问?加入更广泛的 Elastic 社区讨论。




