news 2026/10/21 22:21:31

Spark Task Schedule Delay五大根因与实战调优指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spark Task Schedule Delay五大根因与实战调优指南

1. 这不是“卡顿”而是调度失衡:从Task Schedule Delay看Spark作业的真实瓶颈

你有没有遇到过这样的场景:明明集群资源看着很空,YARN上ApplicationMaster汇报的Container使用率不到30%,Executor堆内存也只用了40%,但一个中等规模的Spark SQL作业却要跑20分钟——其中光“等待调度”的时间就占了7分钟?我在某电商实时数仓项目里连续两周被这个问题卡住,监控面板上Task Duration柱状图里总有一截长长的灰色尾巴,标着“Schedule Delay”,它不报错、不OOM、不GC,却像一根细针扎在性能优化的神经上,让你无从下手。这根本不是网络延迟或磁盘IO的问题,而是Spark调度器在资源分配环节出现了系统性滞涩。Task Schedule Delay这个指标,本质上反映的是Task从被DAGScheduler提交到TaskScheduler,再到最终被分配到某个Executor上执行之间的时间差。它不来自你的代码逻辑,也不来自Shuffle或序列化,而是来自Spark运行时与底层资源管理器(YARN/K8s)之间那层看不见的握手协议。很多人一看到“Delay”就去查网络、调JVM参数、甚至重写UDF,结果白忙活一周,问题纹丝不动。真正该盯住的,是Driver端TaskSetManager的日志、YARN ResourceManager的Allocation延迟日志、以及每个Task Attempt在Executor上的实际启动时间戳。这篇文章不讲概念复读,只拆解真实生产环境里我亲手抓包、比对、验证过的5类Schedule Delay来源,给出每一种对应的日志定位命令、关键参数计算公式和3个立竿见影的规避技巧。如果你正在为“作业跑得慢但监控看不出毛病”而焦头烂额,这篇就是为你写的。

2. Task Schedule Delay的五大源头解析:从调度队列到Executor冷启动

2.1 资源申请排队:YARN Capacity Scheduler的隐形闸门

这是最常见也最容易被误判的源头。很多人以为只要YARN队列有空余资源,Task就能立刻拿到Container。错。YARN的Capacity Scheduler采用基于队列权重的公平调度+最小资源保障机制,而Spark的ResourceRequest是按Executor粒度发起的,不是按Task。举个真实例子:某次作业配置了--num-executors 100 --executor-cores 4,即总共需要400个vCore。但YARN队列A的yarn.scheduler.capacity.root.a.maximum-capacity设为60%,当前已分配给其他应用55%资源,剩余5%对应25个vCore。此时Spark Driver向RM发送的ResourceRequest,要求一次性分配4个vCore的Container,但RM发现单次无法满足,就会把请求挂起,进入Pending Allocation Queue。这个挂起时间,就是Schedule Delay的第一大块。

提示:这种延迟在YARN Web UI的“Scheduler”页签里不可见,必须看ResourceManager日志。搜索关键词"Allocated container"和"Pending container request",对比时间戳差值。实测发现,当Pending队列长度超过15个Container时,平均等待时间会从200ms跳升至1.8秒。

验证方法很简单:在作业提交前,用yarn queue -status <queue_name>查看当前队列使用率;再用yarn top观察实时Container分配速率。如果Containers Allocated/sec长期低于0.5,基本可以锁定是队列资源争抢。解决方案不是加机器,而是调整Spark的资源申请策略:把spark.dynamicAllocation.enabled=true打开,并设置spark.dynamicAllocation.minExecutors=10、spark.dynamicAllocation.maxExecutors=200,让Executor按需伸缩,避免一次性压垮队列。

2.2 Executor启动耗时:JVM预热与磁盘IO的双重拖累

第二个常被忽略的源头,是Executor进程本身的启动时间。Spark默认使用org.apache.spark.executor.CoarseGrainedExecutorBackend作为Executor启动类,它需要完成:JVM初始化 → 加载Spark Core JAR → 反射加载用户Jar → 建立Netty通信通道 → 向Driver注册。这个过程在物理机上约300~500ms,在云环境(尤其是冷启动的K8s Pod)可能飙到2~3秒。更致命的是,如果Executor本地磁盘(SPARK_LOCAL_DIRS)位于机械硬盘或共享存储,JAR解压和临时文件创建会进一步拉长启动链。

