Mapreduce 工业界批式计算经验汇总(上)

发布于:2025-07-08 ⋅ 阅读:(15) ⋅ 点赞:(0)

基本原理

MapperTask

简单概述:inputFile通过split被逻辑切分为多个split文件,通过Record按行读取内容给map(用户自己实现的)进行处理,数据被map处理结束之后交给OutputCollector收集器,对其结果key进行分区(默认使用hash分区),然后写入buffer,每个map task都有一个内存缓冲区,存储着map的输出结果,当缓冲区快满的时候需要将缓冲区的数据以一个临时文件的方式存放到磁盘,当整个map task结束后再对磁盘中这个map task产生的所有临时文件做合并,生成最终的正式输出文件,然后等待reduce task来拉数据

详细步骤:

  1. 读取数据组件 InputFormat (默认 TextInputFormat) 会通过 getSplits 方法对输入目录中文件进行逻辑切片规划得到 block, 有多少个 block就对应启动多少个 MapTask
  2. 将输入文件切分为 block 之后, 由 RecordReader 对象 (默认是LineRecordReader) 进行读取, 以 \n 作为分隔符, 读取一行数据, 返回 <key,value>. Key 表示每行首字符偏移值, Value 表示这一行文本内容
  3. 读取 block 返回 <key,value>, 进入用户自己继承的 Mapper 类中,执行用户重写的 map 函数, RecordReader 读取一行这里调用一次
  4. Mapper 逻辑结束之后, 将 Mapper 的每条结果通过 context.write 进行collect数据收集. 在 collect 中, 会先对其进行分区处理,默认使用 HashPartitioner
    1. MapReduce 提供 Partitioner 接口, 它的作用就是根据 Key 或 Value 及 Reducer 的数量来决定当前的这对输出数据最终应该交由哪个 Reduce task 处理, 默认对 Key Hash 后再以 Reducer 数量取模. 默认的取模方式只是为了平均 Reducer 的处理能力, 如果用户自己对 Partitioner 有需求, 可以订制并设置到 Job 上
  5. 接下来, 会将数据写入内存, 内存中这片区域叫做环形缓冲区, 缓冲区的作用是批量收集 Mapper 结果, 减少磁盘 IO 的影响. 我们的 Key/Value 对以及 Partition 的结果都会被写入缓冲区. 当然, 写入之前,Key 与 Value 值都会被序列化成字节数组
    1. 环形缓冲区其实是一个数组, 数组中存放着 Key, Value 的序列化数据和 Key, Value 的元数据信息, 包括 Partition, Key 的起始位置, Value 的起始位置以及 Value 的长度. 环形结构是一个抽象概念。 缓冲区是有大小限制, 默认是 100MB. 当 Mapper 的输出结果很多时, 就可能会撑爆内存, 所以需要在一定条件下将缓冲区中的数据临时写入磁盘, 然后重新利用这块缓冲区. 这个从内存往磁盘写数据的过程被称为 Spill, 中文可译为溢写. 这个溢写是由单独线程来完成, 不影响往缓冲区写 Mapper 结果的线程. 溢写线程启动时不应该阻止 Mapper 的结果输出, 所以整个缓冲区有个溢写的比例 spill.percent. 这个比例默认是 0.8, 也就是当缓冲区的数据已经达到阈值 buffer size * spill percent = 100MB * 0.8 = 80MB, 溢写线程启动, 锁定这 80MB 的内存, 执行溢写过程. Mapper 的输出结果还可以往剩下的 20MB 内存中写, 互不影响
  6. 当溢写线程启动后, 需要对这 80MB 空间内的 Key 做排序 (Sort). 排序是 MapReduce 模型默认的行为, 这里的排序也是对序列化的字节做的排序
    1. 如果 Job 设置过 Combiner, 那么现在就是使用 Combiner 的时候了. 将有相同 Key 的 Key/Value 对的 Value 合并在起来, 减少溢写到磁盘的数据量. Combiner 会优化 MapReduce 的中间结果, 所以它在整个模型中会多次使用 \ 那哪些场景才能使用 Combiner 呢? 从这里分析, Combiner 的输出是 Reducer 的输入, Combiner 绝不能改变最终的计算结果. Combiner 只应该用于那种 Reduce 的输入 Key/Value 与输出 Key/Value 类型完全一致, 且不影响最终结果的场景. 比如累加, 最大值等. Combiner 的使用一定得慎重, 如果用好, 它对 Job 执行效率有帮助, 反之会影响 Reducer 的最终结果
  7. 合并溢写文件, 每次溢写会在磁盘上生成一个临时文件 (写之前判断是否有 Combiner), 如果 Mapper 的输出结果真的很大, 有多次这样的溢写发生, 磁盘上相应的就会有多个临时文件存在. 当整个数据处理结束之后开始对磁盘中的临时文件进行 Merge 合并, 因为最终的文件只有一个, 写入磁盘, 并且为这个文件提供了一个索引文件, 以记录每个reduce对应数据的偏移量

