☰
Kyuubi SQL网关实战指南:从架构到部署的全面解析
2026/10/3 3:56:11 网站建设 项目流程

做大数据平台的同学,对“SQL网关”这个词应该不陌生。如果你是第一次听说,可以把它理解成:把底层Spark、Flink这些计算引擎包装成一个标准数据库接口,让BI工具、应用程序、甚至编辑器都可以用SQL直接查询数据,不需要关心底层是怎么跑的。Kyuubi就是这类工具里目前非常成熟的一个开源项目。它基于Apache Spark,兼容HiveServer2协议,解决了多租户、高并发、资源隔离这一堆现实问题。这篇文章从一个使用者的角度,把我对Kyuubi的理解、部署过程和踩坑经验都整理出来,适合正在调研SQL网关选型、或者准备把Spark服务化的同学参考。

1. SQL网关到底是什么,先聊聊没有它时的痛点

1.1 没有网关的时候,数据团队都在干什么

早期做大数据,最常见的入口就是Spark Shell或者提交Spark Jar。开发同学写一个程序,打成jar包,扔到集群上跑;数据分析师要取数,要么找开发帮忙,要么自己用Hive客户端敲SQL,要么把数据导到关系型数据库里再用BI查。

这个模式在数据量小、团队小的时候还能忍,但一旦业务多起来就非常难受。报表要数据,临时分析要数据,算法要数据,每个人都跑到集群上提交任务,谁跑的什么、占了多少资源、是否互相干扰,基本没人说得清。日志乱成一锅粥,任务也经常互相把资源挤爆,运气不好还会把NameNode或ResourceManager冲垮。

所以所谓的SQL网关,本质上是给大数据计算引擎装了一个“统一的数据库入口”。对外它像MySQL、像HiveServer2,客户端只要会用JDBC、ODBC就能连上来。对内它负责管理底层计算引擎,接住用户发来的SQL,转换成Spark作业,跑完再把结果返回。这样一来,上层工具不需要关心Spark怎么调优、资源怎么分配,下层引擎也不需要面对一堆直连过来的客户端。

1.2 网关的核心职责:统一入口、资源隔离、权限管控

网关不是简单地把SQL转发到Spark那么轻松。真正在生产环境里,一个合格的SQL网关至少要承担四件事。

统一入口。公司里可能同时存在Spark、Flink、Hive等引擎,不同团队用不同的工具。网关把这些能力收敛成一个标准接口,所有访问都从这一个口子进入,后续做审计、监控、限流都有了抓手。

资源隔离。这是多租户场景下的刚需。业务A跑大查询不能把业务B的小查询拖死,P0级的分析任务也不能被临时取数的任务抢占资源。网关需要把不同用户、不同业务组的请求分到不同的Spark引擎上,互不干扰。

权限管控。谁可以访问哪些库表,谁可以执行哪些操作,不能都由底层引擎自行判断。网关层要能够对接统一权限体系,比如LDAP、Active Directory或者Ranger,在入口处就先做一道身份校验,再通过代理用户去执行SQL。

高可用。网关本身不能是单点,一旦它挂了,整个公司所有数据查询入口都会瘫痪。所以网关的前端、后端、协调节点都需要做冗余和故障转移。

后端计算引擎也是关键。同一个SQL,放在小资源池上跑可能要十几分钟,放在大资源池上可能几十秒就结束。网关要能根据用户和业务场景,把请求路由到合适大小、合适配置的引擎上。

2. Kyuubi在网关这个赛道里凭什么站稳脚跟

2.1 从HiveServer2到Kyuubi,演进过程

提到SQL网关,就绕不开HiveServer2。Hive刚火起来的时候,它把Hive SQL暴露成JDBC接口,BI工具终于能直连Hive查数了。但HiveServer2的问题也很明显:每来一个JDBC连接,它就在底层启动一个MetaStore请求和一个执行线程,连接一多,服务端线程就爆炸,而且Hive本身的执行引擎MapReduce跑得又慢,交互式体验很差。

