Open Distro for Elasticsearch SQL引擎架构详解:一条SQL从ANTLR解析到Elasticsearch DSL的完整旅程
【免费下载链接】sql🔍 Open Distro SQL Plugin项目地址: https://gitcode.com/gh_mirrors/sq/sql
Open Distro for Elasticsearch SQL 插件让你用标准 SQL 直接查询 Elasticsearch 索引,而不用手写 JSON DSL。它的核心是一套自研 SQL 引擎:SQL 语句先经 ANTLR 语法解析,再经过语义分析、逻辑计划优化,最终被翻译成 Elasticsearch DSL 并异步执行返回结果。本文带你完整走一遍一条 SQL 在引擎内部的旅程 🚀。
整体架构:四大模块一张图看懂
这套 SQL 引擎可以拆成四个各司其职的核心模块:
| 模块 | 职责 | 一句话理解 |
|---|---|---|
| Parser 解析器 | 基于 ANTLR 语法文件生成解析树 | 读懂 SQL 的"语法" |
| Analyzer 分析器 | 语法与语义分析、类型检查 | 确认查询"说得通" |
| Core Engine 核心引擎 | 逻辑计划 → 优化 → 物理计划 | 决定"怎么查最快" |
| Execution 执行层 | 在工作线程执行计划并返回结果 | 向 ES 发出真正的 DSL 请求 |
一条 SQL 进入引擎后依次穿越这四个模块,官方架构文档中有完整的流转示意图:
更详细的文字说明可参考架构文档docs/dev/Architecture.md。
第 1 站:ANTLR 把 SQL 字符串变成解析树
一切从语法文件开始。项目用 ANTLR 定义了完整的 SQL 文法规则:
- 词法/语法定义:
sql/src/main/antlr/OpenDistroSQLParser.g4(配套的词法文件是sql/src/main/antlr/OpenDistroSQLLexer.g4) - 解析入口:
sql/src/main/java/com/amazon/opendistroforelasticsearch/sql/sql/antlr/SQLSyntaxParser.java
以这条聚合查询为例:
SELECT user, count(*) AS cnt FROM accounts GROUP BY user解析器做了三件贴心小事:
- 大小写不敏感:内部用
CaseInsensitiveCharStream包装输入,select和SELECT等价; - 友好的报错:挂了一个自定义
SyntaxAnalysisErrorListener,语法写错时给出可读的提示,而不是抛出一串堆栈; - 输出解析树(CST):这是文法层面的树,还不是最终形态,下一站再"提纯"。
第 2 站:AST 构建与语义分析,类型检查在这里把关 🔍
解析树只是"骨架",引擎还需要一个语义化的抽象语法树(AST):
sql/src/main/java/com/amazon/opendistroforelasticsearch/sql/sql/parser/AstBuilder.java负责把 CST 转换为UnresolvedPlan(未解析的 AST);- 随后
core/src/main/java/com/amazon/opendistroforelasticsearch/sql/analysis/Analyzer.java以访问者模式逐节点遍历 AST,完成符号解析与类型推导。
语义分析的核心是类型环境(TypeEnvironment):分析到FROM表时,先把该索引的每个字段及其类型"注册"进符号表;之后每个表达式(比如user字段、count(*)、WHERE age > 18)都会在类型环境中推导自己的类型。
类型是怎么一层层"合成"出来的?下图直观展示了字面量、字段引用、函数调用各自的类型合成规则:
这就是为什么写错函数参数时会收到精确报错,例如:
Function [LOG] cannot work with [INTEGER, KEYWORD]. Usage: LOG(NUMBER T) → DOUBLE
报错里同时告诉你了函数的正确用法,对新手非常友好。WHERE 条件中的谓词(AND/OR/比较/IN)也都在这一阶段被逐一解析、优化:
第 3 站:逻辑计划优化为物理计划 ⚙️
语义分析的输出是一棵逻辑计划树(LogicalPlan)——Filter、Aggregation、Sort、Limit等逻辑算子按查询结构组装,此时还和任何存储无关。
接着core/src/main/java/com/amazon/opendistroforelasticsearch/sql/planner/Planner.java上场:
- 先用基于规则的优化器(
LogicalPlanOptimizer)改写逻辑计划,比如MergeFilterAndFilter(合并相邻过滤)、PushFilterUnderSort(过滤下推)等规则; - 再委托存储引擎实现成物理计划(PhysicalPlan)。
对 Elasticsearch 而言,最有价值的优化是把多个算子合并成一次 ES 请求。规则目录elasticsearch/src/main/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/planner/logical/rule/下就有专门的规则,例如:
MergeAggAndIndexScan.java:把"聚合 + 索引扫描"合并为一个带聚合的 ES 查询,避免先拉全量数据再内存聚合;MergeSortAndIndexScan.java:把排序下推到 ES 的sort,让倒排索引直接排好序。
这些合并规则正是 SQL 引擎在大数据量下保持高性能的关键。
第 4 站:翻译成 Elasticsearch DSL 并异步执行 🛠️
物理计划落地时,elasticsearch/src/main/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/storage/ElasticsearchStorageEngine.java及其存储实现会把算子翻译成 ES 原生能力:
- 过滤条件→ Lucene 查询(Term / Range / Wildcard),无法映射的复杂表达式退化为脚本过滤(
.../storage/script/filter/FilterQueryBuilder.java); - 聚合→ ES 的 Bucket / Metric 聚合构建器(
.../storage/script/aggregation/目录); - 排序→ ES
sort子句(.../storage/script/sort/SortQueryBuilder.java)。
执行阶段由elasticsearch/src/main/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/executor/ElasticsearchExecutionEngine.java负责。它有两个重要的工程设计:
- 线程模型:解析、分析、计划都在 ES 传输线程上完成(禁止阻塞操作),而真正发请求的执行动作放到工作线程池中,避免拖垮集群;
- 分页与保护:大结果集用 scroll 游标分页返回,同时执行保护器(
.../executor/protector/)监控内存消耗,防止 JOIN 等内存操作影响 Elasticsearch 的可用性。
执行完成后,响应被解析、格式化为统一的结果结构(默认 JDBC 风格:schema + 行数据),返回给客户端。至此,一条 SQL 完成了从文本到 ES DSL 的完整旅程。
如何亲手体验这条旅程
不需要深入代码,三种方式任选其一即可跑通:
- Kibana SQL Workbench:安装插件后在 Kibana 中直接写 SQL 并查看执行计划(
workbench/目录即其源码); - 命令行 CLI:
sql-cli/提供了终端版 SQL 客户端; - 拉下源码阅读,跟着本文四个模块的顺序看实现:
git clone https://gitcode.com/gh_mirrors/sq/sql下图是 SQL CLI 的实际使用效果,一条 SQL 回车即得表格化结果:
关键模块速查表:架构到源码的对应关系
| 旅程阶段 | 核心类/文件 | 参考路径 |
|---|---|---|
| ANTLR 语法定义 | OpenDistroSQLParser | sql/src/main/antlr/OpenDistroSQLParser.g4 |
| 解析入口 | SQLSyntaxParser | sql/src/main/java/com/amazon/opendistroforelasticsearch/sql/sql/antlr/SQLSyntaxParser.java |
| CST → AST | AstBuilder | sql/src/main/java/com/amazon/opendistroforelasticsearch/sql/sql/parser/AstBuilder.java |
| 语义分析 | Analyzer | core/src/main/java/com/amazon/opendistroforelasticsearch/sql/analysis/Analyzer.java |
| 逻辑→物理计划 | Planner | core/src/main/java/com/amazon/opendistroforelasticsearch/sql/planner/Planner.java |
| 执行引擎接口 | ExecutionEngine | core/src/main/java/com/amazon/opendistroforelasticsearch/sql/executor/ExecutionEngine.java |
| ES DSL 翻译 | ElasticsearchStorageEngine | elasticsearch/src/main/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/storage/ElasticsearchStorageEngine.java |
| ES 合并优化规则 | logical rule 目录 | elasticsearch/src/main/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/planner/logical/rule/ |
| ES 执行引擎 | ElasticsearchExecutionEngine | elasticsearch/src/main/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/executor/ElasticsearchExecutionEngine.java |
| 插件入口 | SQLPlugin | plugin/src/main/java/com/amazon/opendistroforelasticsearch/sql/plugin/SQLPlugin.java |
| 架构总览文档 | Architecture.md | docs/dev/Architecture.md |
小结
回顾这条 SQL 的旅程:ANTLR 解析树 → AST 与语义/类型检查 → 逻辑计划优化 → 合并为 ES 原生查询 → 工作线程异步执行 → 统一格式返回。整个设计有两个值得借鉴的思路——用"逻辑计划/物理计划"两层抽象把查询语义与存储实现解耦,让引擎可以面向不同存储;以及把重操作移出 ES 传输线程并加上资源保护,保证查询插件不会拖垮集群。理解了这四站,再去看core、sql、elasticsearch三个模块的源码,基本就不会迷路了。
【免费下载链接】sql🔍 Open Distro SQL Plugin项目地址: https://gitcode.com/gh_mirrors/sq/sql
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考