目录
本文主要介绍:
- MapReduce
- 什么是MapReduce
- Map & Reduce
- Ray
- 什么是Ray
- Ray的一些简单用法
- MapReduce on Ray
- 简单用法
- 实战
- Reference
本文实验环境及相关材料:
- macOS, Python 3.6.7 with Anaconda, Ray 0.6.0
- 代码: https://github.com/hatuw
/*
理论上支持其他平台,只要Python的版本和Ray的版本对应即可。笔者已在macOS和Ubuntu上面进行测试,均无错误。如果读者在实现的过程中发现错误,欢迎提issue.
本文中的代码已上传至Github,文中在抄写代码的过程中可能出现纰漏,代码以Github为准。欢迎大家指正,谢谢!
*/
MapReduce
什么是MapReduce?
MapReduce最早(~2004年)是由Jeff Dean等人提出的一种面向大规模数据处理的并行计算模型和方法。源于函数式编程语言中的map和reduce内置函数,MapReduce的主要思想是Map(映射)和Reduce(规约),通常用于大规模数据集的并行运算。
关于MapReduce的原理和例子网络上有挺多优秀的文章,本文只是简单说一下MapReduce的原理和一个粗糙的例子,以便读者快速理解。个人建议可以去翻翻MapReduce的论文和相关的资料(见文末)。
Map(映射):
Map过程就是把一组数据按照某种Map映射成新的数据。如下面这个例子,就是用Python3.6中内置的map函数,将list[0, 1, 2, 3, 4]
中的每个元素进行了平方的操作。(需要注意的是,Python3.x中的map返回的是迭代器,2.x返回的是列表)
# Python2.x 中map方法返回的是列表
# Python3.x 中map方法返回的是迭代器
iter_map = map(lambda x: x ** 2, range(5))
for item in item_map:
print(item, end=" ")
# >> Output: 0 1 4 9 16
Reduce(规约):
Reduce过程就是把map输出的结果汇总到一起。继上面的例子,我们计算了几个数的平方,现在需要求他们的和。reduce方法需要传入两个参数,然后递归地对每个参数(除第一个参数)执行运算
# Python2.x 中可以直接调用reduce
# Python3.x 中将reduce放到functools里面了
from functools import reduce
iter_map = map(lambda x: x ** 2, range(5)) # 0 1 4 9 16
reduce(lambda: x, y: x + y, iter_map) # 0+1+4+9+16 = 30
# >> Output: 30
例子:
Hadoop中的一个例子,用MapReduce实现的词频统计:
假设现在要统计Wikipedia上所有文章的词频,单机是放不下了。一个简单的思路就是将文章分批(Splitting),每个机器分别处理一批数据(Mapping),最后再将数据汇总(Reducing)。在汇总之前,需要将相同的单词放在一起(Shuffling),以便汇总。
在本文中,我们只关心Mapping和Reducing步骤的实现。
Ray
什么是Ray?
“Ray is a flexible, high-performance distributed execution framework.”
Ray是由UC Berkeley的RISELab (RealTime Intelligence with Secure Execution) 提出的一种新型的分布式执行框架。RISELab前身是AMPLab,AMPLab (Algorithms, Machines and People Lab) 主导研发了Spark等在大数据领域著名的项目。从RISE的名字可以知道,实验室目前侧重于安全的、实时的智能系统。
Ray即如此,其主要是为深度学习、增强学习和分布式训练等量身定做,并能做到实时计算(尤其是在增强学习领域,如自动驾驶,人们会更加关注性能表现)。按照目前的发展趋势来看,Ray的出现是很有必要。
有兴趣的读者可以看看《UC Berkeley提出新型分布式执行框架Ray》,和知乎上的问题《如何看UCBerkeley RISELab即将问世的Ray,replacement of Spark?》中的高赞回答。网上可搜到,文末也会给出。两篇资料都分析得挺到位,在此不再复述了。读者如果感兴趣的话也可以读读Ray的论文,当然以后如果时间允许的话我也会写一篇关于Ray的paper reading.
安装Ray很简单:
pip install ray
如果你使用的是Anaconda,注意目前无法通过conda来安装。当然,你也可以通过源码安装/Docker等方式来安装。
在每次使用Ray之前,需要执行 ray.init() 来初始化Ray:
import ray
ray.init()
""" Output:
Process STDOUT and STDERR is being redirected to /tmp/ray/session_2018-12-22_17-15-29_3097/logs.
Waiting for redis server at 127.0.0.1:52922 to respond...
Waiting for redis server at 127.0.0.1:50403 to respond...
Starting the Plasma object store with 6.714851328 GB memory using /dev/shm.
======================================================================
View the web UI at http://localhost:8890/notebooks/ray_ui.ipynb?token=d187db1e2b1d612be2336a469836843fa6d7e9a15fba3564
======================================================================
"""
调用ray.init()
之后,我们可以看到Ray启动了Redis server, 如果只是使用单机版本的Ray的话,可以暂不关心Redis server的地址等信息,只知道它初始化成功即可。
关于ray.init()
的主要启动参数:
- redis_address: (str) – 要连接的Redis server的地址,不填则默认在本地启动。(程序退出时会关掉Redis server)
- num_cpus: (int) – 本地调度器配置的cpu数量
- ignore_reinit_error: (bool) – 如果第二次调用ray.init,程序会报错并退出,设为True可不抛出异常
具体解析和API文档请移步至: The Ray API - Ray 0.6.0 documentation
Ray的简单使用:
在Ray中,分别通过ray.put()
和ray.get()
方法来设置和读取变量的值。其中,ray.put()
方法返回的是对象的id, ray.get()
方法需要传入对象的id,返回变量的值。如:
x = "Hello Ray"
x_id = ray.put(x)
# 当然,ray.put()的传入参数也可以是number, list...等其他类型,如:
# x_id = ray.put([i for i in range(10)])
print(x_id)
# >> Output: ObjectID(ffffffffba55fc8d16f249d14868946b44ff9652)
x_r