我做过一组对照实验:同一套代码,在SSD节点上Executor平均启动耗时420ms,在HDD节点上达1890ms。而一个Stage有200个Task,如果全部分配到HDD节点,仅启动延迟就贡献了近400秒Schedule Delay。这不是调度器的问题,是基础设施选型的硬伤。

注意:这个延迟在Spark UI的“Executors”页签里表现为“Time Since Registration”字段异常偏大(>2s),且集中在少数几个Executor上。用ps aux | grep CoarseGrainedExecutorBackend可确认进程是否真在“启动中”。

规避技巧有三:第一,强制指定Executor启动路径到SSD分区,通过spark.local.dir=/mnt/ssd/spark-local;第二,启用JVM类数据共享(CDS),在spark.executor.extraJavaOptions中加入-Xshare:on -XX:SharedArchiveFile=/path/to/shared.jsa,实测可缩短JVM初始化35%;第三,对高频作业,预热Executor池——用spark.dynamicAllocation.initialExecutors设为一个合理基数,让Executor常驻,避免每次作业都冷启动。

2.3 TaskSetManager内部锁竞争:Driver单线程调度的阿喀琉斯之踵

Spark Driver的TaskSchedulerImpl内部,TaskSetManager是调度核心。但它有个致命设计:所有Task的launchTask操作,必须串行获取TaskSetManager.this锁。当一个Stage包含上千个Task(比如宽依赖后的HashPartitioner打散),TaskSetManager需要逐个生成TaskDescription、序列化、发往Executor。这个过程在Driver端是纯CPU密集型,一旦Task数量爆炸,锁竞争就会成为瓶颈。

我们曾在线上遇到一个极端案例:一个Join后生成3200个Task的Stage,TaskSetManager锁持有时间峰值达1.2秒。这意味着在这1.2秒内,所有其他TaskSet(比如上游Stage的重试Task)都被阻塞,无法进入调度队列。这部分延迟不会出现在YARN日志里,只会沉淀在Driver GC日志和spark.ui.retainedStages配置的Stage历史中。

验证方法:开启Driver JVM的-XX:+PrintGCDetails -XX:+PrintGCTimeStamps,观察Full GC是否频繁;同时用jstack <driver_pid>抓取线程快照,搜索"task-scheduler"线程栈,如果看到大量线程卡在TaskSetManager.launchTask的synchronized块里,就是锁竞争实锤。

解决思路不是改Spark源码(不现实),而是从作业层面拆解:用repartition(200)替代coalesce(200)强制触发Shuffle,把大TaskSet打散成多个小TaskSet;或者对超大Stage,手动插入checkpoint()切断DAG依赖链,让Driver分段调度。这两个操作看似增加开销,实则把1.2秒的单点延迟,摊薄成4个0.3秒的并行调度,整体Schedule Delay下降65%。

2.4 网络拓扑感知失效:机架感知错配引发的跨机架调度

Spark默认开启spark.network.timeout和spark.locality.wait,但很多人忽略了spark.yarn.am.nodeLabelExpression和spark.yarn.executor.nodeLabelExpression这两个关键参数。当集群跨机架部署(比如机架A有10台机器,机架B有15台),而YARN未正确配置机架感知(topology.script.file.name指向错误脚本),RM就无法告诉Spark“哪些Executor离数据最近”。结果就是Task被随机分配到远端机架的Executor上,触发NODE_LOCAL → RACK_LOCAL → ANY的三级降级等待。

这个等待时间由spark.locality.wait控制,默认3s。也就是说,Task会先等3秒看有没有同节点Executor,没有就等3秒看有没有同机架,再没有才放弃本地性。三个3秒,就是9秒纯等待——而这9秒会计入Schedule Delay。

诊断方式很直接:在Spark UI的“Stages”页签,点开任意一个Task,看“Locality Level”字段。如果大量Task显示ANY,且对应Executor的Host IP明显不属于数据Block所在节点(可通过hdfs fsck /path/to/data -files -blocks -locations查Block位置),就是机架感知失效。

