Amazon EMR 是一个托管的集群平台,它简化了在 AWS 上运行大数据框架(如 Apache Hadoop 和 Apache Spark)的过程,用于处理和分析大量数据。使用这些框架和相关的开源项目,您可以处理数据用于分析目的和商业智能工作负载。EMR 与 EC2、S3 等 AWS 服务深度集成,助力企业高效处理海量数据。
在实际使用 EMR 的过程中,客户通常会根据性能需求和预算限制,综合考虑使用不同实例类型及采购方式的组合。例如,同时使用按需(On-Demand)实例和竞价实例(Spot Instance),能够在确保关键任务稳定性的同时,最大限度地降低成本。然而,这种混合使用方式也带来了更复杂的实例启用和释放策略管理。
举两个应用场景为例:
- 在黑五等促销季,为了保证资源供应,一般采取 ODCR 方式提前预定 On-Demand 资源(普通机型的 Spot 资源目前没有预留方式),我们需要 EMR 优先使用这些预定的资源;
- 当 Spot 资源供应紧张时,我们根据统计信息,优先使用中断率较低的机型,降低因为实例回收导致的任务重试。
我们将针对以上两种场景,详细说明如何配置 EMR 来达到资源保障和成本优化的目的。本篇先讲述在 EMR on EC2 中使用 ODCR 进行资源预留。
ODCR (On-Demand Capacity Reservations) 是比 Reserved Instances (RI) 更加灵活的资源预留方式,可以灵活指定自动生效时间和自动终止时间,也可以随时手动终止或者调整数量。与 RI 不一样,它仅和资源有关,并不涉及成本优化,因此一般与 SavingsPlan 结合使用。
当黑五促销季来临,高性价比机型的需求会急剧上涨,在某个时间点,可能会因为个别资源池容量紧张,导致 EMR 集群创建或者扩容失败,影响任务处理时效,严重时可能导致业务 SLA 破线。因此在促销季到来前,通过 ODCR 提前预定资源是一种有效保障资源供应的做法。
ODCR 有两种实例匹配模式:
- Open – 相当于一个公共资源池,如果新建或者已有的实例类型、平台、可用区与 ODCR 相符,则自动匹配 ODCR;
- Targeted – 需要显式的指定 ODCR ID 来匹配,相当于一个私有资源池,不指定或者不知道 ODCR ID,是不会消耗 ODCR 资源的。
在 EMR 中,两种匹配模式均被支持,而且两种匹配模式,可以应用在两个不同的场景:
- 普通集群 – 成本优先,使用 Open ODCR 模式,尽量充分使用 ODCR 资源,多个集群共享 ODCR 资源池,一旦资源池耗尽,将从候补资源池获取资源。例如,可以在 us-west-2c 创建一个 1000台8xlarge 的 Open ODCR 请求,所有 EMR 集群优先从这个资源池获取资源,如果 1000 台 r7g.8xlarge 已经全部被使用,EMR 集群将从其它资源池启动 EC2;
- 高优先集群 – 运行核心任务的集群,效率和可靠性优先。使用 Targeted 模式的 ODCR,资源池专项供应特定集群,不存在争抢行为,严格按照计划使用资源。如果集群缩容,可能存在资源利用率下降的问题。举例来说,有 A、B、C 三个运行小时任务的高优先级集群,我们在 us-west-2b 创建三个 100台16xlarge 的 Targeted ODCR 请求,为每个集群指定特定的 ODCR ID,分别使用特定的 ODCR,互不干扰,也不受其它集群的干扰。
下面我们详细说明 ODCR 在 EMR on EC2 中的配置过程。
0. 环境设置
要使用 ODCR 配置,您的 EMR_DefaultRole(或者您为 EMR 集群指定的其它 role ) 需添加以下权限:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:DescribeCapacityReservations",
"ec2:DescribeLaunchTemplateVersions",
"ec2:DeleteLaunchTemplateVersions",
"resource-groups:ListGroupResources"
],
"Resource": "*"
}
]
}
创建 r7g.4xlarge 和 r7g.2xlarge 的 Targeted ODCR 各 5 台:
aws ec2 create-capacity-reservation \
--instance-type r7g.4xlarge \
--instance-platform 'Linux/UNIX' \
--availability-zone us-west-2a \
--tenancy default \
--instance-count 5 \
--ebs-optimized \
--end-date '2025/09/30 01:00:00' \
--end-date-type limited \
--instance-match-criteria targeted
aws ec2 create-capacity-reservation \
--instance-type r7g.2xlarge \
--instance-platform 'Linux/UNIX' \
--availability-zone us-west-2a \
--tenancy default \
--instance-count 5 \
--ebs-optimized \
--end-date '2025/09/30 01:00:00' \
--end-date-type limited \
--instance-match-criteria targeted
创建 r7g.4xlarge 和 r7g.2xlarge 的 Open ODCR 各 5 台:
aws ec2 create-capacity-reservation \
--instance-type r7g.4xlarge \
--instance-platform 'Linux/UNIX' \
--availability-zone us-west-2c \
--tenancy default \
--instance-count 5 \
--ebs-optimized \
--end-date '2025/09/30 01:00:00' \
--end-date-type limited \
--instance-match-criteria open
aws ec2 create-capacity-reservation \
--instance-type r7g.2xlarge \
--instance-platform 'Linux/UNIX' \
--availability-zone us-west-2c \
--tenancy default \
--instance-count 5 \
--ebs-optimized \
--end-date '2025/09/30 01:00:00' \
--end-date-type limited \
--instance-match-criteria open
1. Open 模式 ODCR
如果不以 EMR 进行额外配置,EMR 也会匹配 ODCR,但不会优先使用 ODCR 资源,而是根据 AllocationStrategy 指定的策略来获取资源,如果**恰好**所使用的资源类型能够匹配 Open 模式的 ODCR,则会自动从 ODCR 资源池中进行扣减。
示例 1:创建集群时 Open ODCR 默认匹配
保持默认 CapacityReservationOptions 配置不变,EMR 将尝试依据 on-demand 指定的分配策略(例如 lowest price)启动 EC2 实例,如果对应的实例类型有 open ODCR,则自动匹配。
操作步骤:
新建一个集群,其中 task fleet 选择 r7g.4xlarge,数量为 16 unit on-demand,保持 CapacityReservationOptions 为默认值。
"LaunchSpecifications": {
"OnDemandSpecification": {
"AllocationStrategy": "LOWEST_PRICE"
}
}
aws emr create-cluster \
--name "hive-6.10.1" \
--log-uri "s3://aws-logs-ACCOUNT-ID-us-west-2/elasticmapreduce" \
--release-label "emr-6.10.1" \
--service-role "arn:aws:iam::ACCOUNT-ID:role/EMR_DefaultRole" \
--unhealthy-node-replacement \
--ec2-attributes '{"InstanceProfile":"EMR_EC2_DefaultRole","EmrManagedMasterSecurityGroup":"sg-1234567890","EmrManagedSlaveSecurityGroup":"sg-1234567890","KeyName":"KEY-NAME","AdditionalMasterSecurityGroups":[],"AdditionalSlaveSecurityGroups":[],"ServiceAccessSecurityGroup":"sg-1234567890","SubnetId":"subnet-1234567890"}' \
--applications Name=Hive \
--instance-fleets '[{"Name":"Task - 1","InstanceFleetType":"TASK","TargetSpotCapacity":0,"TargetOnDemandCapacity":16,"LaunchSpecifications":{"OnDemandSpecification":{"AllocationStrategy":"LOWEST_PRICE"}},"InstanceTypeConfigs":[{"WeightedCapacity":16,"EbsConfiguration":{"EbsBlockDeviceConfigs":[{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":64}},{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":64}},{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":64}},{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":64}}]},"BidPriceAsPercentageOfOnDemandPrice":100,"InstanceType":"r7g.4xlarge"}]},{"Name":"Primary","InstanceFleetType":"MASTER","TargetSpotCapacity":0,"TargetOnDemandCapacity":1,"LaunchSpecifications":{"OnDemandSpecification":{"AllocationStrategy":"LOWEST_PRICE"}},"InstanceTypeConfigs":[{"WeightedCapacity":1,"EbsConfiguration":{"EbsOptimized":true,"EbsBlockDeviceConfigs":[{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":32}},{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":32}}]},"BidPriceAsPercentageOfOnDemandPrice":100,"InstanceType":"m7g.2xlarge"}]},{"Name":"Core","InstanceFleetType":"CORE","TargetSpotCapacity":0,"TargetOnDemandCapacity":8,"LaunchSpecifications":{"OnDemandSpecification":{"AllocationStrategy":"LOWEST_PRICE"}},"InstanceTypeConfigs":[{"WeightedCapacity":4,"EbsConfiguration":{"EbsOptimized":true,"EbsBlockDeviceConfigs":[{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":32}},{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":32}}]},"BidPriceAsPercentageOfOnDemandPrice":100,"InstanceType":"r6g.xlarge"}]}]' \
--scale-down-behavior "TERMINATE_AT_TASK_COMPLETION" \
--auto-termination-policy '{"IdleTimeout":3600}' \
--region "us-west-2"
结果验证:
可以看到 ODCR 会自动匹配

