存算分离实践:构建轻量、云中立的大数据平台

2023年 8月 16日 34.0k 0

今天我们将分享社区用户多点 DMALL 的案例。多点 DMALL 是亚洲领先的全渠道数字零售解决方案服务商,目前已与 380 家零售企业达成合作,覆盖 6 个国家和地区。

面对 B 端客户日益增长的企业数据,存算一体的架构显得力不从心。计算资源冗余浪费、所依靠的CDH发行版技术栈复杂、部署运维困难及计算资源潮汐现象严重等问题,迫使多点启动架构升级的进程。同时,为满足 B 端客户多样化的需求,多点需要构建一个可以在多云环境下更具性价比、可复用的大数据底层基座和平台工具链。基于此,多点的大数据团队开始搭建存算分离的云原生大数据架构。

本文深入剖析这次改造的架构设计与演进过程,分享多点 DMALL 在此过程中的经验和挑战。值得一提的是,他们利用 JuiceFS 社区版实现了与 Ranger 组件进行权限的对接,希望此经验能为其他使用 JuiceFS 的企业提供参考。

一、存算一体架构下的痛点和挑战

1.1 架构原生存在的痛点

存算一体架构带来的成本和运维挑战,是大部分企业在大数据发展中一定会面对的问题。

传统的 Hadoop 生态体系中,数据存储角色与计算角色通常会部署在相同的机器上,一个占据硬盘提供存储,一个利用 CPU 和内存做计算。为此,MapReduce 和 Spark 也适应性的设计了多层级的数据本地化策略,即任务尽可能被分配到存储所需数据的对应节点上做计算,以减少中间数据交互产生的网络开销和额外的存储压力,提升整体的大数据应用效率。

可是,随着企业业务的发展,大数据存储量的增长速率与计算所需节点数量的增长速率很难保持一致。尤其是在“数据就是企业核心资产”的思想下,大量历史数据、冷数据的积累,导致企业数据存储量的增长诉求远远高于计算资源。最后企业只好不断新增机器存储更多数据,但大量计算资源得不到充分利用造成了闲置与浪费。

同样是增加存储资源,存算一体架构下会闲置部分计算资源,存算分离则不会有这个问题。

此外,数据量的不断增长还带来了 HDFS NameNode 元数据压力、集群节点规模扩张受限等问题。这些问题也时时刻刻牵动着各个大数据团队紧绷的神经。

1.2 多点DMALL 面临的挑战

多点DMALL 的大数据体系在构建之初,也是采用传统 Hadoop 存算一体的技术栈。除了上述企业发展中架构原生带来的困境外,面对 To B 多样化的业务场景,多点DMALL 大数据团队面临更多场景化的挑战:

  • 组件多技术栈复杂:之前主要依赖 CDH 发行版本,该套架构组件繁多,架构复杂,共包括11类服务(存储、计算、运维、监控、安全等),22 种角色类型。并且随着时间推移,很多新技术引入异常麻烦,需要考虑非常多兼容性问题。
  • 部署复杂 & 运维困难:私有化部署、SaaS 服务模式一度给大数据团队带来了巨大的工作量,交付效率不高,包括网络规划、容量规划、公有云机型选择、漏洞修复和多环境日常维护等。
  • 计算资源潮汐现象严重:存算一体的架构下大数据集群和业务集群是相互独立的,资源使用有着不同的特点。大数据集群资源使用的高峰在凌晨,白天只有零散的即席查询占资源不多;业务集群的峰值在白天,晚上流量很少,这也是领域内老生常谈的“潮汐现象”,因此计算资源浪费和闲置一直没有彻底解决。

二、存算分离的架构设计

随着多点DMALL 全面 To B 转型,为越来越多的 B 端客户提供零售全渠道解决方案,需要具备在多云环境下提供更具性价比、可复用的大数据底层基座和平台工具链。多点DMALL 大数据团队结合已有经验和后续业务需求,设计搭建存算分离、轻量级、可扩展、云中立大数据集群架构。

而存算分离的第一步,便是要解决数据如何从 HDFS 集群上快速切换到云服务商存储服务的问题。

2.1 小试牛刀:直接对接对象存储

