16 | 内存视角(二):如何有效避免Cache滥用?
Cache 的工作原理
- 深入了解
- 翻译
- 解释
- 总结
Spark的Cache机制在应用开发中能够显著提升执行性能,但滥用Cache可能导致性能下降。本文深入解析了Cache的工作原理,包括存储级别、缓存计算和销毁过程。在存储级别方面,MEMORY_ONLY和MEMORY_AND_DISK是最常用的两种,而缓存计算则涉及Unroll、Transfer和LRU算法。文章还提到了滥用Cache可能带来的隐患和危害,以及在何种情况下适合使用Cache的建议。通过生动的图文解释,读者能更好地理解Spark的Cache机制,并在实际应用开发中避免滥用Cache,从而提升执行性能。文章通过深入浅出的方式,对Spark的Cache机制进行了详细介绍,对于需要优化Spark应用性能的开发者具有一定的参考价值。
《Spark 性能调优实战》,新⼈⾸单¥59
全部留言(17)
- 最新
- 精选
- Sam老师,您好!~我的知识盲区突现了: 对于老师的第三问,自己摸不着头脑,看老师和同学的回答也看得蒙蒙,感觉缺少了某部分基础知识,但又不知道从哪里补,这种感觉很痛苦~ 希望老师能指点迷津。
作者回复: 好问题哈,第三问确实很难,这里老哥我挖了个天坑,老弟见谅哈~ Spark使用cachedData = IndexedSeq[CachedData]()来存储(LogicalPlan,InMemoryRelation)键值对列表,其中的LogicalPlan就是Analyzed Logical Plan。咱们的问题是,为什么Spark选择了使用Analyzed LP作为Key,而没有选择Optimized LP。这个问题的关键,其实是Spark如何判断一个待计算的执行计划是否已经存在缓存结果了。Spark怎么来判断呢?简单的说,就是通过规范化的Logical Plan(CanonicalizedPlan)来进行对比,如果两个查询的CanonicalizedPlan是完全一致的,那么Spark SQL认为:OK,这个计划在缓存中已经有结果了,直接拿来用就好。 那么接下来的问题就是,给定一个Logical Plan,Spark SQL如何生产CanonicalizedPlan,简单来说,就是把AST语法树中的fields、ids不太归一化的字段进行归一化,从而消除不同查询计划之间那些“无关痛痒”的细微差别,这些细微差别与查询结果无关,所以在生成CanonicalizedPlan的过程中,Spark会把这些“细微差别”去掉,从而不影响两个CanonicalizedPlan之间是否相同的对比。 但是,这个规范化或是归一化的过程,Spark SQL其实并不敢完全保证。也就是说,本来计算结果相同的两个查询,他们的CanonicalizedPlan依然有可能不一样,也就是所谓的false negative。对于两个Plan之间CanonicalizedPlan的对比,Spark SQL采取保守的策略,就是宁可错杀1000,也不能漏放一个。也就是,Spark SQL宁可把两个执行结果一样的Logical Plan判定为不同,也要100%地保证:执行结果不同的两个查询,他们的CanonicalizedPlan一定是100%是不同的,也就是不能允许出现false positive的情况。 正是因为CanonicalizedPlan无法完全确保执行计划与计算结果完全一一对应,所以cachedData这个东西越早检查越省事,如果推迟到Optimized Logical Plan之后,那么大多数情况下,要么缓存很难命中,要么缓存好不容易命中了,但是Optimized阶段的优化就都白做了。所以总结下来,把Cache检查放在Analyzed LP之后、Optimized LP之前,就是上面说的这个原因。这个确实比较变态,root cause分析起来很困难,这个其实需要结合源码去顺藤摸瓜。辛苦老弟了哈~ 另外分享给老弟一个小tips:当你发现,你的问题,百度、google或是身边的人,都没办法帮你解答的时候,源码是最好的“老师”,我觉得这个是开源项目最大的意义,分享给老弟~
2021-07-08213 - Fendora范东_1.M_A_D在storage memory可用的情况下,缓存block过程和M_O过程一样,区别在于storage_memory不够情况下的处理。M_O是通过LRU来驱逐block来获取可用缓存;而M_A_D是通过落盘,落盘流程就是把unroll出来的block,通过putBytes()方法直接进行落盘,BlockID为落盘文件名,便于查找。 2.在M_O模式下,为啥不驱逐同一个RDD的memoryentry。我理解,当前RDD正在缓存,那它马上被用到的概率是最高的(类似LRU思想),如果它里面memoryentry被驱逐,那需要用的时候又要重新计算,白白增加耗时。 3.在analyzed阶段匹配,我记得是因为在analyzer生效阶段会把unresolverelation直接解析为InMemoryRelation。这样在在Optimizer阶段有些优化规则就省略了,避免应用规则浪费时间
作者回复: 答得都对,具体细节刚刚在上一个thread都展开了,可以看看哈~
2021-04-197 - 静心老师,RDD调用 Cache 的时候,文中讲到我们要把待缓存数据赋值给一个变量,然后基于这个变量做后续的etl,能够防止cache miss问题,但是我看了一下rdd cache源码,返回值就是原来的RDD,只是更改了rdd的storageLevel为MEMORY_ONLY(rdd默认缓存级别),所以rdd 调用cache后赋值到一个新变量再做etl 与 不赋值而是基于原有RDD进行etl应该不会有cache miss问题吧,有点不太理解,望老师解疑,谢谢。
作者回复: 好问题,RDD确实不存在Cache miss的问题;咱们文中说的Cache miss,都是围绕着DataFrame,DataFrame、Dataset、SQL都走Spark SQL优化流程。Spark SQL有单独的Cache Manager来管理Cache复用,它本身的一些缺陷,会导致上述这些API,在开发的过程中,会有Cache miss的隐患~ 关于Spark SQL的优化流程,可以参考后面Spark SQL那三讲,那几讲展开的比较细哈~
2021-05-065 - Fendora范东_还有几个疑问, 1.驱逐memoryentry的时候,图上两个打叉,我理解如果驱逐一个内存还是不够,就驱逐了两个? 2.linkedhashmap是怎样区分不同RDD的block的,k是blockid,v是memoryentry。没看到所属RDD的属性 3.自己设计cacheManager没啥更好想法,求磊哥提示下。。。我理解这个东西放在最前面比较合理,后面针对缓存的逻辑计划进一步应用或省略优化规则
作者回复: 好问题~ 1. 是的,一个空间不够,因此继续向后扫描,扫了两个之后,发现空间够了,就停止了。 2. 这个要赞一个~ 👍,思考很细致。这里咱们图省事,我没有交代的特别清楚,实际上,blockId不是String,而是一个sealed class,也就是一个类。这个类有不少属性,其中一个是asRDDId,这个函数就可以用来获取RDD Id,从而区分扫描的Block,是属于哪个RDD的,从而实现“兔子不吃窝边草”~ 3. 对,现在的设计,是放在Analyzed LP之后、Optimized LP之前,相当于是一种逻辑优化,就像你说的,能省略掉一些Rules、或者选取一些更优的Rules。 关于Cache Manager的优化,有个思路是SQL Re-write,其实这个思路就来源于传统的DBMS,Spark SQL没有这个环节。如果能够用SQL Re-writer,把Analyzed LP重写,那么其实potentially,很多的Analyzed LP都可以共享一份数据。Query Re-writer本身也并不复杂,参考传统DBMS,就可以很快实现出一个来。如果Cache Manager参考的,不再是“纯字符串”的Analyzed LP,而是重写之后的查询,想必效果要好很多~
2021-04-195 - 科学养牛老师,你的意思是调用.cache之后,需要马上接一个action算子吗? 如果我不马上接action算子,只在最后调用一个action算子,比如saveAsTable,那在之前就算用了cachedRdd这个变量多次缓存也不起效果是吗?
作者回复: 对,是的,要先用Action算子来触发缓存的计算,让Spark真正把分布式数据集缓存到内存或是磁盘中去。否则的话,就像你说的,“就算用了cachedRdd这个变量多次缓存也不起效果”。
2021-05-1024 - 西南偏北1. 第一题,在前面广播变量那一节的课后思考题已经回答过整个MemoryStore和DiskStore的缓存过程了哈哈 2. 因为同一个RDD的Block大概率在接下来就会被用到,因为本身要缓存的就是同一个RDD的其他Block,如果不这样做的话,很可能要缓存的Block被缓存到内存了,却发现要用的Block却被驱逐出内存了 3. 这是我自己的猜测不知道对不对哈哈:像第二种方式那样在数据集上做了一些处理之后进行cache,后续Cache Manager如果采用根据 Optimized Logical Plan 的方式来进行优化的话,那后面所有用到df这个Dataframe的地方,Spark都要解析对应的Optimized Logical Plan和我们之前cache的那个是否相同,这将会非常不可控。而且由于RDD/Dataset/Dataframe的不可变性,我们每次在对数据集进行处理之后,其实都应该养成将其赋值给一个新变量的习惯。
作者回复: 1 没问题,我之前看过你的回答~ 2、3 也都对,满分💯~
2021-05-052 - 阳台请问一下老师,什么是同属一个Rdd数据?第一节里面的一个土豆是一个rdd数据,还是三个土豆在一起被称为一个rdd数据
作者回复: 好问题,3个土豆(更准确地说,是多个,三个麻袋里面的所有土豆)一起,构成一个RDD哈~ RDD是数据“抽象”,它是用来“囊括”分布式数据集的,分布式数据集由数据分片/分区构成,土豆工坊里面的每一个土豆,都是一个RDD数据分片,不是RDD本身,他们凑在一起,够了一个RDD。 RDD是抽象的概念,而数据分片是实体。
2021-07-0821 - 辰以及缓存的完全物化的物化是指什么意思呢
作者回复: 物化(Materialization):把分布式数据集的Iterator迭代器,展开并存储到内存或是磁盘的过程。
2021-04-211 - 辰老师,文中的三种cache方式,我看都没有调用action算子(排除第二种不会cache的场景),是不是意味着都不会缓存落盘的操作
作者回复: 文中的三种方式,主要是为了区别示意哈,目的是为了说清楚缓存复用。实际开发的时候,肯定是需要Action算子来触发缓存计算的。
2021-04-211 - Jay老师,如果用的是RDD,文中的“Cache方式二”是不是就没有问题了?
作者回复: 不是哈,RDD还不如DataFrame,因为DF会走Spark SQL,Cache Manager是Spark SQL的组件之一。虽然CM效率不高、容易miss,但是Spark SQL好歹还有这么个组件帮他复用Cache。 RDD更惨,没有哪个组件来帮他做“复用”这件事,所以,要想充分利用、复用RDD Cache,你只能用第三种方式,也就是变量赋值的方式,明确地引用RDD Cache。
2021-04-1941