ReducerTask

Reduce 大致分为 copy、sort、reduce 三个阶段,重点在前两个阶段。copy 阶段包含一个 eventFetcher 来获取已完成的 map 列表,由 Fetcher 线程去 copy 数据,在此过程中会启动两个 merge 线程,分别为 inMemoryMerger 和 onDiskMerger,分别将内存中的数据 merge 到磁盘和将磁盘中的数据进行 merge。待数据 copy 完成之后,copy 阶段就完成了,开始进行 sort 阶段,sort 阶段主要是执行 finalMerge 操作,纯粹的 sort 阶段,完成之后就是 reduce 阶段,调用用户定义的 reduce 函数进行处理

详细步骤

  1. Copy阶段,简单地拉取数据。Reduce进程启动一些数据copy线程(Fetcher),通过HTTP方式请求maptask获取属于自己的文件。
  2. Merge阶段。这里的merge如map端的merge动作,只是数组中存放的是不同map端copy来的数值。Copy过来的数据会先放入内存缓冲区中,这里的缓冲区大小要比map端的更为灵活。merge有三种形式:内存到内存;内存到磁盘;磁盘到磁盘。默认情况下第一种形式不启用。当内存中的数据量到达一定阈值,就启动内存到磁盘的merge。与map 端类似,这也是溢写的过程,这个过程中如果你设置有Combiner,也是会启用的,然后在磁盘中生成了众多的溢写文件。第二种merge方式一直在运行,直到没有map端的数据时才结束,然后启动第三种磁盘到磁盘的merge方式生成最终的文件。
  3. 合并排序。把分散的数据合并成一个大的数据后,还会再对合并后的数据排序。
  4. 对排序后的键值对调用reduce方法,键相等的键值对调用一次reduce方法,每次调用会产生零个或者多个键值对,最后把这些输出的键值对写入到HDFS文件中。

Shuffle 过程

shuffle 是 Mapreduce 的核心,它分布在 Mapreduce 的 map 阶段和 reduce 阶段。一般把从 Map 产生输出开始到 Reduce 取得数据作为输入之前的过程称作 shuffle。

  1. Collect阶段:将 MapTask 的结果输出到默认大小为 100M 的环形缓冲区,保存的是 key/value,Partition 分区信息等。
  2. Spill阶段:当内存中的数据量达到一定的阀值的时候,就会将数据写入本地磁盘,在将数据写入磁盘之前需要对数据进行一次排序的操作,如果配置了 combiner,还会将有相同分区号和 key 的数据进行排序。
  3. Merge阶段:把所有溢出的临时文件进行一次合并操作,以确保一个 MapTask 最终只产生一个中间数据文件。
  4. Copy阶段:ReduceTask 启动 Fetcher 线程到已经完成 MapTask 的节点上复制一份属于自己的数据,这些数据默认会保存在内存的缓冲区中,当内存的缓冲区达到一定的阀值的时候,就会将数据写到磁盘之上。
  5. Merge阶段:在 ReduceTask 远程复制数据的同时,会在后台开启两个线程对内存到本地的数据文件进行合并操作。
  6. Sort阶段:在对数据进行合并的同时,会进行排序操作,由于 MapTask 阶段已经对数据进行了局部的排序,ReduceTask 只需保证 Copy 的数据的最终整体有效性即可。

Shuffle 中的缓冲区大小会影响到 mapreduce 程序的执行效率,原则上说,缓冲区越大,磁盘io的次数越少,执行速度就越快

缓冲区的大小可以通过参数调整, 参数:mapreduce.task.io.sort.mb 默认100M

MR作业输入输出及压缩格式

输入