后来Spark火起来之后,社区做了Spark Thrift Server,用Spark作为底层执行引擎,套上HiveServer2的协议,查询速度快了不少。但Spark Thrift Server仍然是单机态,它会把SQL全部塞到同一个SparkContext里。多个用户同时用,一个复杂查询把资源占满,其他所有查询全部排队,隔离性几乎没有。而且它没有真正的多租户概念,做权限时也特别绕。

Kyuubi最初就是网易数帆内部为了解决Spark Thrift Server的这些问题而开发的,后来捐给Apache成为顶级项目。它保留了HiveServer2协议兼容这个关键点,但重新设计了架构。核心变化是把“一个网关节点对应一个SparkContext”变成了“一个网关节点对应一个Spark引擎池”。每个用户或每组用户可以拿到属于自己的Spark引擎,查询彼此隔离,资源各自管理。这一刀切下去,之前的问题就都解开了。

2.2 Kyuubi的差异化特性:多租户、引擎复用、高可用

Kyuubi最有价值的一点,是实现了真正的多租户。默认情况下,每一个用户第一次连接时,Kyuubi会为他启动一个独立的Spark引擎,后续该用户的所有查询都在这个引擎里跑。不同用户的引擎互相隔离,而且引擎之间可以配置不同的Spark资源规格。比如数据研发组给60G内存、20个核,临时查询组只给20G内存、4个核,互不影响。

引擎复用是另一个被低估的特性。很多人只知道Kyuubi会为每个用户建一个引擎,但不知道引擎是可以共享的。同一组用户、同一个并组配置,后续新用户连接时可以复用已经存在的引擎,不需要每次冷启动。Spark引擎初始化往往要几十秒,复用之后首次查询速度会快很多。这种机制特别适合报表和BI这种高频短查询场景。

高可用方面,Kyuubi在ZooKeeper上注册所有服务节点,客户端通过ZK发现可用网关地址。任何一个Kyuubi节点挂掉,客户端会自动切换到其他节点,对业务来说是无感知的。再加上引擎本身是分布式的,单台网关宕机并不会影响已经提交到Spark上的任务继续运行。

三年多用下来,我觉得Kyuubi相比其他网关最大的优势是:它把复杂问题简化成了标准组件,部署不复杂,配置有默认值,监控有指标,出了问题也能根据日志一步步查。这也是它能从网易内部走向Apache开源社区的原因。

3. Kyuubi核心架构与关键机制拆解

3.1 整体架构:前端网关、后端引擎池、元数据协调

Kyuubi的架构其实不复杂,搞清楚了就能明白它为什么能撑住高并发。它有几个核心部分。

最外层是Kyuubi Server,也就是网关节点。它的职责很纯粹:接收客户端通过Thrift协议发来的连接请求和SQL请求,做身份认证和权限校验,然后把SQL交给后端的Spark引擎执行。Kyuubi Server本身不跑数据计算,它相当于一个经纪人的角色。

中间是Spark Engine。Kyuubi会动态创建Spark应用的实例,每个实例实际上就是一个SparkContext。这个SparkContext提交到集群上,由YARN或者Kubernetes来调度资源。SQL最终被转成Spark作业在这里执行。

再往下是元数据和状态协调。Kyuubi把引擎的状态信息、多个Server节点的地址信息都注册到ZooKeeper上。客户端首次连接时,从ZK拿到可用的Kyuubi Server列表,然后选择一个建立连接。如果网关节点挂了,ZK里的注册信息会失效,客户端会重新刷新列表。

还有一个不能忽视的部分就是Hive Metastore。Kyuubi本身不管理表结构,它通过Spark SQL的元数据接口去访问外部的Hive Metastore。也就是说,Kyuubi可以看到你已有的库表,不需要重新建表或者做数据迁移。

整个链路串起来就是:客户端连接Kyuubi Server,Server校验身份、向集群申请Spark Engine,Engine作为Spark应用程序在集群上运行,SQL在Engine里执行,结果集返回给Server,Server再回传客户端。数据不经过Kyuubi中转,这是它性能好的一个关键设计。

3.2 会话与连接管理是如何工作的

很多第一次接触Kyuubi的人会把“连接”和“会话”搞混。Kyuubi这里的连接是指物理的TCP/Thrift连接,会话则是一个逻辑概念,对应一组用户状态和引擎绑定关系。

