带有太强个人色彩的系统难以大获成功。一旦最初设计基本完成且足够鲁棒时,由于经验迥然、观点各异的人的加入,真正的考验才刚刚开始。

—— Donald Knuth

在本书的前两部分,我们讨论了请求(requests)和查询(queries),与之相对的是响应(responses)和结果(results)。这种请求应答风格的数据处理是很多现代系统的基本设定:你向系统询问一些事情,或者你发送一个指令,系统稍后(大概率上)会给你一个回复。数据库、缓存、搜索引擎、 web 服务器和其他很多系统,都以类似的方式工作。

在这些在线(online)系统中,不论是 web 浏览器请求一个页面,还是服务调用一个远程 API,我们通常假设这些请求是由真人用户触发,且会等待回复。他们不应该等太久,因此我们花很多精力在优化这些系统的响应延迟(response time)上(参见衡量负载)。

web 服务和日趋增长的基于 HTTP/REST 的 API,让请求/应答风格的交互如此普遍,以至于我们理所当然的认为系统就应该长这样。但须知,这并非构建系统的唯一方式,其他方法也各有其应用场景。我们来对下面三种类型系统进行考察:

我们在本章将会看到,批处理是我们寻求构建可靠的、可扩展的、可维护的应用的重要组成部分。例如,MapReduce ,一个发表于 2004 年的批处理算法,(可能有些夸大)使得“谷歌具有超乎寻常可扩展能力”。该算法随后被多个开源数据系统所实现,包括 Hadoop,CounchDB 和 MongoDB。

相比多年前为数据仓库开发的并行处理系统,MapReduce 是一个相当底层的编程模型,但是它在基于廉价硬件上实现大规模的数据处理上迈出了一大步。尽管现在 MapReduce 的重要性在下降,但它仍然值得深入研究一番,因为通过这个框架,我们可以体会到批处理的为何有用、如何有用。

实际上,批处理是一种非常古老的计算形式。在可编程的数字计算机发明之前,打孔卡制表机——比如用于 1890 年美国人口普查的 Hollerith 制表机(IBM 前身生产的)——实现了一种对大量输入的半机械化批处理。MapReduce 与二十世纪四五十年代 IBM 生产的卡片分类机有着惊人的相似。就像我们常说的,历史总是在自我重复。

在本章,我们将会介绍 MapReduce 和其他几种批处理算法和框架,并探讨下他们如何用于现代数据系统中。作为引入,我们首先来看下使用标准 Unix 工具进行数据处理。尽管你可能对 Unix 工具链非常熟悉,但对 Unix 的哲学做下简单回顾仍然很有必要,因为我们可以将其经验运用到大规模、异构的分布式数据系统中。

使用 Unix 工具进行批处理

让我们从一个简单的例子开始。设你有一个 web 服务器,并且当有请求进来时,服务器就会向日志文件中追加一行日志:

216.58.210.78 - - [27/Feb/2015:17:55:11 +0000] "GET /css/typography.css HTTP/1.1"
200 3377 "<http://martin.kleppmann.com/>" "Mozilla/5.0 (Macintosh; Intel Mac OS X
10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115
Safari/537.36"

注:上面文本其实是一行,只是为了阅读性,拆成了多行。

这行日志信息量很大。为了便于理解,你可能首先需要了解其格式:

$remote_addr - $remote_user [$time_local] "$request"
$status $body_bytes_sent "$http_referer" "$http_user_agent"

因此,上面一行日志的意思是,在 2015 年的 2 月 27 号,UTC 时间 17:55:11 ,服务器从 IP 为 216.58.210.78 的客户端收到了一条请求,请求路径为 /css/typography.css。该用户没有经过认证,因此用户位置显示了一个连字符(-)。响应状态码是 200(即,该请求成功了),响应大小是 3377 字节。web 浏览器是 Chrome 49,由于该资源在 http://martin.kleppmann.com/ 网站中被引用,因此浏览器加载了该 CSS 文件。