示例 2:创建集群时优先使用 Open ODCR
黑五等促销季来临时,我们通常会停止弹性伸缩并将全部资源类型转为 On-Demand,优先资源保障,总体资源用量趋于稳定,非常适合 ODCR 场景。如果实例类型统一,Open ODCR 比 Targeted ODCR 配置简单,适配便捷,是优先的选择项。
操作步骤:
正常情况下,task 节点会配置多种实例类型,这时候可以通过指定 CapacityReservationOptions 中的 UsageStrategy 为 use-capacity-reservations-first 来优先 ODCR 的使用。需要使用的 node fleet 均要配置。
"LaunchSpecifications": {
"OnDemandSpecification": {
"AllocationStrategy": "LOWEST_PRICE",
"CapacityReservationOptions": {
"UsageStrategy": "use-capacity-reservations-first"
}
}
}
只要实例类型、可用区和规格匹配的 ODCR 能覆盖本次资源需求,则优先从 ODCR 覆盖的机型中运行 EC2 实例;剩余的部分,则仍然按分配策略启动 EC2 实例。
新建一个 Task node 配置了 r6g.4xlarge、r7g.2xlarge 和 r7g.4xlarge 的集群,task fleet 为 48 units 的 on-demand,因为 r6g 单价比 r7g 低,使用 lowest price Allocation Strategy 时,原本会优先启动 r6g 的 EC2 实例,但因为现在指定了 use-capacity-reservations-first ,所以当 ODCR 有容量剩余时,先从 open ODCR 池子创建实例,也就是优先创建 r7g 实例。只有机型匹配的所有 ODCR 池子都耗尽,才会按照低价优先创建 r6g 实例。
aws emr create-cluster \
--name "open-odcr" \
--log-uri "s3://aws-logs-ACCOUNT-ID-us-west-2/elasticmapreduce" \
--release-label "emr-6.10.1" \
--service-role "arn:aws:iam::ACCOUNT-ID:role/EMR_DefaultRole" \
--unhealthy-node-replacement \
--ec2-attributes '{"InstanceProfile":"EMR_EC2_DefaultRole","EmrManagedMasterSecurityGroup":"sg-1234567890","EmrManagedSlaveSecurityGroup":"sg-1234567890","KeyName":"KEY-NAME","ServiceAccessSecurityGroup":"sg-1234567890","SubnetId":"subnet-1234567890"}' \
--applications Name=Hive \
--instance-fleets '[{"Name":"Core","InstanceFleetType":"CORE","TargetSpotCapacity":0,"TargetOnDemandCapacity":8,"LaunchSpecifications":{"OnDemandSpecification":{"AllocationStrategy":"LOWEST_PRICE","CapacityReservationOptions":{"UsageStrategy":"use-capacity-reservations-first"}}},"InstanceTypeConfigs":[{"WeightedCapacity":4,"EbsConfiguration":{"EbsOptimized":true,"EbsBlockDeviceConfigs":[{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":32}},{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":32}}]},"BidPriceAsPercentageOfOnDemandPrice":100,"InstanceType":"r6g.xlarge"}]},{"Name":"Task - 1","InstanceFleetType":"TASK","TargetSpotCapacity":0,"TargetOnDemandCapacity":48,"LaunchSpecifications":{"OnDemandSpecification":{"AllocationStrategy":"LOWEST_PRICE","CapacityReservationOptions":{"UsageStrategy":"use-capacity-reservations-first"}}},"InstanceTypeConfigs":[{"WeightedCapacity":16,"EbsConfiguration":{"EbsBlockDeviceConfigs":[{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":64}},{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":64}},{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":64}},{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":64}}]},"BidPriceAsPercentageOfOnDemandPrice":100,"InstanceType":"r7g.4xlarge"},{"WeightedCapacity":8,"EbsConfiguration":{"EbsBlockDeviceConfigs":[{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":64}},{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":64}},{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":64}},{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":64}}]},"BidPriceAsPercentageOfOnDemandPrice":100,"InstanceType":"r7g.2xlarge"},{"WeightedCapacity":16,"EbsConfiguration":{"EbsBlockDeviceConfigs":[{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":64}},{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":64}},{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":64}},{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":64}}]},"BidPriceAsPercentageOfOnDemandPrice":100,"InstanceType":"r6g.4xlarge"}]},{"Name":"Primary","InstanceFleetType":"MASTER","TargetSpotCapacity":0,"TargetOnDemandCapacity":1,"LaunchSpecifications":{"OnDemandSpecification":{"AllocationStrategy":"LOWEST_PRICE","CapacityReservationOptions":{"UsageStrategy":"use-capacity-reservations-first"}}},"InstanceTypeConfigs":[{"WeightedCapacity":1,"EbsConfiguration":{"EbsOptimized":true,"EbsBlockDeviceConfigs":[{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":32}},{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":32}}]},"BidPriceAsPercentageOfOnDemandPrice":100,"InstanceType":"m7g.2xlarge"}]}]' \
--scale-down-behavior "TERMINATE_AT_TASK_COMPLETION" \
--auto-termination-policy '{"IdleTimeout":3600}' \
--region "us-west-2"
结果验证:
可以看到 5 台 r7g.2xlarge 全部使用 (8 x 5 = 40 units):