当用户发起一个JDBC连接时,Kyuubi Server会先做认证,然后根据用户信息、配置组信息去定位或者创建对应的Spark Engine。这个过程可以比作你去酒店开房:前台先核实你的身份,然后分配一个房间给你。房间就是引擎,入住手续就是会话建立。

Kyuubi里有一个“引擎共享”参数叫spark.engine.shared.session?不是,共享的粒度不是session,而是engine。具体地,共享引擎由配置项kyuubi.engine.share.level控制,默认是USER,意思是同一用户共享同一个引擎。如果你想让多个用户共享一个大引擎,比如一个数据分析组共享一个引擎,就需要把share level设为GROUP,并且确保这些用户属于同一个组。如果设置为CONNECTION,每次连接都新建引擎,和Spark Thrift Server就没什么区别了,一般不建议这么用。

连接管理这一块,Kyuubi支持连接池。客户端这边HikariCP也好,Druid也好,都可以维护一组到Kyuubi的JDBC连接,反复利用,避免频繁握手认证。Server端也会管理空闲连接,超过kyuubi.session.timeout没有活动的连接会被回收,防止连接泄漏。

3.3 多租户隔离与资源池策略

Kyuubi的多租户隔离体现在两个层面:引擎隔离和资源池隔离。

引擎隔离是说不同用户、不同组之间,Spark应用是独立的。A用户跑了100个查询,最多只会把A用户的引擎占满,不会影响B用户。这个隔离在物理上就是一个独立的Spark Driver进程,从进程级别就分隔开了。

资源池隔离是配置层面的。Kyuubi允许为每个租户配置不同的Spark资源参数,比如executor数量、内存大小、队列名。假设你的YARN集群上配置了多个队列:root.bi、root.ad_hoc、root.etl,在Kyuubi里就可以针对不同用户组把这些参数预设好。

实际配置时,可以通过kyuubi.engine.spark.yarn.queue指定引擎提交到哪个YARN队列,通过kyuubi.engine.spark.executor.memory等参数控制引擎规格。这些参数可以在kyuubi-defaults.conf里写默认值,也可以通过JDBC连接字符串里的?spark.sql.shuffle.partitions=200方式动态覆盖。动态配置能力非常实用,但不建议让普通用户来改,容易把自己改崩。

3.4 高可用与故障转移

Kyuubi的HA方案用的是ZooKeeper做服务发现,跟很多分布式系统一样。每个Kyuubi Server启动时,会在ZooKeeper的一个指定路径下注册一个临时节点,路径通常是/kyuubi/<server>。客户端通过kyuubi.zk.namespace找到这个路径,读取下面所有的Server信息,然后随机或者按照负载策略选择一个连接。

当某个Server出现故障,它和ZK之间的会话超时,注册的临时节点就会消失,客户端下次获取服务列表时就不会再拿到这个坏节点。已经存在的连接会报错,但新的连接会平滑地指向健康的节点。如果是正在执行到一半的SQL,客户端重连后可以重新提交一次。

生产环境里我一般会部署两个或三个Kyuubi Server节点,前面用负载均衡器做一层TCP转发,后端再接ZK。这样即使整个Kyuubi Server集群需要重启,也能通过滚动更新的方式做到业务无感知。注意,Kyuubi Server是无状态的,只有Spark Engine有状态,所以滚动重启是对业务影响最小的维护方式。

4. 快速上手:从零部署一个Kyuubi网关

4.1 环境准备清单

这里讲的是最常规的部署方式:Spark on YARN,Kyuubi作为网关进程独立部署。

你需要准备的东西有:

  • 一个Hadoop集群,版本建议2.7以上,最好是3.x
  • Spark发行版,建议Spark 3.2以上,和Kyuubi的版本对应关系查官网
  • Kyuubi发行包,可以从Apache官网下载apache-kyuubi-x.y.z-bin.tgz
  • ZooKeeper集群,用于Kyuubi Server的HA注册
  • Hive Metastore服务,Kyuubi需要读元数据
  • Java环境,JDK8或JDK11都可以,看Spark版本的要求

部署机器不需要很高配置,Kyuubi Server本身不跑计算,4核8G起步就够。真正吃资源的是Spark引擎所在的YARN节点。

4.2 最小部署步骤与关键配置

