一套「策略化 Elasticsearch 召回平台」架构设计思路

目录

[一、问题背景:ES 查询正在成为系统演进的"瓶颈"](#一、问题背景:ES 查询正在成为系统演进的“瓶颈”)

[(一)查询逻辑深度绑定 Java 代码](#(一)查询逻辑深度绑定 Java 代码)

(二)需求变更成本极高

(三)查询逻辑不可见、不可验证

(四)无法复用、无法治理

[二、目标:把 ES 查询"产品化、策略化、平台化"](#二、目标:把 ES 查询“产品化、策略化、平台化”)

三、总体架构设计

(一)架构总览

(二)策略化ES查询系统架构和流程图

(三)核心模块拆解

[四、策略的本质:DSL 模板 + 参数模型](#四、策略的本质:DSL 模板 + 参数模型)

(一)什么是"策略"

[(二)策略 DSL 模板设计](#(二)策略 DSL 模板设计)

[(三)DSL 设计原则](#(三)DSL 设计原则)

(四)参数系统设计

[1. 参数类型定义](#1. 参数类型定义)

[2. 参数定义模型](#2. 参数定义模型)

五、策略执行全流程

(一)调用方式

(二)执行流程拆解

[(三)DSL 渲染引擎设计](#(三)DSL 渲染引擎设计)

[1. 支持能力](#1. 支持能力)

[2. 渲染过程](#2. 渲染过程)

[(四)DSL 条件片段 / 可选过滤(Conditional DSL)](#(四)DSL 条件片段 / 可选过滤(Conditional DSL))

[1. 设计目标(非常重要)](#1. 设计目标(非常重要))

[2. DSL 条件片段的设计方案(推荐)](#2. DSL 条件片段的设计方案(推荐))

[2.1 方案一(推荐):结构化条件块(Block DSL)](#2.1 方案一(推荐):结构化条件块(Block DSL))

[2.2 方案二:占位符级条件(不推荐长期)](#2.2 方案二:占位符级条件(不推荐长期))

[2.3 方案三:引入表达式引擎(高级)](#2.3 方案三:引入表达式引擎(高级))

[3. 条件 DSL 的执行模型](#3. 条件 DSL 的执行模型)

[4. 条件表达式设计(刻意简化)](#4. 条件表达式设计(刻意简化))

[4.1 支持的表达式范围](#4.1 支持的表达式范围)

[4.2 表达式执行方式](#4.2 表达式执行方式)

[5.Java 实现设计](#5.Java 实现设计)

[5.1 条件处理入口](#5.1 条件处理入口)

[5.2 识别 if 节点](#5.2 识别 if 节点)

[5.3 if 节点执行逻辑](#5.3 if 节点执行逻辑)

[5.4 条件表达式执行器(简化版)](#5.4 条件表达式执行器(简化版))

[6. 与原执行流程的整合](#6. 与原执行流程的整合)

[六、ES 执行层:彻底屏蔽 Java ES 复杂度](#六、ES 执行层:彻底屏蔽 Java ES 复杂度)

(一)策略执行服务的整体分层设计

[(二)领域模型(Domain Model)](#(二)领域模型(Domain Model))

[1. Strategy(策略主实体)](#1. Strategy(策略主实体))

[2. StrategyVersion(可执行版本)](#2. StrategyVersion(可执行版本))

[3. StrategyParam(参数定义)](#3. StrategyParam(参数定义))

[(三)Repository 层(数据访问)](#(三)Repository 层(数据访问))

[(四)参数处理引擎(Parameter Engine)](#(四)参数处理引擎(Parameter Engine))

[1. 参数处理入口](#1. 参数处理入口)

[2. 默认实现](#2. 默认实现)

[(五)DSL 渲染引擎(核心)](#(五)DSL 渲染引擎(核心))

[1. DSL 渲染接口](#1. DSL 渲染接口)

[2. 基于占位符的实现](#2. 基于占位符的实现)

[(六)ES 执行适配层](#(六)ES 执行适配层)

[1. 统一执行接口](#1. 统一执行接口)

[2. 实现思路(伪实现)](#2. 实现思路(伪实现))

(七)策略执行主流程

[1. StrategyExecuteService](#1. StrategyExecuteService)

[2. 默认实现(核心流程)](#2. 默认实现(核心流程))

七、策略管理平台设计(重头戏)

(二)平台核心能力

(二)策略实时验证(非常关键)

验证流程

[(三)策略调试执行(Draft / Preview Execution)](#(三)策略调试执行(Draft / Preview Execution))

[1. 设计目标](#1. 设计目标)

[2. 整体架构设计](#2. 整体架构设计)

[3. 表结构扩展(支持 Draft)](#3. 表结构扩展(支持 Draft))

[3.1 增加 version_status](#3.1 增加 version_status)

[3.2 Draft 的定义](#3.2 Draft 的定义)

[4. 调试执行 API 设计](#4. 调试执行 API 设计)

[4.1 请求模型](#4.1 请求模型)

[4.2 返回模型(关键)](#4.2 返回模型(关键))

[5. 调试执行核心流程](#5. 调试执行核心流程)

[5.1 调试执行服务接口](#5.1 调试执行服务接口)

[5.2 DebugOptions](#5.2 DebugOptions)

[5.3 核心实现](#5.3 核心实现)

[6. 平台交互体验(非常重要)](#6. 平台交互体验(非常重要))

[7. 安全与边界控制](#7. 安全与边界控制)

[(四)平台侧如何支持条件 DSL?](#(四)平台侧如何支持条件 DSL?)

编辑器能力

调试能力

[八、策略化 ES 查询平台参考"可直接落库"的完整表结构设计](#八、策略化 ES 查询平台参考“可直接落库”的完整表结构设计)

(一)整体数据模型总览

(二)策略主表(strategy)

[1. 表职责](#1. 表职责)

[2. 表结构设计](#2. 表结构设计)

[2.1 为什么 strategy_id 要单独存在?](#2.1 为什么 strategy_id 要单独存在?)

[2.2 biz_domain 的价值](#2.2 biz_domain 的价值)

(三)策略版本表(strategy_version)

[1. 表职责](#1. 表职责)

[2. 表结构设计](#2. 表结构设计)

[2.1 dsl_template(核心)](#2.1 dsl_template(核心))

[2.1 result_mapping(高级)](#2.1 result_mapping(高级))

(四)策略参数表(strategy_param)

[1. 表职责](#1. 表职责)

[2. 表结构设计](#2. 表结构设计)

(五)策略发布表(strategy_publish)

[1. 表职责](#1. 表职责)

[2. 表结构](#2. 表结构)

(六)策略权限表(strategy_permission)

(七)为什么要"拆表这么细"?

(八)缓存、性能与稳定性设计

[1. 策略缓存](#1. 策略缓存)

[2. 查询结果缓存(可选)](#2. 查询结果缓存(可选))

九、总结:你真正做成的是什么?


干货分享,感谢您的阅读!

在很多系统中,Elasticsearch 早已不只是"搜索引擎",而是承载推荐、风控、运营分析等核心业务的关键查询基础设施

但随着业务复杂度上升,ES 查询逐渐演变为代码耦合重、变更成本高、风险难以控制的系统瓶颈。

我们将从真实工程问题出发,分享一套将 ES 查询策略化、平台化、可治理 的完整设计思路,探讨如何把查询能力从代码中解放出来,构建一套可长期演进的查询策略平台。

一、问题背景:ES 查询正在成为系统演进的"瓶颈"

在大多数中大型业务系统中,Elasticsearch 已经不再只是一个"搜索引擎",而是:

  • 推荐系统的召回层

  • 风控系统的规则筛选层

  • 广告系统的人群圈选引擎

  • 内容平台的多维过滤与排序引擎

但与此同时,ES 查询的使用方式却高度工程化、碎片化现状问题:

(一)查询逻辑深度绑定 Java 代码

  • 每一个查询场景 = 一个 Java 方法

  • DSL 写在代码里

  • 参数拼接、条件判断、排序逻辑全靠 if/else

(二)需求变更成本极高

  • 改一个过滤条件 → 改代码 → 发版

  • 运营 / 算法 / 产品完全无法自助

(三)查询逻辑不可见、不可验证

  • DSL 实际长什么样,只有开发知道

  • 是否真的"召回正确数据",只能靠线上日志猜

(四)无法复用、无法治理

  • 类似的查询逻辑大量重复

  • 无统一命名、无版本、无权限、无监控

ES 的能力很强,但使用方式极其原始。

二、目标:把 ES 查询"产品化、策略化、平台化"

我们希望构建的是一套:

以「策略 ID」为入口的 Elasticsearch 查询平台

核心目标

目标 说明
业务零开发 业务方只传 strategyId + 参数
策略可配置 查询 DSL 在平台配置
实时验证 策略配置后可立即测试
版本可控 策略可发布 / 回滚 / 灰度
执行统一 屏蔽 Java ES 开发复杂度
可观测 查询效果、性能、命中率可监控

一句话总结:"ES 查询 = 一条可配置、可验证、可运营的策略"

三、总体架构设计

(一)架构总览

(二)策略化ES查询系统架构和流程图

(三)核心模块拆解

模块 职责
策略管理平台 策略创建、调试、发布
策略执行服务 对外统一策略调用入口
DSL 渲染引擎 参数替换、条件计算
ES 适配层 屏蔽 Java ES API
策略缓存 提高性能、支持热更新
验证与调试 实时验证策略效果

四、策略的本质:DSL 模板 + 参数模型

(一)什么是"策略"

在这个体系中,一条策略等于

一份可参数化的 ES DSL 模板 + 一份参数定义 + 一份执行约束

(二)策略 DSL 模板设计

示例策略 DSL

Groovy 复制代码
{
  "query": {
    "bool": {
      "must": [
        { "term": { "user_id": "${userId}" } }
      ],
      "filter": [
        { "range": { "score": { "gte": "${minScore:0}" } } }
      ]
    }
  },
  "sort": [
    { "create_time": "desc" }
  ],
  "size": "${size:10}"
}

(三)DSL 设计原则

  1. 100% 原生 ES DSL

  2. 仅增加轻量模板能力

  3. 不发明新查询语言

(四)参数系统设计

1. 参数类型定义

参数类型 示例
string userId
number size
boolean enableFilter
list categoryList
enum sortType

2. 参数定义模型

Scala 复制代码
{
  "name": "size",
  "type": "number",
  "required": false,
  "default": 10,
  "desc": "返回条数"
}

五、策略执行全流程

(一)调用方式

html 复制代码
POST /strategy/execute
{
  "strategyId": "user_recommend_v1",
  "params": {
    "userId": "123",
    "size": 5
  }
}

(二)执行流程拆解

复制代码

(三)DSL 渲染引擎设计

1. 支持能力

  • ${param} 强制参数

  • ${param:default} 默认值

  • 条件片段(进阶)

2. 渲染过程

  • 解析 DSL JSON

  • 查找占位符

  • 参数校验

  • 替换并生成最终 DSL

(四)DSL 条件片段 / 可选过滤(Conditional DSL)

现实业务中,ES 查询几乎从来不是"固定结构"。

典型场景

    • 参数存在才加 filter
    • 开关打开才启用某个 must
    • 列表为空时跳过 terms 查询
    • 不同用户走不同召回逻辑

如果没有条件 DSL,结果只能是:

java 复制代码
if (param != null) {
    bool.must(...)
}

❌ 这正是我们要消灭的东西。

1. 设计目标(非常重要)

我们要的 不是

  • ❌ 一个完整脚本语言

  • ❌ 在 DSL 里写 Java / Groovy / JS

我们要的是:结构化、可控、可验证的"条件片段"

2. DSL 条件片段的设计方案(推荐)

三种方案,并明确:👉 第一种是强烈推荐、可长期演进的,仅个人建议。

2.1 方案一(推荐):结构化条件块(Block DSL)

DSL 示例

php 复制代码
{
  "query": {
    "bool": {
      "must": [
        {
          "$if": {
            "condition": "userId != null",
            "then": { "term": { "user_id": "${userId}" } }
          }
        },
        {
          "$if": {
            "condition": "enableScoreFilter == true",
            "then": {
              "range": {
                "score": { "gte": "${minScore:0}" }
              }
            }
          }
        }
      ]
    }
  },
  "size": "${size:10}"
}

设计优势

维度 优势
可读性 非技术人员也能理解
安全性 不执行任意代码
可验证 condition 可单独校验
可扩展 支持 else / 嵌套
2.2 方案二:占位符级条件(不推荐长期)
java 复制代码
"must": [
  ${userId ? {"term":{"user_id":userId}} : ""}
]

❌ 不结构化

❌ 难以校验

❌ 易注入

2.3 方案三:引入表达式引擎(高级)
  • MVEL / SpEL / QLExpress

  • 适合 后期高级策略

不是第一版该做的事情,可长期演进

3. 条件 DSL 的执行模型

  • 执行顺序(非常关键)
  • 解析 JSON 结构
  • 识别 $if 节点
  • 计算 condition 表达式
  • 命中 then → 展开
  • 未命中 → 移除
  • 最终生成合法 ES DSL

⚠️ 注意:条件判断发生在"参数替换之前"还是之后,是一个设计点

推荐顺序:参数解析 → 条件判断 → DSL 展开 → 占位符替换

4. 条件表达式设计(刻意简化)

4.1 支持的表达式范围
perl 复制代码
==  !=  >  <  >=  <=
&&  ||
null / true / false

示例

bash 复制代码
userId != null
size > 10
enableScoreFilter == true
categoryList != null && categoryList.size > 0
4.2 表达式执行方式

自己解析,不引入脚本引擎

  • 安全

  • 可控

  • 易调试

5.Java 实现设计

5.1 条件处理入口
java 复制代码
public interface ConditionalDslProcessor {
    JsonNode process(JsonNode dslRoot, Map<String, Object> params);
}
5.2 识别 $if 节点
java 复制代码
public class DefaultConditionalDslProcessor
        implements ConditionalDslProcessor {

    @Override
    public JsonNode process(JsonNode root, Map<String, Object> params) {
        return walk(root, params);
    }

    private JsonNode walk(JsonNode node, Map<String, Object> params) {

        if (node.isObject() && node.has("$if")) {
            return processIfNode(node.get("$if"), params);
        }

        if (node.isArray()) {
            ArrayNode newArray = JsonNodeFactory.instance.arrayNode();
            for (JsonNode child : node) {
                JsonNode processed = walk(child, params);
                if (processed != null) {
                    newArray.add(processed);
                }
            }
            return newArray;
        }

        if (node.isObject()) {
            ObjectNode newObj = JsonNodeFactory.instance.objectNode();
            node.fields().forEachRemaining(e ->
                newObj.set(e.getKey(), walk(e.getValue(), params))
            );
            return newObj;
        }

        return node;
    }
}
5.3 $if 节点执行逻辑
java 复制代码
private JsonNode processIfNode(JsonNode ifNode,
                               Map<String, Object> params) {

    String condition = ifNode.get("condition").asText();
    boolean match = ConditionEvaluator.evaluate(condition, params);

    if (match) {
        return walk(ifNode.get("then"), params);
    }

    return NullNode.getInstance(); // 被移除
}
5.4 条件表达式执行器(简化版)
java 复制代码
public class ConditionEvaluator {

    public static boolean evaluate(String expr, Map<String, Object> params) {
        // 示例:userId != null
        if (expr.contains("!=")) {
            String key = expr.split("!=")[0].trim();
            return params.get(key) != null;
        }

        if (expr.contains("== true")) {
            String key = expr.replace("== true", "").trim();
            return Boolean.TRUE.equals(params.get(key));
        }

        throw new UnsupportedOperationException("Unsupported expr: " + expr);
    }
}

在实现第一版**完全够用,**后期可以无缝替换为表达式引擎

6. 与原执行流程的整合

原流程:参数解析 → DSL 渲染 → ES 执行

新流程(升级):参数解析 → 条件 DSL 处理 → 占位符替换 → ES 执行

集成示例

java 复制代码
JsonNode dslJson = objectMapper.readTree(version.getDslTemplate());

JsonNode conditionalProcessed =
    conditionalDslProcessor.process(dslJson, resolvedParams);

String finalDsl =
    dslRenderEngine.render(conditionalProcessed.toString(), resolvedParams);

你现在的系统已经具备:

能力 状态
参数化查询
条件化查询
无代码策略变更
策略自治

这已经不是"封装 ES",而是:一个轻量级、可控的查询规则引擎

六、ES 执行层:彻底屏蔽 Java ES 复杂度

(一)策略执行服务的整体分层设计

javascript 复制代码
strategy-executor
├── controller        # 对外API
├── service           # 策略执行主流程
├── domain            # 策略领域模型
├── repository        # DB访问
├── engine
│   ├── dsl           # DSL渲染引擎
│   ├── param         # 参数校验与填充
│   └── es            # ES执行适配
├── cache              # 策略缓存
└── model              # 请求 / 返回模型

(二)领域模型(Domain Model)

Domain 层是整个体系"最重要的稳定锚点"

1. Strategy(策略主实体)

java 复制代码
@Data
public class Strategy {
    private Long id;
    private String strategyId;
    private String name;
    private String bizDomain;
    private String owner;
    private boolean enabled;
}

2. StrategyVersion(可执行版本)

java 复制代码
@Data
public class StrategyVersion {
    private Long id;
    private String strategyId;
    private String version;
    private boolean active;

    private String esCluster;
    private String esIndex;

    private String dslTemplate;     // 原始DSL
    private String resultMapping;   // JSON

    private int timeoutMs;
    private int maxSize;
}

3. StrategyParam(参数定义)

java 复制代码
@Data
public class StrategyParam {
    private Long id;
    private Long strategyVersionId;

    private String name;
    private ParamType type;
    private boolean required;
    private String defaultValue;
    private String validateRule;
}


public enum ParamType {
    STRING, NUMBER, BOOLEAN, LIST
}

(三)Repository 层(数据访问)

java 复制代码
public interface StrategyRepository {
    Strategy findByStrategyId(String strategyId);
}

public interface StrategyVersionRepository {
    StrategyVersion findActiveVersion(String strategyId);
}

public interface StrategyParamRepository {
    List<StrategyParam> findByVersionId(Long versionId);
}

⚠️ 注意:
执行路径永远只查"active version",灰度由上层控制。

(四)参数处理引擎(Parameter Engine)

1. 参数处理入口

java 复制代码
public interface ParamResolver {
    Map<String, Object> resolve(
        List<StrategyParam> paramDefs,
        Map<String, Object> inputParams
    );
}

2. 默认实现

java 复制代码
public class DefaultParamResolver implements ParamResolver {

    @Override
    public Map<String, Object> resolve(
            List<StrategyParam> defs,
            Map<String, Object> input) {

        Map<String, Object> resolved = new HashMap<>();

        for (StrategyParam def : defs) {
            Object value = input.get(def.getName());

            if (value == null) {
                if (def.isRequired() && def.getDefaultValue() == null) {
                    throw new IllegalArgumentException(
                        "Missing required param: " + def.getName()
                    );
                }
                value = def.getDefaultValue();
            }

            validateType(def, value);
            resolved.put(def.getName(), value);
        }

        return resolved;
    }

    private void validateType(StrategyParam def, Object value) {
        // 简化示例,可扩展正则 / 表达式
        if (value == null) return;

        switch (def.getType()) {
            case NUMBER:
                if (!(value instanceof Number)) {
                    throw new IllegalArgumentException(def.getName() + " must be number");
                }
                break;
            case BOOLEAN:
                if (!(value instanceof Boolean)) {
                    throw new IllegalArgumentException(def.getName() + " must be boolean");
                }
                break;
        }
    }
}

(五)DSL 渲染引擎(核心)

1. DSL 渲染接口

java 复制代码
public interface DslRenderEngine {
    String render(String dslTemplate, Map<String, Object> params);
}

2. 基于占位符的实现

java 复制代码
public class PlaceholderDslRenderEngine implements DslRenderEngine {

    private static final Pattern PATTERN = Pattern.compile("\\$\\{(.+?)\\}");

    @Override
    public String render(String template, Map<String, Object> params) {
        Matcher matcher = PATTERN.matcher(template);
        StringBuffer sb = new StringBuffer();

        while (matcher.find()) {
            String key = matcher.group(1);
            Object value = params.get(key);

            if (value == null) {
                throw new IllegalArgumentException("Missing param: " + key);
            }

            matcher.appendReplacement(sb, formatValue(value));
        }
        matcher.appendTail(sb);
        return sb.toString();
    }

    private String formatValue(Object value) {
        if (value instanceof String) {
            return "\"" + value + "\"";
        }
        return value.toString();
    }
}

优点:

  • 简单

  • 可控

  • 不引入复杂脚本引擎

(六)ES 执行适配层

1. 统一执行接口

java 复制代码
public interface EsExecutor {
    SearchResult execute(
        String cluster,
        String index,
        String dsl,
        int timeoutMs
    );
}

2. 实现思路(伪实现)

java 复制代码
public class RestHighLevelEsExecutor implements EsExecutor {

    @Override
    public SearchResult execute(
            String cluster,
            String index,
            String dsl,
            int timeoutMs) {

        // 1. 构建 SearchRequest
        // 2. 设置 index / source / timeout
        // 3. 执行并返回结果

        return new SearchResult();
    }
}

(七)策略执行主流程

1. StrategyExecuteService

java 复制代码
public interface StrategyExecuteService {
    StrategyResult execute(String strategyId, Map<String, Object> params);
}

2. 默认实现(核心流程)

java 复制代码
public class DefaultStrategyExecuteService implements StrategyExecuteService {

    private StrategyVersionRepository versionRepo;
    private StrategyParamRepository paramRepo;
    private ParamResolver paramResolver;
    private DslRenderEngine dslRenderEngine;
    private EsExecutor esExecutor;

    @Override
    public StrategyResult execute(String strategyId, Map<String, Object> params) {

        // 1. 获取生效版本
        StrategyVersion version = versionRepo.findActiveVersion(strategyId);
        if (version == null) {
            throw new IllegalStateException("No active version for strategy " + strategyId);
        }

        // 2. 获取参数定义
        List<StrategyParam> paramDefs =
            paramRepo.findByVersionId(version.getId());

        // 3. 参数解析
        Map<String, Object> resolvedParams =
            paramResolver.resolve(paramDefs, params);

        // 4. DSL 渲染
        String finalDsl =
            dslRenderEngine.render(version.getDslTemplate(), resolvedParams);

        // 5. 执行 ES
        SearchResult esResult =
            esExecutor.execute(
                version.getEsCluster(),
                version.getEsIndex(),
                finalDsl,
                version.getTimeoutMs()
            );

        // 6. 结果映射
        return StrategyResult.from(esResult, version.getResultMapping());
    }
}

到这一步,已经完成了:

✅ 策略 = 可配置实体

✅ 查询 = 数据驱动

✅ 执行 = 统一入口

✅ Java 代码 不再关心具体 ES DSL

这已经是一个 完整可运行的策略执行内核

七、策略管理平台设计(重头戏)

(二)平台核心能力

能力 说明
策略创建 DSL 编辑
参数配置 参数定义
实时调试 在线测试
发布管理 版本 / 灰度
权限控制 团队隔离
监控分析 调用效果

(二)策略实时验证(非常关键)

验证流程

  • 输入测试参数

  • 生成最终 DSL

  • 实际请求 ES

  • 展示结果 & 命中数

  • 高亮 DSL 与参数映射

这是"让策略可信"的核心能力

(三)策略调试执行(Draft / Preview Execution)

现实问题

  • DSL 写对了 ≠ 召回对了

  • 参数填对了 ≠ 命中预期数据

  • ES 查询逻辑复杂,靠"脑补"不可靠

没有调试能力的后果

角色 痛点
策略配置者 不敢发布
开发 被迫陪跑
线上 发布即事故

1. 设计目标

调试执行必须满足:

维度 要求
不影响线上 不走 active version
即时反馈 秒级返回
结果可解释 展示 DSL、条件命中
安全 不能误写数据
可对比 新旧策略对比

2. 整体架构设计

调试执行与正式执行的核心区别

维度 正式执行 调试执行
策略版本 active 指定 version / draft
是否发布 必须 ❌ 不需要
缓存 可用 ❌ 禁用
限制 正常 强限制
记录 日志 调试日志

3. 表结构扩展(支持 Draft)

3.1 增加 version_status
sql 复制代码
ALTER TABLE strategy_version
ADD COLUMN version_status VARCHAR(20)
DEFAULT 'DRAFT'
COMMENT 'DRAFT / PUBLISHED / OFFLINE';
3.2 Draft 的定义
状态 说明
DRAFT 编辑态,仅用于调试
PUBLISHED 可被发布
OFFLINE 下线

4. 调试执行 API 设计

4.1 请求模型
java 复制代码
POST /strategy/debug/execute
{
  "strategyId": "user_recommend",
  "version": "v2",
  "params": {
    "userId": "123",
    "enableScoreFilter": true
  },
  "options": {
    "limit": 20,
    "explain": true
  }
}

4.2 返回模型(关键)

java 复制代码
{
  "finalDsl": "{ ... }",
  "conditionResult": [
    {
      "condition": "enableScoreFilter == true",
      "matched": true
    }
  ],
  "hits": [ ... ],
  "total": 120
}

finalDsl 是灵魂

没有它,调试毫无意义

5. 调试执行核心流程

  • 加载指定版本(可为 DRAFT)
  • 参数校验(同正式)
  • 条件 DSL 执行(记录命中情况)
  • DSL 渲染
  • 强制只读 + limit
  • ES 执行
  • 结果解释与返回
5.1 调试执行服务接口
java 复制代码
public interface StrategyDebugService {
    StrategyDebugResult debug(
        String strategyId,
        String version,
        Map<String, Object> params,
        DebugOptions options
    );
}
5.2 DebugOptions
java 复制代码
@Data
public class DebugOptions {
    private int limit = 20;
    private boolean explain = true;
}
5.3 核心实现
java 复制代码
public class DefaultStrategyDebugService
        implements StrategyDebugService {

    private StrategyVersionRepository versionRepo;
    private StrategyParamRepository paramRepo;
    private ConditionalDslProcessor conditionalProcessor;
    private DslRenderEngine dslRenderEngine;
    private EsExecutor esExecutor;

    @Override
    public StrategyDebugResult debug(
            String strategyId,
            String version,
            Map<String, Object> params,
            DebugOptions options) {

        StrategyVersion sv =
            versionRepo.findByStrategyIdAndVersion(strategyId, version);

        if (sv == null) {
            throw new IllegalArgumentException("Version not found");
        }

        // 1. 参数解析
        Map<String, Object> resolved =
            resolveParams(sv.getId(), params);

        // 2. 条件 DSL 处理(带记录)
        ConditionTrace trace = new ConditionTrace();
        JsonNode processedDsl =
            conditionalProcessor.processWithTrace(
                sv.getDslTemplate(), resolved, trace
            );

        // 3. DSL 渲染
        String finalDsl =
            dslRenderEngine.render(processedDsl.toString(), resolved);

        // 4. 强制限制
        finalDsl = enforceLimit(finalDsl, options.getLimit());

        // 5. ES 执行
        SearchResult esResult =
            esExecutor.execute(
                sv.getEsCluster(),
                sv.getEsIndex(),
                finalDsl,
                sv.getTimeoutMs()
            );

        // 6. 构造返回
        return StrategyDebugResult.of(
            finalDsl,
            trace,
            esResult
        );
    }
}

6. 平台交互体验(非常重要)

UI 推荐展示顺序

  • 输入参数区

  • 条件命中列表(✔ / ✖)

  • 最终 DSL(可复制)

  • 命中条数

  • Top N 结果

7. 安全与边界控制

调试执行 必须强约束

风险 防护
大查询 强制 size
写操作 禁止
scroll 禁止
update/delete 拦截

(四)平台侧如何支持条件 DSL?

编辑器能力

  • $if 自动补全

  • condition 校验

  • then 结构校验

调试能力

  • 显示命中 / 未命中条件

  • 高亮被裁剪的 DSL 片段

你现在的策略平台已经具备:

✅ 可配置

✅ 可条件化

可验证

✅ 可发布

这四点齐了,才配叫"平台"

八、策略化 ES 查询平台参考"可直接落库"的完整表结构设计

(一)整体数据模型总览

我们先看全局 ER 关系(逻辑层):

(二)策略主表(strategy)

1. 表职责

策略的"产品级"定义

  • 策略唯一标识(strategy_id)

  • 策略归属(业务线 / 团队)

  • 生命周期管理(启用 / 禁用 / 删除)

  • 权限与治理的锚点

2. 表结构设计

sql 复制代码
CREATE TABLE strategy (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    strategy_id VARCHAR(100) NOT NULL COMMENT '策略唯一标识(对外使用)',
    strategy_name VARCHAR(200) NOT NULL COMMENT '策略名称',
    strategy_desc TEXT COMMENT '策略描述',

    biz_domain VARCHAR(100) NOT NULL COMMENT '业务域(推荐/搜索/风控等)',
    owner VARCHAR(100) NOT NULL COMMENT '负责人',
    team VARCHAR(100) COMMENT '所属团队',

    status TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1-启用 0-禁用',
    is_deleted TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除',

    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    UNIQUE KEY uk_strategy_id (strategy_id)
) COMMENT='策略主表';
2.1 为什么 strategy_id 要单独存在?
  • id:数据库内部

  • strategy_id业务和接口唯一标识

  • 支持跨环境、跨系统引用

2.2 biz_domain 的价值
  • 查询治理

  • 权限隔离

  • 监控聚合

(三)策略版本表(strategy_version)

1. 表职责

策略真正"会被执行"的实体

  • DSL 内容

  • 索引配置

  • 查询约束

  • 灰度、发布、回滚的核心

2. 表结构设计

sql 复制代码
CREATE TABLE strategy_version (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    strategy_id VARCHAR(100) NOT NULL COMMENT '策略ID',

    version VARCHAR(50) NOT NULL COMMENT '版本号,如 v1、v2',
    is_active TINYINT NOT NULL DEFAULT 0 COMMENT '是否当前生效版本',

    es_cluster VARCHAR(100) NOT NULL COMMENT 'ES集群标识',
    es_index VARCHAR(200) NOT NULL COMMENT '索引或索引模式',

    dsl_template JSON NOT NULL COMMENT 'ES DSL 模板',
    result_mapping JSON COMMENT '结果映射配置',

    timeout_ms INT DEFAULT 2000 COMMENT '查询超时',
    max_size INT DEFAULT 1000 COMMENT '最大size限制',

    status TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1-可用 0-禁用',

    creator VARCHAR(100) NOT NULL,
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,

    UNIQUE KEY uk_strategy_version (strategy_id, version)
) COMMENT='策略版本表';

关键字段深度解释

2.1 dsl_template(核心)
  • JSON 类型

  • 存储原始模板,含 ${param} 占位符

  • 不做任何预渲染

2.1 result_mapping(高级)

php 复制代码
{
  "fields": ["id", "title", "score"],
  "rename": {
    "create_time": "ctime"
  }
}

(四)策略参数表(strategy_param)

1. 表职责

参数的"强约束来源"

  • 参数类型

  • 是否必填

  • 默认值

  • 校验规则

2. 表结构设计

sql 复制代码
CREATE TABLE strategy_param (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    strategy_version_id BIGINT NOT NULL COMMENT '策略版本ID',

    param_name VARCHAR(100) NOT NULL COMMENT '参数名',
    param_type VARCHAR(50) NOT NULL COMMENT '类型:string/number/list/boolean',
    required TINYINT NOT NULL DEFAULT 0 COMMENT '是否必填',
    default_value VARCHAR(500) COMMENT '默认值',

    validate_rule VARCHAR(500) COMMENT '校验规则(正则或表达式)',
    param_desc VARCHAR(200) COMMENT '参数说明',

    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,

    UNIQUE KEY uk_version_param (strategy_version_id, param_name)
) COMMENT='策略参数表';

参数校验逻辑来源

校验项 来源
是否存在 required
类型校验 param_type
范围校验 validate_rule
默认值 default_value

(五)策略发布表(strategy_publish)

1. 表职责

记录策略发布、切换、灰度历史

2. 表结构

sql 复制代码
CREATE TABLE strategy_publish (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    strategy_id VARCHAR(100) NOT NULL,
    from_version VARCHAR(50),
    to_version VARCHAR(50) NOT NULL,

    publish_type VARCHAR(50) NOT NULL COMMENT 'full / gray',
    gray_rule JSON COMMENT '灰度规则',

    publisher VARCHAR(100) NOT NULL,
    publish_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
) COMMENT='策略发布记录表';

(六)策略权限表(strategy_permission)

sql 复制代码
CREATE TABLE strategy_permission (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    strategy_id VARCHAR(100) NOT NULL,

    subject_type VARCHAR(50) NOT NULL COMMENT 'user / role / team',
    subject_id VARCHAR(100) NOT NULL,

    permission VARCHAR(50) NOT NULL COMMENT 'read / edit / publish',

    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
) COMMENT='策略权限表';

(七)为什么要"拆表这么细"?

解决的问题
strategy 生命周期 & 治理
strategy_version 演进 / 灰度 / 回滚
strategy_param 稳定性 & 安全
strategy_publish 可审计
strategy_permission 可协作

这套表能支撑什么?

✅ 可配置策略

✅ 在线调试

✅ 安全参数校验

✅ 多版本并行

✅ 灰度发布

✅ 策略治理

(八)缓存、性能与稳定性设计

1. 策略缓存

  • Redis / Caffeine

  • key = strategyId + version

  • 支持发布后热更新

2. 查询结果缓存(可选)

  • 高频、幂等策略

  • TTL + 参数 hash

九、总结:你真正做成的是什么?

回到最初的问题------这套设计,并不是一个 ES 的二次封装工具

你真正构建的是一套 面向复杂业务系统的「查询策略基础设施」

它完成了几件非常关键、但常被低估的事情:

  • 查询逻辑从代码中剥离,变成可配置、可演进的策略资产

  • 一次性 DSL 实现,升级为有版本、有发布、有回滚的长期能力

  • 风险后置的线上试错,前移为可验证、可解释的调试执行

  • 个人经验驱动的查询设计,沉淀为组织级、平台级的能力体系

最终,你得到的不是"更好写 ES"的方式,而是:

一套可治理、可验证、可运营、可持续演进的查询能力平台。

当查询具备了 配置、条件、验证、发布 这四个基本能力时,它才真正从"工程实现细节",跃迁为"系统级基础设施"。

相关推荐
Giggle12189 小时前
外卖 O2O 系统怎么选?从架构到部署方式的完整拆解
大数据·架构
九.九9 小时前
CANN 算子生态的底层安全与驱动依赖:固件校验与算子安全边界的强化
大数据·数据库·安全
Coder个人博客15 小时前
Linux6.19-ARM64 mm mmu子模块深入分析
大数据·linux·车载系统·系统架构·系统安全·鸿蒙系统
财经三剑客19 小时前
AI元年,春节出行安全有了更好的答案
大数据·人工智能·安全
岁岁种桃花儿20 小时前
Flink CDC从入门到上天系列第一篇:Flink CDC简易应用
大数据·架构·flink
TOPGUS20 小时前
谷歌SEO第三季度点击率趋势:榜首统治力的衰退与流量的去中心化趋势
大数据·人工智能·搜索引擎·去中心化·区块链·seo·数字营销
2501_9336707921 小时前
2026 高职大数据与会计专业零基础能考的证书有哪些?
大数据
ClouderaHadoop21 小时前
CDH集群机房搬迁方案
大数据·hadoop·cloudera·cdh
TTBIGDATA21 小时前
【Atlas】Ambari 中 开启 Kerberos + Ranger 后 Atlas Hook 无权限访问 Kafka Topic:ATLAS_HOOK
大数据·kafka·ambari·linq·ranger·knox·bigtop