-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathatom.xml
283 lines (265 loc) · 25.3 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<id>http://javaht.github.io</id>
<title>周记</title>
<updated>2022-05-10T12:32:38.743Z</updated>
<generator>https://github.com/jpmonette/feed</generator>
<link rel="alternate" href="http://javaht.github.io"/>
<link rel="self" href="http://javaht.github.io/atom.xml"/>
<subtitle>不知名周某人之记录</subtitle>
<logo>http://javaht.github.io/images/avatar.png</logo>
<icon>http://javaht.github.io/favicon.ico</icon>
<rights>All rights reserved 2022, 周记</rights>
<entry>
<title type="html"><![CDATA[Spark内存]]></title>
<id>http://javaht.github.io/post/spark_memory/</id>
<link href="http://javaht.github.io/post/spark_memory/">
</link>
<updated>2022-05-09T13:20:15.000Z</updated>
<content type="html"><![CDATA[<h3 id="spark堆内内存如下">Spark堆内内存如下:</h3>
<figure data-type="image" tabindex="1"><img src="https://s2.loli.net/2022/05/09/dNsBUkwRlJZq4SX.png" alt="image-20220509204810466" loading="lazy"></figure>
<ul>
<li>**Storage 内存:**主要用于存储 Spark 的 cache 数据,例如 RDD 的 cache,Broadcast 变量,Unroll 数据等。需要主要的是,unrolled 的数据如果内存不够,会存储在 driver 端。</li>
<li>**Execution 内存:**用于存储 Spark task 执行过程中需要的对象,如 Shuffle、Join、Sort、Aggregation等计算过程中的临时数据。</li>
<li>**User 内存:**分配 Spark Memor</li>
</ul>
<p>y 剩余的内存,用户可以根据需要使用,可以存储 RDD transformations 需要的数据结构。</p>
<ul>
<li>**Reserved 内存:**这部分内存是预留给系统用的,固定不变。</li>
</ul>
<h3 id="spark堆外内存">Spark堆外内存</h3>
<p>在默认情况下堆外内存并不启用,可通过配置 spark.memory.offHeap.enabled 参数启用,并由spark.memory.offHeap.size 参数设定堆外空间的大小。除了没有 other 空间,堆外内存与堆内内存的划分方式相同如下图所示(以统一内存管理机制为例),所有运行中的并发任务共享存储内存和执行内存。</p>
<figure data-type="image" tabindex="2"><img src="https://s2.loli.net/2022/05/09/35nqSajuvAcfQXs.png" alt="image-20220509211927238" loading="lazy"></figure>
<h3 id="spark的统一内存管理">Spark的统一内存管理</h3>
<p>Spark 1.6 之后引入的统一内存管理机制,存储内存(Storeage Memory)和执行内存(Execution Memory)可以动态占用对方的空闲区域(如下图),在设定基本的的存储内存和执行内存区域(由 spark.storage.memoryFraction 参数控制)后,便确定了双方各自拥有的空间容量。统一内存管理的核心在内存区域的动态占用机制,其占用规则如下:</p>
<ul>
<li>双方空间都不足时,则存储到硬盘;如己方空间不足而对方空余时,可借用对方的空间;(存储空间不足是指不足以放下一个完整的 Block)。</li>
<li>执行内存的空间被对方占用后,可让对方将占用的部分存储转存到硬盘,然后“归还”借用的空间。</li>
<li>存储内存的空间被对方占用后,无法让对方“归还”,因为需要考虑到 Shuffle 过程中很多因素,实现起来较为复杂。</li>
</ul>
<figure data-type="image" tabindex="3"><img src="https://s2.loli.net/2022/05/09/xpCdEDFy5ohV1e3.png" alt="image006" loading="lazy"></figure>
<h3 id="spark-内存参数">Spark 内存参数</h3>
<pre><code>参数:spark.memory.fraction
含义:用于执行和存储的部分(堆空间-300MB)。值越低,溢出和缓存数据逐出的频率就越高。此配置的目的是为稀疏的,异常大的记录留出用于内部元数据,用户数据结构和大小估计不精确的内存。建议将其保留为默认值。
默认值:0.6 (Spark 1.6 默认为 0.75,Spark 2.0+ 默认为 0.6)
参数:spark.memory.storageFraction
含义:可以逐出的存储内存量,以spark.memory.fraction预留的区域大小的一部分表示。数值越高,则可用于执行的工作内存就越少,任务可能会更频繁地溢出到磁盘上. 建议将其保留为默认值。
默认值:0.5
参数:spark.memory.offHeap.enabled
含义:如果为true,Spark将尝试将堆外内存用于某些操作。如果启用了堆外内存使用,则spark.memory.offHeap.size必须为正。
默认值:false
参数:spark.memory.offHeap.size
含义:可用于堆外分配的绝对内存量(以字节为单位)。此设置对堆内存使用没有影响,因此,如果执行者的总内存消耗必须在某个硬限制内,那么请确保相应地缩小JVM堆大小。当spark.memory.offHeap.enabled=true时,必须将此值设置为正值.
默认值:0
参数:spark.memory.useLegacyMode
含义:是否启用Spark 1.5及之前版本中使用的旧版内存管理模式。传统模式将堆空间严格划分为固定大小的区域,如果未调整应用程序,则可能导致过多的溢出. 除非已启用,否则不会读取以下不推荐使用的内存分数配置: spark.shuffle.memoryFraction、spark.storage.memoryFraction、spark.storage.unrollFraction
默认值:false
参数:spark.shuffle.memoryFraction
含义:(不建议使用)仅在启用spark.memory.useLegacyMode读。在 shuffle 期间用于聚合和协同分组的Java堆的分数。在任何给定时间,用于随机播放的所有内存映射的集合大小都受到此限制的限制,超出此限制,内容将开始溢出到磁盘。如果经常发生泄漏,请考虑以spark.storage.memoryFraction为代价增加此值.
默认值:0.2
参数:spark.storage.memoryFraction
含义:(不建议使用)仅在启用spark.memory.useLegacyMode读。用于Spark的内存缓存的Java堆的分数。这不应大于JVM中对象的"旧"代,默认情况下,该对象的堆大小为0.6,但是如果您配置自己的旧代大小,则可以增加它。
默认值:0.6
参数:spark.storage.unrollFraction
含义:(不建议使用)仅在启用spark.memory.useLegacyMode读。spark.storage.memoryFraction分数,用于展开内存中的块。当没有足够的可用存储空间来完全展开新块时,通过删除现有块来动态分配该块。
默认值:0.2
</code></pre>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Spark优化]]></title>
<id>http://javaht.github.io/post/spark_better/</id>
<link href="http://javaht.github.io/post/spark_better/">
</link>
<updated>2022-05-04T14:30:07.000Z</updated>
<content type="html"><![CDATA[<h3 id="1首先是资源调优">1.首先是资源调优</h3>
<p>从提交参数:<br>
1.1 executor-cores:每个excutor的最大核数 一般是3到6<br>
1.2 num-executors:其实是总的excutor数量 就是单个节点的executor数量*节点数。<br>
(每个节点的executor数量 =单节点yarn的总核数/单个excutor的核数)<br>
总的excutor数量 = (单节点yarn的总核数/单个excutor的核数)*节点数<br>
1.3 executor-memory:单个executor的内存=yarn上配置的单个节点内存/excutor数量</p>
<p>RDD持久化: rdd持久化可以将数据复用<br>
cache() 和persist() 其实cache就是默认的persist() 级别是disk和memory</p>
<h3 id="2cpu优化">2.CPU优化</h3>
<p>RDD的并行度:一个job一次所能执行的task数目,即一个job对应的总的core资源个数(<strong>spark.default.parallelism</strong>)<br>
(没有设置时,由 join、reduceByKey 和 parallelize 等转换决定。)<br>
SparkSql的并行度:默认是200 只能控制Spark sql、DataFrame、DataSet 分区个数<br>
不能控制 RDD 分区个数 <strong>spark.sql.shuffle.partitions</strong></p>
<p>并发度:同时执行的 task 数 有几个core并发度就是多少</p>
<p>一般设置并行度是并发度(就是有多少个core)的2到三倍</p>
<p>CPU 低效原因<br>
1)并行度较低、数据分片较大容易导致 CPU 线程挂起<br>
2)并行度过高、数据过于分散会让调度开销更多</p>
<h3 id="3sparksql语法优化">3.SparkSql语法优化</h3>
<p>3.1基于RBO的优化(基于规则的优化器) 主要分为三类 1.谓词下推 2.列裁剪 3.常量替换<br>
3.2基于RBO的优化(基于代价的优化器)<br>
生成表级别统计信息(扫表):ANALYZE TABLE 表名 COMPUTE STATISTICS<br>
生成表级别统计信息(不扫表):ANALYZE TABLE src COMPUTE STATISTICS NOSCAN<br>
生成列级别统计信息:ANALYZE TABLE 表名 COMPUTE STATISTICS FOR COLUMNS 列 1,列 2,列 3<br>
DESC FORMATTED 表名</p>
<p>3.3广播join<br>
通过参数指定自动广播 .set("spark.sql.autoBroadcastJoinThreshold","10m")<br>
强行广播: select /*+ BROADCASTJOIN(sc) */ sc是表的别名</p>
<p>3.4 SMB join sort merge bucket 操作,首先会进行排序,然后根据 key值合并,把相同 key 的数据放到同一个 bucket 中(按照 key 进行 hash)。<br>
使用条件:1.两表进行分桶,桶的个数必须相等 2.两边进行 join 时,join的列=排序的列=分桶的列</p>
<h3 id="4数据倾斜">4.数据倾斜</h3>
<h4 id="41产生原因">4.1产生原因?</h4>
<p>设计shuffle类的算子时,某个key的数量特别大</p>
<h4 id="42-数据倾斜大-key-定位">4.2 数据倾斜大 key 定位</h4>
<p>使用sample算子对key不放回采样 计算出现的次数后排序取前十</p>
<h4 id="43-单表数据倾斜优化">4.3 单表数据倾斜优化 (*)</h4>
<p>两阶段聚合(加盐局部聚合+去盐全局聚合)</p>
<h4 id="44-join数据倾斜优化">4.4 join数据倾斜优化</h4>
<h5 id="441-广播-join">4.4.1 广播 Join</h5>
<p>适用于小表 join 大表。小表足够小,可被加载进 Driver 并通过 Broadcast 方法广播到各个 Executor 中</p>
<h5 id="442-拆分大-key-打散大表-扩容小表">4.4.2 拆分大 key 打散大表 扩容小表</h5>
<p> 1.首先将大表分为带有倾斜key和不带有倾斜key的</p>
<pre><code> 1. 将倾斜的key打散 打散36份
2. 小表进行扩容 扩大36倍
3. 倾斜的大key和扩容后的表进行join
4. 没有倾斜大key的部分 与 原来的表进行join
5. 将倾斜key join后的结果与普通key join后的结果 union起来
</code></pre>
<h3 id="5job优化">5.job优化</h3>
<h4 id="51map端聚合">5.1Map端聚合</h4>
<p>RDD 的话建议使用 reduceByKey 或者 aggregateByKey 算子来替代掉 groupByKey 算子。因为 reduceByKey 和 aggregateByKey 算子都会使用用户自定义的函数对每个节点本地的相同 key 进行预聚合。而 groupByKey 算子是不会进行预聚合的,全量的数据会在集群的各个节点之间分发和传输,性能相对来说比较差。<br>
SparkSQL 本身的 HashAggregte 就会实现本地预聚合+全局聚合</p>
<h4 id="52读取小文件优化">5.2读取小文件优化</h4>
<p>spark.sql.files.maxPartitionBytes=128MB (设置每个分区最大为128M 默认 128m)<br>
spark.files.openCostInBytes=4194304 (打开文件的开销)默认 4m</p>
<p>totalBytes = (单个文件的大小+openCostInbytes)*文件个数</p>
<p>bytesPerCore =totalBytes/defaultParallelism 文件的总大小/默认的并行度<br>
Math.min(defaultMaxSplitBytes,Math.max(openCostInbytes,bytesPerCore))</p>
<p>当(文件 1 大小+ openCostInBytes)+(文件 2 大小+ openCostInBytes)+…+(文件n-1 大小+ openCostInBytes)+ 文件 n <= maxPartitionBytes 时,n 个文件可以读入同一个分区,即满足: N 个小文件总大小 + (N-1)*openCostInBytes <= maxPartitionBytes 的话</p>
<h4 id="53-增大-map-溢写时输出流-buffer">5.3 增大 map 溢写时输出流 buffer</h4>
<figure data-type="image" tabindex="1"><img src="https://s2.loli.net/2022/05/04/8CKke45qThaDMsR.png" alt="image-20220504180522834" loading="lazy"></figure>
<pre><code> buffer缓冲区大小是5M(超过会*2) 和缓冲的批次10000条 这两个参数无效
溢写时使用输出流缓冲区默认 32k 我们可以修改这个
.set("spark.shuffle.file.buffer", "64")
</code></pre>
<h4 id="54-reduce端的优化">5.4 Reduce端的优化</h4>
<h5 id="541-合理设置reduce的个数">5.4.1 合理设置reduce的个数</h5>
<p>就是shuffle后的分区数 也就是task的数量 比如sparksql的默认200 并行度是并发度的2到3倍</p>
<h5 id="542输出产生的小文件优化">5.4.2输出产生的小文件优化</h5>
<h6 id="一-join-后的结果插入新表">一、Join 后的结果插入新表</h6>
<p>join 结果插入新表,生成的文件数等于 shuffle 并行度,默认就是 200 份文件插入到hdfs 上。</p>
<p>1.可以在插入表数据前进行缩小分区操作来解决小文件过多问题,如 coalesce、repartition 算子。<br>
2.调整 shuffle 并行度。</p>
<p>RDD的并行度:一个job一次所能执行的task数目,<strong>spark.default.parallelism</strong><br>
SparkSql的并行度:默认是200 只能控制Spark sql、DataFrame、DataSet 分区个数,不能控制 RDD 分区个数 <em><strong>spark.sql.shuffle.partitions</strong></em></p>
<h6 id="二-动态分区插入数据">二、动态分区插入数据</h6>
<p>1.没有 Shuffle 的情况下。最差的情况下,每个 Task 中都有表各个分区的记录,那文件数最终文件数将达到 Task 数量 * 表分区数。这种情况下是极易产生小文件的。<br>
INSERT overwrite table A partition ( aa )<br>
SELECT * FROM B;</p>
<p>2.有 Shuffle 的情况下,上面的 Task 数量 就变成了 spark.sql.shuffle.partitions(默认值200)。那么最差情况就会有 spark.sql.shuffle.partitions * 表分区数。当 spark.sql.shuffle.partitions 设 置 过 大 时 , 小 文 件 问 题 就 产 生 了 ; 当<br>
spark.sql.shuffle.partitions 设置过小时,任务的并行度就下降了,性能随之受到影响。最理想的情况是根据分区字段进行 shuffle,在上面的 sql 中加上 distribute by aa。把同一分区的记录都哈希到同一个分区中去,由一个 Spark 的 Task 进行写入,这样的话只会产生 N 个文件, 但是这种情况下也容易出现数据倾斜的问题。</p>
<p>解决思路:<br>
结合第 4 章解决倾斜的思路,在确定哪个分区键倾斜的情况下,将倾斜的分区键单独<br>
拎出来:<br>
将入库的 SQL 拆成(where 分区 != 倾斜分区键 )和 (where 分区 = 倾斜分区键) 几<br>
个部分,非倾斜分区键的部分正常 distribute by 分区字段,倾斜分区键的部分 distribute by<br>
随机数,sql 如下:</p>
<p>//1.非倾斜键部分<br>
INSERT overwrite table A partition ( aa )<br>
SELECT * FROM B where aa != 大 key<br>
distribute by aa;</p>
<p>//2.倾斜键部分<br>
INSERT overwrite table A partition ( aa )<br>
SELECT * FROM B where aa = 大 key<br>
distribute by cast(rand() * 5 as int);</p>
<h5 id="543-增大-reduce-缓冲区减少拉取次数">5.4.3 增大 reduce 缓冲区,减少拉取次数</h5>
<p>Spark Shuffle 过程中,shuffle reduce task 的 buffer 缓冲区大小决定了 reduce task 每次能够缓冲的数据量,也就是每次能够拉取的数据量,如果内存资源较为充足,适当增加拉取数据缓冲区的大小,可以减少拉取数据的次数,也就可以减少网络传输的次数,进而提升性能。<br>
reduce 端数据拉取缓冲区的大小可以通过 <strong>spark.reducer.maxSizeInFlight</strong> 参数进行设置,默认为 <strong>48MB</strong></p>
<h5 id="544-调节-reduce-端拉取数据重试次数">5.4.4 调节 reduce 端拉取数据重试次数</h5>
<p>reduce 端拉取数据重试次数可以通过 <strong>spark.shuffle.io.maxRetries</strong> 参数进行设置,该参数就代表了可以重试的最大次数。如果在指定次数之内拉取还是没有成功,就可能会导致作业执行失败,<strong>默认为 3</strong></p>
<h5 id="545-调节-reduce-端拉取数据等待间隔">5.4.5 调节 reduce 端拉取数据等待间隔</h5>
<p>reduce 端拉取数据等待间隔可以通过 <strong>spark.shuffle.io.retryWait</strong> 参数进行设置,<strong>默认值为 5s</strong></p>
<h4 id="55-合理利用-bypass">5.5 合理利用 bypass</h4>
<pre><code> .set("spark.shuffle.sort.bypassMergeThreshold", "30") //bypass阈值,默认200,改成30对比效果
</code></pre>
<h3 id="6整体优化">6.整体优化</h3>
<h4 id="61调节数据本地化等待时长">6.1调节数据本地化等待时长</h4>
<p>在 Spark 项目开发阶段,可以使用 client 模式对程序进行测试,此时,可以在本地看到比较全的日志信息,日志信息中有明确的 Task 数据本地化的级别,如果大部分都是<strong>PROCESS_LOCAL(本地)、NODE_LOCAL(节点)</strong>,那么就无需进行调节,但是如果发现很多的级别都是 <strong>RACK_LOCAL(机架)、ANY(不同机架)</strong>,那么需要对本地化的等待时长进行调节,</p>
<pre><code>spark.locality.wait //建议 6s、10s
spark.locality.wait.process //建议 60s
spark.locality.wait.node //建议 30s
spark.locality.wait.rack //建议 20s
</code></pre>
<h4 id="62-使用堆外内存">6.2 使用堆外内存</h4>
<p><strong>spark.executor.memory</strong>:提交任务时指定的堆内内存。</p>
<p>**spark.executor.memoryOverhead:**堆外内存参数(内存额外开销)。默认开启,默认值为 spark.executor.memory*0.1 并且会与最小值 384mb 做对比,取最大值。</p>
<p>所以 spark on yarn 任务堆内内存申请 1 个 g,而实际去 yarn 申请的内存大于 1 个 g 的原因。</p>
<p><strong>spark.memory.offHeap.size</strong> : 堆 外 内 存 参 数 , spark 中 默 认 关 闭 , 需 要 将spark.memory.enable.offheap.enable 参数设置为 true。</p>
<p>不同版本不同:</p>
<pre><code>spark2.4.5 executor.memory+executor.memoryOverhead+pySparkWorkerMemory
spark3.0.0 executor.memory+memory.offHeap.size(堆外内存)+executor.memoryOverhead(内存开销 默认堆内内存*0.1)+pySparkWorkerMemory
</code></pre>
<h5 id="621使用堆外缓存">6.2.1使用堆外缓存</h5>
<pre><code>// TODO 指定持久化到 堆外内存
result.persist(StorageLevel.OFF_HEAP)
</code></pre>
<h4 id="63-调节连接等待时长">6.3 调节连接等待时长</h4>
<p> 在 Spark 作业运行过程中,Executor 优先从自己本地关联的 BlockManager 中获取某份数据,如果本地 BlockManager 没有的话,会通过 TransferService 远程连接其他节点上Executor 的 BlockManager 来获取数据。<br>
如果 task 在运行过程中创建大量对象或者创建的对象较大,会占用大量的内存,这回导致频繁的垃圾回收,但是垃圾回收会导致工作现场全部停止,也就是说,垃圾回收一旦执行,Spark 的 Executor 进程就会停止工作,无法提供相应,此时,由于没有响应,无法建立网络连接,会导致网络连接超时。<br>
在生产环境下,有时会遇到 file not found、file lost 这类错误,在这种情况下,很有可能是 Executor 的 BlockManager 在拉取数据的时候,无法建立连接,然后超过默认的连接等待时长 120s 后,宣告数据拉取失败,如果反复尝试都拉取不到数据,可能会导致 Spark 作业的崩溃。这种情况也可能会导致 DAGScheduler 反复提交几次 stage,TaskScheduler 反复提交几次 task,大大延长了我们的 Spark 作业的运行时间。为了避免长时间暂停(如 GC)导致的超时,可以考虑调节连接的超时时长,连接等待时长需要在 spark-submit 脚本中进行设置,设置方式可以在提交时指定:</p>
<p>--conf spark.core.connection.ack.wait.timeout=300s</p>
<h3 id="7-spark-aqe">7. spark AQE</h3>
<p>Spark 在 3.0 版本推出了 AQE(Adaptive Query Execution),即自适应查询执行。<strong>AQE 是Spark SQL 的一种动态优化机制</strong>,在运行时,每当 Shuffle Map 阶段执行完毕,AQE 都会结 合这个阶段的统计信息,基于既定的规则动态地调整、修正尚未执行的逻辑计划和物理计划,来完成对原始查询语句的运行时优化。</p>
<h4 id="71-动态合并分区">7.1 动态合并分区</h4>
<pre><code>.set("spark.sql.adaptive.enabled", "true") //AQE的总开关
.set("spark.sql.adaptive.coalescePartitions.enabled", "true") // 合并分区的开关
.set("spark.sql.adaptive.coalescePartitions.initialPartitionNum","1000") // 初始的并行度
.set("spark.sql.adaptive.coalescePartitions.minPartitionNum","10") // 合并后的最小分区数
.set("spark.sql.adaptive.advisoryPartitionSizeInBytes", "20mb") // 合并后的分区,期望有多大
.set("spark.dynamicAllocation.enabled","true") // 动态申请资源 默认false 这样就能申请更多的excutor 并发度得到提升
//.set("spark.dynamicAllocation.shuffleTracking.enabled","true") // shuffle动态跟踪
</code></pre>
<h4 id="72动态切换join策略">7.2动态切换Join策略</h4>
<p>比如有A表和B表 A表4.2G B表2个G 筛选条件后B表为10M 如果没有动态切换join策略开关 就走普通的sortshuffle</p>
<p>如果开启后 就走广播了</p>
<pre><code>.set("spark.sql.adaptive.enabled", "true") //AQE的总开关
.set("spark.sql.adaptive.localShuffleReader.enabled", "true") //在不需要进行shuffle重分区时,尝试使用本地shuffle读取器。将sort-meger join 转换为广播join
</code></pre>
<h4 id="73-动态优化join倾斜">7.3 动态优化Join倾斜</h4>
<pre><code>1)spark.sql.adaptive.skewJoin.enabled :是否开启倾斜 join 检测,如果开启了,那么会将倾斜的分区数据拆成多个分区,默认是开启的,但是得打开 aqe。
2)spark.sql.adaptive.skewJoin.skewedPartitionFactor :默认值 5,此参数用来判断分区数 据量是否数据倾斜,当任务中最大数据量分区对应的数据量大于的分区中位数乘以此参数,并且也大于 spark.sql.adaptive.skewJoin.skewedPartitionThresholdInBytes 参数,那么此任务是数据倾斜。
3)spark.sql.adaptive.skewJoin.skewedPartitionThresholdInBytes :默认值 256mb,用于判断是否数据倾斜
4)spark.sql.adaptive.advisoryPartitionSizeInBytes :此参数用来告诉 spark 进行拆分后推荐分区大小是多少
.set("spark.sql.adaptive.enabled", "true")// 开启AQE的总开关
判断条件:
条件1:skewedPartitionFactor(默认是5) * 当前分区大小> 所有分区大小的中值(比如有200个分区 这里指的是第100个分区的大小)
条件2:skewedPartitionFactor(默认是5) * 当前分区大小> skewedPartitionThresholdInBytes
当条件1和条件2都满足时,认为当前分区是倾斜的。
需要注意的是和合并分区(spark.sql.adaptive.coalescePartitions.enabled)有冲突会影响对分区大小的判断
如果同时开启会先合并分区
</code></pre>
<h3 id="8dpp动态裁剪-谓词下推的根">8.DPP(动态裁剪 谓词下推的根)</h3>
<p>Spark3.0 支持动态分区裁剪 Dynamic Partition Pruning,简称 DPP,核心思路就是先将join 一侧作为子查询计算出来,再将其所有分区用到 join 另一侧作为表过滤条件,从而实现对分区的动态修剪。如下图所示:</p>
<figure data-type="image" tabindex="2"><img src="https://s2.loli.net/2022/05/06/IkZGaD38VHKUJME.png" alt="image-20220506195226809" loading="lazy"></figure>
<pre><code>将 select t1.id,t2.pkey from t1 join t2 on t1.pkey =t2.pkey and t2.id<2 优化成了
select t1.id,t2.pkey from t1 join t2 on t1.pkey=t2.pkey and t1.pkey in(select t2.pkey from t2 where t2.id<2)
触发条件:
(1)待裁剪的表 join 的时候,join 条件里必须有分区字段
(2)如果是需要修剪左表,那么 join 必须是 inner join ,left semi join 或 right join,反之亦然。但如果是 left out join,无论右边有没有这个分区,左边的值都存在,就不需要被裁剪
(3)另一张表需要存在至少一个过滤条件,比如 a join b on a.key=b.key and a.id<2
参数 spark.sql.optimizer.dynamicPartitionPruning.enabled 默认开启。
</code></pre>
<h3 id="9-hint增强">9 .Hint增强</h3>
<h4 id="91-broadcasthast-join">9.1 broadcasthast join</h4>
<pre><code>sparkSession.sql("select /*+ BROADCAST(school) */ * from test_student student left join test_school school on student.id=school.id").show()
sparkSession.sql("select /*+ BROADCASTJOIN(school) */ * from test_student student left join test_school school on student.id=school.id").show()
sparkSession.sql("select /*+ MAPJOIN(school) */ * from test_student student left join test_school school on student.id=school.id").show()
</code></pre>
<h4 id="92-sort-merge-join">9.2 sort merge join</h4>
<pre><code>sparkSession.sql("select /*+ SHUFFLE_MERGE(school) */ * from test_student student left join test_school school on
student.id=school.id").show()
sparkSession.sql("select /*+ MERGEJOIN(school) */ * from test_student student left join test_school school on student.id=school.id").show()
sparkSession.sql("select /*+ MERGE(school) */ * from test_student student left join test_school school on student.id=school.id").show()
</code></pre>
<h4 id="93-shuffle_hash-join">9.3 shuffle_hash join</h4>
<pre><code>sparkSession.sql("select /*+ SHUFFLE_HASH(school) */ * from test_student student left join test_school school on student.id=school.id").show()
</code></pre>
]]></content>
</entry>
</feed>