如何为 Job 确定合理的 map/reduce task 的数量?

  • 确定 map task 数量的原则:
    • 控制单个 map task 在1-3 分钟内运行完成;
    • 控制单个 map task 的输出数据大小,避免需要多个 pass 的 merge sort. 判断标准:Job Counter 中,Spilled Records <= Map Output Records;
    • 对每一个 Job,总是考虑是否可以使用 combiner 来减少 map 阶段的输出;
    • Why?
      • 过小过多的 map task,task 启动的 overhead 会比较大,shuffle 阶段效率不高,会给 jobtracker 带来较大的压力(Hadoop1);尤其当 Job 输入是很多小文件时,建议在上游对文件进行 merge,或者使用 CustomCombineFileInputFormat 来合并输入。
      • 过大的 map task,长时间占用槽位,再不开启抢占的情况下,不利于 scheduler 在不同的 Job 间公平的调度;map task 输出数据量太大,在 merge sort 阶段可能需要多个 pass,会产生 I/O 放大效应,造成额外的 I/O 消耗;
  • 确定 reduce task 数量的原则:
    • 对较大的 Job: reducer 数量不要超过集群/队列的 slot/container 数量;
    • 对较小的 Job: 保持较少的 reducer 数量,可以提高 shuffle 阶段的效率;

怎样限制 map task 的数量

See: 《11. MR 控制map数量(内部)》

Task的并发执行

我们可以通过设置map和reduce task的最大并发度,来避免过多地占用队列资源或者避免并发访问对其他系统造成压力(比如DB),

参数 含义 默认值 更多
mapreduce.job.running.map.limit 最大并行map task个数 0 0或者负数不生效
mapreduce.job.running.reduce.limit 最大并行reduce task个数 0 0或者负数不生效

map only作业

部分作业的逻辑比较简单,不需要reduce,此时只需将reduce个数设置为0即可。MapReduce不会创建reduce task,map task的输出就是作业的最后输出。

-D mapreduce.job.reduces=0

加速 split

如果 split 数超过了10w,split 速度会显著变慢,可以通过下面参数来进行多线程 split

-Dmapreduce.input.fileinputformat.split.num-threads=4

输出

CustomMulitOutputFormat, 多路输出

hadoop streaming

STREAMING="${HADOOP_HOME}/contrib/streaming/hadoop-streaming-1.0.3.jar"
HADOOP_CLASSPATH=$HADOOP_CLASSPATH:${HADOOP_HOME}/contrib/streaming/bytedance-data-1.0.1.jar

${HADOOP_BIN} jar $STREAMING -libjars ${HADOOP_HOME}/contrib/streaming/bytedance-data-1.0.1.jar -outputformat com.bytedance.data.CustomMultiOutputFormat //添加这两个参数

reduce输出示例:
print "output_path_1\t", "helloworld" #在reducer输出目录新建 output_path_1 文件夹, 然后在文件夹内部输出结果。注意,如果key比较多会创建较多文件夹,请按需使用。

压缩

怎样使用snappy压缩方式

hadoop streaming有5个参数,设计snappy 压缩方式。

-Dmapred.output.compression.type=BLOCK //块压缩方式
-Dmapred.compress.map.output=false //Map输出是否采用压缩
-Dmapred.map.output.compression.codec="org.apache.hadoop.io.compress.SnappyCodec" //Map输出采用压缩库为snappy
-Dmapred.output.compress=false //MapReduce输出是否采用压缩
-Dmapred.output.compression.codec="org.apache.hadoop.io.compress.SnappyCodec"  //MapReduce输出采用压缩库为snappy

使用方式

${HADOOP_BIN} streaming  
-Dmapred.compress.map.output=true
-Dmapred.map.output.compression.codec="org.apache.hadoop.io.compress.SnappyCodec"
-Dmapred.output.compress=true
-Dmapred.output.compression.codec="org.apache.hadoop.io.compress.SnappyCodec"

//说明: 默认采用BLOCK级别压缩,提交作业勿需指定。 输出文件以snappy后缀结尾,表示为snappy压缩方式输出。

//最好在tiger账号下提交作业

怎样查看snappy输出文件

由于采用BLOCK级别压缩,不方便使用第三方的snappy工具查看文件内容,因为仍然使用hadoop streaming作业来查看文件内容。