不足部分使用了 1 台 r7g.4xlarge(16 x 1 = 16 units):

以上是创建集群时的配置和示例,如果促销季来临时并不重建集群,而是对现有集群进行扩容,则需要配置集群扩缩容时的 On-Demand 行为。以 Java 代码为例,需要指定 InstanceFleetResizingSpecifications 中的 onDemandResizeSpecification:
OnDemandCapacityReservationOptions onDemandCapacityReservationOptions = OnDemandCapacityReservationOptions.builder()
.capacityReservationPreference(OnDemandCapacityReservationPreference.OPEN)
.usageStrategy(OnDemandCapacityReservationUsageStrategy.USE_CAPACITY_RESERVATIONS_FIRST)
.build();
OnDemandResizingSpecification onDemandResizingSpecification = OnDemandResizingSpecification.builder()
.allocationStrategy(OnDemandProvisioningAllocationStrategy.LOWEST_PRICE)
.capacityReservationOptions(onDemandCapacityReservationOptions)
.timeoutDurationMinutes(5)
.build();
InstanceFleetResizingSpecifications instanceFleetResizingSpecifications = InstanceFleetResizingSpecifications.builder()
.onDemandResizeSpecification(onDemandResizingSpecification)
.spotResizeSpecification(spotResizingSpecification)
.build();
当修改配置后,我们可以进行测试验证。
示例 3:Open ODCR 手动扩缩容
手动扩容 task fleet 到 100 units,r7g.4xlarge ODCR 的利用率也提到了 80% (16 x 4 = 64 units):

