写点什么

云原生 Spark UI Service 在腾讯云云原生数据湖产品 DLC 的实践

  • 2023-09-12
    广东
  • 本文字数:3401 字

    阅读完需:约 11 分钟

云原生Spark UI Service在腾讯云云原生数据湖产品DLC的实践

图片作者:余建涛,大数据平台产品中心高级工程师


摘要

Spark UI 是查看 Spark 作业运行情况的重要窗口,用户经常需要根据 UI 上的信息来判断作业失败的原因或者分析作业如何优化。DLC 团队实现了云原生的 Spark UI Sevice,相较于开源的 Spark History Server,存储成本降低 80%,大规模作业 UI 加载速度提升 70%。目前已在公有云多个地域上线,为 DLC 用户提供 Spark UI 服务。


背景

Spark History Server 原理

Spark History Server(以下简称 SHS)是 Spark 原生的 UI 服务,为了更好了解本文工作的背景,这里先简单介绍下 SHS 的原理。概况来讲,SHS 建立在 Spark 事件(Spark Event)之上,通过持久化和回放 Spark Event 来还原 Spark 作业当前的状态和运行过程中的统计信息。

图 1 原生 Spark History Server 原理


如图 1 左侧,在作业运行过程中,Spark Driver 内部各模块会不断产生与作业运行相关的事件,如 ApplicationStart/ApplicationEnd,JobStart/JobEnd,StageStart/StageEnd,TaskStart/TaskEnd 等,所有事件都会发送到 LiveListenerBus,然后在 LiveListenerBus 内部分发到各个子队列,由子队列上注册的 Listener 来处理。SHS 实现了 EventLogQueue 队列和监听该队列的 EventLoggingListener,EventLoggingListener 负责将 Event 序列化为 Json 格式,然后由 EventLogFileWriter 持久化到 Event Log 文件。Event Log 文件通常通常存储在分布式文件系统。


图 1 右侧是 Spark History Server,在其内部 FsHistoryProvider 负责事件回放,即将事件反序列化后发送到 ReplayListenerBus,然后由相应的 Listener 处理。这里主要包含两个过程,首先是 Application listing,FsHistoryProvider 启动一个线程间歇性地扫描 Event Log 目录下的所有 Application 目录,检查 log 文件是否有更新,对于有更新的 Application,则读取 Event log,通过 AppListingListener 回放部分事件提取 Application 运行的概要信息,存储到 KVStore。其次是 Loading UI,当用户访问 UI 时从 KVStore 中查找请求的 Application,如果存在,则完整读取 Application 目录下的 Event Log 文件,再通过 AppStatusListener 从事件中提取运行数据然后更新到 KVStore 中,还原任务当前的状态信息。WebUI 从 KvStore 查询所需要的数据,实现页面的渲染。


痛点存储开销大

Spark 作业运行过程中每个 Task 都会产生相关事件,也就说作业越复杂,Task 数越多,产生的事件也会越多。其次 Event 以 json 格式序列化,导致占用空间也较大。实际生产中,一个大规模作业的 Event Log 可以达到数十 G。


回放效率低

SHS 通过解析回放 Event Log 来还原 Spark 作业的状态信息,大量事件的反序列化处理开销大,UI 加载延迟明显。对于大规模的作业,从发起访问到看到 UI,用户可能需要等待数分钟甚至几十分钟,体验较差。


扩展性差

SHS 服务节点通过定期扫描 Event log 目录,在本地 KVStore 更新维护 Application 列表,是一个有状态的服务。每次服务重启,需要重新扫描整个目录,才能对外服务。当目录下积累的作业日志增多,每一次扫描的耗时也会相应增加,此外,日志文件合并、清理负担也会加大,必须对服务节点进行纵向扩容。


不支持多租户

在公有云 DLC 产品中,我们希望为用户提供 SAAS 化的 Spark UI 服务,用户无需自己搭建 SHS。一种方案是由服务方为每个用户搭建一套 SHS,显然成本会很高,同时也会增加维护的负担;如果一个地域只部署一套 SHS,一方面要求服务能通过水平扩展提升处理能力,另外还要求服务支持用户间的资源隔离,比如 Event log 目录、访问权限,开源 SHS 不具备相关能力。


DLC UI Service 方案

Spark Driver 在运行过程中本身就会通过 AppStatusListener 监听事件并将作业运行的状态数据存储到 ElementTrackingStore(数据存储在基于内存的 KVStore),以便跟踪作业的运行情况。History Server 回放 Event log 其实是重复这一过程。如果在作业运行过程中直接将状态数据持久化到 FileSystem,这样就不用再存储大量 Event 了。

org.apache.spark.status.JobDataWrapperorg.apache.spark.status.ExecutorStageSummaryWrapperorg.apache.spark.status.ApplicationInfoWrapperorg.apache.spark.status.PoolDataorg.apache.spark.status.ExecutorSummaryWrapperorg.apache.spark.status.StageDataWrapperorg.apache.spark.status.AppSummaryorg.apache.spark.status.RDDOperationGraphWrapperorg.apache.spark.status.TaskDataWrapperorg.apache.spark.status.ApplicationEnvironmentInfoWrapper
# SQLorg.apache.spark.sql.execution.ui.SQLExecutionUIDataorg.apache.spark.sql.execution.ui.SparkPlanGraphWrapper
# Structure Streamingorg.apache.spark.sql.streaming.ui.StreamingQueryDataorg.apache.spark.sql.streaming.ui.StreamingQueryProgressWrapper
复制代码

