This commit is contained in:
louzefeng
2024-07-09 18:38:56 +00:00
parent 8bafaef34d
commit bf99793fd0
6071 changed files with 1017944 additions and 0 deletions

View File

@@ -0,0 +1,190 @@
<audio id="audio" title="01 | 为什么MapReduce会被硅谷一线公司淘汰" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/38/64/3876128a2cd0e848213e68d470455264.mp3"></audio>
你好,我是蔡元楠。
今天我要与你分享的主题是“为什么MapReduce会被硅谷一线公司淘汰”。
我有幸几次与来Google参观的同行进行交流当谈起数据处理技术时他们总是试图打探MapReduce方面的经验。
这一点让我颇感惊讶因为在硅谷早已没有人去谈论MapReduce了。
今天这一讲我们就来聊聊为什么MapReduce会被硅谷一线公司淘汰。
我们先来沿着时间线看一下超大规模数据处理的重要技术以及它们产生的年代。
<img src="https://static001.geekbang.org/resource/image/54/ca/54a0178e675d0054cda83b5dc89b1dca.png" alt="">
我认为可以把超大规模数据处理的技术发展分为三个阶段:石器时代,青铜时代,蒸汽机时代。
### 石器时代
我用“石器时代”来比喻MapReduce诞生之前的时期。
数据的大规模处理问题早已存在。早在2003年的时候Google就已经面对大于600亿的搜索量。
但是数据的大规模处理技术还处在彷徨阶段。当时每个公司或者个人可能都有自己的一套工具处理数据。却没有提炼抽象出一个系统的方法。
### 青铜时代
2003年MapReduce的诞生标志了超大规模数据处理的第一次革命而开创这段青铜时代的就是下面这篇论文《MapReduce: Simplified Data Processing on Large Clusters》。
<img src="https://static001.geekbang.org/resource/image/ae/61/ae9083e7b1f5cdd97deda1c8a1344861.png" alt="">
杰夫Jeff Dean和桑杰Sanjay Ghemawat从纷繁复杂的业务逻辑中为我们抽象出了Map和Reduce这样足够通用的编程模型。后面的Hadoop仅仅是对于GFS、BigTable、MapReduce 的依葫芦画瓢,我这里不再赘述。
### 蒸汽机时代
到了2014年左右Google内部已经几乎没人写新的MapReduce了。
2016年开始Google在新员工的培训中把MapReduce替换成了内部称为FlumeJava不要和Apache Flume混淆是两个技术的数据处理技术。
这标志着青铜时代的终结,同时也标志着蒸汽机时代的开始。
我跳过“铁器时代”之类的描述是因为只有工业革命的概念才能解释从MapReduce进化到FlumeJava的划时代意义。
Google内部的FlumeJava和它后来的开源版本Apache Beam所引进的统一的编程模式将在后面的章节中为你深入解析。
现在你可能有一个疑问 为什么MapReduce会被取代今天我将重点为你解答。
## 高昂的维护成本
使用MapReduce你需要严格地遵循分步的Map和Reduce步骤。当你构造更为复杂的处理架构时往往需要协调多个Map和多个Reduce任务。
然而每一步的MapReduce都有可能出错。
为了这些异常处理很多人开始设计自己的协调系统orchestration。例如做一个状态机state machine协调多个MapReduce这大大增加了整个系统的复杂度。
如果你搜 “MapReduce orchestration” 这样的关键词就会发现有很多书整整一本都在写怎样协调MapReduce。
你可能会惊讶于MapReduce的复杂度。我也经常会看到一些把MapReduce说得过度简单的误导性文章。
例如“把海量的××数据通过MapReduce导入大数据系统学习就能产生××人工智能”。似乎写文的“专家”动动嘴就能点石成金。
而现实的MapReduce系统的复杂度是超过了“伪专家”的认知范围的。下面我来举个例子告诉你MapReduce有多复杂。
想象一下这个情景,你的公司要**预测美团的股价**,其中一个重要特征是活跃在街头的美团外卖电动车数量,而你负责**处理所有美团外卖电动车的图片**。
在真实的商用环境下为了解决这个问题你可能至少需要10个MapReduce任务
<img src="https://static001.geekbang.org/resource/image/44/c7/449ebd6c5950f5b7691d34d13a781ac7.jpg" alt="">
首先,我们需要搜集每日的外卖电动车图片。
数据的搜集往往不全部是公司独自完成,许多公司会选择部分外包或者众包。所以在**数据搜集**Data collection部分你至少需要4个MapReduce任务
<li>
数据导入data ingestion用来把散落的照片比如众包公司上传到网盘的照片下载到你的存储系统。
</li>
<li>
数据统一化data normalization用来把不同外包公司提供过来的各式各样的照片进行格式统一。
</li>
<li>
数据压缩compression你需要在质量可接受的范围内保持最小的存储资源消耗 。
</li>
<li>
数据备份backup大规模的数据处理系统我们都需要一定的数据冗余来降低风险。
</li>
仅仅是做完数据搜集这一步,离真正的业务应用还差得远。
真实的世界是如此不完美我们需要一部分数据质量控制quality control流程比如
<li>
数据时间有效性验证 date validation检测上传的图片是否是你想要的日期的。
</li>
<li>
照片对焦检测focus detection你需要筛选掉那些因对焦不准而无法使用的照片。
</li>
最后才到你负责的重头戏——找到这些图片里的外卖电动车。而这一步因为人工的介入是最难控制时间的。你需要做4步
<li>
数据标注问题上传question uploading上传你的标注工具让你的标注者开始工作。
</li>
<li>
标注结果下载answer downloading抓取标注完的数据。
</li>
<li>
标注异议整合adjudication标注异议经常发生比如一个标注者认为是美团外卖电动车另一个标注者认为是京东快递电动车。
</li>
<li>
标注结果结构化structuralization: 要让标注结果可用,你需要把可能非结构化的标注结果转化成你的存储系统接受的结构。
</li>
这里我不再深入每个MapReduce任务的技术细节因为本章的重点仅仅是理解MapReduce的复杂度。
通过这个案例我想要阐述的观点是因为真实的商业MapReduce场景极端复杂像上面这样10个子任务的MapReduce系统在硅谷一线公司司空见惯。
在应用过程中每一个MapReduce任务都有可能出错都需要重试和异常处理的机制。所以协调这些子MapReduce的任务往往需要和业务逻辑紧密耦合的状态机。
这样过于复杂的维护让系统开发者苦不堪言。
## 时间性能“达不到”用户的期待
除了高昂的维护成本MapReduce的时间性能也是个棘手的问题。
MapReduce是一套如此精巧复杂的系统如果使用得当它是青龙偃月刀如果使用不当它就是一堆废铁。不幸的是并不是每个人都是关羽。
在实际的工作中不是每个人都对MapReduce细微的配置细节了如指掌。
在现实中业务往往需求一个刚毕业的新手在3个月内上线一套数据处理系统而他很可能从来没有用过MapReduce。这种情况下开发的系统是很难发挥好MapReduce的性能的。
你一定想问MapReduce的性能优化配置究竟复杂在哪里呢
我想Google500多页的MapReduce性能优化手册足够说明它的复杂度了。这里我举例讲讲MapReduce的分片sharding难题希望能窥斑见豹引发大家的思考。
Google曾经在2007年到2012年间做过一个对于1PB数据的大规模排序实验来测试MapReduce的性能。
从2007年的排序时间12小时到2012年的排序时间缩短至0.5小时。即使是Google也花了5年的时间才不断优化了一个MapReduce流程的效率。
2011年他们在Google Research的博客上公布了初步的成果。
<img src="https://static001.geekbang.org/resource/image/db/6b/db4bb58536ffe3b6addd88803a77396b.jpg" alt="">
其中有一个重要的发现就是他们在MapReduce的性能配置上花了非常多的时间。包括了缓冲大小(buffer size分片多少number of shards预抓取策略prefetch缓存大小cache size等等。
所谓的分片,是指把大规模的的数据分配给不同的机器/工人,流程如下图所示。
<img src="https://static001.geekbang.org/resource/image/b0/38/b08b95244530aeb0171e3e35c9bfb638.png" alt="">
选择一个好的分片函数sharding function为何格外重要让我们来看一个例子。
假如你在处理Facebook的所有用户数据你选择了按照用户的年龄作为分片函数sharding function。我们来看看这时候会发生什么。
因为用户的年龄分布不均衡假如在20~30这个年龄段的Facebook用户最多导致我们在下图中worker C上分配到的任务远大于别的机器上的任务量。
<img src="https://static001.geekbang.org/resource/image/5c/91/5c719600021f738e8c7edf82197eac91.png" alt="">
这时候就会发生掉队者问题stragglers。别的机器都完成了Reduce阶段只有worker C还在工作。
当然它也有改进方法。掉队者问题可以通过MapReduce的性能剖析profiling发现。 如下图所示,箭头处就是掉队的机器。
<img src="https://static001.geekbang.org/resource/image/63/ca/6399416524eb0dec1e292ea01b2294ca.png" alt="">
图片引用Chen, Qi, Cheng Liu, and Zhen Xiao. “Improving MapReduce performance using smart speculative execution strategy.” IEEE Transactions on Computers 63.4 (2014): 954-967.
回到刚刚的Google大规模排序实验。
因为MapReduce的分片配置异常复杂在2008年以后Google改进了MapReduce的分片功能引进了动态分片技术 (dynamic sharding大大简化了使用者对于分片的手工调整。
在这之后,包括动态分片技术在内的各种崭新思想被逐渐引进,奠定了下一代大规模数据处理技术的雏型。
## 小结
这一讲中我们分析了两个MapReduce之所以被硅谷一线公司淘汰的“致命伤”高昂的维护成本和达不到用户期待的时间性能。
文中也提到了下一代数据处理技术雏型。这就是2008年左右在Google西雅图研发中心诞生的FlumeJava它一举解决了上面MapReduce的短板。
另外它还带来了一些别的优点更好的可测试性更好的可监控性从1条数据到1亿条数据无缝扩展不需要修改一行代码等等。
在后面的章节中我们将具体展开这几点通过深入解析Apache BeamFlumeJava的开源版本揭开MapReduce继任者的神秘面纱。
## 思考题
如果你在Facebook负责处理例子中的用户数据你会选择什么分片函数来保证均匀分布的数据分片?
欢迎你把答案写在留言区,与我和其他同学一起探讨。
如果你觉得有所收获,也欢迎把文章分享给你的朋友。

View File

@@ -0,0 +1,144 @@
<audio id="audio" title="02 | MapReduce后谁主沉浮怎样设计下一代数据处理技术" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/cf/2e/cfa3ad6a9032653d841af44d83ab0e2e.mp3"></audio>
你好,我是蔡元楠。
在上一讲中我们介绍了2014年之前的大数据历史也就是MapReduce作为数据处理的默认标准的时代。重点探讨了MapReduce面对日益复杂的业务逻辑时表现出的不足之处那就是1. 维护成本高2. 时间性能不足。
同时我们也提到了2008年诞生在Google西雅图研发中心的FlumeJava它成为了Google内部的数据处理新宠。
那么为什么是它扛起了继任MapReduce的大旗呢
要知道在包括Google在内的硅谷一线大厂对于内部技术选择是非常严格的一个能成为默认方案的技术至少满足以下条件
<li>
经受了众多产品线,超大规模数据量例如亿级用户的考验;
</li>
<li>
自发地被众多内部开发者采用,简单易用而受开发者欢迎;
</li>
<li>
能通过内部领域内专家的评审;
</li>
<li>
比上一代技术仅仅提高10%是不够的必须要有显著的比如70%的提高才能够说服整个公司付出技术迁移的高昂代价。就看看从Python 2.7到Python 3的升级花了多少年了就知道在大厂迁移技术是异常艰难的。
</li>
今天这一讲,我不展开讲任何具体技术。
我想先和你一起设想一下假如我和你站在2008年的春夏之交在已经清楚了MapReduce的现有问题的情况下我们会怎么设计下一代大规模数据处理技术带领下一个十年的技术革新呢
### 我们需要一种技术抽象让多步骤数据处理变得易于维护
上一讲中我提到过,维护协调多个步骤的数据处理在业务中非常常见。
<img src="https://static001.geekbang.org/resource/image/44/c7/449ebd6c5950f5b7691d34d13a781ac7.jpg" alt="">
像图片中这样复杂的数据处理在MapReduce中维护起来令人苦不堪言。
为了解决这个问题作为架构师的我们或许可以用有向无环图DAG来抽象表达。因为有向图能为多个步骤的数据处理依赖关系建立很好的模型。如果你对图论比较陌生的话可能现在不知道我在说什么你可以看下面一个例子或者复习一下极客时间的《数据结构与算法之美》。
<img src="https://static001.geekbang.org/resource/image/26/83/26072f95c409381f3330b77d93150183.png" alt="">
西红柿炒鸡蛋这样一个菜,就是一个有向无环图概念的典型案例。
比如看这里面番茄的处理最后一步“炒”的步骤依赖于切好的番茄、打好的蛋、热好的油。而切好的番茄又依赖于洗好的番茄等等。如果用MapReduce来实现的话在这个图里面每一个箭头都会是一个独立的Map或Reduce。
为了协调那么多Map和Reduce你又难以避免会去做很多检查比如番茄是不是洗好了鸡蛋是不是打好了。
最后这个系统就不堪重负了。
但是,如果我们用有向图建模,图中的每一个节点都可以被抽象地表达成一种通用的**数据集**,每一条边都被表达成一种通用的**数据变换**。如此,你就可以用**数据集**和**数据变换**描述极为宏大复杂的数据处理流程,而不会迷失在依赖关系中无法自拔。
### 我们不想要复杂的配置,需要能自动进行性能优化
上一讲中提到MapReduce的另一个问题是配置太复杂了。以至于错误的配置最终导致数据处理任务效率低下。
这种问题怎么解决呢?很自然的思路就是,如果人容易犯错,就让人少做一点,让机器多做一点呗。
我们已经知道了,得益于上一步中我们已经用有向图对数据处理进行了高度抽象。这可能就能成为我们进行自动性能优化的一个突破口。
回到刚才的番茄炒鸡蛋例子,哪些情况我们需要自动优化呢?
设想一下,如果我们的数据处理食谱上又增加了番茄牛腩的需求,用户的数据处理有向图就变成了这个样子了。
<img src="https://static001.geekbang.org/resource/image/dc/a7/dc07e6cccdcc892bf6dff9a288e7f3a7.jpg" alt="">
理想的情况下,我们的计算引擎要能够自动发现红框中的两条数据处理流程是重复的。它要能把两条数据处理过程进行合并。这样的话,番茄就不会被重复准备了。
同样的,如果需求突然不再需要番茄炒蛋了,只需要番茄牛腩,在数据流水线的预处理部分也应该把一些无关的数据操作优化掉,比如整个鸡蛋的处理过程就不应该在运行时出现。
另一种自动的优化是计算资源的自动弹性分配。
比如还是在番茄炒蛋这样一个数据处理流水线中如果你的规模上来了今天需要生产1吨的番茄炒蛋明天需要生产10吨的番茄炒蛋。你发现有时候是处理1000个番茄有时候又是10000个番茄。如果手动地去做资源配置的话你再也配置不过来了。
我们的优化系统也要有可以处理这种问题的弹性的劳动力分配机制。它要能自动分配比如100台机器处理1000个番茄如果是10000个番茄那就分配1000台机器但是只给热油1台机器可能就够了。
这里的比喻其实是很粗糙也不精准的。我想用这样两个例子表达的观点是,在数据处理开始前,我们需要有一个自动优化的步骤和能力,而不是按部就班地就把每一个步骤就直接扔给机器去执行了。
### 我们要能把数据处理的描述语言,与背后的运行引擎解耦合开来
前面两个设计思路提到了很重要的一个设计就是有向图。
用有向图进行数据处理描述的话,实际上**数据处理描述语言**部分完全可以和后面的**运算引擎**分离了。有向图可以作为**数据处理描述语言**和**运算引擎**的前后端分离协议。
举两个你熟悉的例子可能能更好理解我这里所说的前后端分离client-server design是什么意思
比如一个网站的架构中服务器和网页通过HTTP协议通信。
<img src="https://static001.geekbang.org/resource/image/22/b4/22c92b5a9dd6e4d9fc07a8ac61fff2b4.png" alt="">
比如在TensorFlow的设计中客户端可以用任何语言比如Python或者C++描述计算图运行时引擎runtime) 理论上却可以在任何地方具体运行比如在本地在CPU或者在TPU。
<img src="https://static001.geekbang.org/resource/image/f9/06/f9e2bb76a168469f572c91d0c5a0bf06.png" alt="">
那么我们设计的数据处理技术也是一样的,除了有向图表达需要**数据处理描述语言**和**运算引擎**协商一致,其他的实现都是灵活可拓展的。
比如我的数据描述可以用Python描述由业务团队使用计算引擎用C++实现,可以由数据底层架构团队维护并且高度优化;或者我的数据描述在本地写,计算引擎在云端执行。
<img src="https://static001.geekbang.org/resource/image/d7/b8/d77857341e194bae59ce099e7d68c9b8.png" alt="">
### 我们要统一批处理和流处理的编程模型
关于什么是批处理和流处理概念会在后面的章节展开。这里先简单解释下,批处理处理的是有界离散的数据,比如处理一个文本文件;流处理处理的是无界连续的数据,比如每时每刻的支付宝交易数据。
MapReduce的一个局限是它为了批处理而设计的应对流处理的时候不再那么得心应手。即使后面的Apache Storm、Apache Flink也都有类似的问题比如Flink里的批处理数据结构用DataSet但是流处理用DataStream。
但是真正的业务系统,批处理和流处理是常常混合共生,或者频繁变换的。
比如你有A、B两个数据提供商。其中数据提供商A与你签订的是一次性的数据协议一次性给你一大波数据你可以用批处理。而数据提供商B是实时地给你数据你又得用流处理。更可怕的事情发生了本来是批处理的数据提供商A突然把协议修改了现在他们实时更新数据。这时候你要是用Flink就得爆炸了。业务需求天天改还让不让人活了
因此,我们设计的数据处理框架里,就得有更高层级的数据抽象。
不论是批处理还是流处理的都用统一的数据结构表示。编程的API也需要统一。这样不论业务需求什么样开发者只需要学习一套API。即使业务需求改变开发者也不需要频繁修改代码。
### 我们要在架构层面提供异常处理和数据监控的能力
真正写过大规模数据处理系统的人都深有感触:在一个复杂的数据处理系统中,难的不是开发系统,而是异常处理。
事实正是如此。一个Google内部调研表明在大规模的数据处理系统中90%的时间都花在了异常处理中。常常发生的问题的是比如在之前的番茄炒鸡蛋处理问题中你看着系统log明明买了1000个鸡蛋炒出来的菜却看起来只有999个鸡蛋你仰天长叹少了一个蛋到底去哪里了
这一点和普通的软件开发不同。比如服务器开发中偶尔一个RPC请求丢了就丢了重试一下重启一下能过就行了。可如果在数据处理系统中数据就是钱啊不能随便丢。比如我们的鸡蛋都是真金白银买回来的。是超市买回来数错了是打蛋时候打碎了还是被谁偷吃了你总得给老板一个合理的交代。
我们要设计一套基本的数据监控能力,对于数据处理的每一步提供自动的监控平台,比如一个监控网站。
在番茄炒蛋系统中,要能够自动的记录下来,超市买回来是多少个蛋,打蛋前是多少个蛋,打完蛋是多少个蛋,放进锅里前是多少个蛋等等。也需要把每一步的相关信息进行存储,比如是谁去买的蛋,哪些人打蛋。这样出错后可以帮助用户快速找到可能出错的环节。
## 小结
通过上面的分析我们可以总结一下。如果是我们站在2008年春夏之交来设计下一代大规模数据处理框架一个基本的模型会是图中这样子的
<img src="https://static001.geekbang.org/resource/image/53/2e/53aa1aad08b11e6c2db5cf8bb584572e.png" alt="">
但是这样粗糙的设计和思想实验离实现还是太远。你可能还是会感到无从下手。
后面的章节会给你补充一些设计和使用大规模数据处理架构的基础知识。同时也会深入剖析两个与我们这里的设计理念最接近的大数据处理框架Apache Spark和Apache Beam。
## 思考题
你现在在使用的数据处理技术有什么问题,你有怎样的改进设计?
欢迎你把自己的想法写在留言区,与我和其他同学一起讨论。
如果你觉得有所收获,也欢迎把文章分享给你的朋友。

View File

@@ -0,0 +1,205 @@
<audio id="audio" title="03 | 大规模数据处理初体验:怎样实现大型电商热销榜?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/d0/7d/d0e1677e708ecbb575ed559e120db17d.mp3"></audio>
你好,我是蔡元楠。
今天我要与你分享的主题是“怎样实现大型电商热销榜”。
我在Google面试过很多优秀的候选人应对普通的编程问题coding能力很强算法数据结构也应用得不错。
可是当我追问数据规模变大时该怎么设计系统他们却说不出所以然来。这说明他们缺乏必备的规模增长的技术思维mindset of scaling。这会限制这些候选人的职业成长。
因为产品从1万用户到1亿用户技术团队从10个人到1000个人你的技术规模和数据规模都会完全不一样。
今天我们就以大型电商热销榜为例来谈一谈从1万用户到1亿用户从GB数据到PB数据系统技术思维需要怎样的转型升级
同样的问题举一反三可以应用在淘宝热卖App排行榜抖音热门甚至是胡润百富榜因为实际上他们背后都应用了相似的大规模数据处理技术。
<img src="https://static001.geekbang.org/resource/image/46/ec/469707990cf33d24d8713efab8fe34ec.png" alt="">
真正的排序系统非常复杂仅仅是用来排序的特征features就需要多年的迭代设计。
为了便于这一讲的讨论,我们来构想一个简化的玩具问题,来帮助你理解。
假设你的电商网站销售10亿件商品已经跟踪了网站的销售记录商品id和购买时间 {product_id, timestamp}整个交易记录是1000亿行数据TB级。作为技术负责人你会怎样设计一个系统根据销售记录统计去年销量前10的商品呢
举个例子,假设我们的数据是:
<img src="https://static001.geekbang.org/resource/image/bd/9d/bdafa7f74c568c107c38317e0a1a669d.png" alt="">
我们可以把热销榜按 product_id 排名为1, 2, 3。
## 小规模的经典算法
如果上过极客时间的《数据结构与算法之美》,你可能一眼就看出来,这个问题的解法分为两步:
<img src="https://static001.geekbang.org/resource/image/3e/af/3eaea261df4257f0cff4509d82f211af.png" alt="">
第一步统计每个商品的销量。你可以用哈希表hashtable数据结构来解决是一个O(n)的算法这里n是1000亿。
第二步找出销量前十可以用经典的Top K算法也是O(n)的算法。
如果你考虑到了这些,先恭喜你答对了。
在小规模系统中我们确实完全可以用经典的算法简洁漂亮地解决。以Python编程的话可能是类似这样的
```
def CountSales(sale_records):
&quot;&quot;&quot;Calculate number of sales for each product id.
Args:
sales_records: list of SaleRecord, SaleRecord is a named tuple,
e.g. {product_id: “1”, timestamp: 1553721167}.
Returns:
dict of {product_id: num_of_sales}. E.g. {“1”: 1, “2”: 1}
&quot;&quot;&quot;
sales_count = {}
for record in sale_records:
sales_count[record[product_id]] += 1
return sales_count
def TopSellingItems(sale_records, k=10):
&quot;&quot;&quot;Calculate the best selling k products.
Args:
sales_records: list of SaleRecord, SaleRecord is a named tuple,
e.g. {product_id: “1”, timestamp: 1553721167}.
K: num of top products you want to output.
Returns:
List of k product_id, sorted by num of sales.
&quot;&quot;&quot;
sales_count = CountSales(sale_records)
return heapq.nlargest(k, sales_count, key=sales_count.get)
```
但在一切系统中,随着尺度的变大,很多方法就不再适用。
比如,在小尺度经典物理学中适用的牛顿力学公式是这样的:
<img src="https://static001.geekbang.org/resource/image/d1/dd/d1baf9cb72b099990c0f0476a79be2dd.png" alt="">
这在高速强力的物理系统中就不再适用,在狭义相对论中有另外的表达。
<img src="https://static001.geekbang.org/resource/image/2c/f7/2c59194f8bebaecd88f5942bcccf75f7.png" alt="">
在社会系统中也是一样管理10人团队和治理14亿人口的国家复杂度也不可同日而语。
具体在我们这个问题中同样的Top K算法当数据规模变大会遇到哪些问题呢
第一,内存占用。
对于TB级的交易记录数据很难找到单台计算机容纳那么大的哈希表了。你可能想到那我不要用哈希表去统计商品销售量了我把销量计数放在磁盘里完成好了。
比如就用一个1000亿行的文件或者表然后再把销量统计结果一行一行读进后面的堆树/优先级队列。理论上听起来不错,实际上是否真的可行呢,那我们看下一点。
第二磁盘I/O等延时问题。
当数据规模变大我们难以避免地需要把一些中间结果存进磁盘以应对单步任务出错等问题。一次磁盘读取大概需要10ms的时间。
如果按照上一点提到的文件替代方法因为我们是一个O(n * log k)的算法就需要10ms * 10^9 = 10 ^ 7 s = 115 天的时间。你可能需要贾跃亭附体,才能忽悠老板接受这样的设计方案了。
这些问题怎么解决呢?你可能已经想到,当单台机器已经无法适应我们数据或者问题的规模,我们需要横向扩展。
## 大规模分布式解决方案
之前的思路依然没错。但是,我们需要把每一步从简单的函数算法,升级为计算集群的分布式算法。
<img src="https://static001.geekbang.org/resource/image/3e/af/3eaea261df4257f0cff4509d82f211af.png" alt="">
### 统计每个商品的销量
我们需要的第一个计算集群,就是统计商品销量的集群。
例如1000台机器每台机器一次可以处理1万条销售记录。对于每台机器而言它的单次处理又回归到了我们熟悉的传统算法数据规模大大缩小。
下图就是一个例子图中每台机器输入是2条销售记录输出是对于他们的本地输入而言的产品销量计数。
<img src="https://static001.geekbang.org/resource/image/8e/8a/8eeff3376743e886d5f2d481ca8ddb8a.jpg" alt="">
### 找出销量前K
我们需要的第二个计算集群,则是找出销量前十的集群。
这里我们不妨把问题抽象一下抽象出是销量前K的产品。因为你的老板随时可能把产品需求改成前20销量而不是前10了。
在上一个统计销量集群得到的数据输出将会是我们这个处理流程的输入。所以这里需要把分布在各个机器分散的产品销量汇总出来。例如把所有product_id = 1的销量全部叠加。
下图示例是K = 1的情况每台机器先把所有product_id = 1的销量叠加在了一起再找出自己机器上销量前K = 1的商品。可以看到对于每台机器而言他们的输出就是最终排名前K = 1的商品候选者。
<img src="https://static001.geekbang.org/resource/image/38/fc/38933e25ca315bd56321753573d5bbfc.jpg" alt="">
### 汇总最终结果
到了最后一步你需要把在“销量前K集群”中的结果汇总出来。也就是说从所有排名前K=1的商品候选者中找出真正的销量前K=1的商品。
这时候完全可以用单一机器解决了。因为实际上你汇总的就是这1000台机器的结果规模足够小。
<img src="https://static001.geekbang.org/resource/image/ca/ab/cab28c9e3ba9031072a4e6949328bbab.jpg" alt="">
看到这里,你已经体会到处理超大规模数据的系统是很复杂的。
当你辛辛苦苦设计了应对1亿用户的数据处理系统时可能你就要面临另一个维度的规模化scaling。那就是应用场景数量从1个变成1000个。每一次都为不同的应用场景单独设计分布式集群招募新的工程师维护变得不再“可持续发展”。
这时,你需要一个数据处理的**框架**。
## 大规模数据处理框架的功能要求
在第二讲“MapReduce后谁主沉浮怎样设计现代大规模数据处理技术”中我们对于数据处理**框架**已经有了基本的方案。
今天这个实际的例子其实为我们的设计增加了新的挑战。
很多人面对问题,第一个想法是找有没有开源技术可以用一下。
但我经常说服别人不要先去看什么开源技术可以用而是从自己面对的问题出发独立思考忘掉MapReduce忘掉Apache Spark忘掉Apache Beam。
如果这个世界一无所有,你会设计怎样的大规模数据处理框架?你要经常做一些思维实验,试试带领一下技术的发展,而不是永远跟随别人的技术方向。
在我看来,两个最基本的需求是:
<li>
高度抽象的数据处理流程描述语言。作为小白用户,我肯定再也不想一一配置分布式系统的每台机器了。作为框架使用者,我希望框架是非常简单的,能够用几行代码把业务逻辑描述清楚。
</li>
<li>
根据描述的数据处理流程,自动化的任务分配优化。这个框架背后的引擎需要足够智能,简单地说,要把那些本来手动配置的系统,进行自动任务分配。
</li>
那么理想状况是什么?对于上面的应用场景,我作为用户只想写两行代码。
第一行代码:
```
sales_count = sale_records.Count()
```
这样简单的描述,在我们框架设计层面,就要能自动构建成上文描述的“销量统计计算集群”。
第二行代码
```
top_k_sales = sales_count.TopK(k)
```
这行代码需要自动构建成上文描述的“找出销量前K集群”。
看到这里,你能发现这并不复杂。我们到这里就已经基本上把现代大规模数据处理架构的顶层构造掌握了。而背后的具体实现,我会在后面的专栏章节中为你一一揭晓。
## 小结
这一讲中,我们粗浅地分析了一个电商排行榜的数据处理例子。
从GB数据到TB数据我们从小规模算法升级到了分布式处理的设计方案从单一TB数据场景到1000个应用场景我们探索了大规模数据处理框架的设计。
这些都是为了帮助你更好地理解后面所要讲的所有知识。比如,为什么传统算法不再奏效?为什么要去借助抽象的数据处理描述语言?希望在后面的学习过程中,你能一直带着这些问题出发。
## 思考题
在你的工作中,有没有随着数据规模变大,系统出问题的情况,你又是怎么解决的?
欢迎你把自己的想法写在留言区,与我和其他同学一起讨论。
如果你觉得有所收获,也欢迎把文章分享给你的朋友。