智能路由的设计目标是让用户对底层集群无感知。
用户只提交查询意图和 SLA,平台根据 SQL 指纹、历史执行画像、当前租户权限、数据源能力、集群健康状态和实时资源情况,自动选择最合适的执行环境。
路由层负责生成可解释的
RouteDecision,Linkis 负责将决策落到 EngineConn 和具体计算引擎上。在执行失败时,我会把业务任务和具体执行尝试分开,通过状态机驱动重试和重新路由,而不是简单地重复提交。这样既能保证任务语义不变,又能根据最新资源状态选择新的集群或引擎。
成功率验证:最终通过 SLA 命中率、路由后成功率、重试恢复率、排队时间和资源利用率验证这套智能路由是否真正有效。
总体来说
路由层解决的是多引擎、多集群环境下的执行决策问题。它基于标准化查询意图、SQL 指纹、历史执行画像和实时资源状态,先做权限、兼容性和配额过滤,再根据 SLA、耗时和资源成本选择最终执行环境,生成可解释的
RouteDecision。这个决策通过标准化请求、标签和运行配置转发给 Linkis,由 Linkis 完成 EngineConn 管理和实际执行。如果路由判断错误,则在ExecutionAttempt层进行切换和恢复,执行结果再反馈给画像系统,持续优化后续路由。
1、路由层要解决的问题和设计
路由层要解决的核心问题是:
在多个计算引擎、多个集群和多个资源队列并存的情况下,用户不需要指定 Spark、Trino 还是 JDBC,平台能够根据查询特征和运行状态,自动选择合适的执行环境,并对路由结果负责。
存在的意义
- 业务和引擎耦合:如果没有路由层,自助查询平台就需要直接绑定具体引擎,导致业务语义和基础设施耦合。
- 基于当前情况动态选择资源:平台统一考虑查询规模(元数据提供)、SLA(成功率)、租户配额(你有没有权限、权限的资源够不够)和集群负载。
因此,我会把路由层设计成一个独立的执行决策模块。它不负责真正提交任务,只负责将标准化的查询请求转换为可解释的 RouteDecision。
路由层主要包含四个设计点。
第一,统一路由:统一提交协议、
自助查询平台先将用户的指标、维度、数据集、筛选条件和 SLA 转换成统一的 SubmitRequest,其中包含:
text
1、查询内容:用户到底想执行什么。
QueryIntent
SQL 或 LogicalQueryPlan
2、数据源信息:查询涉及哪些数据,哪些引擎具备执行能力。
DataSource
QueryType
3、租户信息:谁在执行,以及他可以使用什么资源和数据。
Tenant
User
4、历史的SLA:能够给资源推荐
SLA
ResourceHint
路由层不直接依赖页面参数,也不通过 SQL 文本简单猜测引擎,而是基于结构化查询信息做决策。
第二,建立 SQL 指纹和执行画像
路由层对 SQL 做规范化和参数化,生成 SQLFingerprint,并记录该查询过去的执行画像:
- 过去在哪里执行:engine 和 cluster 描述过去在哪里执行
- 查询规模:scanBytes 和 shuffleBytes 描述查询规模
- 查询排队和执行时间:queueTime 和 executeTime 描述性能
- 执行成功率:successRate 和 slaHitRate 描述稳定性
- 使用的内存:peakMemory 和 runtimeConfig 用于资源调优
- 调度与重试:failureReason 用于决定失败后是重试、切换集群还是直接返回错误。
路由层基于这些历史画像,再结合当前租户权限和集群状态,生成最终的执行决策。
这样,路由决策就不只是静态规则,而是可以利用历史执行结果判断:
text
这个 SQL 适合什么引擎
通常需要多少资源
在哪个集群执行更稳定
是否能够满足 SLA
第三,候选过滤和路由决策
- 生成候选集群之后进行约束过滤:硬约束(权限、对应队列是否有权限、集群健康、配额是否够),软约束(历史成功率、预计执行时间、排队时间、集群负载等),进行推荐集群执行。
路由层先生成候选执行环境:
text
Trino / interactive-a
Trino / interactive-b
Spark / offline-a
然后分两步决策。
第一步是硬约束过滤:
text
权限是否满足
数据源是否兼容
SQL 方言是否支持
租户是否有队列权限
集群是否健康
租户配额是否足够
第二步是软指标评分:
text
历史成功率
SLA 命中率
预计执行时间
当前排队时间
资源成本
集群负载
最终形成:
text
engine: Trino
cluster: interactive-a
queue: tenant_a
resource: small
reason:
- scan_size_below_threshold
- historical_sla_hit_rate_high
- cluster_healthy
这里需要强调,路由结果必须可解释。平台不仅要告诉执行哪个引擎,还要保留为什么选择它,便于故障排查、审计和后续优化。
第四,执行失败后的恢复
路由是执行前的预估,可能出现统计信息不准确、实际数据量变大或集群状态变化。因此要把业务任务和具体执行尝试分开:
text
Task:
用户原始查询意图
ExecutionAttempt:
一次具体的执行尝试
如果 Trino 执行超时,可以保留原始 Task,并创建新的 Attempt 切换到 Spark:
text
Attempt 1:Trino / interactive-a,执行超时
Attempt 2:Spark / offline-a,重新执行
不同失败采用不同策略:
text
SQL 语义错误:直接失败,不重试
EngineConn 创建失败:Attempt 级重试
外部提交超时:先查询 applicationId,避免重复提交
资源不足:调整资源或切换集群
集群不可用:重新选择候选环境
所以路由层的完整职责是:
text
识别查询特征
-> 生成候选环境
-> 做权限和资源过滤
-> 选择引擎、集群、队列和资源
-> 输出可解释的 RouteDecision
-> 在 Attempt 层支持失败恢复
2、实际例子:订单查询如何与 Linkis 结合
假设用户在自助查询页面中选择:
text
指标:订单金额
维度:销售渠道
时间范围:近 7 天
SLA:10 秒
自助查询平台先完成业务语义解析和权限校验,生成标准请求:
json
{
"dataset": "orders",
"queryType": "aggregation",
"sql": "SELECT channel, SUM(payment_amount) ...",
"tenant": "tenant_a",
"user": "user_1001",
"slaSeconds": 10
}
路由层根据 SQL 指纹查询历史画像,发现:
text
平均扫描量:5 GB
Trino 平均耗时:12 秒
Spark 平均耗时:90 秒
Trino SLA 命中率:98%
然后获取当前候选环境:
text
Trino / interactive-a:健康,排队 2 秒
Trino / interactive-b:健康,排队 40 秒
Spark / offline-a:健康,但属于离线队列
经过权限、数据源兼容性、租户配额和集群健康度过滤后,路由层选择:
text
engine: Trino
cluster: interactive-a
queue: tenant_a
resource: small
并生成:
text
RouteDecision
路由层不会直接调用 Trino,也不会自己创建连接,而是把这个决策转换成 Linkis 的标准请求和标签:
text
engineType = trino
cluster = interactive-a
queue = tenant_a
runtimeConfig = small
然后提交给 Linkis:
text
RouteDecision
-> SubmitRequest / Labels
-> Linkis Orchestrator
-> EngineConnManager
-> Trino EngineConn
-> Trino Cluster
Linkis 负责完成:
text
任务编排
EngineConn 复用或创建
资源和租户校验
查询提交
状态跟踪
日志获取
结果获取
自助查询平台只需要依赖统一的任务和结果接口,不需要感知底层是:
text
Trino Session
Spark Application
JDBC Statement
如果执行过程中发现实际扫描量远大于预估,Trino 执行超过 SLA,路由层可以终止当前:
text
ExecutionAttempt 1
然后重新生成一个执行决策:
text
engine: Spark
cluster: offline-a
queue: tenant_a
resource: 4 cores / 8 GB
再通过 Linkis 创建:
text
ExecutionAttempt 2
原始任务仍然是同一个:
text
Task:查询近 7 天订单金额
执行结束后,Linkis 返回任务状态、执行指标和结果引用,路由层再将以下信息回写至 SQL 指纹画像,用于持续优化:
text
实际引擎
实际集群
扫描量
排队时间
执行耗时
资源消耗
SLA 是否命中
失败原因
最终形成:
text
自助查询:
用户想查什么
路由层:
选择什么引擎、集群和资源
Linkis:
如何管理和提交任务
Trino / Spark:
真正执行查询
其他
运行时间估算
预计排队时间不是一个精确值,而是基于实时队列状态和历史资源释放速度计算出的预测值。
- 对于 Trino,我会从 Resource Group 获取当前运行查询数、排队查询数、并发上限和查询优先级,再结合最近一段时间的查询完成速率估算。例如当前查询排在第 5 位,集群每秒平均完成 2.5 个查询,那么预计排队约 2 秒。
- 对于 Spark,我会从 YARN 获取目标队列的可用 CPU、内存、Pending Application、前方任务资源需求和当前任务最低启动资源,再结合历史资源释放速度计算。例如前方任务加当前任务共需要释放 32 个 vCore 和 128 GB 内存,队列每秒释放 0.8 个 vCore 和 3.2 GB 内存,那么预计资源等待约 40 秒,再加上调度和 ApplicationMaster 启动开销,预计排队约 45 秒。
路由层最终比较的不是单独的排队时间,而是预计端到端完成时间,也就是排队时间、EngineConn 获取时间、执行时间和结果交付时间之和。任务真正启动后,再用实际排队时间修正预测模型,形成持续反馈闭环。
SLA 计算
Trino SLA 命中率 98% 的意思是:
在统计范围内,路由到 Trino 的同类查询中,有 98% 在约定时间内完成了用户可用的结果交付。
例如,某类 SQL 的 SLA 是 10 秒,最近 7 天共执行了 1000 次:
text
980 次在 10 秒内完成
20 次超过 10 秒或执行失败
SLA 命中率 = 980 / 1000 = 98%
这里的"完成时间"最好按用户端到端体验计算:
text
SLA 耗时 = 排队时间 + EngineConn 获取时间 + 执行时间 + 结果交付时间
不能只计算 Trino 的 SQL 执行时间。比如 SQL 在 Trino 中执行了 6 秒,但排队 5 秒、结果封装 2 秒,总耗时 13 秒,对于 10 秒 SLA 来说仍然没有命中。
指标也不能只说"Trino 整体命中率 98%",必须带上统计维度:
text
SQL 指纹:哪一类查询
集群:哪个 Trino 集群
SLA 档位:10 秒还是 30 秒
租户或资源组:使用什么配额
统计窗口:最近 7 天还是最近 30 天
样本量:总共执行了多少次
完整表达应该是:
最近 7 天,该 SQL 指纹在
trino-interactive-a集群执行了 1000 次,其中980次在10秒内完成结果交付,因此该候选环境的10秒 SLA 命中率是98%。
路由时,98% 表示历史上 Trino 对这类查询具有较高的时效稳定性,但它不能单独决定路由。还要结合样本量、当前排队时间、集群资源、查询扫描量和租户权限。
例如:
text
Trino 历史 SLA 命中率:98%
当前预计排队时间:2 秒
预计扫描量:5 GB
租户有权限,资源充足
可以优先路由到 Trino。但如果当前 Trino 集群已经严重拥堵,即使历史命中率是 98%,本次也可能选择备用 Trino 集群或 Spark。
Trino SLA 命中率 98%,表示在指定统计周期内,同类 SQL 路由到指定 Trino 集群后,有 98% 能在约定时间内完成端到端结果交付。这个指标反映的是历史时效稳定性,路由时还需要结合当前集群负载、预计扫描量、租户权限和样本量一起判断。