在架构升级探索期,能想到最直接的方案就是通过 API 对接云厂商的对象存储。

从架构图上看这逻辑非常简洁清晰。考虑到各大云厂商都提供了稳定的对象存储服务以及完善的API,直接加以利用应该会降低架构升级的难度。为了快速检验这一思路的可行性,我们首先选择了大数据平台上,与 HDFS 会产生交互的部分功能做切换,将其换成与对象存储进行交互的方式。

快速检验的结果是,这样的设计不仅没有达到预期,反而使大数据平台开发的复杂度成倍增加。

出现问题的核心点在于:

  • 部分 B 端客户可能会选择自己信任/合作的云服务商,而选择的结果不可控。
  • 虽然底层都是 S3 的协议,为了构建技术壁垒,各大云服务商的对象存储 API 仍然存在一定差异。
  • 为了满足不同客户的不同云服务商需求,大数据平台工具链将需要适配开发多套代码,开发工作量巨大。

经过验证,上述探索方案只能进行小型试点,无法支撑整个大数据架构的规模化调整,还需探寻新的解决方案。于是,JuiceFS 进入了我们的视线。

2.2 JuiceFS:平滑过渡利器

多点大数据团队很早便开始关注 JuiceFS了。在直接使用对象存储的方案宣告不可行之后,我们就一直在寻找能帮助大数据应用及引擎平滑切换到对象存储的方式。幸运的是,我们注意到了 JuiceFS 合伙人苏锐的一篇分享:从 Hadoop 到云原生, 大数据平台如何做存算分离。而后经过不断探索与验证,我们意识到这就是一直在寻找的问题解决之道。

采用 JuiceFS 的优势如下:

  • 已对接市面主流公有云对象存储:为了将存储和计算剥离,对象存储是最佳选择,其本身是公有云最基础服务之一。JuiceFS 底层存储对接了市场上绝大部分云服务厂商提供的对象存储服务,可以帮助我们彻底剥离存储和计算资源,做到存算分离的效果。
  • 完美兼容 HDFS 协议,大数据引擎平滑切换:JuiceFS 提供了 Hadoop Java SDK,帮助所有使用传统的 HDFS API 的计算引擎和应用平滑切换,基本可以做到只需要修改相应的配置便可以直接执行,大大降低了新架构下引擎间调试适配的复杂性。
  • 独立元数据引擎,解决 NameNode 瓶颈问题:JuiceFS 的元数据存储在独立的存储引擎中,彻底解决了 NameNode 内存限制及单点问题。元数据引擎独立部署,对其单独的调优和运维也更加便利。没有元数据扩展的压力,集群扩张的限制也不再存在。
  • 提供 CSI 方式,支持云原生设计:在构建云原生架构的道路上,JuiceFS 提供的 Kubernetes CSI 驱动,让这个架构设计实现更加完善,在 K8s 上使用 JuiceFS 更加方便。

2.3 最终架构设计

以下是多点大数据最终的存算分离架构设计:

我们将整体架构逻辑分为以下几层:

  • 工具层:最上层是多点大数据团队自研的 UniData 大数据平台工具链,提供完善的大数据开发治理能力,包括数据集成、数据开发、任务调度、数据资产等,实现了“用”。
  • 计算层:接下来是由 Kubernetes 管理的数据计算层,提供 Spark、Flink 等计算组件。这一层就是“存算分离”中的“算”。
  • 管控层:再下一层中,提供了除了数据计算外,元数据的存储、权限的管控、查询代理等功能,负责了架构中的“管”这一层。
  • 存储层:最后就是 JuiceFS 和各个云服务提供的对象存储,提供协议适配和加速能力,实现了“存”。

在不断探索和尝试中,我们最终确定 JuiceFS 的引入和使用。JuiceFS 作为存储中间层,对下屏蔽了底层实际存储介质,隔离了不同的云环境,对上提供了统一的 HDFS API,保证了引擎执行和应用功能的一致性和稳定性,从而保障了集群整体对外服务的质量。

三、JuiceFS 的深入运用实践

