Apache Kyuubi 在小米大数据平台的应用实践
导读:今天分享的主题是《Kyuubi 在小米大数据平台的应用实践》,主要分为四部分内容:
Kyuubi 在小米的落地过程
打造易用和高可用的 Kyuubi 服务
基于 kyuubi 的改进
kyuubi 的一些新特性在业务场景的应用
01 Kyuubi 在小米的落地过程
第一个主题:关于 Kyuubi 在小米的大数据平台落地过程和实施路径的分享。
1. 背景介绍
先介绍一下背景,小米的大数据体系在不断更新和迭代,随着业务架构、组织架构和技术架构的调整,内部大数据平台逐渐出现一些状况:
出现了多个基于 SQL 的大数据平台服务,服务于各个业务部门,各自定位又有一定的差异,这样就给用户带来了困扰,到底选择哪个平台好,而且我们在用户支持的过程中发现,同一业务可能需要跨多个数据服务平台,流程繁琐。
对于底层表资源的使用存在多套账号和权限体系:
a. MySQL/Doris: 系统的自有的 User&Password 认证和权限体系
b. Hive/Kudu 基于 Kerberos 认证和 Sentry 的权限体系
c. Talos 是基于小米内部平台组织和团队的认证与授权体系
给用户使用和管理上带来了麻烦,没有统一的资源管理和权限管理视角,并且底层系统服务账号会直接暴露给用户,还会存在安全风险。
2. 构建一站式的大数据开发平台
上述现象直接导致了如下问题:
①对用户:
多个平台和多体系给用户体验较差,开发数据流程长,不能快速上手。
开发管理效率成本高,资源成本结算和任务管理没有统一的视图。
②对平台:
各自的侧重点不同,都不能完全覆盖大数据场景下的能力需求,同时还有能力重复建设问题,导致资源浪费。
出现问题排查和维护困难,需要堆人力解决。
面对数据平台难用的情况,提出了构建统一易用的大数据服务平台整体目标。整体架构能力围绕数据链路解决方案、数仓解决方案、数据服务解决方案来进行建设,提供统一的元数据管理和权限管理体系。
在这个大背景和动机下,统一的数据入口服务成为了一个非常重要的能力,它主要解决:
用户的易用性(一致的入口体验)
SQL 流量治理(代理多引擎)
数据访问的安全性管控(入口收敛和降低安全风险)
3. 小米 SQL 服务历史发展情况
从上面的背景问题中可以看到,小米内部有几套大数据处理的 SQL 服务入口,总体还是围绕经典的 SQL On Hadoop 架构体系来构建,逐步从 ThriftServer 演进到向上抽象一层的 SQL Proxy 服务,在底层集成了 Hive/Spark/Doris 等引擎为 ETL 作业、Ad-Hoc 查询提供支持。
抽离的 SparkThriftServer 的实现模块作为独立的 SQL Proxy 服务,提供:
ETL 场景下的 HiveServer 和 Spark APP 代理(非常驻)
Ad-Hoc 场景下的 STS、Kylin、Druid 代理
从这里可以看到 SQL Proxy 和 Kyuubi Server 的定位非常相似,但是存在很多不足:
a. SQL Proxy 没有完全剥离 STS 的实现,通过反射的方式进行复用,代码耦合很高,依赖 Spark 特定版本,升级困难
b. 底层引擎代理层没有统一抽象,与其他引擎适配困难,对底层引擎扩展性差
c. 无法本地调试,依赖 hadoop 配置,在办公和服务环境网络隔离情况下,必须在开发机上完成完整的功能测试和调试,开发和部署路径长
4. 基于 Kyuubi 构建统一 SQL 入口
(1) 为什么选择 Kyuubi
通过上面的分析,我们发现在业务和架构上都存在着一些问题需要解决。
① 业务上:
在重新打造统一的大数据体系的推动下,构建统一的 SQL 入口服务势在必行。
需要更快的分析引擎,这里我们选择了 Trino。
一套易用、高可用并可以持续演进的服务架构,提升大数据研发的生产力。
SQLProxy 架构需要升级:
完全兼容 HiveThrift 协议。
松耦合的实现,基于 STS 实现的完全剥离。
灵活可扩展的代理多引擎的适配。
Kyuubi 的优势在于:
与 STS 和 HS2 的完全兼容一致
高可用和资源隔离
清晰简洁的架构,可测试、可维护、可扩展
社区高质量实现和业界生产环境大量运用
SQLProxy 和 Kyuubi 的架构非常相似,切换成本低。在业务需求和架构升级的双重推动下,我们选择了 Kyuubi。
(2)架构升级
升级过程和效果与我们的预期一致,可以看到架构相比 SQLProxy 更加简洁,扩展底层引擎非常容易,而且本地可测试可调试,极大提升了开发效率。从开发到上线新架构两周时间就完成了平滑迁移。
升级新架构带来的效果也非常明显,相比之前的架构不论代码质量、服务稳定性、可维护性和可扩展性上都有了重大提升:
多引擎的代理能力(主要支持 Spark/Trino/Hive/Doris)。
基于数据平台 workspace 的体系在 Kyuubi Server 端实现了权限验证和资源隔离。
更加规范化的 Hive Thrift API 支持,各种生态可视化工具(Redash/Datagrip 等)完美兼容。
(3) 统一 SQL 服务的现状
经过半年的迁移推动,每日 SQL 有效处理量从 5W 提升到现在的 50W 规模,已经占据了整个 SQL 流量的 80%。特别是 SparkSQL 的流量半年新增到 30W。大体流量分布:Spark 36w/ Trino 12w / Hive 2.5w
各个引擎请求耗时:
Spark 和 Trino 持平,平均延时 30 秒左右,P50 在 5 秒左右
Hive 的执行效率明显低于以上两个引擎,跟 Hive 的大任务有关,ETL 偏多
目前 Kyuubi Server 承载真实的 SQL 流量日均 100w 左右,可用性仍然可达 99.9% 以上,非常稳定。
02 打造易用易维护高可用的 Kyuubi 服务
1. 构建符合业务需求的 Kyuubi
(1) 整体架构
整体架构和流程,主要分为入口服务、认证和权限适配、底层引擎管理及服务的可观测性:
Kyuubi Server 为基础构建了 SQL 统一入口服务
Kyuubi Engine 作为 Spark SQL 执行引擎层
独立 Engine Manager 服务管理各类计算引擎
Kyuubi Server 层集成 Ranger 服务,支持基于数据平台的统一权限验证
扩展适配 Trino/Hive/Doris 引擎服务指标和审计日志的可视化
(2) 用户使用交互
以工作空间(workspace)粒度来保计算资源的隔离的存储资源(表)安全,与 Kyuubi Group 的多租户类似,我们这里扩展到了其他引擎。
一次完成交互过程:
WorkspaceA 下面的用户使用平台发放的 Token,选择各类客户端工具,向引擎提交 SQL 查询,Kyuubi Server 会自动将用户 SQL 提交到该空间所属的计算引擎上去,来保证用户使用资源的隔离性。与其他 workspace 用户虽是同一入口,但是资源的使用上是隔离的。
Kyuubi Server 服务并不具体执行 SQL,同一的入口服务不会有太大压力。
2. 提升用户侧的易用性
(1) 统一认证和表坐标的统一
去 Kerberos 化,采用平台统一 Token 方式,解决:
Kerberos 接入流程繁琐
普通用户对 kerberos 机制难以理解,出现问题排查困难
用户管理不当,同一账号下用户膨胀问题
审计和追踪不能精确定位到用户个人
表资源命名的统一规范化,小米内部存在多区域和多类数据源,如果使用统一的 SQL 入口服务,需要统一 SQL 语句的表名规范来避免冲突和统一的管理:
采用 Catalog.Schema.Table 三级表名为唯一表名
Kyuubi Server 端支持 JDBC URL 预设 Catalog/Schema,兼容之前 SQL 中二级或者一级表名
结合 URL 和 SQL Table 生成完整的三级表坐标,以供用户权限认证
(2) Kyuubi Engine 公共资源池
引入 Kyuubi Engine 公共池主要解决用户首次进入空间提交 SparkSQL 的查询性能问题。上面提到的用户提交的 SQL 分析统计,50% 的 SQL 查询延时都在 5 秒以下。在没有提前分配的资源的情况下,用户提交查询会冷启动一个 Kyuubi Engine,这是 Kyuubi 当前的机制。由于小米 Yarn 提交一个 APP 的延时在分钟级别,用户一个简单的秒级查询会延迟到分钟级别,体感非常差。
因此,借助 Kyuubi Engine Pool 的实现,对没有提前配置和指定资源的 workspace 用户,会将 SQL 路由到已经预先启动好的 Kyuubi Engine Pool,以加快用户的查询速度,提升 SQL 查询体验。
3. 升级 Spark2.X 到 Kyuubi Engine
Kyuubi Engine 目前只支持 Spark3 以上,之前我们内部版本都是 Spark2,在升级到 Kyuubi Engine 之前做了相关对比测试,在 Kyuubi 架构和 SQLProxy 架构下,有明显的性能提升:
在 TPC-DS 标准测试集上,P50 延时有 75% 的性能提升,长尾基本和 SQLProxy 性能持平。
在真实的业务场景下,P50 延时也有 37% 的性能提升,长尾也基本跟 SQLProxy 一致,也就是升级的 Kyuubi Engine 的性能在多数情况下要优于 Spark2,整体上不会比 Spark2 更差。
4. Kyuubi Server 的容器化
在 Kyuubi Server 的高可用上利用容器化的方式替换了当前 Kyuubi Client 端通过 ZK 进行服务发现的高可用模式:
在 K8s 上部署 Kyuubi Server 服务,充分利用 K8s 的弹性能力保障高可用。
Kyuubi Server 和 Kyuubi Engine 的部署彻底解耦,作为一个单独的 Thrift RPC 代理服务和 HTTP 服务,去除 Hadoop 相关的配置环境依赖,和普通业务服务一样使用 LVS 做流量负载均衡。
同时借助内部 K8s 平台的 CI/CD 能力,实现了 Kyuubi Server 服务的全自动灰度发布,支持一键升级和扩缩容。
5. 基于 Workspace 的计算资源管理
(1)Engine Manager
由于之前已经实现了对 Spark Engine 的管理服务,我们将 Kyuubi Engine 的管理直接从 Kyuubi Server 剥离,形成了单独的 Engine Manager 服务,负责 Engine 的生命周期管理,配置上下文管理,同时提供服务发现和负载均衡能力。
为管理入口提供引擎配置和生命周期管理。
为 Kyuubi Server 提供 SQL 路由的能力。
为运维提供可视化的监控能力,包括 Engine 的服务状态、资源占用以及繁忙程度等,便于快速运维。
用户提交的 SQL 的流程:
首先经过 Kyuubi Server 入口的认证和权限验证。
Kyuubi Server 向 EngineManager 可用的 Kyuubi Engine 地址。
EngineManager 向 ZK 获取当前用户空间下可用的 Engine,然后统计当前可用 Engine 的繁忙指标,返回相对空闲的 Engine 给 Kyuubi Server。
Kyuubi Server 将 SQL 提交到 EngineManager 建议的 Engine 上去执行。
(2) 用户提交
图上是我们的用户平台 SQL 查询入口,在 workspace 下的用户可以非常方便地启动一个 Kyuubi Engine。为降低用户的门槛,只暴露了资源相关和排队策略的配置。同时,用户还可以配置多个 Kyuubi Engine 实例,来保障当前 workspace 下的 SQL 执行的高可用。
(3) Engine 的高可用
为什么需要 Kyuubi Engine 的高可用?因为在实际环境中,Kyuubi Engine 是一直长时间运行的,Spark 的 SQL 执行过程非常复杂,时间一长其稳定性就有了问题:
开启动态资源策略后丢事件的 Bug,导致资源无法释放。
大任务占用时间长,可能阻塞一些小任务的运行。
Driver 端 JVM Full GC 时间过长和 OOM。
SQL 不合理导致的 Engine 频繁重启。
因此实施了一些高可用的保障策略:
workspace 级别隔离 Engine 异常,避免影响其他用户。
观测 Engine 可用指标,通过繁忙和探活信息标记是否当前可用。
同一 workspace 下多个 Engine 实例(Kyuubi 的 Engine Pool 机制),提升整体可用性,同时提供基于负载的分发。
发现异常及时自动重启。
频繁重启 Engine 通过告警机制,人工及时介入。
03 基于 Kyuubi 的改造
1. Trino 和 Doris 的代理
引入 Trino 和 Doris 主要解决 OLAP 场景的查询效率问题。
Kyuubi 在 1.1.0 版本还未支持 Trino,我们在 kyuubi Server 端使用 Trino-JDBC 完成了对 Trino 引擎的适配。
Trino-JDBC 实现了流迭代器的模式,每次 nextResult 都会触发一次对 Trino 引擎的请求。
当前社区 Trino-Client 实现,会一次性的拉取所有结果集可能导致 OOM 的风险。
对于 Doris 的适配也采用了 JDBC 的方式,由于 Doris 客户端本身支持 Mysql JDBC,MySQL JDBC 的实现方式是全量拉取模式,Kyuubi Server 端有 OOM 的风险。目前通过限制 Doris 查询的超时时间来降低大结果集导致 OOM 的风险。
如果大家后面要扩展 Kyuubi 代理其他 JDBC 的数据库支持,一定谨慎处理。
2. SQL HTTP API 的支持
关于 HTTP API 的支持一共实现了 V1 版本和 V2 版本,相比社区还是有一些区别。
① V1 版本
简化用户的交互过程,简化 Hive Thrift RPC 的调用流程,用户直接在上层应用程序中通过 HTTP 请求就能提交 SQL,对一些研发用户来说是非常友好的。提交 SQL 根据 QueryID,不断轮询获取结果。
复用了 Thrift backend Service 的实现,水平扩展了一层 HTTP Fronted Service。底层实现跟 Thrift API 完全保持一致。
但是也存在一些问题:
Kyuubi Service 端是有 Session 状态的,Step1 和 Step2 必须路由的同一个实例才能获取到结果,采用 IP Hash 不能完全解决。
这样也导致 Kyuubi Server HTTP 服务无法水平扩展和平滑升级。
②V2 版本
为了彻底解决 V1 的水平扩展性问题,在 V2 版本中将 Kyuubi HTTP Server 完全无状态化,通过 Kyuubi Engine 直接提供 HTTP SQL API。Kyuubi Server 只起到出代理的作用。
另外的两点改进:
彻底解决大结果集的导致 Kyuubi Engine OOM 的问题,将查询类的结果直接持久化到 HDFS,不经过 Spark Driver 端。
用户在获取结果的时候不经过 Kyuubi Engine,直接从 HDFS 层流式获取结果集。
同时,也不用维持长链接,非常适合 ETL 的场景。
3. SQL 表列解析
我们在 Kyuubi Server 端做了权限认证,需要获取用户 SQL 的真实表名,单独开发了一个纯 SQL 的解析模块:支持表列血缘关系和 SQL 类型的提取,支持 SparkSQL、Trino 两种语法。
具体解析后的格式如图,包括类型、输入输出表和队列的列。
后续在具体实际场景中该模块的也应用到了其业务场景,比如表血缘审计日志,SQL 的统计请求分析等安全质量场景,完全复用了我们的 SQL 表列提取的能力。
04 Kyuubi 新特性的应用
1. 小文件合并
解决用户写场景可能导致的小文件过多的问题。用户一般会提交两个 SQL:一个是业务处理 SQL,一个是合并 SQL,通过通过 workflow 方式串联起来,维护不变。
开启也非常简单,可以在 Kyuubi Engine 启动阶段,SQL 提交阶段开启开关。
2. 增量获取和获取结果集限制
主要是 JDBC 下用户有结果集的查询导致的 OOM 问题,开启增量模式。但有些场景下会有部分分区的结果太大,导致取结果过程阻塞,导致有不好的用户体验。推荐采用 HTTP API 异步结果获取方式解决。
对用户一些预览数据的 SQL,如果访问的表非常大,限制查询条数输出是一个非常好的功能,避免不必要的开销
3. Z-Ordering
在我们内部画像场景做相关的测试,Z-Ordering 有显著的提升。
业务查询时间
存储空间
查询扫描数据量
文件数量
在具体应用中,Z-Ordering 的排序规则需要根据实际业务表的数据做相应调整:
我们画像场景查询频次高的列进行排序,效果明显
超过 3 个列后的优化并不理想
排序列应选择基数较大且没有倾斜的列
Kyuubi Engine Z-Ordering 的实现非常巧妙,没有增加额外的列,直接复用了 parquet 的原生能力,所以一次生成可以支持多个引擎查询(只要该引擎支持 parequet 格式的读取)。
4. PlanOnly 模式
主要用于非 SQL 执行的 SQL 相关场景,比如:
为数据平台提供语法语义校验服务
SQL 提交前的检查
SQL 语法语义兼容性的检查(Spark2.X->Spark3.X 的升级)
PlanOnly 模式下 SQL 不会真正执行,只会输出解析后的 LogicalPlan/SparkPlan。目前为数据平台单独提供了语法语义校验服务,就是采用 Kyuubi Engine 的 PlanOnly 模式。
这种应用场景也为我们提供了一种新思路:将 Kyuubi Engine 作为 Yarn APP 的服务框架,提供其他场景的服务,比如校验服务、血缘关系提取服务和 SQL 的预计算服务等。
5. Scala mode
Scala Code 模式完全解放了 Kyuubi Engine 能力,具备直接通过 JDBC 提交 Scala 代码的能力,专门处理一些复杂逻辑的业务。
目前我们的应用场景在运维这块做了一些尝试,主要解决我们的运维效率。例如我们要在运行时动态加载用户自定义的 jar 包,读取 Thrift 格式化的数据。相比之前登录到生产集群机器打包代码运行的流程大大简化。
05 未来规划和总结
规划:
基于业务场景、SQL 规则和执行代价事前预测,实现多引擎下的自动路由能力。
HTTP API 代替 Thrift API 提交的 ETL 作业,异步化取代长连接的方式。
总结:
Kyuubi 是非常优秀开源实践,已经成为小米内部大 数据服务入口的重要基础架构服务 。
非常感谢 Kyuubi 的社区的贡献,加速了我们统一 SQL 服务的落地 。
相信未来 Kyuubi 会成为大数据场景下的 SQL Gateway 标杆,与大家一起共建 Kyuubi 生态 。
今天的分享就到这里,谢谢大家。
分享嘉宾
评论