部署步骤并不复杂,我按自己的习惯整理成几条。

第一步,把Kyuubi发行包解压到安装目录,然后配置环境变量KYUUBI_HOME。

第二步,在$KYUUBI_HOME/conf目录下,把kyuubi-defaults.conf.template复制为kyuubi-defaults.conf。最基础的内容是告诉Kyuubi你的Spark目录在哪、Hive Metastore在哪、ZooKeeper在哪:

kyuubi.frontend.bind.host 0.0.0.0 kyuubi.frontend.port 10009 kyuubi.server.zk.namespace kyuubi kyuubi.zookeeper.quorum host1:2181,host2:2181,host3:2181 kyuubi.engine.spark.home /usr/local/spark spark.sql.hive.metastore.uris thrift://metastore-host:9083

这里提一下端口。默认端口其实是10009,不是10000。如果你习惯用10000,就得显式配置kyuubi.frontend.port=10000,否则会连不上。

第三步,配置Spark。把$SPARK_HOME/conf/spark-defaults.conf里的必要参数检查一下。由于Kyuubi要为每个用户动态提交Spark应用,Spark需要能提交到YARN,所以HADOOP_CONF_DIR和YARN_CONF_DIR必须能被Kyuubi进程读到。建议在kyuubi-defaults.conf里显式设置这两个环境变量:

kyuubi.engine.env.HADOOP_CONF_DIR /etc/hadoop/conf kyuubi.engine.env.YARN_CONF_DIR /etc/hadoop/conf

第四步,把/etc/hadoop/conf/hive-site.xml放到Kyuubi的classpath里,或者确保Spark能访问到Metastore地址。Kyuubi提交的Spark引擎需要连Metastore,这一步配置不到就会报“Unable to instantiate SparkSession”。

第五步,启动和验证:

$KYUUBI_HOME/bin/kyuubi start

然后看日志$KYUUBI_HOME/logs/kyuubi.out,出现“INFO Server: Started”就算起来了。

4.3 先用一个简单SQL验证网关可用

Kyuubi启动后,用Hive的beeline就能连接测试。

beeline -u "jdbc:hive2://kyuubi-host:10009/default" -n youruser

如果认证没配,默认会先用系统用户登录。连接成功后执行:

show databases;

正常就能看到Hive Metastore里已有的数据库列表。再跑一条带聚合的SQL,比如:

select count(*) from your_table group by dt;

如果能在几十秒内执行完并返回结果,说明Kyuubi到Spark再到Metastore这条链路是通的。第一次查询会比较慢,因为Kyuubi需要动态提交一个Spark引擎,引擎启动、申请资源都需要时间,之后再去连接就能感受到复用引擎带来的速度提升。

一个小技巧:在Kyuubi的日志里看引擎注册信息,能看到引擎的application id。去YARN ResourceManager UI上找到这个应用,点进去能看到Spark的Executor资源、运行日志。如果SQL执行失败,直接去YARN上这个Driver日志里排查会比在Kyuubi日志里看更直观。

5. 实际用起来的几个典型姿势

5.1 给BI/报表工具提供SQL入口

Kyuubi最普及的用法,是作为BI工具的查询入口。Tableau、帆软、Superset、Metabase这些工具都支持Hive JDBC协议,直接把JDBC地址指到Kyuubi的端口就可以。

在我们团队,给BI工具分配的账号会单独建一个Kyuubi配置组。比如用kyuubi.engine.share.level=GROUP让所有BI用户共用一个引擎,同时给这个引擎配置相对充足的资源。因为BI报表都是高频小查询,共用引擎可以避免频繁启停,资源利用率也更高。把spark.sql.shuffle.partitions调小到50-100,减少小文件产生,查询延迟会更稳定。

接入BI后还会发现一个好处:Kyuubi的引擎复用可以让多次查询之间共享缓存数据。比如cache table命中的表,对于同一个引擎连接的下一次查询会直接走内存,速度肉眼可见地提升。不过要提醒一点,BI工具的连接池会保持大量连接,有些工具甚至打开仪表盘就建立几十个连接,Kyuubi配置里要适当调大kyuubi.server.max.connections,否则连接数会打满。

5.2 给数据开发平台提供即席查询能力