修复步骤分三步:第一,确认YARN的core-site.xml中net.topology.script.file.name指向正确的Python脚本(如/etc/hadoop/conf/topology.py);第二,该脚本必须能根据IP返回机架名(如/rack1),且输出格式严格为<ip> <rack>;第三,Spark作业提交时显式设置--conf spark.yarn.executor.nodeLabelExpression="!production"(假设生产标签为production),确保Executor只部署在数据所在机架。我们实测,修复后ANY级Task占比从68%降至3%,Schedule Delay中位数从4.2秒压到0.7秒。

2.5 Shuffle服务阻塞:ExternalShuffleService未启用的连锁反应

最后一个隐蔽但杀伤力巨大的源头,是Shuffle数据传输环节。当spark.shuffle.service.enabled=false(默认关闭),Executor的Shuffle数据由自身管理。一旦某个Executor因GC或OOM挂掉,它上面的所有Shuffle Block就永久丢失,下游Task只能等待spark.shuffle.io.maxRetries次重试(默认3次),每次间隔spark.shuffle.io.retryWait(默认5s)。这15秒的被动等待,全算在Schedule Delay里。

更糟的是,这种丢失会引发雪崩:一个Executor挂掉→下游Task重试→重试Task又占用新Executor资源→新Executor压力增大→又挂掉……整个Stage的Schedule Delay呈指数级增长。

判断依据非常清晰:在Spark UI的“Storage”页签,看Shuffle Blocks的“Disk Size”是否持续为0;同时用lsof -i :7337(Shuffle端口)确认ExternalShuffleService进程是否存在。如果不存在,且日志里频繁出现"Failed to fetch shuffle data",就是它了。

启用方案只需三步:第一,在所有Worker节点的spark-defaults.conf中添加spark.shuffle.service.enabled true;第二,启动$SPARK_HOME/sbin/start-shuffle-service.sh;第三,作业提交时加--conf spark.shuffle.service.enabled=true。注意:必须保证Shuffle Service的端口(默认7337)在集群内网互通。我们上线后,因Shuffle失败导致的Schedule Delay归零,Stage失败率下降92%。

3. 实操排查四步法:从日志定位到根因闭环

3.1 第一步:精准捕获Schedule Delay原始数据

别信Spark UI上那个笼统的“Average Schedule Delay”。它把所有Task的Delay求平均,会掩盖长尾问题。真实排查必须落到单个Task Attempt。方法是调用Spark History Server的REST API:

curl -X GET "http://history-server:18080/api/v1/applications/<app_id>/stages/<stage_id>/<attempt_id>/taskList?sortBy=SCHEDULE_DELAY&desc=true&length=10"

这个请求返回JSON,重点看taskSummary.scheduleDelay字段。我通常取前10个最大Delay的Task,记录它们的executorId、host、attemptNumber。然后去对应Executor的日志里搜"Starting task",比对时间戳。例如:

# Executor日志 2023-09-15 14:22:36,128 INFO Executor: Starting task 123.0 in stage 45.0 (TID 12345) # Driver日志(grep "Submitting task") 2023-09-15 14:22:34,012 INFO DAGScheduler: Submitting task 123.0 of stage 45.0

差值2116ms,就是这个Task真实的Schedule Delay。把10个Task的Delay值画成箱线图,如果Q3(75%分位)>1500ms,说明存在系统性问题,不是偶发抖动。

3.2 第二步:分层日志交叉验证

单一日志源会误导判断。必须Driver、Executor、YARN RM三端日志联动分析。我建立了一个标准检查表:

日志来源关键搜索词正常表现异常信号
Driver日志"Launching task"+"Registered executor"Launch与Register时间差<500ms差值>2s,且伴随"Lost executor"
Executor日志"Starting task"+"Registered"Start与Register差<300msStart时间远晚于Register,说明Task积压
YARN RM日志"Allocated container"+"Container launched on host"两事件间隔<1s间隔>3s,且前有"Pending container request"

实操中,我用awk写了个一键分析脚本:

# 提取Driver端Task提交时间 grep "Submitting task" driver.log | awk '{print $1,$2,$NF}' > submit_times.txt # 提取Executor端Task启动时间 grep "Starting task" executor*.log | awk '{print $1,$2,$NF}' > start_times.txt # 用Python脚本join两个文件,计算差值分布 python3 calc_delay.py submit_times.txt start_times.txt

这个脚本输出的直方图,能立刻告诉你Delay是均匀分布(资源不足),还是集中在某些Executor(节点故障),或是随时间推移恶化(GC风暴)。

