当前位置: 首页 > 原理解释

mapreduce原理实例-MapReduce 原理实例

猜您喜欢::
在之前的代码片段里,我们尝试用 `split` 切分字符串,结局发现庞大的 `SplitWorker` 进程被瞬间卡死,整个任务挂在了第一阶段。 回想一下 `MapReduce` 那个经典的“数百万个 Map 任务并发”的故事。我们那会儿一直当作这是为了保证速度,结局一忙起来才发现这是个庞大的 Bug。当节点数量从 $M$ 增添到 $2M$ 时,Map 任务加载数据变成了 $2M$,并行度直接翻倍,理论算工夫从 $O(N)$ 降到了 $O(log N)$。可下一秒,内存就爆了。 这是出于“吞吐量”和“延迟”是解不开的矛盾。为了跑得快,我们开了几十个进程;为了跑得稳,每个进程里还得塞几个 Map 任务。
这就好比两个人抬货,一个人跑得快但力气小,一个人跑不动但力气大。
只要一个人没满负荷,想让他再跑点,得等别人忙完,这中间就卡了。 下降延迟的核心思路挺好办:让每个节点处理的数据量尽可能大。
这就需求我们“合并”数据。但在合并之前,得先把数据切分好。为了减小切分块的大小,一般我们会用 `TextSplitter` 要么 `Split` 这种工具。 实际操作中,我发现要是直接使用默认的序列化方式,往往挺好办出现 `OutOfMemoryError`。
这时候就得改改参数了,把序列化策略改成 `Java` 要么把类型改成 `String`,哪怕性能差点,先把数据跑起来再说。 为了进一步下降延迟,一个极实际上用的技巧是管住 Map 任务的大小。还不如让每个节点都去读整个文件,不如让每个节点只读一小块,比如 100MB。
这样做的益处是,就算某个节点卡住了,其他节点还能持续跑。 让我们看看一个具体的场景。假设我们要处理一个 100GB 的文本文件,我们需求把文件切分成 500MB 的块。
要是每块都单独跑 Map 任务,那可能需求几十万个任务。但要是我们把块先合并成一个大的 TextBlock,然后由一个 Map 去读它呢? 举个例子,我们有两个节点,每个节点负责处理 500MB 的块。节点 A 处理完一块,节点 B 也处理完一块。
这时候我们不需求让两个 Map 任务去争抢同一块数据了。节点 A 处理完一块,能够直接把处理结局传给节点 B。节点 B 拿到结局后,再把这些小块合并成最终的 Map Input。 这个过程就像是在做接力赛。之前的做法是每人都跑全程,结局出于中间有各自的任务需求处理,害得每个环节都排了长队,整个队伍堵死了。目前的做法是,只有当一个人跑完一段,再交给下一个节点,中间的空档期就能够用来处理其他任务。 这种“合并”思想实际上贯穿了整个 MapReduce 的核心。甭管是 Stage 1 还是 Stage 2,本质上都是在不断合并数据。Map 阶段负责读取,Reducer 阶段负责合并结局。
要是我们在 Map 阶段就把数据合并好了,Reducer 反而不用管如此多细节了。 但在合并数据时,我们不仅要寻思性能,还要寻思对性。
要是两个不同的 Map 任务处理了块里的相同数据,它们的输出结局可能会不一致。
这时候就需求引入 CheckSum 机制。 检查一下 CheckSum 的必要性,实际上挺好办。
为啥需求它?出于数据在传输过程中,要么在不同节点之间换,难免会有毛病。
要是两个 Map 任务处理的是同一个 Input 块,但输出的 Key 不同,那我们在合并结局时就会出错。 为了规避这个难题,我们能够设计一种机制,让 Map 任务在输出结局前,先验证一下整个块的数据是否一致。
要是验证通过,就把结局传给下一个节点;要是验证黄了,直接丢弃当前的映射结局,防止毛病数据污染下游。别看这会增添一点延迟,但能保住最终结局的准性。 另外,还有一个常见的坑是垃圾回收(GC)。在 MapReduce 中,要是内存占满了,任务就会瞬间卡死,就连整个集群都瘫痪。
这是出于 Map 任务在运行过程中会不断形成中间对象,要是没给 GC 留出充足的运行工夫,这些对象就会堆积起来,把可用内存挤干。 解决这个难题的一个办法是设置较小的 GC 运行工夫。但这有个代价:任务会变得更慢。
不过换个角度看,为了提速而牺牲一点延迟,在大数据处理中是能够接纳的。
毕竟,整个集群的吞吐量才是最关键的指标。 最终,我们来看看一个整个的例子。假设我们要对 1000 个文档进行取。我们先把文档按行切分,每个 Mapping 任务处理 1000 行。假设 Step 1 中,我们处理了 50000 个 Input,每个 Input 大小是 10MB,总共 500MB。
这时候某个 Map 节点可能就要处理 500MB 的数据了。 要是我们在 Map 阶段就把这 500MB 的数据合并成一个大的 TextBlock,然后由一个 Map 去读它,那这个 Map 任务就能一次性读完所有数据。
这样,后续的阶段就只需求处理少量的 Input 了,而不是处理几百个大的 Input。 别看这样看起来仿佛把负担都压在了 Map 节点上,但仔细想想,Map 节点本来就要处理数据,为啥要多处理?要是所有数据都由一个 Map 节点一次性读完,那其他节点自然就空闲了。
这时候,合并点就在 Map 阶段,Reducer 阶段再根据合并后的结局进行输出。 这种做法的核心在于,尽可能削减中间阶段的 Input 数量。
要是中间阶段的输入忒多,那整个流程就会出于输入过多而变得贼慢。而在 Map 阶段,我们自然也要尽量削减输入的数量,否则 Map 任务也会出于输入过大而崩溃。 故此,MapReduce 的设计哲学实际上挺好办:把数据尽可能多地塞进一个 Map 任务里,让每个节点都跑满。
这样,下游就没有那么多数据需求处理了,整体效率就高了。自然,也要确保数据是对的,用 CheckSum 来验证。 在实际开发中,要落地这个原则,我们得在 Code 里仔细调整配置。
比方说,调整序列化策略,设置合适的大小参数,利用 `TextSplitter` 进行合理的切分,就连能够在代码里手动合并 Input。
这些看似繁琐的细节,实际上都是为了达成“少 Input、多并行”的目标。 在这个项目中,我们最终发现,只要严格管住每个节点处理的 Input 数量,并辅以 CheckSum 验证,整个集群的延迟就能显著下降。别看 Map 阶段的开销看起来更大,但 Map 阶段的输入是削减的,故此整体吞吐量和计算速度反而提升了。
这就是为啥 MapReduce 不用做复杂的事,坚持“简化”和“合并”这两个点。 通过串联这些实例,我们就能明白,MapReduce 并不是一本教科书,它更像是一套在海量数据面前,如何用最好办规则做最大化的取舍的智慧。
好文推荐::
相关标签:

猜你喜欢

热门阅读

  • 赖柴尔定理-赖柴尔定理
  • 迪拜哪个国家的城市?-迪拜在哪国城市
  • 李毅吧番号及出处-李毅吧番号及出处
  • 贴春联的由来简介50字-春联由来简述
  • 思乡的名言和出处-思乡名言及出处

其他分站