方法:提交一个只有mapper的hadopp streaming作业, mapper的内容是输出前1000条输入,输出不使用压缩方式,即可查看文件的前1000条内容。

注意:参数配置为

-D mapred.reduce.tasks=0 //没有reducer
-Dmapred.compress.map.output=false //不压缩
-Dmapred.output.compress=false   //不压缩

4mc压缩格式

输出压缩格式为snappy,zstd等格式的数据是不能split的。如果数据倾斜,则下游处理会出现长尾问题。对于此种情况,建议试用4mc格式。

生成数据时加如下参数

-D mapred.output.compression.codec="com.hadoop.compression.fourmc.FourMcCodec"
-libjars "/opt/tiger/yarn_deploy/hadoop/hadoop-4mc-2.0.0.jar,/opt/tiger/yarn_deploy/hadoop/bytedance-data-1.0.2.jar"
-outputformat com.bytedance.data.CustomMultiOutputFormat

MR常用参数设置

1. 基本参数

参数 含义 默认值 其余计算框架的设置
mapreduce.job.name 作业名称 如何指定 Job 名称?
mapreduce.job.queuename 作业队列
mapreduce.job.priority 作业优先级 Job 优先级

2. 隐含参数

这些参数指由作业框架主动为作业添加的参数,方便debug等。

参数 含义 默认值 更多
mapreduce.job.user.name 提交用户名 取本地提交用户名
mapreduce.job.submithostname 提交机器名称 提交机机器ip
mapreduce.job.submithostaddress 提交机器地址 提交机机器域名

3. Client参数

参数 含义 默认值 更多
mapreduce.client.submit.file.replication 10 作业文件的副本数量
mapreduce.client.output.filter FAILED Client终端输出的用户日志,可选的是NONE, KILLED, FAILED, SUCCEEDED and ALL
mapreduce.client.completion.pollinterval 5000 Job客户端向AM获取Job状态信息的时间间隔,毫秒
mapreduce.client.progressmonitor.pollinterval 1000 Job客户端向Am获取Job完成进度的时间间隔,毫秒

4. AM参数

参数 含义 默认值 更多
yarn.app.mapreduce.am.staging-dir /tmp/hadoop-yarn/staging 作业的staging目录
mapreduce.am.max-attempts 2 AM retry最大次数,当大于RM的全局设置时将会被覆盖掉
yarn.app.mapreduce.am.job.client.thread-count 1 AM与client进行rpc通信的线程数
yarn.app.mapreduce.am.job.client.port-range AM与client进行rpc通信的端口范围
yarn.app.mapreduce.am.scheduler.heartbeat.interval-ms 1000 AM与RM的心跳间隔
yarn.app.mapreduce.am.job.task.listener.thread-count 30 AM与tasks进行rpc通信的线程数
yarn.app.mapreduce.am.resource.mb 512M AM内存
yarn.app.mapreduce.am.resource.cpu-vcores AM vcore数量

5. map和reduce启动参数

参数 含义 默认值 JIRA 更多
mapreduce.job.reduce.slowstart.completedmaps 当map完成多少比例后开始为reduce申请资源 0.05 MAPREDUCE-279
yarn.app.mapreduce.am.job.reduce.rampup.limit 在map task完成之前,最多启动reduce task比例 0.5 MAPREDUCE-2864
yarn.app.mapreduce.am.job.reduce.preemption.limit **每次心跳判断:**当map task需要资源但暂时无法获取资源时,
为了保证至少一个map task可以得到资源,最多可以抢占reduce task比例
0.5 MAPREDUCE-2864 注意:即使这个设置了0,还会为map抢占资源,因为默认抢占的最小个数为mapResouce/reduceResouce。
mapreduce.job.reducer.preempt.delay.sec 当队列资源不足时为等待多久的map抢占reduce资源 0s MAPREDUCE-5844 将此参数设置为-1可避免资源不足的时候抢占reduce资源。
mapreduce.job.reducer.unconditional-preempt.delay.sec 不考虑队列资源情况为等待多久的map抢占reduce资源 300s MAPREDUCE-6302 将此参数设置为-1可避免抢占reduce资源。****(最先执行uncondition抢占)
mapreduce.job.reducer.preempt 控制是否启用抢占逻辑 true INFOI-19415 将此参数设置为false可避免抢占reduce资源。
mapreduce.map.cpu.vcores

6. 推测执行