新技术的引入总是伴随着折腾的过程。在探索和使用 JuiceFS 的过程中,多点DMALL 大数据团队不出意外地踩了一些坑,幸而最终都找到了较为合理的解决方案。在此将遇到的部分典型问题整理分享出来,希望给所有计划和正在使用 JuiceFS 的同学一些启发和帮助。

3.1 添加基于 Ranger 的安全管控

开源的 JuiceFS 项目中,Hadoop Java SDK 没有安全管控的功能。因此在选择使用 JuiceFS 时,安全成为我们最关注的问题。

通过对该模块代码的细致研究,参考 HDFS 的鉴权逻辑方案,我们在 JuiceFS 的 FileSystem 的实现类中,对每个API 的实现实际操作触发前都添加了权限拦截的处理。

“权限”一词的计算机语言内涵就是“实体+动作”,Ranger 的权限设计本质也是一样的。我们将拦截的对应操作(例如创建)和相关路径转化为Ranger HDFS模块所需鉴权的动作和实体,并与操作用户组合成RangerAccessRequest与 Ranger HDFS 模块打通进行鉴权。这个改动解决了 JuiceFS 在系统中“裸奔”的情况,为数据的安全做了一道防护。

当然,从整体的权限体系设计来讲,考虑到 Ranger一直被人所诟病的 Ranger Admin 连接风暴和策略本地化等问题,我们设计增加了权限鉴权的代理层,来进行鉴权的分流、权限映射和缓存等。但这些架构上的优化不影响 JuiceFS 接入 Ranger 的权限管控的本质目标。

除了正常的权限管控,对于可能存在的恶意使用我们也做了准备。考虑到 JuiceFS 开源代码的公开性,为了避免部分用户在了解到底层架构和引擎选择后,恶意破解调用以非法获取数据,我们对 JuiceFS 还做了额外的代码调整,包括修改核心参数的取值方式等。在保留和充分利用 JuiceFS 的核心功能前提下添加防护墙,提升整体的安全水平。

3.2 Spark 的 Shuffle 数据处理

在 Spark on K8s 的云原生设计中,Shuffle 数据的处理是需要重点关注的。相比于通过机器堆出来的 YARN 集群可以直接利用超大的本地磁盘存储 Shuffle 数据而言,试图避免依赖底层机器、存算分离设计下 K8s 上的任务只能另谋他路。

在看到 JuiceFS 提供的的 K8s CSI 驱动时,我们最初以此作为突破点。在设想中,可以利用 JuiceFS K8s CSI 驱动的 writeback 模式,Shuffle 数据先放置临时存储目录,超过阈值后载入远端对象存储中。这样的逻辑下,Spark 的Shuffle 数据就无需依赖本地机器磁盘大小,有海量对象存储作为最终存储介质,理论上不再担心执行压力和数据临时存储压力。

但经过实际验证,在进行on YARN 和 on K8s 的性能测试对比时发现,使用这个方案的实际效果是:慢得不止一点点。

以下为测试中最典型的一个 Query 结果:

image.png

在深入分析研究后,我们发现 Shuffle 场景本身会存在大量小文件及随机读操作,JuiceFS K8s CSI 并不适合这种场景,会产生较大的性能瓶颈。在与 JuiceFS 社区沟通探讨后,我们开始调研开源的 Remote Shuffle Service,将 CSI 的方式切换为利用独立的 Shuffle 服务,并根据测试最终选用了 Apache Celeborn(后简称Celeborn)支持这一场景,其整体性能表现跟 on YARN 差异不大。Apache Celeborn 自身支持分级存储能力,极大提升了各类实际负载适配能力,我们将 JuiceFS 作为内存/磁盘容量不足情况下最后的兜底 Shuffle 数据存储。

3.3 Alpine 镜像问题

上文提到,我们有一些大数据平台应用是直接通过 Hadoop Java SDK 与 HDFS 进行交互的。这些应用都是 Java 应用,在云原生的转换过程中发现以下报错:

`initial-exec TLS resolves to dynamic definition in /tmp/libjfs-amd64.7.so`

经过探索,并与 JuiceFS 社区沟通后,我们发现这个问题,是由于使用的基础镜像 openjdk-Alpine 本身的 bug,后来我们换成了eclipse-temurin 解决了问题。

