大数据成本为什么越跑越高?别急着加机器,Spot + 队列调度可能省下一半
作者:Echo_Wish
做大数据运维久了,你会发现一个很有意思的现象:
很多公司一看到 Spark、Flink、Hive、Trino 作业跑得慢,第一反应是什么?
加机器。
CPU 不够?扩容。
内存不够?扩容。
任务排队?扩容。
每天凌晨任务跑不完?继续扩容。
结果机器越来越多,账单也越来越厚。
但真正值得思考的问题其实不是:
“我们的集群够不够大?”
而是:
“这些机器,有没有在真正需要的时候干真正重要的事情?”
这两个问题,看起来差不多,实际上完全不是一回事。
我越来越觉得,大数据成本优化不是单纯的“降机器”,而是三个东西配合起来:
Spot 实例负责便宜,队列优先级负责把钱花在刀刃上,作业调度负责让资源别闲着。
三者配合好了,很多时候不需要砍业务,也不需要天天让开发改 SQL,成本就能明显下来。
一、先说一个最容易被忽略的问题:你的大数据集群可能有一半时间在“摸鱼”
假设公司有一个 100 台机器的 Spark 集群。
白天业务高峰:
CPU:85%
内存:90%
任务:爆满
晚上:
CPU:15%
内存:20%
任务:零零散散
这时候很多公司的做法是:
“晚上也不能关机器,万一突然有任务怎么办?”
于是 100 台机器继续开着。
这就产生了一个非常典型的问题:
资源峰值决定了集群规模,但平均使用率决定了你的实际成本。
比如:
白天:100 台
晚上:20 台
如果你全天固定运行 100 台,相当于晚上有 80 台机器在“待命”。
机器没有坏,也没有报错。
但它们正在持续烧钱。
所以第一步成本优化,不应该是:
“怎么把 Spark 跑得更快?”
而应该是:
“哪些任务真的需要这么贵的资源?”
这时候,Spot 实例就开始有价值了。
二、Spot 实例:能便宜,为什么一定要买最贵的?
云厂商的 Spot 实例,本质上就是:
拿云厂商暂时闲置的计算资源,用更低的价格卖给你。
价格通常比按需实例便宜很多。
但它有一个特点:
云厂商可以收回。
这也是为什么 Spot 特别适合大数据计算,却不适合所有业务。
比如:
不适合 Spot 的任务
MySQL
Redis
核心 API
支付服务
主数据库
这些服务一旦机器突然没了,事情就比较麻烦。
但是大数据任务呢?
比如:
Spark ETL
Hive 离线统计
日志分析
数据清洗
模型训练
临时数据处理
批量报表
这些任务通常有一个特点:
死了可以重新跑。
那为什么不用 Spot?
三、Spark 为什么特别适合 Spot?
我们来看一下 Spark 的架构。
简单来说:
Driver
|
+---- Executor 1
+---- Executor 2
+---- Executor 3
+---- Executor 4
真正干活的大量计算其实发生在 Executor 上。
如果某个 Executor 所在的 Spot 实例被回收:
Executor 3
↓
机器被回收
↓
Task 失败
↓
Spark 重新调度 Task
↓
其他 Executor 继续执行
只要任务设计合理,整个 Job 并不一定需要从头开始。
这就是 Spot 最大的价值:
把“计算失败的风险”换成“更低的计算价格”。
当然,这不是说所有 Spark Job 直接丢到 Spot 上就完事了。
至少要考虑:
checkpoint
task retry
executor loss
shuffle
数据持久化
任务幂等
例如 Spark 可以配置任务失败重试:
spark-submit \
--conf spark.task.maxFailures=4 \
--conf spark.stage.maxConsecutiveAttempts=4 \
--conf spark.speculation=true \
app.py
其中:
spark.task.maxFailures
就是允许 Task 失败后重新尝试。
但这里我要提醒一句:
Spot 不是“免费机器”。
Spot 的本质是:
用可中断性换价格。
所以它最适合的是可以重试、可以容错、可以延迟完成的计算任务。
四、真正聪明的玩法,不是“全 Spot”,而是混合实例
这是很多团队容易走极端的地方。
刚知道 Spot 很便宜:
“那我们全部换 Spot!”
然后某一天云厂商资源紧张:
Spot:大规模回收
Executor:大量消失
任务:疯狂重试
业务:开始报警
运维:开始加班
这就属于省了云服务器的钱,增加了运维人员的精神损耗。
比较合理的方式应该是:
Spark Cluster
|
+---------+---------+
| |
On-Demand Spot
核心资源 弹性资源
| |
20 台 80 台
比如:
On-Demand:20%
Spot:80%
核心 Executor 放在稳定实例上。
大量弹性 Executor 放 Spot。
这样即使 Spot 被回收:
80 台 Spot ↓
|
↓
20 台稳定节点继续工作
|
↓
自动补充 Spot
|
↓
任务继续执行
这才是比较成熟的设计。
五、但光有 Spot 还不够:真正浪费钱的是“资源优先级混乱”
假设晚上有三个任务:
任务 A:老板明早 8 点要看的经营日报
任务 B:用户画像计算
任务 C:开发人员测试数据
结果三个任务优先级一样。
资源来了以后:
A:排队
B:运行
C:运行
你觉得合理吗?
显然不合理。
因为资源是有限的。
所有任务都高优先级,本质上就等于没有优先级。
所以大数据平台一定要建立队列。
比如:
production
↓
high
business
↓
normal
development
↓
low
甚至可以进一步:
P0:核心生产任务
P1:重要业务任务
P2:普通离线任务
P3:测试/开发任务
六、队列优先级真正解决的,是“钱应该先花在哪里”
举个非常现实的例子。
集群现在只有:
CPU:100 核
突然来了三个任务:
订单统计:40 核
推荐计算:40 核
开发测试:40 核
如果没有优先级:
40 + 40 + 40 = 120
资源不够。
调度器只能让它们互相抢。
但是如果我们定义:
订单统计:P0
推荐计算:P1
开发测试:P3
那么调度器就应该:
订单统计 40 核 √
推荐计算 40 核 √
开发测试 20 核 等待
甚至直接让开发测试任务暂停。
这不是“欺负开发”。
这是资源管理。
七、以 YARN 为例,队列其实可以这么设计
比如:
root
├── production
│ ├── critical
│ └── normal
├── business
└── development
然后给不同队列配置资源。
例如:
<queue name="production">
<capacity>60</capacity>
</queue>
<queue name="business">
<capacity>30</capacity>
</queue>
<queue name="development">
<capacity>10</capacity>
</queue>
意思很简单:
生产:60%
业务:30%
开发:10%
这样就不会出现一个开发人员跑了一个超级大的测试 SQL,把整个集群拖死的情况。
当然,生产环境的容量配置不会这么简单。
通常还要结合:
capacity
maximum-capacity
user-limit-factor
maximum-am-resource-percent
preemption
node-label
一起控制。
核心思想却很简单:
资源不是平均分,而是按照业务价值分。
八、然后问题来了:高优先级任务是不是就应该永远霸占资源?
也不应该。
这就是作业调度最有意思的地方。
假设:
任务 A:高优先级,运行 5 小时
任务 B:低优先级,运行 10 分钟
如果 A 一直占满资源:
A █████████████████
B .................
那么 B 永远进不来。
这时候就需要:
抢占、限额、动态调度。
例如:
高优先级任务出现
↓
调度器发现资源不足
↓
暂停低优先级任务
↓
释放资源
↓
高优先级任务运行
↓
资源释放
↓
低优先级任务恢复
这其实就是 Kubernetes、YARN、Flink 等系统里面非常重要的一套思想:
资源应该随着任务价值动态流动。
九、别忘了一个特别贵的东西:数据搬来搬去
很多大数据成本优化只盯着:
EC2 / ECS / VM
实际上还有一笔经常被忽略的钱:
网络 IO。
比如:
Spark
↓
S3 / OSS / HDFS
↓
跨可用区
↓
另一套集群
如果数据量达到:
10 TB
100 TB
1 PB
网络流量和存储访问成本就可能非常可观。
所以作业调度不仅仅是:
“哪个机器有空?”
还应该考虑:
“数据在哪里?”
例如:
数据在 AZ-A
优先:
AZ-A Executor
其次:
AZ-B Executor
最后:
跨 Region
这就是所谓的数据本地性。
调度得好,既能降低延迟,也能降低成本。
十、进一步一点:让调度器知道“这个任务值多少钱”
我个人特别喜欢一个思路:
不要只按照 CPU、内存调度任务。
还可以给任务增加一个“成本意识”。
例如:
jobs = [
{
"name": "daily_report",
"priority": 100,
"deadline": "08:00",
"spot": False
},
{
"name": "user_portrait",
"priority": 70,
"deadline": "12:00",
"spot": True
},
{
"name": "dev_test",
"priority": 10,
"deadline": None,
"spot": True
}
]
然后调度的时候考虑:
priority
deadline
resource
spot
runtime
data_size
最终形成一个简单的评分:
score = (
priority * 0.4
+ deadline_score * 0.3
+ resource_score * 0.1
+ cost_score * 0.2
)
谁分高,谁先跑。
虽然生产环境不会真的这么简单,但这个思路非常重要:
作业调度,本质上也是一种资源经济学。
十一、甚至可以进一步做到“便宜的时候多跑,贵的时候少跑”
如果你使用云计算平台,这个思路尤其有意思。
假设:
凌晨 1 点
Spot 很便宜
那么:
普通 ETL
用户画像
日志分析
历史数据清洗
模型训练
全部可以集中跑。
而到了:
上午 9 点
业务高峰
自动减少低优先级任务。
可以设计成:
01:00 - 06:00
↓
大量 Spot
↓
离线任务集中计算
06:00 - 09:00
↓
逐渐释放资源
09:00 - 18:00
↓
保障在线业务
↓
暂停低优先级任务
这其实就是:
错峰计算。
和我们平时家里晚上用电一个道理。
电价便宜的时候:
洗衣机、洗碗机一起开。
电价贵的时候:
能不开就不开。
大数据平台其实也一样。
十二、我认为真正成熟的大数据成本体系应该是这样的
最终可以形成这样一套架构:
Job Submit
|
↓
+---------------+
| Job Scheduler |
+---------------+
|
+-----------+-----------+
| | |
P0 P1 P3
生产核心 普通业务 开发测试
| | |
+-----------+-----------+
|
↓
Resource Queue
|
+-------+-------+
| |
On-Demand Spot
稳定资源 弹性资源
| |
+-------+-------+
|
↓
Spark / Flink
|
↓
Object Storage
然后再配合:
监控
↓
CPU / Memory / IO
↓
任务耗时
↓
Spot 回收率
↓
单位任务成本
↓
自动调度
这时候你的大数据平台才真正开始“聪明”。
十三、别只看服务器账单,要看“每个任务到底花了多少钱”
这是我特别想强调的一点。
很多运维团队每个月看:
云服务器账单:
¥500,000
然后开始讨论:
“这个月服务器怎么又涨了?”
其实这个指标太粗了。
更应该统计:
Job
↓
运行时间
↓
CPU
↓
Memory
↓
网络
↓
存储
↓
实例类型
↓
最终成本
最后形成:
daily_report ¥32
user_portrait ¥87
recommendation ¥230
dev_test ¥156
然后你会发现一个非常有意思的问题:
有些任务不是跑得慢,而是跑得太贵。
比如一个 SQL:
SELECT *
FROM huge_table
WHERE ...
明明只需要 5 个字段:
SELECT id, user_id, amount, status, created_at
FROM huge_table
WHERE ...
结果因为:
SELECT *
扫描了 30 TB 数据。
你给它上 Spot,只能说:
便宜地浪费资源。
真正的成本优化,最后还是会回到:
SQL
数据分区
数据格式
Shuffle
并发度
资源配置
任务调度
十四、所以我一直认为:大数据成本优化不是“抠门”,而是资源管理能力
很多人一听:
“降低云成本。”
第一反应就是:
删机器
降配置
关服务
这种方法当然有效,但它往往只能解决一部分问题。
真正高级一点的做法应该是:
第一层:Spot
让不重要、可重试的任务尽量用便宜资源。
第二层:队列
让重要任务拥有资源优先权。
第三层:调度
让资源随着业务高峰和低谷动态流动。
第四层:数据本地性
尽量减少无意义的数据搬运。
第五层:任务成本
知道每一个 Job 到底花了多少钱。
最终形成一个闭环:
任务
↓
评估价值
↓
选择队列
↓
选择实例
↓
动态调度
↓
执行
↓
统计成本
↓
优化
↓
再次调度
这才是真正意义上的大数据 FinOps。
最后聊两句
我越来越觉得,很多公司的云成本之所以越来越高,并不是因为云太贵。
而是因为我们把云当成了:
“无限的服务器。”
开发想跑任务就跑,运维发现资源不够就扩容,业务高峰过去以后机器继续开着。
最后形成一种非常奇怪的状态:
业务在管理资源,而不是资源在服务业务。
Spot 解决的是:
“能不能便宜一点?”
队列优先级解决的是:
“有限资源应该先给谁?”
作业调度解决的是:
“什么时候跑、在哪跑、跑多少?”
而真正成熟的平台,还应该继续回答:
“这个任务到底值不值得花这么多钱?”
所以,如果你的大数据集群最近账单又涨了,我建议先别急着砍机器。
先打开监控看看:
集群利用率
任务排队时间
Spot 使用比例
任务失败重试
CPU 利用率
内存利用率
Shuffle 数据量
网络流量
单 Job 成本
你很可能会发现一个扎心的事实:
真正贵的从来不是机器,而是没有被合理调度的机器。
—— Echo_Wish