用Erlang实现MapReduce算法(一)

本文深入探讨了MapReduce算法的核心思想及其在Erlang中的实现方式,包括如何利用Erlang的并发特性实现并行计算。通过实例解释了Map和Reduce阶段的功能,并详细介绍了两种原型实现方法,旨在提高大规模数据处理的效率。此外,提供了阶乘函数测试以验证算法性能,最后展望了如何进一步扩展到多台机器上的分布式计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

由于cnblogs的代码着色系统不支持erlang,所以就直接从博客上贴过来了,如果大家看的不习惯的话,就直接来我的博客上看吧

本文章为本人个人博客相应文章的镜像:

原文地址: http://www.greatony.com/index.php/2010/03/21/mapreduce-algorithm-implemented-in-erlang-i/

 Google曾经发过3篇引起巨大反响的论文:

  1. The Google File System
  2. MapReduce: Simplified Data Processing on Large Clusters
  3. Bigtable: A Distributed Storage System for Structured Data

这三篇论文对于大规模并行计算这个领域,无疑是三颗“小男孩”,就是在这三篇论文的基础上,有了著名的开源项目Apache Hadoop
GFS是一个高性能、分布式、高可靠的文件存储系统,MapReduce是一种在大规模集群上,进行高效的并行计算的方法,而BigTable类似于分布式的数据库系统。

在本文中,我们讨论MapReduce算法的思想,以及如何在Erlang中实现一个简单、高效、可靠的MapReduce算法。

MapReduce算法

MapReduce算法讲大规模计算的过程分成了两个阶段:

  1. Map阶段:在这个阶段,通过Map过程,将原始数据列表,处理成中间数据,用于Reduce过程的处理
  2. Reduce阶段:将Map阶段产生的中间数据综合归纳成输出结果

这样说起来似乎比较抽象,我们用一个实例(好像是mr论文里面的例子,otz)来说明这个过程:
任务:我们现在有200篇文章,我们需要统计这200篇文章中,每一个英文单词都出现了几次。
Map阶段:这个阶段是分别针对每一篇文章的,统计出这一篇文章中,每个单词出现了几次。它的运算结果类似这样:
在第1篇文章中:找到了hello * 1, world * 1
在第2篇文章种:找到了hello * 1, tony * 1, huang * 1
...
Reduce阶段:这个阶段就是将上面的中间结果进行综合,它的运算结果类似这样:
在所有文章中,一共有:hello * 2, tony * 1, world * 1, huang * 1
所以,我们就会发现,我们很容易将Map过程分配到不同的计算机上执行(最简单的,每台机器计算一篇文章),而对于Reduce阶段也可以并行化(比如第一台机器Reduce1~4篇文章的数据,第二台机器Reduce5~8篇文章的数据,最后通过递归的reduce过程就可以把所有文章的数据整合在一起了)。
所以,这个算法非常有利于对巨大的数据的并行化处理(paper的副标题里就这么写的嘛)

Erlang实现 - 原型1

罗唆了那么久,终于讲到该如何实现这个算法了。
好了,首先,我们直接根据MapReduce的思想,利用erlang内置的lists库的函数来实现这个功能,代码如下:

1 map_reduce(MapReduceSource) ->
2     MapResult=lists:map(MapSource),
3     lists:foldl(Reduce, [], MapResult).

哇,这也太简单了吧?!首先调用lists:map函数将原结果通过Map函数生成中间结果(MapResult),然后又通过foldl进行Reduce过程。
(电视购物的口气)没错,用Erlang就是那么简单!
观众:这样你不是在串行执行嘛?!MapReduce的优势一点也没有发挥出来嘛。
别着急嘛,这个是第一个原型嘛,下面我们就对它进行并行化的改造!

Erlang实现 - 原型2

在Erlang中实现并行化的最简单的方式(也是唯一的方式)当然就是进程(process)啦。所有的erlang大大们都教导我们,开erlang的进程的开销是很小的,所以,我们的思路就是针对源数据中的每一个元素创建一个map的进程,并发的执行map操作。同时呢,创建一个monitor进程去进行Reduce操作,最后再把最终结果返回给主进程。
ok,直接上代码:

