很多人对 Spark Thrift Server(STS)的印象是:
“能跑,但一多用户就出问题。”
但很少有人从一条 SQL 的完整生命周期,把问题说清楚。
这篇文章只做两件事:
- 把JDBC → Driver → Executor → YARN的完整调用链,按时间顺序拆开
- 用同一条链路,解释Kyuubi 是怎么把 STS 的 7 个结构性问题逐个拆掉的
一、一条 SQL 在 STS 里,到底走了什么路
假设你从 BI 工具点了一下刷新:
SELECTdept,sum(amount)FROMt_orderGROUPBYdept;1️⃣ JDBC 层:入口很简单,但“太集中”
BI 工具通过 JDBC 连接:
jdbc:hive2://sts-host:10000背后发生的是:
- 一个 Thrift 连接被 STS 的
HiveServer2接收 - STS 内部维护一个 JDBC Session(用户身份、临时视图)
- 但所有 Session 都落在同一个 JVM 里
👉 到这里,第一个隐患已经埋下:连接可以很多,但大脑只有一个。
2️⃣ Driver:大脑开始干活
这条 SQL 进入 Driver,顺序非常固定:
SQL 字符串 ↓ Parser(ANTLR)→ Unresolved Logical Plan ↓ Analyzer(查 Catalog,绑定表/列) ↓ Catalyst Optimizer(RBO / CBO) ↓ SparkPlanner → Physical Plan(DAG)然后进入调度层:
DAGScheduler └── 按 Shuffle 切 Stage TaskScheduler └── 把 Task 封装成对象,等待 Executor注意一个关键事实:
这些步骤,100% 发生在一个 Driver JVM 里,而且是所有用户共享这一个 JVM。
3️⃣ Executor:劳工,但不认识“用户”
Driver 向 YARN 申请到 Container 后,Executor 启动。
Executor 内部:
- 接收 Task
- 读 Parquet / HDFS
- 做 Aggregate
- Shuffle Write / Shuffle Read
Executor 只知道一件事:
“Driver 让我算这个 Task。”
它不知道:
- 这是谁发的 SQL
- 属于哪个部门
- 该不该限制资源
4️⃣ YARN:只认识 Application,不认识 SQL
在 YARN 眼里,整个 STS 是:
一个 Application ├── 一个用户(spark / hive) ├── 一个 Queue ├── 一组 Container(Driver + Executors)无论你背后有 1 个用户还是 100 个用户,YARN 看到的信息完全不变。
5️⃣ 结果回流:Driver 再当一次中转站
SQL 执行完:
- 小结果:
collect()回 Driver - Driver 通过 Thrift 一行行发给 JDBC 客户端
- 大结果:写 HDFS,再让客户端读
Driver 同时是:
- 调度器
- 编译器
- 结果缓冲区
把这条链画成一句话
JDBC 连接很多 ↓ 同一个 Driver 编译 + 调度 ↓ 同一组 Executor 计算 ↓ YARN 只看到一个 App所有问题,都长在这条链上。
二、STS 的 7 个问题,本质都是“链上断点”
| 断点位置 | 问题表现 |
|---|---|
| 所有 Session → 一个 Driver | 共享 Driver,用户互相影响 |
| Driver串行调度 | 高并发压垮 Driver |
| Executor 共享 | 一个烂 SQL 拖死所有人 |
| YARN 只看 App | 队列、用户无法精细控制 |
| Driver 单 JVM | 一挂全挂 |
| Executor 生命周期 = App | SQL 结束资源不释放 |
| 无用户维度 | 无法按租户治理 |
STS 不是“没调好”,而是:这条链路从设计上,就不是一个多租户服务。
三、Kyuubi 进来后,链路被改成了什么
Kyuubi 的核心思想,可以用一句话概括:
不在一个 Driver 里服务所有用户,而是让每个用户(或租户)拥有自己的 Driver。
它把原来的“单点大脑”,拆成两层。
四、Kyuubi 的新链路:先看架构分层
JDBC Client ↓ Kyuubi Server(无状态网关) ↓ ──────── 按用户 / 租户路由 ─────── ┌───────────────┐ │ Spark Engine │ ← 一个 Spark App │ (Driver A) │ └───────────────┘ ┌───────────────┐ │ Spark Engine │ │ (Driver B) │ └───────────────┘- Kyuubi Server:只干接入、认证、路由
- Spark Engine:就是一个 Spark Application(等价于一个 STS,但只服务一部分人)
五、同一条 SQL,在 Kyuubi 里怎么走
还是那条 SQL。
1️⃣ JDBC → Kyuubi Server
BI 连接的是 Kyuubi:
jdbc:hive2://kyuubi-host:10009Kyuubi Server 收到后:
- 解析用户名 / 组(LDAP / Kerberos / Ranger)
- 决定这个用户属于哪个 Engine Pool
- 找已有 Engine,或拉起一个新的
👉这一步,STS 没有。
2️⃣ Engine = 专属 Driver
如果这是用户第一次连接:
- Kyuubi 用
spark-submit启动一个 Spark Application - 这个 App 里运行
KyuubiSparkEngine - Engine 内部:
- 有自己的 Driver
- 有自己的 Executor
- 向 YARN 注册成一个独立 Application
现在链路变成:
用户 A 的 SQL → Engine A(Driver A) 用户 B 的 SQL → Engine B(Driver B)3️⃣ Driver 内:和 STS 一模一样,但范围变小了
Engine 内部的 Driver,做的事和 STS 一样:
- 解析 SQL
- 优化
- 切 Stage
- 调度 Task
区别是:
这个 Driver 只服务一个用户 / 一个组。
4️⃣ Executor:终于可以“认人”了 (资源隔离,不同用户会调度到不同的运行资源)
因为 Engine 是独立的 Spark App:
- 每个 Engine 有自己专属的 Executor
- 这些 Executor 只跑这个 Engine 的 Task
YARN 看到的是:
App1 → 用户 A → Queue A App2 → 用户 B → Queue B👉队列、资源、权限,第一次对齐到“人”。
5️⃣ 结果回流:网关只做转发
Engine 把结果返回给 Kyuubi Server:
- Kyuubi Server 不计算
- 不编译 SQL
- 不维护物理计划
- 只负责 Thrift RPC 转发
Driver 不再是“接入 + 调度 + 结果中转”三合一。
六、逐条对照:Kyuubi 怎么解决 STS 的问题
1️⃣ STS:所有用户共享 Driver
Kyuubi:一人一个 Engine
- 用户 A 的烂 SQL 只打爆自己的 Driver
- 用户 B 完全无感
2️⃣ STS:Driver 高并发瓶颈
Kyuubi:压力被 Engine 水平拆分
- 原来:100 个连接 → 1 个 Driver
- 现在:100 个连接 → 10 个 Engine,每个 10 个连接
Driver 不再是全局瓶颈。
3️⃣ STS:用户资源不隔离
Kyuubi:Executor 天然隔离
- 一个 Engine OOM → 只杀这个 Engine
- 其他 Engine 的 Executor 不受影响
4️⃣ STS:一挂全挂
Kyuubi:Engine 是独立 Application
- 某个 Engine Driver 挂了
- Kyuubi Server 还在
- 其他用户继续用
- 这个用户下次连,自动拉新 Engine
5️⃣ STS:YARN 队列控制不精细
Kyuubi:每个 Engine 是独立 App
可以配置:
user A → queue.finance user B → queue.biKyuubi 在spark-submitEngine 时,直接带:
--queue queue.financeYARN 第一次“看见用户”。
6️⃣ STS:生命周期耦合
Kyuubi:Engine 可自动销毁
Kyuubi 支持:
- 空闲 Engine 自动退出
- SQL 结束 → Executor 回收(Dynamic Allocation)
App 生命周期 ≈ 用户活跃周期,而不是“永远活着”。
7️⃣ STS:无法按租户治理
Kyuubi:天然多租户模型
- 可以统计:每个 Engine 的 CPU / 内存
- 可以限制:单用户最大 Engine 数
- 可以限制:单个 Engine 的 core / memory
治理从“猜”变成“可观测”。
七、一句话对比两条链路
STS 的链路
JDBC ↓ 一个 Driver(所有用户) ↓ 一组 Executor(所有人) ↓ YARN:一个 AppKyuubi 的链路
JDBC ↓ 无状态网关 ↓ 按用户路由 ↓ 专属 Driver ↓ 专属 Executor ↓ YARN:多个 App八、本质差异不是“功能”,而是“抽象”
STS 的假设是:
Spark 是一个计算引擎,我给它加个 JDBC 壳。
Kyuubi 的假设是:
数据服务应该是多租户系统,Spark 只是计算内核。
所以:
- STS 把 Spark 当成“服务进程”
- Kyuubi 把 Spark 当成“可被编排的计算单元”
九、最后给架构师的一句话
如果你今天还在用 STS,而且:
- 用户数 > 10
- 有部门隔离诉求
- 有 SLA 要求
那么问题从来不是:
“STS 参数怎么调?”
而是:
“你是否需要一个真正的多租户 SQL Gateway。”
Kyuubi 的答案,不在功能列表里,而在那条被拆开的 Driver 链路上。