ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Kyuubi SQL网关架构详解:从Spark Thrift Server到多租户引擎管理

Kyuubi SQL网关架构详解:从Spark Thrift Server到多租户引擎管理 1. Kyuubi 到底解决了什么问题——SQL 网关的诞生背景SQL 网关这个词听起来像是数据库中间件但在 Spark 生态里它解决的问题要更细。Apache Kyuubi 是一个分布式 SQL 网关定位是在 JDBC/ODBC 客户端与底层计算引擎Spark SQL、Flink SQL、Trino之间加一层独立的服务。简单说用户拿 beeline 或者 BI 工具连上来发一条 SQLKyuubi 负责接收请求、分配引擎、管理会话并把结果返回回去用户完全不用关心背后引擎实例是怎么拉起、复用和销毁的。这篇文章是 Kyuubi 系列的第一篇适合刚接触 SQL 网关、或者用过 Spark Thrift Server 但觉得不好用的读者。我会先讲 Kyuubi 要解决的问题然后拆解核心架构讲清楚 Engine 共享级别这几个关键概念再给出一套本地最小安装步骤最后聊聊运行中最常遇到的坑。如果你正在给团队搭一个统一 SQL 查询入口这篇可以当作选题前的背景资料也可以直接照着跑通一个验证环境。1.1 一个个 Spark 任务进程的「临时性」痛点在 Kyuubi 出现之前团队用 Spark 做 SQL 分析最常见的方式是spark-submit提交一个 Jar 包或者在 Notebook 里写一段代码。这两种方式有一个共同点Spark 任务是临时的跑完就退出。每次提交都要重新拉起一个 SparkContext从任务调度到 executor 分配快则几十秒慢则几分钟。如果只是做一次跑批这个开销可以接受但要让业务同学拿着 SQL 来回试探、看数据分布、调 join 逻辑那就非常痛苦了。我自己经历过一个真实场景数据团队接到一个需求要给运营临时开几十个查询每个人都是那种「我改一个 where 条件帮我再跑一遍」的节奏。如果每个人来了都重启一次 Spark 作业集群资源会被反复占满排队时间比执行时间还长。更重要的是业务同学根本不想接触命令行他们只知道填一个 SQL 查询框。这个场景就是 SQL 网关的核心价值把「一段 SQL」和「一个计算引擎进程」解耦。用户只需要像连普通数据库一样连接一个服务剩下的引擎生命周期统一由网关管理。1.2 从 Spark Thrift Server 到独立网关的演进提到 SQL 网关很多人会先想到 Spark 自带的 Thrift Server。Spark Thrift Server 确实解决了一部分问题它的做法是在 Spark Driver 进程里直接启动一个 Thrift 服务客户端可以像连接 Hive 一样连接这个端口提交 Spark SQL。但这个方案有几个绕不开的短板单点风险非常高。Thrift Server 和 Spark Driver 绑死在同一个进程里进程一挂所有连接全部断掉而且没有自动恢复机制。引擎粒度太粗。Thrift Server 内部只维护一个 SparkContext所有用户、所有查询都共享这同一个计算上下文。不同业务之间的资源隔离基本靠运气。集群模式下不好拓展。Thrift Server 通常跑在一个固定节点上资源上限被单节点限制做不到按用户、按组动态拉起多个引擎。我见过不少团队一开始图省事上了 Spark Thrift Server结果线上业务一多就频繁出现「某个大查询把队列打满其他查询全部饿死」的情况。问题不在于 Spark 本身而是 Thrift Server 把网关逻辑和引擎逻辑耦合在了一起。Kyuubi 的思路正好相反把网关和引擎拆开。网关是一个独立的前端服务负责接入和转发引擎是独立的 Spark 应用可以按需启动、横向扩展、按用户隔离。两者之间通过注册中心发现前端挂了可以横跨多个节点做高可用引擎挂了不会影响网关本身。1.3 Kyuubi 在其中的角色定位放到整条数据链路里看Kyuubi 的定位很清楚它是客户端和计算引擎之间的一道「调度 接入」层而不是一个新的计算引擎。Kyuubi 自己不做 SQL 解析执行它只负责把请求代理给一个合适的引擎然后再把引擎返回的结果透传给客户端。具体来说Kyuubi 承担了这么几件事统一接入入口。不管底层用 Spark SQL、Flink SQL 还是 Trino对客户端都暴露一个兼容 Hive 协议的 JDBC 端口。引擎生命周期管理。当用户第一次连接时拉起一个引擎空闲时按策略关闭多个连接之间可以复用同一个引擎。多租户隔离。通过共享级别控制引擎是每个用户独享、每个组共享还是全局共享。认证与授权衔接。支持 LDAP、Kerberos 等认证方式也能和 Ranger 这类权限体系对接。所以如果只看名字Kyuubi 好像只是「又一个 SQL 服务」但在实际架构里它更像是计算引擎前面的智能路由器。你不需要再告诉用户「去连这个 Spark 端口、那个 Flink 端口」只要给一个稳定的 SQL 网关地址就够了。2. Kyuubi 核心架构拆解一次 SQL 请求的完整旅程2.1 前端 KyuubiServer 与后端 Engine 边界Kyuubi 的架构可以从两个角色切入前端叫 KyuubiServer后端叫 Engine。KyuubiServer 是一个独立运行的 Java 进程对外提供 Thrift 服务。它做的事情包括监听客户端连接、解析连接参数、认证、根据用户信息决定复用哪个引擎、把 Session 和 Operation 转发给引擎。这里的关键点在于KyuubiServer 本身绝不执行 SQL也不持有计算资源所以它可以做得很轻可以部署多个实例实现高可用。Engine 是真正执行查询的进程。以 Spark 引擎为例一个 Engine 本质上就是一个跑起来的 Spark 应用里面初始化了一个 SparkSession并在该 Session 内注册一个 Thrift 服务等待 KyuubiServer 过来转发请求。引擎一旦启动可以服务多个来自同一用户或同一组的连接这样能省掉大量重复启动 SparkContext 的开销。这两层分得越清楚架构优势就越明显。KyuubiServer 需要扩容时直接多起几个实例即可Engine 需要不同资源配置时也可以按用户、按业务单独指定 Spark 参数而不是整个网关共享一套配置。2.2 请求链路Connection、Session、Operation 三层理解了分层之后再看一次 SQL 请求的完整链路会容易很多。Kyuubi 沿用 Hive 协议里的三层模型Connection、Session、Operation。第一步是建立 Connection。客户端通过 JDBC URL 连接到 KyuubiServer例如jdbc:hive2://localhost:10009/default。KyuubiServer 收到连接请求后先做身份认证然后基于连接信息确定这个用户应该使用哪种共享级别的引擎。第二步是创建 Session。Session 是 Kyuubi 里管理状态的最小单元类似于一个数据库连接下的上下文。同一个连接可以创建多个 SessionSession 里可以设置变量、切换数据库、提交多批查询。第三步是执行 Operation。当客户端执行select * from table时这个 SQL 会被封装成一个 Operation。KyuubiServer 把 Operation 转发给目标 EngineEngine 执行后把结果集返回。KyuubiServer 在其中只是做了一个透明代理并把执行状态、日志信息同步给客户端。这个链路看起来多了一层实际收益非常明显。客户端只需要和 KyuubiServer 通信不需要知道 Engine 的地址Engine 也不需要直接暴露给用户外部无法绕过网关去访问底层计算资源。2.3 高可用与引擎发现ZooKeeper 的作用Kyuubi 的高可用和引擎发现依赖 ZooKeeper这一点在生产环境特别重要。在 HA 模式下多个 KyuubiServer 实例会同时启动并在 ZooKeeper 上注册同一个服务节点。客户端通过 ZooKeeper 自动发现当前可用的 KyuubiServer避免单点故障。如果其中一个 KyuubiServer 宕机客户端可以自动切换到另一个实例原有的连接会按协议断掉重连但整体服务不会立即可用性归零。Engine 的发现同样依赖 ZooKeeper。每个 Engine 启动后会在 ZooKeeper 的某个路径下注册自己的地址和元信息。KyuubiServer 收到新连接时先到 ZooKeeper 里找有没有符合「共享级别 用户 引擎类型」条件的 Engine如果有就直接复用没有才拉起新的 Engine。我建议做本地验证时至少也要把 ZooKeeper 跑起来因为如果跳过引擎发现环节Kyuubi 根本没法复用引擎也就无法体现它和 Spark Thrift Server 的核心差异。3. 关键设计Engine Share Level、多租户与扩展点3.1 CONNECTION/USER/GROUP/SERVER 四档共享级别Kyuubi 最值得先搞懂的概念是 Engine 的共享级别也就是kyuubi.engine.share.level这个配置项。它决定了一个 Engine 可以被多少个客户端连接共用是理解 Kyuubi 资源模型的核心。常见的四种级别如下共享级别粒度含义适用场景CONNECTION连接级每个 JDBC 连接都单独拉起一个 Engine需要严格隔离的场景但资源开销最大USER用户级同一个用户的所有连接共用一个 Engine大多数业务分析场景推荐GROUP用户组级属于同一个用户组的所有用户共用一个 Engine团队共享资源按组隔离SERVER服务级所有用户共用一个 Engine轻量测试不推荐生产使用我在实际使用中最常用的是 USER 级别。它的好处很直观同一个分析师反复打开 BI 报表、执行多条 SQL 时会复用同一个 Engine不用每次连接都等 Spark 启动同时不同用户之间又有引擎级别的隔离某人跑一个大查询不会直接影响其他人。CONNECTION 级别听起来隔离性最好但容易被滥用。如果业务方连接池开了 50 个连接可能就意味着 50 个 Spark 引擎同时被拉起来集群瞬间被打爆。所以除非有强隔离需求否则不要轻易把所有连接都设为 CONNECTION。3.2 认证与授权多租户的安全底座多租户并不是只靠共享级别就能实现的权限才是真正决定「谁可以用什么资源」的部分。Kyuubi 的认证层面支持多种方式默认可能是匿名访问也支持 LDAP、Kerberos 等企业级认证。配置认证时要注意KyuubiServer 本身只是接入层真正的 SQL 执行权限最终还是由引擎端承担例如 Spark SQL 的 Hive Metastore Authorization或通过 Ranger 做细粒度权限控制。有一种很容易被忽略的情况KyuubiServer 由服务账号启动拉起来的 Engine 默认也可能以这个服务账号运行。如果不开用户模拟impersonation那么所有 SQL 实际访问 HDFS 或 Hive 表的身份都是同一个服务账号权限区分就形同虚设。所以当出现「明明给用户 A 配了表权限但查询却提示没有权限」的时候首先要检查的不是权限策略而是引擎到底以哪个身份在跑。另外网关层也要对 SQL 内容做基本校验和审计至少要把异常查询、慢查询、失败查询记录下来。安全这块不能只依赖数据库本身网关是面向用户的统一入口天然适合做访问审计。3.3 事件处理器与服务扩展机制如果只把 Kyuubi 当作一个连接代理会低估它的可扩展性。Kyuubi 提供了事件处理器Event Handler机制可以订阅连接建立、连接关闭、引擎创建、操作完成等事件。这套机制对数据团队的运维价值很高。比如你可以写一个事件处理器把每次查询的用户、执行时间、扫描行数、返回行数写入监控系统做一个统一的 SQL 审计面板。Kyuubi 的连接数、引擎数、空闲引擎数也可以作为指标暴露出来方便在集群资源紧张的时候提前发现风险。在系列后面的文章里我会单独讲插件扩展和事件接入的具体写法。第一次接触时不需要钻太深但心里要清楚Kyuubi 不是一个封闭的黑盒它留了很多口子给平台侧做定制。4. 本地跑通最小 Kyuubi 环境从下载到第一条 SQL4.1 版本与依赖怎么选最省心的一套组合纸上谈兵没用跑一个真实环境才能理解 Kyuubi。先说版本组合我验证过比较省心的一套是Kyuubi 1.7.x Spark 3.3.x Java 8/11 本地单节点 ZooKeeper。Kyuubi 和 Spark 的版本兼容性不需要完全死板但尽量选接近官方发布文档里的组合能少踩不少坑。如果你还没有安装包可以去 Apache 官网下载编译好的发行包。Spark 侧不需要手动编译用标准发行版即可ZooKeeper 也只需要一个本地节点测试环境不需要集群。依赖方面要注意Kyuubi 运行时要能找到SPARK_HOME因为引擎是通过 Spark Launcher 拉起来的。Java 版本必须满足 Kyuubi 和 Spark 两边的要求如果本机同时装了多个 JDK需要在启动脚本里显式把 Java 路径指对否则会看到一些奇怪的 ClassNotFound 异常其实只是 JDK 版本不对。4.2 配置与启动步骤下面按最小可行环境来操作。假设你已经把 Kyuubi 解压到了/opt/kyuubiSpark 解压到了/opt/spark。第一步设置环境变量并复制默认配置export KYUUBI_HOME/opt/kyuubi export SPARK_HOME/opt/spark cd $KYUUBI_HOME cp conf/kyuubi-defaults.conf.template conf/kyuubi-defaults.conf第二步编辑conf/kyuubi-defaults.conf写入最简配置kyuubi.engine.typeSPARK_SQL kyuubi.engine.share.levelUSER kyuubi.engine.spark.masterlocal[*] kyuubi.engine.spark.sql.catalogImplementationin-memory kyuubi.engine.spark.sql.warehouse.dir/tmp/kyuubi-warehouse kyuubi.frontend.bind.host0.0.0.0 kyuubi.frontend.bind.port10009 kyuubi.zookeeper.quorumlocalhost:2181这里把master设置成local[*]只是为了本地验证生产环境通常换成 YARN 或 Kubernetes。catalogImplementationin-memory可以避免 Spark 会话在启动时去连 Hive Metastore省掉本地环境的额外依赖但生产环境一般要改成hive并配置真实的 Metastore 地址。第三步先启动 ZooKeeper再启动 Kyuubi# 启动 ZooKeeper不同发行版的启动方式不同这里是通用命令 zkServer.sh start # 启动 Kyuubi $KYUUBI_HOME/bin/kyuubi start $KYUUBI_HOME/bin/kyuubi status如果status显示正在运行就可以继续验证。启动过程中遇到报错先去$KYUUBI_HOME/logs目录看kyuubi.log这个日志比控制台输出完整得多。4.3 beeline 验证与日志观察Kyuubi 启动了用 Spark 自带的 beeline 连一下$SPARK_HOME/bin/beeline -u jdbc:hive2://localhost:10009/default -n test进入 beeline 后执行show databases; select 1;第一次执行show databases时会有几秒延迟因为 Kyuubi 需要先为当前用户拉起一个 Spark 引擎。等引擎启动完成后再执行第二条语句就会明显变快。USER 级别的引擎会继续保留在后台后续同一个用户的新连接直接复用。这里有个很容易误判的地方select 1本身很快但如果你是从零连接等待时间可能主要花在「拉起 Spark 引擎」上而不是查询本身。要验证引擎复用可以再开一个 beeline 窗口用同一个用户连进去执行查询观察第二次连接是不是秒回。5. 实战中绕不开的坑启动失败、内存与权限5.1 Spark 会话初始化失败本地环境最常见的第一道坎第一次跑 Kyuubi很多人在第一步就卡住Kyuubi 启动成功但一执行 SQL 就报 Spark Session 相关的错误。最常见的表现是类似「Unable to instantiate SessionHiveMetaStoreClient」的异常。原因一般是 Kyuubi 拉起的 Spark 引擎想初始化 Hive Metastore但本地没有配置任何 Metastore 地址。对于本地验证只要在kyuubi-defaults.conf里设置kyuubi.engine.spark.sql.catalogImplementationin-memory就能绕过去。如果你确实需要测试 Hive 表那就不能再省这一步了必须搭一个独立 Hive Metastore并正确配置 Metastore 连接参数。这类问题排查的通用方法是不要只看 KyuubiServer 的日志还要看引擎进程自己的日志。Kyuubi 拉起 Spark 引擎时Spark 侧的日志通常会输出到$SPARK_HOME/logs或 YARN 的日志目录具体位置取决于提交方式和配置。光看网关日志很难定位到真正的根因。5.2 Engine 内存与资源不匹配Kyuubi 本身的进程内存通常不用配很大因为它的业务是「转发」而不是「计算」。真正吃内存的是 Spark 引擎也就是每个被拉起来的 SparkSession 进程。常见的问题有两个。第一个是本地验证时把kyuubi.engine.spark.executor.memory配得过大机器根本扛不住引擎反复挂掉。第二个是在 YARN 环境里没有考虑队列资源多个用户各自拉起 Engine 后集群总内存瞬间不够提交新 Engine 时一直失败。我的建议是先在配置里显式控制引擎资源上限即使默认值看着没问题也要写清楚kyuubi.engine.spark.driver.memory2g kyuubi.engine.spark.executor.memory4g kyuubi.engine.spark.executor.cores2有人会把 KyuubiServer 的内存和 Spark Engine 的内存搞混这是块容易踩坑的点。调优时先确认是谁在报错如果 KyuubiServer 本身频繁 OOM那是接入层内存不足如果是 Spark 引擎启动后不久丢失那是引擎资源配置的问题。两者参数完全不一样不能混着调。另外USER 级别共享引擎会放大单用户资源占用。如果一个用户连接了多个 BI 报表所有查询都落在同一个 Engine 上这个 Engine 的负载可能很高。遇到这种情况可以考虑把少数重度用户调到 CONNECTION 级别或者单独给这些用户配置更大资源的 Engine。5.3 认证和权限配置不生效权限不生效是生产环境最让人头疼的问题表面上看配置都已经打开了但用户执行查询时该能看到的数据就是看不到或者不该看到的数据反而看到了。一个很隐蔽的原因是前面提过的用户模拟没开。比如 LDAP 认证已经配好KyuubiServer 也确认了登录用户是zhangsan但 Engine 在拉起来的时候并没有切换到zhangsan身份执行 SQL而是继续以kyuubi这个服务账号运行。这种情况下Hive 表权限、HDFS 目录权限都只会按服务账号来校验所有用户的行为看起来都一样。建议在接入 Ranger 或 Hive Authorization 之前先做一个「引擎身份确认」的小测试在 beeline 里执行select current_user()看看返回的到底是登录用户还是服务账号。如果不一致优先检查 Kyuubi 的 impersonation 相关配置。不同版本配置项名称有差异不要凭记忆去猜直接查对应版本文档的认证与代理配置章节。安全上还有一个容易被想的太简单的地方Kyuubi 暴露的是 JDBC 端口只要网络可达任何支持 Hive 协议的客户端都能连上来。内网环境也不能裸奔至少要开启认证并配合防火墙或安全组限制来源 IP。千万不要把网关做成一个「谁都能匿名连接」的服务。6. Kyuubi 生态里的位置和 STS/HiveServer2 的关系6.1 三张表的横评HiveServer2、Spark Thrift Server、Kyuubi接触 Kyuubi 的过程中很多人会拿它和 HiveServer2、Spark Thrift Server 做对比。简单整理一张对照表能看得更清晰组件底层引擎引擎粒度多租户能力高可用动态引擎HiveServer2Hive on MR/Tez/Spark一个 HiveServer2 进程内共享一般靠 Session 隔离可多实例部署弱Spark Thrift ServerSpark SQL单个 SparkContext弱所有客户端共用单点为主弱KyuubiSpark SQL / Flink SQL / Trino按连接/用户/组独立 Engine强引擎级隔离多 KyuubiServer ZK强这并不意味着 Kyuubi 可以完全替代 HiveServer2。如果你的技术栈还是纯 Hive并且团队已经围绕 HiveServer2 建立了一套成熟的工具链那么迁移 Kyuubi 的收益要重新评估。Kyuubi 更适合的场景是「底层本来就是 Spark 分布式计算引擎但需要一个企业级 SQL 接入层」的团队。6.2 在数据湖/数仓建设中的典型接入方式在实际数仓体系里Kyuubi 最典型的接入形态是放在 BI 工具和计算引擎之间。举个例子BI 报表平台通过 JDBC 连接 KyuubiKyuubi 根据报表账号把请求路由到 Spark 引擎同时数据开发需要跑 Flink SQL 做流式数仓也可以复用同一个网关层只是把引擎类型指向 Flink。这样的设计给平台带来的改变是明显的应用侧不需要记多个引擎地址一个 SQL 网关搞定。引擎部署方式变化时应用侧零感知。比如从 Spark 3.2 升级到 3.5只需要调整 Kyuubi 的引擎配置JDBC 地址不变。审计、监控、权限可以收敛在网关层做统一处理。与数据湖的集成也顺理成章。Kyuubi 的 Spark 引擎可以直接对接 Iceberg、Hudi、Delta Lake 这类表格式业务侧看起来仍然只是在执行标准 SQL不需要感知底层文件格式。6.3 后续系列学习路线基础讲到这里Kyuubi 的框架性内容已经差不多。如果想要继续深入我建议下一个阶段按这几个方向走一是 Kyuubi 与 Ranger 的权限细粒度集成二是生产环境的高可用部署与 ZooKeeper 会话治理三是基于 Event Handler 构建 SQL 审计平台四是多引擎统一网关的踩坑记录。这个系列的后续文章我会逐步拆这些方向。第一篇的目的很简单就是帮你在脑子里立起一张 Kyuubi 的架构地图它不是一个临时工具而是一个值得长期打磨的 SQL 接入层。搞懂它解决的问题再看后面每一个配置项和报错都会顺很多。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进