图 2 运行状态数据类


基于上述朴素的想法,我们设计了如下方案。



图 3   DLC Spark UI Service

1.UIMetaListener

UIMetaListener 创建一个 ElementTrackingStore 实例,用作 Temp Store。通过一个线程定期遍历 Original ElementTrackingStore 中的数据,对于每一条数据,检查 Temp Store 是否存在相同 key 的旧数据。若不存在,就将数据写入 Backup Store,然后再写出到 UI Meta 文件;若存在则计算两条数据的 MD5 并进行对比,若不一致,说明数据已更新,就将新的数据写入 Backup Store,然后再写出到 UI Meta 文件。



图 4 Dump UI Meta


Temp Store 用于临时存储可能还会发生变化的数据,而对于已经完成的 Job/Stage/Task,其状态数据不会再变,而且已经持久化到 UI Meta 文件,因此需要及时将其从 Temp Store 清理掉,避免占用太多内存资源。UIMetaListener 通过两种方式触发清理,一种是监听到 TaskStart/TaskEnd 事件时触发,一种是往 Temp Store 写入数据时触发。

2.UIMetaWriter

UIMetaWriter 定义了 UI Meta 文件的数据结构,单条结构如下:       



图 5 数据结构


每个 UI 相关的数据类实例会序列化成四个部分:类名长度(4 字节整型)+ 类型(字符串类型)+ 数据长度(4 字节整型)+ 序列化数据(二进制类型)。数据的序列化使用 Spark 自带的序列化器 KVStoreSerializer,支持 GZIP 压缩。数据在文件中连续存放。

DLC 使用对象存储 COS 来存储 UI Meta 文件,COS 对 Append 方式写存在诸多限制,同时为了避免 Streaming 场景下单个文件过大,DLC Spark UI Service 实现了 RollingUIMetaWriter,支持按文件大小滚动写;同时也实现了 SingleUIMetaWriter,适用于支持 Append 写和对文件个数敏感的文件系统,比如 HDFS。

3.UIMetaProvider

UIMetaProvider 重新实现了 ApplicationHistoryProvider,去掉了 FsHistoryProvider 里的日志路径定期扫描,不再维护全量的 Application 列表。当收到某个 Application UI 请求时,UIMetaProvider 根据路径规则直接读取对应 Application 目录下的 UI Meta 文件,反序列化数据并写入 KVStore。简化后的 History Server 只需要处理加载 UI 的请求,因此很容易通过水平扩展提升服务整体的处理能力。

跟 FsHistoryProvider 一样,UIMetaProvider 也支持缓存已加载的 Active UI 数据。但不同的是,对于缓存中的 Active UI,UIMetaProvider 会定期检查对应的作业状态或日志文件是否有变化,如果有则自动读取新增的 UI Meta 文件,更新 KVStore 里的数据,无需每次都从头开始加载。

4.多租户

原生 SHS 没有多租户设计,默认所有的作业日志都存放在同一个目录下,ACL 由每个作业在其运行参数里设置。而 DLC 为不同用户分配了不同的日志目录,同时希望基于公有云账号进行认证和鉴权,为此 Spark UI Service 做了一些改造。

用户通过 DLC 访问 Spark UI Service 时,首先跳转到公有云登陆入口,完成登陆后在请求 cookie 中添加 userId。Spark UI Service 通过 HttpRequestUserFilter 拦截请求,将 Cookie 中携带的 userId 保存在请求处理线程的 ThreadLocal 变量中。在加载 UI Meta 时根据 userId 查询用户的日志目录,然后拼接请求参数中携带的 appId 和 attemptId 组成完整的日志路径。同时在缓存 Active UI 时也会将 userId 信息随之保存,当命中缓存中 UI 时也要校验 userId 和请求中携带的 userId 是否一致。


测试结果

以 SparkPi 作为测试作业,分别在四种参数下进行测试。如下图所示,DLC Spark UI Serice 相较于开源 Spark History Server,日志大小减少了 80%,大型作业的 UI 加载时间减少 70%,用户体验明显改善。



图 6 日志大小对比



图 7 UI 加载时间对比

总结

针对云原生场景下的 Spark UI 需求,DLC 重新设计了 Spark UI Service 方案,并对开源 Spark 进行了改造,从成本上降低了日志存储开销,从用户体验上加速了 UI 访问,从架构上实现了服务的水平扩展。

用户头像

还未添加个人签名 2020-06-19 加入

欢迎关注,邀您一起探索数据的无限潜能!

评论

发布
暂无评论
云原生Spark UI Service在腾讯云云原生数据湖产品DLC的实践_数据湖_腾讯云大数据_InfoQ写作社区