01 -module(emr).
02 -export([map_reduce/3]).
03  
04 % the monitor waiting for the map result, and then call the reduce to generate the final result
05 monitor(ProcessPidResultReduceCount) ->
06     receive
07         MapResult ->
08             ReducedResult Reduce(MapResultResult),
09             case Count of
10                 1 -> ProcessPid ReducedResult;
11                 _ -> monitor(ProcessPidReducedResultReduceCount - 1)
12             end
13     end.
14  
15 % a delegate to send the map result to the monitor
16 map(MonitorPidMapElement) -> MonitorPid Map(Element).
17  
18 % the map-reduce main function
19 map_reduce(_Map, _Reduce, []) -> [];
20 map_reduce(MapReduceList) ->
21     Self = self(),
22     Length = length(List),
23     MonitorPid = spawn(fun() -> monitor(Self, [], ReduceLengthend),
24     lists:foreach(fun(Element) -> spawn(fun()->map(MonitorPidMap,Element)endendList),
25     receive
26         Result -> Result
27     end.

这里的map_reduce函数首先创建一个monitor进程,去处理计算结果,然后针对源数据中的没一个元素创建一个map函数的进程,最后再等待monitor进程把最终的计算结果发送回来。
这里的map方法不是原始的Map函数,而是Map函数的一个马甲,map函数会把Map函数的计算结果发送给monitor进程。

测试1

写了这两个map_reduce函数,总得找点东西来测试一下吧?!,erlang的例子里面不是必然会出现阶乘函数嘛?!我们也就不要免俗了:

01 -module(emr_test).
02 -export([factorial/1, test/3, exec_test/4]).
03  
04 % an algorithm function for test
05 factorial(1) -> 1;
06 factorial(N) -> N * factorial(N - 1).
07  
08 % test a method on (Size) data for (Times) times, and give the {TotalTimeCost, AverageTimeCost}
09 test(MethodSizeTimes) ->
10     Map fun(X) -> factorial(X) end,
11     Reduce fun(MapResultFinalResult) -> FinalResult ++ [MapResult]end,
12     Source lists:seq(1, Size),
13     {TimeCost, _Result} = timer:tc(?MODULE, exec_test, [MapReduce,MethodSource]),
14     case Times of
15         1 -> {TimeCostTimeCost};
16         N -> {OtherTimeCost, _OtherAvgTimeCost} = test(MethodSize, N - 1),
17              {TimeCost OtherTimeCost, (TimeCost OtherTimeCost) / N}
18     end.
19  
20 % execute the real test progress
21 exec_test(MapReduceMethodSource) ->
22     case Method of
23         map_reduce -> emr:map_reduce(MapReduceSource);
24         sequence -> AllMapResult lists:map(MapSource),
25                     lists:foldl(Reduce, [], AllMapResult)
26     end.

这里的factorial就是标准的阶乘函数,这里的test是为了方便测试运算速度的一个代理。第一个参数表示了用什么方法来进行计算(map_reduce表示并行计算,sequence表示串行计算,也就是原型1的方法),第2个参数表示要计算到几的阶乘,第3个参数表示要进行几次测试计算平均值。而exec_test就是具体进行计算的函数了。

这里放上我的测试环境和结果:
测试环境:
CPU:Intel Core 2 Quad Q9400S 2.66GHz (4 cores)
内存:Kingston 2GB DDR3 1333MHz * 2
操作系统:Apple Mac OS X Snow Leopard (10.6.2)
(没错拉,是黑苹果。。。)
计算1~10000的所有数的阶乘

测试结果:

1 emr_test:test(sequence, 10000, 2).

总时间:361.98s,每次时间:180.99s

1 emr_test:test(map_reduce, 10000, 2).

总时间:107.22s,每次时间:53.61s

观众们:这个还没有分布到其他计算机上呢~~~
不要着急嘛,下一篇文章,就讲如何分布到多台机器上。

《ErLang程序设计》上MapReduce实现

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值