3.3 第三步:YARN资源视图深度下钻

很多团队只看YARN Web UI的“Cluster Metrics”,这远远不够。必须下钻到Application维度:

  1. 进入http://rm:8088/cluster/app/<app_id>,看“Scheduling Information”里的Allocated VCores和Pending VCores曲线。如果Pending线长期高于0,就是队列瓶颈;
  2. 点击“Scheduler”页签,找到该App所在的LeafQueue,看Used Capacity和Absolute Used Capacity。如果Used Capacity接近100%但Absolute Used Capacity只有60%,说明队列设置了maximum-capacity硬限制;
  3. 在“Applications”页签,按Started Time排序,找同一时段提交的其他App,看它们的Running Containers是否抢占了资源。

我们曾发现一个定时ETL作业,每天凌晨2点固定申请200个vCore,而实时流作业在2:05提交,正好撞上资源低谷。解决方案不是扩容,而是给ETL作业加yarn.app.mapreduce.am.resource.mb=4096,把它从“大胃王”变成“细嚼慢咽”,释放出碎片资源。

3.4 第四步:Executor健康度快筛

Schedule Delay高,未必是调度器的问题,很可能是Executor本身病了。我总结了5个10秒内可完成的健康快筛命令:

  1. CPU负载:top -b -n1 | grep "Cpu(s)" | awk '{print $2}'—— 持续>90%说明GC或计算过载;
  2. 磁盘IO:iostat -x 1 3 | grep nvme0n1 | tail -1 | awk '{print $10}'—— %util>95%说明磁盘瓶颈;
  3. 网络连接:ss -s | grep "established"—— 如果<50,说明Netty连接池枯竭;
  4. JVM内存:jstat -gc <executor_pid> | tail -1 | awk '{print ($3+$4)/($3+$4+$6+$7)*100}'—— Eden区使用率>95%预示 imminent GC;
  5. Shuffle端口:lsof -i :7337 | wc -l—— 应该>100,否则Shuffle Service未生效。

把这些命令写成check_executor.sh,在Delay高的Executor上批量执行,5分钟内就能定位是硬件、JVM还是配置问题。记住:80%的Schedule Delay问题,根源不在Spark调度器,而在Executor的亚健康状态。

4. 配置调优黄金七参数:从理论到生产实测

4.1spark.locality.wait:本地性等待的平衡木

这个参数控制Task等待本地数据的时间,默认3s。调大能提升数据本地性,但会拉长Schedule Delay;调小能加速调度,但增加网络传输。没有银弹,必须按数据分布特征调整。

我们的实测结论:

  • 数据完全存于HDFS,且HDFS副本数=3,集群节点数>50 → 设为1s。因为数据块大概率在本地机架,无需苦等3s;
  • 数据来自Kafka实时流,无本地性可言 → 直接设为0s,禁用本地性等待;
  • 小规模作业(<100个Task),数据量<10GB → 设为500ms,减少不必要的等待。

计算公式:locality_wait = (平均Block大小 / 网络带宽) * 1.5。例如Block平均256MB,万兆网络带宽1.25GB/s,理论传输200ms,乘以1.5安全系数得300ms。

4.2spark.scheduler.maxRegisteredResourcesWaitingTime:资源注册超时阀值

当Executor启动后,需向Driver注册。如果注册超时,Driver会认为Executor死亡,重新申请。这个超时时间默认30s,但在云环境(如K8s Pod启动慢)常被触发。

我们线上将它设为60s,并配合spark.scheduler.registeredResourcesWaitingTime(默认5s)一起调。后者是Driver等待新Executor注册的间隔。把registeredResourcesWaitingTime设为10s,maxRegisteredResourcesWaitingTime设为120s,给Executor留足启动缓冲,避免频繁的“假死”重试。

4.3spark.sql.adaptive.enabled:自适应查询执行的Schedule Delay减震器

Spark 3.0+的AQE特性,能在运行时动态合并小Task、优化Join策略。这对Schedule Delay有奇效。例如,一个原本生成1000个Task的Stage,在AQE下可能被合并为200个,直接减少80%的调度开销。

开启后必须配spark.sql.adaptive.coalescePartitions.enabled=true,并设spark.sql.adaptive.coalescePartitions.enabled=true。我们实测,对Shuffle-heavy的SQL,Schedule Delay中位数下降41%,Stage总耗时下降28%。

