{"id":28740938,"url":"https://github.com/aliyuncontainerservice/benchmark-for-spark","last_synced_at":"2025-06-28T11:32:46.680Z","repository":{"id":81541842,"uuid":"292162674","full_name":"AliyunContainerService/benchmark-for-spark","owner":"AliyunContainerService","description":"benchmark-for-spark","archived":false,"fork":false,"pushed_at":"2025-05-06T10:05:28.000Z","size":45740,"stargazers_count":16,"open_issues_count":0,"forks_count":10,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-05-06T10:33:37.762Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"HCL","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/AliyunContainerService.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2020-09-02T02:51:24.000Z","updated_at":"2025-04-24T09:49:44.000Z","dependencies_parsed_at":"2025-04-24T04:36:41.970Z","dependency_job_id":null,"html_url":"https://github.com/AliyunContainerService/benchmark-for-spark","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/AliyunContainerService/benchmark-for-spark","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AliyunContainerService%2Fbenchmark-for-spark","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AliyunContainerService%2Fbenchmark-for-spark/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AliyunContainerService%2Fbenchmark-for-spark/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AliyunContainerService%2Fbenchmark-for-spark/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AliyunContainerService","download_url":"https://codeload.github.com/AliyunContainerService/benchmark-for-spark/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AliyunContainerService%2Fbenchmark-for-spark/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260116644,"owners_count":22961065,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-06-16T07:09:37.882Z","updated_at":"2025-06-16T07:09:38.776Z","avatar_url":"https://github.com/AliyunContainerService.png","language":"HCL","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Spark on ACK 最佳实践和基准测试\n\n[Apache Spark](https://spark.apache.org/) 是一种专门为大规模数据处理而设计的快速且通用的计算引擎，并已经广泛应用于各行各业的大数据处理场景中。\n\n[阿里云容器服务 Kubernetes 版](https://help.aliyun.com/zh/ack/)（Container Service for Kubernetes，简称容器服务 ACK）提供高性能可伸缩的容器应用管理服务，支持企业级 Kubernetes 容器化应用的生命周期管理。\n\n本文将介绍在容器服务 ACK 集群中运行 Spark 工作负载的最佳实践和基准测试结果。\n\n## 为什么要在 Kubernetes 上运行 Spark 作业？\n\nSpark 自 2.3 版本开始支持将作业提交至 Kubernetes 集群中，详情参见 [[SPARK-18278]](https://issues.apache.org/jira/browse/SPARK-18278)。在此之前，Spark 支持的集群管理器只有 Apache Hadoop Yarn、Apache Mesos 以及 Spark 自身实现的一个集群管理器 。\n\n\u003c!-- TODO: Kubernetes 的发展 --\u003e\n\n在 Kubernetes 上运行 Spark 工作负载有以下优点：\n\n- **标准化和可移植性**：通过把 Spark 应用及其依赖打包成容器镜像，享受容器的各种优点，很容易的解决 Hadoop 版本不匹配和兼容性问题。还可以给容器镜像打上标签控制版本，这样如果需要测试不同版本的Spark或者依赖项的话，选择对应的版本做到了。\n- **资源按需供给**：Spark 可以按需申请资源，例如申请 10 个 Executor，每个 Executor 分配 8 个 CPU core 和 32GB 内存，避免资源浪费。\n- **认证和鉴权**：由于 Driver pod 负责创建多个 Executor pods，可以利用 Kubernetes 的 ServiceAccount 和 RBAC 资源（Role、ClusterRole 等）做权限控制，从而保证集群的安全性。\n- **支持多租户**：可利用 Kubernetes 的命名空间机制（Namespace）和资源配额机制（ResourceQuota）做用户粒度资源隔离和资源分配，并利用 Kubernetes 的节点选择机制（NodeSelector）保证 Spark 工作负载可以获得专用的资源。\n- **复用 Kubernetes 生态**：重用 Kubernetes 生态的各种组件，例如 Prometheus 监控、日志等。通过把 Spark 工作负载部署在已有的 Kubernetes 集群中，能够快速开始工作，大大降低运维成本。\n- **Spark 多版本支持**：在传统的部署方式下，客户端使用的 Spark 版本必须和集群中使用的 Spark 版本一致。而在 Kubernetes 环境下，用户可以自定义 Spark 作业所使用的容器镜像，从而可以实现在一个 Kubernetes 集群中同时运行多个版本的 Spark 作业。\n- **工作流编排**：把 Spark 和管理数据生命周期的应用运行在同一个集群中，可以使用业界流行的工作流编排解决方案（例如 [Apache Airflow](https://airflow.apache.org/) 和 [Argo Workflow]([https://](https://argoproj.github.io/argo-workflows/))）构建端到端的解决方案，并能很容易的复制到其他区域部署，甚至是在私有化环境中部署。\n\n## 如何在 Kubernetes 上运行 Spark 作业？\n\n在 Spark 中，作业通常由一个 Driver 进程和若干个 Executor 进程构成。在 Kubernetes 中，Pod 是集群中最小的可部署单元和执行单元。\n\n将 Spark 作业运行在 Kubernetes 上的大致想法就是在单独的 Pod 中分别运行 Driver 进程和 Executor 进程。从 Spark Core 代码实现上来看，其最主要的组件是 `KubernetesClusterSchedulerBackend`，它是 `CoarseGrainedSchedulerBackend` 的一个子类，通过调用 Kubernetes API 创建和删除 Pod 来启动和停止 Spark 作业。\n\n向 Kubernetes 集群提交 Spark 作业常见的有两种方式，一是使用 Spark 中自带的 `spark-submit` 脚本来提交作业，二是使用 [Spark Operator](https://github.com/kubeflow/spark-operator) 提交作业，下面将分别介绍这两种提交方式。\n\n### spark-submit\n\n客户端可以直接使用 `spark-submit` 命令提交 Spark 作业到 Kubernetes 集群中。\n\n首先，我们需要给 Spark 作业中的 Driver pod 创建一个服务账号（ServiceAccount）并对其授予一定的权限，否则 Driver pod 会由于权限不足无法创建出 Executor pods，从而导致作业执行失败。\n\n```yaml\n# spark-rbac.yaml\n\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: spark\n\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: spark-role\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - pods\n  - services\n  - configmaps\n  verbs:\n  - \"*\"\n\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: spark-rolebinding\nsubjects:\n- kind: ServiceAccount\n  name: spark\n  namespace: default\nroleRef:\n  kind: Role\n  name: spark-role\n  apiGroup: rbac.authorization.k8s.io\n```\n\n将上述 RBAC 资源清单文件保存为 `spark-rbac.yaml`，并执行如下命令创建相应的资源：\n\n```shell\n$ kubectl apply -f spark-rbac.yaml\nserviceaccount/spark created\nrole.rbac.authorization.k8s.io/spark-role created\nrolebinding.rbac.authorization.k8s.io/spark-rolebinding created\n```\n\n然后，我们需要获取 Kubernetes 集群的 API Server 的访问 URL：\n\n```shell\nKUBERNETES_API_SERVER_URL=`kubectl cluster-info | grep \"Kubernetes control plane\" | egrep -o \"https://[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+:[0-9]+\"`\n```\n\n最后， 调用 `spark-submit` 脚本提交 Spark 作业：\n\n```shell\n# Spark 镜像\nSPARK_IMAGE=apache/spark:3.5.0\n\n# 提交作业\n${SPARK_HOME}/bin/spark-submit \\\n    --master k8s://${KUBERNETES_API_SERVER_URL} \\\n    --deploy-mode cluster \\\n    --name spark-pi \\\n    --class org.apache.spark.examples.SparkPi \\\n    --conf spark.executor.instances=2 \\\n    --conf spark.kubernetes.container.image=${SPARK_IMAGE} \\\n    --conf spark.kubernetes.authenticate.driver.serviceAccountName=spark \\\n    local:///opt/spark/examples/jars/spark-examples_2.12-3.5.0.jar\n```\n\n### Spark Operator\n\nKubernetes 中的 [Operator 模式](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/) 允许用户在不修改 Kubernetes 自身代码的情况下，通过为一个或多个自定义资源（Custom Resource，CR）关联控制器（Controller），从而扩展集群的能力。\n\n使用 Operator 可以实现非常多的功能，例如自动化部署应用和管理应用的生命周期。[Spark Operator](https://github.com/kubeflow/spark-operator) 定义了 `SparkApplication` 和 `ScheduledSparkApplication` 两种 CRD（CustomResourceDefinition）并为它们实现了相应的控制器，用户只需要编写相应的清单文件，就可以像管理 Kubernetes 中内置的资源一样去管理 Spark 作业。\n\n例如，用户在集群中部署好 Spark Operator 之后，可以编写如下作业清单文件：\n\n```yaml\n# spark-pi.yaml\n\napiVersion: sparkoperator.k8s.io/v1beta2\nkind: SparkApplication\nmetadata:\n  name: spark-pi\n  namespace: default\nspec:\n  type: Scala\n  mode: cluster\n  image: apache/spark:3.5.0\n  mainClass: org.apache.spark.examples.SparkPi\n  mainApplicationFile: local:///opt/spark/examples/jars/spark-examples_2.12-3.5.0.jar\n  sparkVersion: \"3.5.0\"\n  driver:\n    cores: 1\n    coreLimit: 1200m\n    memory: 512m\n    serviceAccount: spark\n  executor:\n    cores: 1\n    instances: 1\n    memory: 512m\n```\n\n上述清单文件定义了一个 SparkApplication 对象，其中定义了 Spark 作业所需的参数，Driver/Executor Pod 的资源配置等，将其保存为 `spark-pi.yaml`，然后可以执行如下操作：\n\n```shell\n# 提交作业\nkubectl apply -f spark-pi.yaml\n\n# 查看作业\nkubectl get sparkapplication spark-pi\n\n# 删除作业\nkubectl delete -f spark-pi.yaml\n```\n\n关于使用 Spark Operator 提交作业的更多细节，请参考 [Spark Operator 用户文档](https://github.com/kubeflow/spark-operator/blob/master/docs/user-guide.md)。\n\n## 快速开始\n\n关于如何在 ACK 集群中运行 Spark 工作负载，请参考[使用Spark Operator运行Spark作业](https://help.aliyun.com/ack/ack-managed-and-ack-dedicated/use-cases/use-spark-operator-to-run-spark-jobs-on-ack)。\n\n## 性能优化\n\n为了提高在 Kubernetes 运行工作负载时的性能和易用性，并降低成本，阿里云 EMR 和 ACK 团队做了很多优化工作，主要有以下这些：\n\n- [Spark Operator 优化](docs/performance/spark-operator.md)\n- [Spark 优化](docs/performance/emr-spark.md)\n- [Shuffle 优化](https://developer.aliyun.com/article/772329)\n- [OSS 优化](docs/performance/oss.md)\n- [分布式缓存优化](docs/performance/jindofs.md)\n- [Serverless Spark](docs/performance/serverless-spark/index.md)\n\n## 最佳实践\n\n- [使用 EMR Spark 运行 Spark工作负载](./docs/bestpractice/emrspark.md)\n- [使用 EMR Spark + Remote Shuffle Service 运行 Spark 工作负载](./docs/bestpractice/emrspark-ess.md)\n- [使用 EMR Spark + JindoFS 运行 Spark 工作负载](./docs/bestpractice/emrspark-jindofs.md)\n- [使用 EMR Spark + JindoFS + Remote Shuffle Service 运行Spark工作负载](./docs/bestpractice/emrspark-ess-jindofs.md)\n\n## 基准测试\n\n### 关于 TPC-DS 基准测试\n\n[TPC-DS](http://www.tpc.org/tpcds/) 由第三方社区创建和维护，是事实上的做性能压测，协助确定解决方案的工业标准。这个测试集包含对大数据集的统计、报表生成、联机查询、数据挖掘等复杂应用，测试用的数据和值是有倾斜的，与真实数据一致。可以说 TPC-DS 是与真实场景非常接近的一个测试集，也是难度较大的一个测试集。\n\nTPC-DS 基准测试有以下几个主要特点：\n\n- 遵循 SQL 2003 的语法标准，SQL 案例比较复杂；\n- 分析的数据量大，并且测试案例是在回答真实的商业问题；\n- 测试案例中包含各种业务模型(如分析报告型，迭代式的联机分析型，数据挖掘型等)；\n- 几乎所有的测试案例都有很高的 IO 负载和 CPU 计算需求。\n\nTPC-DS 基准测试总包含 99 条压测查询，其中有 4 条查询包含 2 个变体（14、23、24、39），另外还有一个 `s_max` 查询进行全量扫描和最大的一些表的聚合，因此一共是 104 条查询语句，这些查询覆盖了 SQL 2003 语法的大部分标准。\n\n由于上述原因，本文将采用 TPC-DS 基准测试来评测 Spark 在 ACK 上的性能。\n\n### 如何运行 TPC-DS 基准测试\n\n基准测试采用 terraform 管理基准测试环境，保证测试环境可以被轻松一键复现。同时，相关 Spark 作业已经被制作成 Helm Chart，可以通过 Helm 命令行工具一键配置并提交基准作业。关于如何运行 TPC-DS 基准测试的具体步骤，请参考[运行 TPC-DS 基准测试](docs/benchmark/tpcds-benchmark.md)。\n\n### Spark on ACK TPC-DS 基准测试\n\n为了了解在 Kubernetes 容器环境下运行 Spark 作业的性能，本次基准测试创建了包含 1个 `ecs.g7.2xlarge` 实例和 6 个 `ecs.g7.8xlarge` 实例的 ACK 集群，并运行了 5 轮数据量为 3 TB（SF=3072）的 TPC-DS 基准测试。\n\n关于基准测试的详细步骤和测试结果，请参考 [Spark on ACK TPC-DS 基准测试](docs/benchmark/spark-on-ack/index.md)。\n\n### Apache Spark v.s. EMR Spark\n\n阿里云 EMR Spark 基于开源的 Apache Spark 做了大量优化，下面将在同一 ACK 集群环境中分别使用 Apache Spark 和 EMR Spark 运行相同的规模的 TPC-DS 基准测试，基准测试的配置和结果请参考 [Apache Spark v.s. EMR Spark](docs/benchmark/apache-spark-vs-emr-spark.md)。\n\n### Spark on ECS v.s. on ACK\n\nSpark 作业可以以非容器化的方式直接运行在 ECS 中，也可以以容器化的方式运行在 ACK 集群中，下面将在相同数量和相同规格的 ECS 集群和 ACK 集群中分别运行相同规模的 TPC-DS 基准测试，以对比容器化和非容器化环境下的性能差异，基准测试的配置和结果请参考 [Spark on ECS v.s. on ACK](docs/benchmark/spark-on-ecs-vs-on-ack/index.md)。\n\n### Spark x86 v.s. on arm64\n\n为了对比 Spark on ACK 在 x86/arm64 架构下的性能和成本，下面将在相同数量和相同规模的 x86/arm64 架构集群中分别运行相同规模的 TPC-DS 基准测试，它们具有相同的节点数量、vCPU 数量和内存大小，并挂载了相同数量和规模的云盘，区别主要在于 x86 架构集群使用了 `g7.8xlarge` 实例规格族 ECS，arm64 架构集群使用了 `g8y.8xlarge` 实例规格族 ECS，基准测试的配置和结果请参考 [Spark x86 v.s. on arm64](docs/benchmark/spark-on-x86-vs-on-arm64/index.md)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faliyuncontainerservice%2Fbenchmark-for-spark","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faliyuncontainerservice%2Fbenchmark-for-spark","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faliyuncontainerservice%2Fbenchmark-for-spark/lists"}