再次扩容到 150 units,r7g.2xlarge 和 r7g.4xlarge 的 ODCR 池子已全部耗尽 (8 x 5 + 16 x 5 = 120 units),剩下的 30 units 由 2 台 r6g.4xlarge 填充 (分配策略为 lowest price)。
示例 4:Open ODCR Managed Scaling 扩缩容
将集群伸缩管理从手动切换到 Managed Scaling。
并且把 task fleet 扩容到 200 units:

集群现在是 5 台 r7g.2xlarge, 5 台 r7g.4xlarge 和 5 台 r6g.4xlarge。ODCR 利用率 100%。
2. Targeted 模式 ODCR
示例 5:创建集群时优先使用 targeted ODCR
操作步骤:
在创建完 ODCR request 后,需要再创建一个 Resource Group,并绑定 ODCR ID:
aws resource-groups create-group \
--name RESOURCE-GROUP \
--configuration '{"Type":"AWS::EC2::CapacityReservationPool"}' '{"Type":"AWS::ResourceGroups::Generic", "Parameters": [{"Name": "allowed-resource-types", "Values": ["AWS::EC2::CapacityReservation"]}]}'
aws resource-groups group-resources \
--group RESOURCE-GROUP \
--resource-arns arn:aws:ec2:us-west-2:ACCOUNT-ID:capacity-reservation/cr-1234567890
要使用 targeted ODCR,需要在之前 open ODCR 的配置后,再添上 Resource Group ARN。
"LaunchSpecifications": {
"OnDemandSpecification": {
"AllocationStrategy": "LOWEST_PRICE",
"CapacityReservationOptions": {
"UsageStrategy": "use-capacity-reservations-first",
"CapacityReservationResourceGroupArn": "arn:aws:resource-groups:us-west-2:ACCOUNT-ID:group/RESOURCE-GROUP"
}
}
},
同样新建一个 task fleet 为 48 units 的集群。
aws emr create-cluster \
--name "hive-6.10.1" \
--log-uri "s3://aws-logs-ACCOUNT-ID-us-west-2/elasticmapreduce" \
--release-label "emr-6.10.1" \
--service-role "arn:aws:iam::ACCOUNT-ID:role/EMR_DefaultRole" \
--unhealthy-node-replacement \
--ec2-attributes '{"InstanceProfile":"EMR_EC2_DefaultRole","EmrManagedMasterSecurityGroup":"sg-1234567890","EmrManagedSlaveSecurityGroup":"sg-1234567890","KeyName":"KEY-NAME","ServiceAccessSecurityGroup":"sg-1234567890","SubnetId":"subnet-1234567890"}' \
--applications Name=Hive \
--instance-fleets '[{"Name":"Core","InstanceFleetType":"CORE","TargetSpotCapacity":0,"TargetOnDemandCapacity":8,"LaunchSpecifications":{"OnDemandSpecification":{"AllocationStrategy":"LOWEST_PRICE","CapacityReservationOptions":{"UsageStrategy":"use-capacity-reservations-first","CapacityReservationResourceGroupArn":"arn:aws:resource-groups:us-west-2:ACCOUNT-ID:group/RESOURCE-GROUP"}}},"InstanceTypeConfigs":[{"WeightedCapacity":4,"EbsConfiguration":{"EbsOptimized":true,"EbsBlockDeviceConfigs":[{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":32}},{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":32}}]},"BidPriceAsPercentageOfOnDemandPrice":100,"InstanceType":"r6g.xlarge"}]},{"Name":"Task - 1","InstanceFleetType":"TASK","TargetSpotCapacity":0,"TargetOnDemandCapacity":48,"LaunchSpecifications":{"OnDemandSpecification":{"AllocationStrategy":"LOWEST_PRICE","CapacityReservationOptions":{"UsageStrategy":"use-capacity-reservations-first","CapacityReservationResourceGroupArn":"arn:aws:resource-groups:us-west-2:ACCOUNT-ID:group/RESOURCE-GROUP"}}},"InstanceTypeConfigs":[{"WeightedCapacity":16,"EbsConfiguration":{"EbsBlockDeviceConfigs":[{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":64}},{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":64}},{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":64}},{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":64}}]},"BidPriceAsPercentageOfOnDemandPrice":100,"InstanceType":"r7g.4xlarge"},{"WeightedCapacity":8,"EbsConfiguration":{"EbsBlockDeviceConfigs":[{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":64}},{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":64}},{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":64}},{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":64}}]},"BidPriceAsPercentageOfOnDemandPrice":100,"InstanceType":"r7g.2xlarge"},{"WeightedCapacity":16,"EbsConfiguration":{"EbsBlockDeviceConfigs":[{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":64}},{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":64}},{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":64}},{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":64}}]},"BidPriceAsPercentageOfOnDemandPrice":100,"InstanceType":"r6g.4xlarge"}]},{"Name":"Primary","InstanceFleetType":"MASTER","TargetSpotCapacity":0,"TargetOnDemandCapacity":1,"InstanceTypeConfigs":[{"WeightedCapacity":1,"EbsConfiguration":{"EbsOptimized":true,"EbsBlockDeviceConfigs":[{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":32}},{"VolumeSpecification":{"VolumeType":"gp2","SizeInGB":32}}]},"BidPriceAsPercentageOfOnDemandPrice":100,"InstanceType":"m7g.2xlarge"}]}]' \
--scale-down-behavior "TERMINATE_AT_TASK_COMPLETION" \
--auto-termination-policy '{"IdleTimeout":3600}' \
--region "us-west-2"
结果验证:成功从 targeted ODCR 创建 3 台 r7g.4xlarge。

扩缩容配置与 Open ODCR 类似,只是需要额外指定 Resource Group。
总结
综上,ODCR 可以让 EMR on EC2 在资源紧张时通过资源预留方式确保资源的供应,且选择多样、配置灵活。本文通过实际场景,详细介绍了 EMR 集群创建与扩缩容时的两种策略:为普通集群使用 Open ODCR 实现成本与资源的平衡,为核心任务集群应用 Targeted ODCR 确保效率与可靠性。文中提供了完整配置示例供您参考。
Spot 实例作为在 AWS 进行成本优化的一把利器,咱们后续的一篇 blog 将详细介绍如何通过 capacity-optimized-prioritized 来优化 Spot 实例在 EMR 集群中的资源配置。
本篇作者