3.4 root 不会被设置为 Owner

在 Celeborn 的使用中发现一个漏洞。Celeborn 会自动创建其存储 Shuffle 数据的 HDFS 目录,当该服务的启动用户是 root 时,自动创建的 HDFS 目录并没有被自动设置 Owner 为 root。在上面提到的我们模仿 HDFS 鉴权思路中,对于一些目录的操作会去校验是否是这个目录的 Owner。root 没有被设置,自然后续 Celeborn 很多针对这个 HDFS 目录的操作都会被权限拦截。

虽然我们可以通过切换 Celeborn 的启动用户,或者给他单独设置权限等方式绕过这个拦截。考虑到创建后设置Owner 是合理行为,而且除了 root 外的其他用户都会被正常设置,我们还是将这个疑问向 JuiceFS 社区提出来。感谢 JuiceFS 社区第一时间的响应和支持,很快就修复了这个小漏洞。

3.5 数据缓存运用与 OOM 问题

将 CSI 驱动切换成 Celeborn 后,我们又一次开始做 Spark on YARN 和 on K8s 的性能测试。对比中发现,相同的任务和资源,on K8s 的任务总是会报错 OOM。通过细致的 Spark 内存分析,并不断对比多个环境任务和差异点后,团队内一位同学发现了 JuiceFS 的数据缓存参数设置区别,深入挖掘,最终找到答案。

Spark 任务执行时,需要特别配置juicefs.cache-dir,不然 JuiceFS 就会默认将数据缓存放进内存中,从而对每一个 executor 多出好几百兆的额外内存占用。如果不做特殊配置,那就需要在 Spark 任务切换到on K8s的环境时,多配一些 off-heap 堆外内存,用以支持 JuiceFS 的额外数据缓存。

3.6 数据缓存目录的权限

在使用数据缓存目录(后简称 cache 目录)的应用中,我们还遇到了另一个问题。Spark on K8s 的 Jupyter 应用中我们使用 JuiceFS K8s CSI 驱动建立的 PVC,与使用 JuiceFS Hadoop Java SDK 挂载 cache 目录,当二者使用同一个目录,会产生权限冲突的问题,在 Spark 运行日志中出现 warn 日志无法落地/获取缓存数据。仔细跟踪后发现,是因为两条链路生成的缓存文件目录默认权限不同,相互修改权限最终导致了文件写入失败,这样相当于根本没利用上 JuiceFS 客户端缓存,每次都直接与对象存储交互,这样对 Spark 任务性能而言影响很大。

该问题在反馈给 JuiceFS 社区后,社区通过对 JuiceFS K8s CSI 驱动增加参数“cache-mode”进行了修复。

3.7 TiKV & Write Conflict

在做容量规划的时候考虑到线上集群规模,TiKV 一开始就被我们选择为 JuiceFS 的元数据存储引擎。在我们对云原生架构开发测试的大部分时间内,TiKV 的表现一直很稳定,直到我们选择 Celeborn 作为独立 Shuffle 服务。

根据 Celeborn 的功能设计,在当本地磁盘存储 Shuffle 数据满时,将把数据下推到 HDFS 中(当然我们在这里利用 JuiceFS 让其实际下推到了对象存储)。但在具体测试时发现,多个 Celeborn 的 Worker 同时写一个 JuiceFS 的目录会出现 Write Conflict 问题并触发重试操作。重试操作会有次数极限,而且不断重试很明显降低了整体Shuffle 效率延长了任务执行时长,在很长一段时间内这个问题也困扰着我们。

最终,社区的另一位 JuiceFS 用户给出了方案。Write Conflict 的根本原因是所有的写文件都要修改父目录的更新时间,这个报错并非是因为写文件,而是修改同一个目录属性产生的异常。再进一步,产生 Write Conflict 的不是JuiceFS 管理的数据,而是元数据,也就是 TiKV 的锁问题。最终,考虑到除了 Shuffle 场景,这样高并发的修改同一个目录的属性并不常见,我们决定为 Celeborn 部署提供单独的 JuiceFS 的 Hadoop Java SDK,这个 SDK 是单独处理的,写数据不再更新父目录的属性。

