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


    f27cc8d5e490f7792c06480c32a5b997.gif

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

    摘要

    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作业当前的状态和运行过程中的统计信息。

    118ebd5d727c172d3fb11c070d0442f7.png

         图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了。

    1. org.apache.spark.status.JobDataWrapper
    2. org.apache.spark.status.ExecutorStageSummaryWrapper
    3. org.apache.spark.status.ApplicationInfoWrapper
    4. org.apache.spark.status.PoolData
    5. org.apache.spark.status.ExecutorSummaryWrapper
    6. org.apache.spark.status.StageDataWrapper
    7. org.apache.spark.status.AppSummary
    8. org.apache.spark.status.RDDOperationGraphWrapper
    9. org.apache.spark.status.TaskDataWrapper
    10. org.apache.spark.status.ApplicationEnvironmentInfoWrapper
    11. # SQL
    12. org.apache.spark.sql.execution.ui.SQLExecutionUIData
    13. org.apache.spark.sql.execution.ui.SparkPlanGraphWrapper
    14. # Structure Streaming
    15. org.apache.spark.sql.streaming.ui.StreamingQueryData
    16. org.apache.spark.sql.streaming.ui.StreamingQueryProgressWrapper

    图2 运行状态数据类

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

    8ebaeb1c06ae33ef0424ed235bbeb15c.png

    图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文件。

    121e5f08e68bf40e097b42f215d7d14f.png

    图4 Dump UI Meta

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

    1. UIMetaWriter

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

    97ec5a0cafe7631861c0044e77b2266a.png

    图5 数据结构

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

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

    1. 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里的数据,无需每次都从头开始加载。

    1. 多租户

    原生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%,用户体验明显改善。

    648498821847439905aa2f4a2d0b1fbe.png

    图6 日志大小对比

    2bb574e7ac86cbc8c5c63054426ae30f.png

    图7 UI加载时间对比

    总结

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

    推荐阅读

    5ff1927e9d5d8911e09a3bcb4861d259.jpeg

    7e13e9937cbd1877175ba03f92608bc7.jpeg

    1cdd4cd32e95ca7cc8a96f16d557fd37.jpeg

    关注腾讯云大数据公众号

    邀您探索数据的无限可能

    cd0522d31816fd0cc4494c0871f915b9.png

    点击“阅读原文”,了解相关产品最新动态

    ↓↓↓

  • 相关阅读:
    vue2添加(修改)数据后需要刷新才能显示的问题
    期末前端web大作业——HTML+CSS+JavaScript仿京东购物商城网页制作(7页)
    一文解析推特上最常见的加密骗局
    IPQ9574 with QCN9274 Solution for Industrial Applications|WiFi 7
    Python之第六章 内置容器 --- 字符串
    RK3399平台开发系列讲解(中断篇)13.16、request_irq的实现
    国内研究者在关注着大家的哪些心理变化?横断历史研究介绍与趋势分析
    创作纪念日-我的第1024天
    linux 硬盘存储剩余容量自动化监控+报警通知
    SpringCloud-Gateway无法使用Feign服务(2021.X版本)
  • 原文地址:https://blog.csdn.net/cloudbigdata/article/details/126188579