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