3.8 TiKV 垃圾回收机制

加入 JuiceFS 社区群后,我们也时常关注群内其他企业使用的问题反馈,可以帮助我们在正式上线前覆盖更多的测试案例。TiKV 的垃圾回收机制问题就是其中一个。当看到群里有其他同学反馈后,我们快速分析了该问题发生的原因,并检查补充了部署策略。TiKV 的独立服务并不会自动触发垃圾回收机制,只有同步安装 TiDB 这个组件才会正常运转。而我们在元信息服务 TiKV 部署策略中会同步安装 TiDB,不会遇到这个问题。另外,JuiceFS 1.0.4 版本开始已经新增 TiKV gc worker 后台线程适时触发垃圾回收动作。

3.9 HDFS 回收站文件无法清理

当 HDFS 配置文件中开启了 HDFS 回收站功能(fs.trash.intervalfs.trash.checkpoint.interval),只要存活的客户端实例都会检查并触发回收站中文件清理工作。但是最开始我们测试发现,清理线程总是报错提示没有文件操作权限。跟 JuiceFS 社区沟通后发现,的确存在 bug 导致过期文件没法清理,并迅速提供了 PR 修复。

四、最终测试结果

正如上文中提到的,我们在架构升级过程中多次在公司开发环境进行了 Spark on YARN 和 on K8s 的性能测试对比,分别执行多次 TPC-DS SQL。以下为最终的对比结果:

上述测试是通过大数据平台 UniData 配置任务进行数据计算对比,变量包含平台调度策略的调整、Spark 版本升级等。排除其他变量,深入分析时间差异后,我们得出以下结论:

  • Spark 任务基于 HDFS 的on YARN 执行时长与基于 JuiceFS 的 on K8s 执行时长基本持平,性能差异较小。

  • JuiceFS 的数据缓存设计对数据查询存在明显的加速作用,同样的 SQL 在多次执行后,执行速度明显提升。

  • JuiceFS 会占用部分内存,总体而言比基于 HDFS 的任务所需内存更多。

  • 从上述测试结果来看,已经达到了我们新架构正式上线的要求。目前这套架构已在多个公有云环境中平稳运转,接下来我们会启动现有历史 CDH 存算一体集群下线,并升级为新的存算分离新架构的动作。另外,为进一步提升Spark 执行性能,我们也在积极开展引入向量化执行引擎框架 Gluten 的测试验证工作。

    五、小结

    在多点DMALL 从传统 Hadoop 存算一体到存算分离的升级过程中,JuiceFS 的出现填补了存储设计的空缺,推动了升级闭环。它对上保持了同样的 HDFS 协议,降低各个应用和引擎适配复杂度,对下完美对接各个云服务厂商提供的对象存储服务,提升了整体架构的升级效率。

    经过整体向云原生的存算分离架构的升级,我们获得了多方面的收益:

    • 节约成本:存算分离可以为企业客户节约大量硬件或云服务商的成本,从而提升客户满意度,这也推动了我们服务续约率的提升。
    • 技术扩展性好:我们之前使用 CDH 发行版进行组件的管理,因为引擎间版本限制,和重要组件升级带来的风险高等问题,客户有些技术升级诉求无法响应。存算分离后我们也摆脱了这个限制。现在,我们可以针对性地升级和调试单一组件,甚至在同一集群内进行AB测试,大大降低了升级风险。
    • 部署和运维效率提升:升级前我们的交付最快只能达到天级,这还不算前期的集群设计和准备工作。现在可以达到小时级,资源是按需使用的,随用随取,没有之前那些复杂的预投入,大数据平台一键拉起,释放了大量人力成本。
      我们很幸运在整体架构升级的过程中遇到了 JuiceFS 这个项目,也希望通过这篇实践分享能帮助到更多的企业更好的运用 JuiceFS。未来我们也会持续关注 JuiceFS 社区,持续为社区建设做出更多的贡献。

    希望这篇内容能够对你有一些帮助,如果有其他疑问欢迎加入 JuiceFS 社区与大家共同交流。

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论