自建数据平台的公司,通常都会有一个SQL编辑器,业务同学在上面跑查询。以前这类平台大多直连HiveServer2或者Spark Thrift Server,并发稍高就各种超时。接入Kyuubi之后,平台侧不用大改,只要把JDBC URL和认证方式换掉就行。

数据开发平台有个特殊需求,就是同一用户跑不同查询时,要能走到不同资源队列。这个可以用Kyuubi的“连接字符串动态参数”来实现。平台根据用户选择的队列,拼出这样的URL:

jdbc:hive2://kyuubi-host:10009/default?spark.yarn.queue=root.develop

Kyuubi创建引擎时会读取连接串里的Spark配置,把它提交到对应对列。这样就做到了同一网关、不同用户、不同队列的目标。平台后端只要维护一个队列列表,就能灵活调度资源,也不需要为每个队列部署一套网关。

5.3 服务化你的Spark任务:把批处理变成“数据库”

还有一种玩法是把自己的Spark应用改造成通过SQL来驱动。比如我们有个离线推荐特征计算,原来是用Spark SQL写一大堆脚本通过spark-submit跑,每次都要打包、调度。后来我们把核心逻辑改成存储过程风格的SQL片段,通过JDBC发送给Kyuubi执行,省去了打包环节,新同学也能通过SQL接入。

当然,不是说所有ETL都要用Kyuubi来跑。Kyuubi适合的是有交互需求、有临时变化查询需求的任务。真正稳定周期的核心ETL,用调度系统定时提交Spark任务还是更可控。Kyuubi在这个场景里更像是给开发人员提供的一个“可交互的Spark控制台”,而不是取代调度系统。

另外一个场景是给外部系统提供数据服务。比如业务系统需要批量查询接口,可以封装一个服务,通过JDBC连Kyuubi执行指定SQL,把结果转换为JSON返回给前端。因为底层是Spark,即使表有数亿行,只要SQL写得合理,聚合查询也扛得住。比起直接用RDBMS,成本更低,不需要提前导入。

6. 常见问题与避坑实录

6.1 连接超时、会话堆积

我遇到最多的排查问题是客户端连接超时。表象是BI工具报“Could not connect to host”,或者在DataGrip里连接一直转圈。先检查Kyuubi进程本身,curl http://kyuubi-host:10009不一定有效,因为它是Thrift协议,用nc -zv kyuubi-host 10009测TCP端口更直接。

如果端口能通,再检查Kyuubi日志。常见的超时原因有这么几个:

  • 下单时引擎还没创建好,客户端就等不及断开了。默认连接超时时间可以调,参数是kyuubi.connection.wait.enable和相关等待时长。
  • Spark任务积压,Driver没有及时响应Kyuubi的心跳,导致Kyuubi认为引擎不健康。
  • 客户端用的ZooKeeper地址写不对,连到了别的环境的Kyuubi集群。

会话堆积的问题则更多是客户端连接池没有正确关闭连接。如果你看到Kyuubi日志里session数量只增不减,先查客户端代码里的Connection.close()是不是被吞了异常,再看kyuubi.session.timeout配置是否合理,默认值一般是60秒,如果业务有长连接需求,适当调大到600秒。

6.2 引擎内存与Spark参数调优

Kyuubi的引擎参数默认沿Spark的默认值,这在实际生产中经常不够用。我踩过的坑之一是默认的spark.executor.memory=1g,跑一个稍微大一点的shuffle就直接内存溢出。后来在kyuubi-defaults.conf里针对不同租户做了配置,才稳定下来。

调优时先看业务查询的复杂度。简单聚合查询,一个Executor给4g-8g足够。如果查询里有大表join或者复杂的group by,建议给executor memory 8g以上,并打开spark.sql.adaptive.enabled=true。AOE是Spark 3.x默认打开的,但还是确认一下,它能动态调整reduce分区数,避免小文件过多和大文件倾斜。

还有一个参数spark.sql.shuffle.partitions,默认200。如果你的输入数据不大,200个分区会生成太多小文件;如果数据量很大,200又不够。这个值最好按业务调整,不要一个值走到头。