4.4spark.task.maxFailures与spark.stage.maxConsecutiveAttempts:失败重试的节制阀

默认spark.task.maxFailures=4,意味着一个Task最多重试3次。每次重试都要重新走一遍调度流程,把Schedule Delay翻3倍。对于易失败的Task(如访问外部API),应降低重试次数,改为快速失败+业务层重试。

我们把spark.task.maxFailures设为2(即只重试1次),同时用try-catch在UDF里封装HTTP调用,失败时返回空值而非抛异常。这样Schedule Delay没增加,业务可用性反而提升。

4.5spark.executor.heartbeatInterval与spark.executor.heartbeatMaxFailure:心跳机制的精度校准

Executor默认10s向Driver发一次心跳,连续2次失败(20s)判定死亡。在高负载集群,心跳可能延迟,导致误杀。我们把heartbeatInterval设为5s,heartbeatMaxFailure设为4,即20s才判定死亡,既避免误杀,又不放任僵尸Executor。

4.6spark.sql.adaptive.skewJoin.enabled:数据倾斜的Schedule Delay终结者

Skew Join会导致部分Task处理数据量是其他Task的百倍,这些Task不仅执行久,还会阻塞整个Stage的调度完成。AQE的Skew Join优化能自动切分倾斜Key,生成多个小Task并行处理。

开启后,spark.sql.adaptive.skewJoin.enabled=true,并设spark.sql.adaptive.skewJoin.skewMapThreshold=10485760(10MB)。我们一个日志分析作业,因User-Agent字段倾斜,Schedule Delay高达12s,开启后压到0.8s。

4.7spark.sql.adaptive.localShuffleReader.enabled:本地Shuffle读取的加速器

当Shuffle数据量小(<20MB),AQE可启用本地读取,绕过网络传输。这直接消除了RACK_LOCAL和ANY级Task的Schedule Delay。设spark.sql.adaptive.localShuffleReader.enabled=true,spark.sql.adaptive.localShuffleReader.threshold=20971520。实测对中小规模作业,Schedule Delay归零率超70%。

5. 常见问题速查表与独家避坑指南

5.1 典型问题与根因速查

现象可能根因快速验证命令解决方案
Schedule Delay集中在作业开始阶段Executor冷启动jps | grep CoarseGrained+ps -p <pid> -o etime=预热Executor池,启用CDS
Delay随Stage推进逐渐增大TaskSetManager锁竞争jstack <driver_pid> | grep -A5 "launchTask"插入checkpoint(),拆分大Stage
某几个Executor上Delay特别高节点硬件故障iostat -x 1 3,mpstat 1 3下线故障节点,设置spark.blacklist.enabled=true
Delay在夜间陡增其他作业资源抢占yarn top,yarn queue -status设置YARN队列权重,错峰调度
所有Task Delay都>3s本地性等待超时Spark UI看Locality Level调小spark.locality.wait,修复机架感知

5.2 我踩过的三个深坑与血泪教训

坑一:盲目调大spark.executor.memory引发GC风暴
以为内存大了就快,把Executor内存从8G调到32G。结果Young GC从50ms飙升到800ms,Executor频繁STW,Task启动延迟从400ms涨到3.2s。教训:JVM堆内存不是越大越好,-Xms和-Xmx必须相等,且不超过物理内存的75%;新生代比例用-XX:NewRatio=3固定,避免动态调整。

坑二:spark.sql.adaptive.enabled开启后Stage失败率上升
AQE的动态优化有时会误判数据分布,导致Join策略错误。我们一个报表作业开启AQE后,Stage失败率从0.2%升到5%。解决方案:不是关AQE,而是用spark.sql.adaptive.enabled=false局部关闭,对关键Stage加/*+ AQE(false) */提示。

坑三:YARN队列maximum-capacity设为100%仍被限流
以为100%就无限,结果发现yarn.scheduler.capacity.root.a.user-limit-factor默认是1,即单用户最多用队列容量的100%。但如果有10个用户,每人100%,实际还是被限。必须设为2,允许用户超量使用。这个参数藏得深,文档里都不提。

5.3 生产环境监控告警建议