参数 含义 默认值 更多
mapreduce.map.speculative 是否开启map的推测执行 true
mapreduce.reduce.speculative 是否开启reduce的推测执行 true
yarn.app.mapreduce.am.job.speculator.class 推测执行器 DefaultSpeculator
yarn.app.mapreduce.am.job.task.estimator.class 执行时间估计器 LegacyTaskRuntimeEstimator 推荐使用SimpleExponentialTaskRuntimeEstimator,以获得更好的时间推测。
参数值全称:org.apache.hadoop.mapreduce.v2.app.speculate.SimpleExponentialTaskRuntimeEstimator
mapreduce.job.speculative.minimum-allowed-tasks 可以推测重新执行允许的最小任务数 10 如果希望有更多的speculation task产生,请增大该参数。
mapreduce.job.speculative.speculative-cap-total-tasks 可以推测执行的task占总task的比例 0.01
mapreduce.job.speculative.speculative-cap-running-tasks 可以推测执行的task占running task的比例 0.1
mapreduce.job.speculative.retry-after-no-speculate 如果此轮没有task推测执行,下轮的等待时间 1000
mapreduce.job.speculative.retry-after-speculate 如果此轮有task推测执行,下轮的等待时间 15000
mapreduce.job.speculative.slowtaskthreshold 推测执行task超过平均task执行时长的标准差 1.0
mapreduce.job.speculative.multiplier 用于预估推测执行task的预期执行时间的因子 0 头条自定义参数(只在LegacyTaskRuntimeEstimator起作用)
推测task的预估执行时间=task平均执行时间 - multiplier * task执行时间标准差
如果希望更激进地推测执行,则设置该参数为大于0的某个值,推荐值0~3

7. 超时参数

下面是常见的超时参数,如果作业确实需要很长时间,请增加超时参数

参数 含义 集群默认值(ms) 备注
mapreduce.task.timeout task超时时间,task没有读写、也没有进度更新,任务taskTimedOut: true退出,设置为0表示禁用 3600000 (1h) 任务timeout内没有更新进度,没有一条数据进行读写,任务被认为运行时超时(卡住)。
mapreduce.task.maxfrozentime task hung时间,在该超时时间内任务进度没有变化10e-6以上,任务hung: true退出,设置0表示禁用 43200000 (12h) 任务在maxfrozentime内执行过于缓慢,进度没有更新10e-6以上,任务被认为运行时卡住。
mapreduce.task.maxstucktime Task stuck时间,mapreduce的mapper或者reducer在maxstucktime时间内没有写出一条数据,则任务任务卡住,任务stuck: true退出 1800s(30min) 用户需要在maxstucktime内至少有一条数据有输出,否则任务会被认为启动时卡住。
  • 失败taskAttempt示例1,命中了参数mapreduce.task.timeout,如果确信自己的处理逻辑没有这么长时间,建议在运行时,直接jstack查看卡在哪一步。

  • 失败taskAttempt示例2,命中了参数mapreduce.task.maxfrozentime。

  • 失败taskAttempt示例3,命中了参数mapreduce.task.maxstucktime,这个一般是机器问题,联系Yarn人员解决。

8. 容错参数

参数 含义 默认值 更多
mapreduce.map.maxattempts 每个Map Task最大重试次数 4
mapreduce.reduce.maxattempts 每个Reduce Task最大重试次数 4
mapreduce.map.failures.maxpercent 当Map Task的失败比例超过此,作业即失败;但失败比例小于此则认为成功。 0
mapreduce.reduce.failures.maxpercent 当Reduce Task的失败比例超过此,作业即失败;但失败比例小于此则认为成功。 0

9. 日志参数