Kyuubi的引擎数量也要控制。如果每个用户都共享一个引擎,使用者很多时,YARN上没有足够资源,引擎会一直处于ACCEPTED状态,查询永远跑不起来。这种情况优先看YARN队列资源,看是不是队列最大资源被占满,再考虑是否需要配置引擎组的资源上限。

6.3 权限体系怎么接LDAP/Ranger

Kyuubi支持两种基本的认证方式:默认的匿名认证和LDAP认证。生产环境肯定要用LDAP。配置比较简单,把Default接入LDAP:

kyuubi.authentication=LDAP kyuubi.authentication.ldap.url=ldap://ldap-host:389 kyuubi.authentication.ldap.base.dn=dc=example,dc=com

配置完成后,客户端连接时的用户名密码会去LDAP校验。Kyuubi拿到通过校验的用户名后,会以这个用户的身份去提交Spark作业。这意味着Hadoop层面要支持spark.yarn.principal代理或者hadoop.proxyuser配置,不然YARN会拒绝使用代理用户。具体就是在Hadoop的core-site.xml里配置代理用户规则:

<property> <name>hadoop.proxyuser.kyuubi.groups</name> <value>*</value> </property> <property> <name>hadoop.proxyuser.kyuubi.hosts</name> <value>*</value> </property>

这里kyuubi是运行Kyuubi进程的系统用户名。如果不配这个,Kyuubi提交Spark应用时会被Hadoop拒绝,报“User kyuubi is not authorized to proxy”.

表级权限校验,我用的是Ranger。Kyuubi支持Ranger插件去做列级、行级的权限控制。大概思路是:Kyuubi Server加载了Ranger的Hive插件后,会在SQL提交给Spark前做一次权限审计。如果用户没有该表的访问权限,直接返回permission denied。需要注意的是,权限策略是在Ranger的Hive服务里配置的,资源路径要匹配。

6.4 我踩过的几个真实坑

最后讲几个很难从文档里查到的细节。

第一个坑是关于Spark的Driver日志。Kyuubi动态创建的Spark引擎,Driver日志默认打在节点本地存储器上。如果你需要收集日志,建议在Spark配置里开启spark.eventLog.enabled=true,并把spark.history.fs.logDirectory指向HDFS。否则排查历史SQL问题时,日志很可能已经被删掉了。

第二个坑是JDBC结果集大小的限制。Kyuubi返回给客户端的行数默认不是无限大的,有一个kyuubi.query.return.max.rows之类的参数限制。如果查询结果非常大,超出限制会被截断。做数据抽取时一定要留意,别让几十万行的结果被悄悄截断,最终落到同步表里的数据不全。

第三个坑是用户名不一致导致引擎重复创建。Kyuubi引擎共享是按用户维度绑定的。如果客户端前面走了Nginx转发,Nginx把用户信息弄丢了或改写了,Kyuubi就可能认为每次都是不同用户,导致同一用户不断新建引擎。结果就是YARN上引擎数量暴涨,资源被打满。排查时对比YARN上应用列表的提交用户,看在Kyuubi日志中解析出的user,如果两者对不上,大概率是代理层搞了鬼。

第四个坑是引擎空闲超时。Kyuubi有参数控制引擎在没有连接多久后自动关闭,默认值我记得较短,比如几分钟。如果你习惯白天频繁连接,隔一段时间不使用,引擎被回收,下次又要冷启动,体验会很差。可以调大kyuubi.engine.idle.timeout让它空闲半小时或更久再回收,既能省资源,也能保体验。

第五个坑是show tables这类元数据操作也会走Spark引擎。也就是说,客户端第一次连接时,如果只是想看一下有哪些表,也会触发整个引擎的启动。为了让元数据操作更轻量,你可以在客户端配置里使用kyuubi.frontend.diagnostic.enable相关的诊断模式,或者接受第一次冷启动的延迟,在BI工具和业务层面做好超时预估。

这些问题的经验往往是一次次凌晨盯出来的。Kyuubi整体已经比我最早用的时候完善很多,社区活跃,版本迭代也快,新特性比如支持Flink引擎也在规划里。如果你也打算趟这条河,建议先从单机、最小配置跑通,再逐步叠加HA、权限和队列策略。把基础机制吃透之后,后面遇到问题就不慌了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询