Schedule Delay不能只靠事后排查,必须前置监控。我推荐三个必建告警:

  1. P95 Schedule Delay > 2s:用Prometheus采集spark_task_schedule_delay_seconds指标,触发企业微信告警。这个阈值覆盖了95%的正常场景,超过即有人工介入;
  2. Executor启动耗时 > 1.5s:监控spark_executor_startup_time_seconds,连续5分钟超标,自动触发yarn application -kill并重启;
  3. YARN Pending Container > 10个:用YARN REST APIhttp://rm:8088/ws/v1/cluster/scheduler解析JSON,Pending数>10立即告警,避免雪崩。

最后分享一个小技巧:在Spark作业最开头,插入一段“探针代码”:

sc.parallelize(range(100), 100).map(lambda x: time.sleep(0.01)).count()

这段代码会生成100个极轻量Task,专门用来测量基础调度延迟。如果它的Schedule Delay都>500ms,说明集群底座有问题,不用跑业务逻辑就能提前止损。这个习惯,帮我们拦截了70%的线上性能事故。

我在实际运维中发现,90%的Schedule Delay问题,根源都在资源配置与基础设施匹配度上,而不是Spark代码本身。与其花三天重写UDF,不如花半小时检查YARN队列配置和Executor磁盘类型。真正的性能优化,往往藏在那些被忽视的默认参数和基础设施细节里。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 22:20:14

lingbot-map:多传感器融合的语义建图系统实战解析

1. 这个项目要解决什么问题第一次看到lingbot-map这个名字&#xff0c;我第一反应是&#xff1a;这八成又是一个机器人建图相关的模块。跟几位做移动机器人的朋友聊完&#xff0c;再结合我自己在室内外巡检、仓储搬运这些小项目里折腾过的经历&#xff0c;基本可以确认&#xf…

作者头像 李华
网站建设 2026/10/21 22:19:57

Unity中利用BMFont与Mesh渲染自定义艺术字性能优化方案

1. 项目概述&#xff1a;为什么要在Unity里用BMFont做艺术字接手这个项目的时候&#xff0c;我面对的需求其实特别具体&#xff1a;游戏里有一套战斗飘字系统&#xff0c;要显示伤害数字、暴击提示、连击计数&#xff0c;还得配合一套定制的艺术字体风格。直接用Unity的Text组件…

作者头像 李华
网站建设 2026/10/21 22:19:52

AI让临电配电箱检测报告不再“走过场”:从人工巡检到智能审核

工地上的临时用电配电箱&#xff0c;大概是所有安全巡检对象里最容易沦为“走过场”的那个。脚手架有第三方检测&#xff0c;塔吊有月检复核&#xff0c;唯独配电箱&#xff0c;很多时候就是电工师傅开箱瞄一眼、随手勾个“正常”就完事。直到一次模拟演练里&#xff0c;现场编…

作者头像 李华
网站建设 2026/10/21 22:18:52

别把“知道”当“会”:技能学习的底层逻辑与刻意练习指南

直接说结论&#xff1a;大多数人缺的不是“技能”&#xff0c;而是对“skills”这个词的理解方式。我做过一段时间的团队技能梳理&#xff0c;也在招聘时见过大量简历。你会发现一个特别普遍的现象&#xff1a;简历上技能列表写得满满当当&#xff0c;一到实操就露馅。不是人不…

作者头像 李华
网站建设 2026/10/21 22:18:35

VS Code 高效使用指南:从命令面板到配置与插件生态

先说一个比较反直觉的事实&#xff1a;VS Code 的“Code”虽然只有四分之一个 Chrome 标签页那么重&#xff0c;但真正决定它是编辑器还是“编辑器中的瑞士军刀”的关键&#xff0c;不是你装了多少插件&#xff0c;而是你对它底层机制的理解程度。我见过太多人把 VSCode 当成一…

作者头像 李华
网站建设 2026/10/21 22:16:38

Redis持久化全解析:RDB、AOF与混合持久化原理及生产实践

1. 聊聊 Redis 持久化这件事 先说个结论&#xff1a; Redis 作为缓存中间件能扛住高并发&#xff0c;靠的是全内存操作&#xff1b;但 Redis 能成为生产环境的"标配"&#xff0c;靠的恰恰是持久化。 这俩一点也不矛盾。缓存挂了可以重建&#xff0c;数据丢了就得背…

作者头像 李华