参数 含义 默认值 更多
mapreduce.logs.retention.policy 日志保存规则 AM_AND_FAILED_CONTAINERS_ONLY ALL_CONTAINERS 保存所有container日志
mapreduce.map.log.level map task的日志级别 INFO The logging level for the map task. The allowed levels are: OFF, FATAL, ERROR, WARN, INFO, DEBUG, TRACE and ALL.
mapreduce.reduce.log.level reduce task的日志级别 INFO The logging level for the reduce task. The allowed levels are: OFF, FATAL, ERROR, WARN, INFO, DEBUG, TRACE and ALL.
mapreduce.task.userlog.limit.kb task日志大小的限制 0 The maximum size of user-logs of each task in KB. 0 disables the cap.
yarn.app.mapreduce.am.container.log.limit.kb am日志大小的限制 0 The maximum size of the MRAppMaster attempt container logs in KB. 0 disables the cap.
yarn.app.mapreduce.task.container.log.backups task日志的滚动个数 0 Number of backup files for task logs when using ContainerRollingLogAppender (CRLA). See org.apache.log4j.RollingFileAppender.maxBackupIndex. By default, ContainerLogAppender (CLA) is used, and container logs are not rolled. CRLA is enabled for tasks when both mapreduce.task.userlog.limit.kb and yarn.app.mapreduce.task.container.log.backups are greater than zero.
yarn.app.mapreduce.am.container.log.backups AM日志的滚动个数 0 Number of backup files for the ApplicationMaster logs when using ContainerRollingLogAppender (CRLA). See org.apache.log4j.RollingFileAppender.maxBackupIndex. By default, ContainerLogAppender (CLA) is used, and container logs are not rolled. CRLA is enabled for the ApplicationMaster when both mapreduce.task.userlog.limit.kb and yarn.app.mapreduce.am.container.log.backups are greater than zero.
mapreduce.job.userlog.retain.hours 作业结束后用户日志的保留时长 24 The maximum time, in hours, for which the user-logs are to be retained after the job completion.

10. 限制Map和Reduce的并发

(要求 2.7 版本及以上)

mapreduce.job.running.map.limit

mapreduce.job.running.reduce.limit

11. 环境变量

ENV="YARN_CONTAINER_RUNTIME_TYPE=docker,YARN_CONTAINER_RUNTIME_DOCKER_IMAGE=hub.byted.org/yarnruntime:7d1c46803069fff9e0476e2df2fa7fdc" 

/opt/tiger/yarn_deploy/hadoop/bin//hadoop jar /opt/tiger/yarn_deploy/hadoop/./share/hadoop/tools/lib/hadoop-streaming-2.6.0-cdh5.4.4.jar \ 

     -D mapreduce.job.name=test_{your-name} \ 

     -D yarn.app.mapreduce.am.env=$ENV \ # am环境变量 

     -D mapreduce.map.env=$ENV \ # map环境变量 

     -D mapreduce.reduce.env=$ENV \ # reduce环境变量

MR任务的CPU内存设置

Client内存相关参数

默认client是4g内存,可以通过环境变量方式修改,比如修改为8g,

export HADOOP_HEAPSIZE=8192

Client内存监控

看提交命令的进程即可,

AM && Task内存相关参数

参数 含义 默认值 更多
mapreduce.map.memory.mb Map Container 内存大小 (MB) 2048
mapreduce.map.cpu.vcores map的vcores 1
mapreduce.reduce.memory.mb Reduce Container 内存大小 (MB) 2048
mapreduce.reduce.cpu.vcores reduce的vcores 1
yarn.app.mapreduce.am.resource.mb AM的内存 1536
yarn.app.mapreduce.am.resource.cpu-vcores AM的vcores 1
mapreduce.map.java.opts Map JVM 参数,重点是 -Xmx 参数 -Xmx1024m
mapreduce.reduce.java.opts Reduce JVM 参数,重点是 -Xmx 参数 -Xmx1024m
yarn.app.mapreduce.am.command-opts AM JVM 参数,重点是 -Xmx 参数 -Xmx1024m

参数使用建议

  • 从属关系:Container = Java + Streaming Process(如 python 进程)
  • 内存相关参数:
    • Map JVM 一般 2 ~ 4 GB 即可,Reduce 视数据量决定
    • 留给 Python 或其它子进程的内存 = Container - JVM - 256MB(堆外内存),需要确保足够
    • java opts 指定的内存大小,任何时候都应该小于 container memory 的 90%
    • 但map和reduce的个数较大,比如map个数为10w个,建议增加AM的资源。特别是JVM大小,这个容易忽略。

AM&&Task内存监控

当AM或者Task报出OOM时,需要增加资源,增加时需要根据真实使用来调整。

  1. 获取ContainerId;
  2. 通过Dtop来监控资源真实使用
export HADOOP_HEAPSIZE=8192 
 遇到 Client 内存 Out of Memory的报错,是什么情况下会出现呢?之前遇到的情况是 一次性处理的文件太多太大。还有其他可能性吗?内部机制是啥?