This commit is contained in:
louzefeng
2024-07-11 05:50:32 +00:00
parent bf99793fd0
commit d3828a7aee
6071 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,110 @@
<audio id="audio" title="11 | Hive是如何让MapReduce实现SQL操作的" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/4d/e1/4d365cd6e9f442c94468fcb1c44a53e1.mp3"></audio>
前面我们讲过MapReduce的出现大大简化了大数据编程的难度使得大数据计算不再是高不可攀的技术圣殿普通工程师也能使用MapReduce开发大数据程序。但是对于经常需要进行大数据计算的人比如从事研究商业智能BI的数据分析师来说他们通常使用SQL进行大数据分析和统计MapReduce编程还是有一定的门槛。而且如果每次统计和分析都开发相应的MapReduce程序成本也确实太高了。那么有没有更简单的办法可以直接将SQL运行在大数据平台上呢
在给出答案前我们先看看如何用MapReduce实现SQL数据分析。
## MapReduce实现SQL的原理
坚持学习到这里的同学一定还记得我在专栏第7期留了一道思考题对于常见的一条SQL分析语句MapReduce如何编程实现
```
SELECT pageid, age, count(1) FROM pv_users GROUP BY pageid, age;
```
错过这期内容的同学可以先返回[第7期文章](http://time.geekbang.org/column/article/67968)思考一下这个问题思考之余也可以看看其他同学给出的方案我看留言很多同学的思路都是正确的我们来详细看看MapReduce实现SQL的原理。
这是一条非常常见的SQL统计分析语句统计不同年龄的用户访问不同网页的兴趣偏好对于产品运营和设计很有价值。具体数据输入和执行结果请看下面的图示。
<img src="https://static001.geekbang.org/resource/image/0a/37/0ade10e49216575962e071d6fe9a7137.jpg" alt="">
左边是要分析的数据表右边是分析结果。实际上把左边表相同的行进行累计求和就得到右边的表了看起来跟WordCount的计算很相似。确实也是这样我们看下这条SQL语句的MapReduce的计算过程按照MapReduce编程模型map和reduce函数的输入输出以及函数处理过程分别是什么。
首先看下map函数的输入Key和Value我们主要看Value。Value就是左边表中每一行的数据比如&lt;1, 25&gt;这样。map函数的输出就是以输入的Value作为KeyValue统一设为1比如&lt;&lt;1, 25&gt;, 1&gt;这样。
map函数的输出经过shuffle以后相同的Key及其对应的Value被放在一起组成一个&lt;Key, Value集合&gt;作为输入交给reduce函数处理。比如&lt;&lt;2, 25&gt;, 1&gt;被map函数输出两次那么到了reduce这里就变成输入&lt;&lt;2, 25&gt;, &lt;1, 1&gt;&gt;这里的Key是&lt;2, 25&gt;Value集合是&lt;1, 1&gt;
在reduce函数内部Value集合里所有的数字被相加然后输出。所以reduce的输出就是&lt;&lt;2, 25&gt;, 2&gt;
讲起来有点拗口,我把这个过程画成了一张图,看起来就清楚多了。
<img src="https://static001.geekbang.org/resource/image/bc/57/bc088edf00478c835003272696c44c57.jpg" alt="">
这样一条很有实用价值的SQL就被很简单的MapReduce计算过程处理好了。
在数据仓库中SQL是最常用的分析工具既然一条SQL可以通过MapReduce程序实现那么有没有工具能够自动将SQL生成MapReduce代码呢这样数据分析师只要输入SQL就可以自动生成MapReduce可执行的代码然后提交Hadoop执行也就完美解决了我们最开始提出的问题。问题的答案也就是这个神奇的工具就是Hadoop大数据仓库Hive。
## Hive的架构
Hive能够直接处理我们输入的SQL语句Hive的SQL语法和数据库标准SQL略有不同调用MapReduce计算框架完成数据分析操作。下面是它的架构图我们结合架构图来看看Hive是如何实现将SQL生成MapReduce可执行代码的。
<img src="https://static001.geekbang.org/resource/image/26/ea/26287cac9a9cfa3874a680fdbcd795ea.jpg" alt="">
我们通过Hive的ClientHive的命令行工具JDBC等向Hive提交SQL命令。如果是创建数据表的DDL数据定义语言Hive就会通过执行引擎Driver将数据表的信息记录在Metastore元数据组件中这个组件通常用一个关系数据库实现记录表名、字段名、字段类型、关联HDFS文件路径等这些数据库的Meta信息元信息
如果我们提交的是查询分析数据的DQL数据查询语句Driver就会将该语句提交给自己的编译器Compiler进行语法分析、语法解析、语法优化等一系列操作最后生成一个MapReduce执行计划。然后根据执行计划生成一个MapReduce的作业提交给Hadoop MapReduce计算框架处理。
对于一个较简单的SQL命令比如
```
SELECT * FROM status_updates WHERE status LIKE michael jackson;
```
它对应的Hive执行计划如下图。
<img src="https://static001.geekbang.org/resource/image/cb/a6/cb1236ad035ca01cffbb9df47fa88fa6.jpg" alt="">
Hive内部预置了很多函数Hive的执行计划就是根据SQL语句生成这些函数的DAG有向无环图然后封装进MapReduce的map和reduce函数中。这个例子中map函数调用了三个Hive内置函数TableScanOperator、FilterOperator、FileOutputOperator就完成了map计算而且无需reduce函数。
## Hive如何实现join操作
除了上面这些简单的聚合group by、过滤where操作Hive还能执行连接join on操作。文章开头的例子中pv_users表的数据在实际中是无法直接得到的因为pageid数据来自用户访问日志每个用户进行一次页面浏览就会生成一条访问记录保存在page_view表中。而age年龄信息则记录在用户表user中。
<img src="https://static001.geekbang.org/resource/image/82/2d/8254710229b1d749d08f7a0bb799ac2d.jpg" alt="">
这两张表都有一个相同的字段userid根据这个字段可以将两张表连接起来生成前面例子的pv_users表SQL命令是
```
SELECT pv.pageid, u.age FROM page_view pv JOIN user u ON (pv.userid = u.userid);
```
同样这个SQL命令也可以转化为MapReduce计算连接的过程如下图所示。
<img src="https://static001.geekbang.org/resource/image/25/2a/25d62b355c976beb5b26af865ac2b92a.jpg" alt="">
从图上看join的MapReduce计算过程和前面的group by稍有不同因为join涉及两张表来自两个文件所以需要在map输出的时候进行标记比如来自第一张表的输出Value就记录为&lt;1, X&gt;这里的1表示数据来自第一张表。这样经过shuffle以后相同的Key被输入到同一个reduce函数就可以根据表的标记对Value数据求笛卡尔积用第一张表的每条记录和第二张表的每条记录连接输出就是join的结果。
所以我们如果打开Hive的源代码看join相关的代码会看到一个两层for循环对来自两张表的记录进行连接操作。
## 小结
在实践中工程师其实并不需要经常编写MapReduce程序因为网站最主要的大数据处理就是SQL分析也因此Hive在大数据应用中的作用非常重要。
后面随着Hive的普及我们对于在Hadoop上执行SQL的需求越加强烈对大数据SQL的应用场景也多样化起来于是又开发了各种大数据SQL引擎。
Cloudera开发了Impala这是一种运行在HDFS上的MPP架构的SQL引擎。和MapReduce启动Map和Reduce两种执行进程将计算过程分成两个阶段进行计算不同Impala在所有DataNode服务器上部署相同的Impalad进程多个Impalad进程相互协作共同完成SQL计算。在一些统计场景中Impala可以做到毫秒级的计算速度。
后来Spark出道以后也迅速推出了自己的SQL引擎Shark也就是后来的Spark SQL将SQL语句解析成Spark的执行计划在Spark上执行。由于Spark比MapReduce快很多Spark SQL也相应比Hive快很多并且随着Spark的普及Spark SQL也逐渐被人们接受。后来Hive推出了Hive on Spark将Hive的执行计划转换成Spark的计算模型当然这是后话了。
此外我们还希望在NoSQL的数据库上执行SQL毕竟SQL发展了几十年积累了庞大的用户群体很多人习惯了用SQL解决问题。于是Saleforce推出了Phoenix一个执行在HBase上的SQL引擎。
这些SQL引擎基本上都只支持类SQL语法并不能像数据库那样支持标准SQL特别是数据仓库领域几乎必然会用到嵌套查询SQL也就是在where条件里面嵌套select子查询但是几乎所有的大数据SQL引擎都不支持。然而习惯于传统数据库的使用者希望大数据也能支持标准SQL我当时在Intel的大数据团队就决定开发一款可以支持标准SQL的大数据引擎我作为最主要的开发者参与其中。江湖传说开发数据库、编译器、操作系统是程序员的三大梦想。我将在专栏里专门讲述如何设计、开发一个大数据SQL引擎一起感受开发数据库是怎样一种体验。
最后我们还是回到Hive。Hive本身的技术架构其实并没有什么创新数据库相关的技术和架构已经非常成熟只要将这些技术架构应用到MapReduce上就得到了Hadoop大数据仓库Hive。**但是想到将两种技术嫁接到一起,却是极具创新性的**通过嫁接产生出的Hive可以极大降低大数据的应用门槛也使Hadoop大数据技术得到大规模普及。
在我们工作中也可以借鉴一下这种将两种技术嫁接到一起产生极大应用创新性的手段说不定下一个做出类似Hive这种具有巨大应用价值技术产品的就是你。
## 思考题
在软件编程的上古时代,各种编程语言有各种编译器,将软件工程师编写的程序编译成可执行代码。软件工程师必须要在另外一个文本编辑器里将代码编写好,然后保存,再调用编译器对这个程序源代码文件进行编译。
后来有人把编译器集成到文本编辑器里面工程师可以在文本编辑器里面编写代码、编译调试代码工作效率得到极大提高这就是软件开发的集成开发环境IDE。
类似这样将两个(或更多个)软件集成(嫁接)到一起,产生巨大创新应用价值的软件产品还有哪些?
欢迎你写下自己的思考或疑问,与我和其他同学一起讨论。

View File

@@ -0,0 +1,113 @@
<audio id="audio" title="12 | 我们并没有觉得MapReduce速度慢直到Spark出现" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/4c/20/4c8e69e780760457ea3c45553b9fe020.mp3"></audio>
Hadoop MapReduce虽然已经可以满足大数据的应用场景但是其执行速度和编程复杂度并不让人们满意。于是UC Berkeley的AMP Lab推出的Spark应运而生Spark拥有更快的执行速度和更友好的编程接口在推出后短短两年就迅速抢占MapReduce的市场份额成为主流的大数据计算框架。
读到这里请你先停一下,请给这段看似“没毛病”的引子找找问题。
不知道你意识到没有我在这段开头说的“Hadoop MapReduce虽然已经可以满足大数据的应用场景但是其执行速度和编程复杂度并不让人们满意”这句话其实是错误的。这样说好像可以让你更加清晰地看到事物发展的因果关系同时也可以暗示别人自己有洞察事物发展规律的能力。然而这种靠事后分析的因果规律常常是错误的**往往把结果当作了原因**。
事实上在Spark出现之前我们并没有对MapReduce的执行速度不满我们觉得大数据嘛、分布式计算嘛这样的速度也还可以啦。至于编程复杂度也是一样一方面Hive、Mahout这些工具将常用的MapReduce编程封装起来了另一方面MapReduce已经将分布式编程极大地简化了当时人们并没有太多不满。
真实的情况是人们在Spark出现之后才开始对MapReduce不满。原来大数据计算速度可以快这么多编程也可以更简单。而且Spark支持Yarn和HDFS公司迁移到Spark上的成本很小于是很快越来越多的公司用Spark代替MapReduce。也就是说因为有了Spark才对MapReduce不满而不是对MapReduce不满所以诞生了Spark。真实的因果关系是相反的。
这里有一条关于问题的定律分享给你:**我们常常意识不到问题的存在,直到有人解决了这些问题。**
当你去询问人们有什么问题需要解决,有什么需求需要被满足的时候,他们往往自己也不知道自己想要什么,常常言不由衷。但是如果你真正解决了他们的问题,他们就会恍然大悟:啊,这才是我真正想要的,以前那些统统都是“垃圾”,我早就想要这样的东西(功能)了。
所以顶尖的产品大师(问题解决专家),并不会拿着个小本本四处去做需求调研,问人们想要什么。而是在旁边默默观察人们是如何使用产品(解决问题)的,然后思考更好的产品体验(解决问题的办法)是什么。最后当他拿出新的产品设计(解决方案)的时候,人们就会视他为知己:你最懂我的需求(我最懂你的设计)。
乔布斯是这样的大师Spark的作者马铁也是这样的专家。
说了那么多我们回到Spark。Spark和MapReduce相比有更快的执行速度。下图是Spark和MapReduce进行逻辑回归机器学习的性能比较Spark比MapReduce快100多倍。
<img src="https://static001.geekbang.org/resource/image/8d/82/8ddcf04a70141da96e83cb07b8969c82.png" alt="">
除了速度更快Spark和MapReduce相比还有更简单易用的编程模型。使用Scala语言在Spark上编写WordCount程序主要代码只需要三行。
```
val textFile = sc.textFile(&quot;hdfs://...&quot;)
val counts = textFile.flatMap(line =&gt; line.split(&quot; &quot;))
.map(word =&gt; (word, 1))
.reduceByKey(_ + _)
counts.saveAsTextFile(&quot;hdfs://...&quot;)
```
不熟悉Scala语言没关系我来解释一下上面的代码。
第1行代码根据HDFS路径生成一个输入数据RDD。
第2行代码在输入数据RDD上执行3个操作得到一个新的RDD。
<li>
将输入数据的每一行文本用空格拆分成单词。
</li>
<li>
将每个单词进行转换,`word =&gt; (word, 1)`,生成&lt;Key, Value&gt;的结构。
</li>
<li>
相同的Key进行统计统计方式是对Value求和`(_ + _)`
</li>
第3行代码将这个RDD保存到HDFS。
RDD是Spark的核心概念是弹性数据集Resilient Distributed Datasets的缩写。RDD既是Spark面向开发者的编程模型又是Spark自身架构的核心元素。
我们先来看看作为Spark编程模型的RDD。我们知道大数据计算就是在大规模的数据集上进行一系列的数据计算处理。MapReduce针对输入数据将计算过程分为两个阶段一个Map阶段一个Reduce阶段可以理解成是**面向过程的大数据计算**。我们在用MapReduce编程的时候思考的是如何将计算逻辑用Map和Reduce两个阶段实现map和reduce函数的输入和输出是什么这也是我们在学习MapReduce编程的时候一再强调的。
而Spark则直接针对数据进行编程将大规模数据集合抽象成一个RDD对象然后在这个RDD上进行各种计算处理得到一个新的RDD继续计算处理直到得到最后的结果数据。所以Spark可以理解成是**面向对象的大数据计算**。我们在进行Spark编程的时候思考的是一个RDD对象需要经过什么样的操作转换成另一个RDD对象思考的重心和落脚点都在RDD上。
所以在上面WordCount的代码示例里第2行代码实际上进行了3次RDD转换每次转换都得到一个新的RDD因为新的RDD可以继续调用RDD的转换函数所以连续写成一行代码。事实上可以分成3行。
```
val rdd1 = textFile.flatMap(line =&gt; line.split(&quot; &quot;))
val rdd2 = rdd1.map(word =&gt; (word, 1))
val rdd3 = rdd2.reduceByKey(_ + _)
```
RDD上定义的函数分两种一种是转换transformation函数这种函数的返回值还是RDD另一种是执行action函数这种函数不再返回RDD。
RDD定义了很多转换操作函数比如有计算**map**(func)、过滤**filter**(func)、合并数据集**union**(otherDataset)、根据Key聚合**reduceByKey**(func, [numPartitions])、连接数据集**join**(otherDataset, [numPartitions])、分组**groupByKey**([numPartitions])等十几个函数。
我们再来看看作为Spark架构核心元素的RDD。跟MapReduce一样Spark也是对大数据进行分片计算Spark分布式计算的数据分片、任务调度都是以RDD为单位展开的每个RDD分片都会分配到一个执行进程去处理。
RDD上的转换操作又分成两种一种转换操作产生的RDD不会出现新的分片比如map、filter等也就是说一个RDD数据分片经过map或者filter转换操作后结果还在当前分片。就像你用map函数对每个数据加1得到的还是这样一组数据只是值不同。实际上Spark并不是按照代码写的操作顺序去生成RDD比如`rdd2 = rdd1.map(func)`这样的代码并不会在物理上生成一个新的RDD。物理上Spark只有在产生新的RDD分片时候才会真的生成一个RDDSpark的这种特性也被称作**惰性计算**。
另一种转换操作产生的RDD则会产生新的分片比如`reduceByKey`来自不同分片的相同Key必须聚合在一起进行操作这样就会产生新的RDD分片。实际执行过程中是否会产生新的RDD分片并不是根据转换函数名就能判断出来的具体我们下一期再讨论。
总之你需要记住Spark应用程序代码中的RDD和Spark执行过程中生成的物理RDD不是一一对应的RDD在Spark里面是一个非常灵活的概念同时又非常重要需要认真理解。
当然Spark也有自己的生态体系以Spark为基础有支持SQL语句的Spark SQL有支持流计算的Spark Streaming有支持机器学习的MLlib还有支持图计算的GraphX。利用这些产品Spark技术栈支撑起大数据分析、大数据机器学习等各种大数据应用场景。
<img src="https://static001.geekbang.org/resource/image/38/0f/3894be10797c657af3a54bc278ab780f.png" alt="">
我前面提到,顶尖的产品设计大师和问题解决专家,不会去询问人们想要什么,而是分析和观察人们的做事方式,从而思考到更好的产品设计和问题解决方案。
但是这种技巧需要深邃的观察力和洞察力,如果没有深度的思考,做出的东西就会沦为异想天开和自以为是。要知道大众提出的需求虽然也无法触及问题的核心,但是好歹是有共识的,大家都能接受,按这种需求做出的东西虽然平庸,但是不至于令人厌恶。
而缺乏洞见的自以为是则会违反常识,让其他人本能产生排斥感,进而产生对立情绪。这种情绪之下,设计没有了进一步改进的基础,最后往往成为悲剧。这两年在所谓互联网思维的鼓吹下,一些缺乏专业技能的人,天马行空创造需求,受到质疑后公开批评用户,也是让人倍感惊诧。
我们在自己的工作中,作为一个不是顶尖大师的产品经理或工程师,如何做到既不自以为是,又能逐渐摆脱平庸,进而慢慢向大师的方向靠近呢?
有个技巧可以在工作中慢慢练习:**不要直接提出你的问题和方案**,不要直接说“你的需求是什么?”“我这里有个方案你看一下”。
直向曲中求,对于复杂的问题,越是直截了当越是得不到答案。迂回曲折地提出问题,一起思考问题背后的规律,才能逐渐发现问题的本质。通过这种方式,既能达成共识,不会有违常识,又可能产生洞见,使产品和方案呈现闪光点。
<li>
你觉得前一个版本最有意思(最有价值)的功能是什么?
</li>
<li>
你觉得我们这个版本应该优先关注哪个方面?
</li>
<li>
你觉得为什么有些用户在下单以后没有支付?
</li>
## 思考题
你在工作、生活中通过提问发现问题背后的本质、现象背后的规律的例子有哪些?或者你观察到同事、朋友这样的例子有哪些?
欢迎你写下自己的思考或疑问,与我和其他同学一起讨论。

View File

@@ -0,0 +1,101 @@
<audio id="audio" title="13 | 同样的本质为何Spark可以更高效" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/0a/6d/0aa70ae339115159fedc54c27690566d.mp3"></audio>
上一期我们讨论了Spark的编程模型这期我们聊聊Spark的架构原理。和MapReduce一样**Spark也遵循移动计算比移动数据更划算这一大数据计算基本原则**。但是和MapReduce僵化的Map与Reduce分阶段计算相比Spark的计算框架更加富有弹性和灵活性进而有更好的运行性能。
## Spark的计算阶段
我们可以对比来看。首先和MapReduce一个应用一次只运行一个map和一个reduce不同Spark可以根据应用的复杂程度分割成更多的计算阶段stage这些计算阶段组成一个有向无环图DAGSpark任务调度器可以根据DAG的依赖关系执行计算阶段。
还记得在上一期我举了一个比较逻辑回归机器学习性能的例子发现Spark比MapReduce快100多倍。因为某些机器学习算法可能需要进行大量的迭代计算产生数万个计算阶段这些计算阶段在一个应用中处理完成而不是像MapReduce那样需要启动数万个应用因此极大地提高了运行效率。
所谓DAG也就是有向无环图就是说不同阶段的依赖关系是有向的计算过程只能沿着依赖关系方向执行被依赖的阶段执行完成之前依赖的阶段不能开始执行同时这个依赖关系不能有环形依赖否则就成为死循环了。下面这张图描述了一个典型的Spark运行DAG的不同阶段。
<img src="https://static001.geekbang.org/resource/image/c8/db/c8cf515c664b478e51058565e0d4a8db.png" alt="">
从图上看整个应用被切分成3个阶段阶段3需要依赖阶段1和阶段2阶段1和阶段2互不依赖。Spark在执行调度的时候先执行阶段1和阶段2完成以后再执行阶段3。如果有更多的阶段Spark的策略也是一样的。只要根据程序初始化好DAG就建立了依赖关系然后根据依赖关系顺序执行各个计算阶段Spark大数据应用的计算就完成了。
上图这个DAG对应的Spark程序伪代码如下。
```
rddB = rddA.groupBy(key)
rddD = rddC.map(func)
rddF = rddD.union(rddE)
rddG = rddB.join(rddF)
```
所以你可以看到Spark作业调度执行的核心是DAG有了DAG整个应用就被切分成哪些阶段每个阶段的依赖关系也就清楚了。之后再根据每个阶段要处理的数据量生成相应的任务集合TaskSet每个任务都分配一个任务进程去处理Spark就实现了大数据的分布式计算。
具体来看的话负责Spark应用DAG生成和管理的组件是DAGSchedulerDAGScheduler根据程序代码生成DAG然后将程序分发到分布式计算集群按计算阶段的先后关系调度执行。
那么Spark划分计算阶段的依据是什么呢显然并不是RDD上的每个转换函数都会生成一个计算阶段比如上面的例子有4个转换函数但是只有3个阶段。
你可以再观察一下上面的DAG图关于计算阶段的划分从图上就能看出规律当RDD之间的转换连接线呈现多对多交叉连接的时候就会产生新的阶段。一个RDD代表一个数据集图中每个RDD里面都包含多个小块每个小块代表RDD的一个分片。
一个数据集中的多个数据分片需要进行分区传输写入到另一个数据集的不同分片中这种数据分区交叉传输的操作我们在MapReduce的运行过程中也看到过。
<img src="https://static001.geekbang.org/resource/image/d6/c7/d64daa9a621c1d423d4a1c13054396c7.png" alt="">
是的这就是shuffle过程Spark也需要通过shuffle将数据进行重新组合相同Key的数据放在一起进行聚合、关联等操作因而每次shuffle都产生新的计算阶段。这也是为什么计算阶段会有依赖关系它需要的数据来源于前面一个或多个计算阶段产生的数据必须等待前面的阶段执行完毕才能进行shuffle并得到数据。
这里需要你特别注意的是,**计算阶段划分的依据是shuffle不是转换函数的类型**有的函数有时候有shuffle有时候没有。比如上图例子中RDD B和RDD F进行join得到RDD G这里的RDD F需要进行shuffleRDD B就不需要。
<img src="https://static001.geekbang.org/resource/image/4e/8b/4e5c79d1ad7152bc8ab8bc350cf6778b.png" alt="">
因为RDD B在前面一个阶段阶段1的shuffle过程中已经进行了数据分区。分区数目和分区Key不变就不需要再进行shuffle。
<img src="https://static001.geekbang.org/resource/image/46/25/4650b622d9c6ed5f65670482cc8ca325.png" alt="">
这种不需要进行shuffle的依赖在Spark里被称作窄依赖相反的需要进行shuffle的依赖被称作宽依赖。跟MapReduce一样shuffle也是Spark最重要的一个环节只有通过shuffle相关数据才能互相计算构建起复杂的应用逻辑。
在你熟悉Spark里的shuffle机制后我们回到今天文章的标题同样都要经过shuffle为什么Spark可以更高效呢
其实从本质上看Spark可以算作是一种MapReduce计算模型的不同实现。Hadoop MapReduce简单粗暴地根据shuffle将大数据计算分成Map和Reduce两个阶段然后就算完事了。而Spark更细腻一点将前一个的Reduce和后一个的Map连接起来当作一个阶段持续计算形成一个更加优雅、高效的计算模型虽然其本质依然是Map和Reduce。但是这种多个计算阶段依赖执行的方案可以有效减少对HDFS的访问减少作业的调度执行次数因此执行速度也更快。
并且和Hadoop MapReduce主要使用磁盘存储shuffle过程中的数据不同Spark优先使用内存进行数据存储包括RDD数据。除非是内存不够用了否则是尽可能使用内存 这也是Spark性能比Hadoop高的另一个原因。
## Spark的作业管理
我在专栏上一期提到Spark里面的RDD函数有两种一种是转换函数调用以后得到的还是一个RDDRDD的计算逻辑主要通过转换函数完成。
另一种是action函数调用以后不再返回RDD。比如**count**()函数返回RDD中数据的元素个数**saveAsTextFile**(path)将RDD数据存储到path路径下。Spark的DAGScheduler在遇到shuffle的时候会生成一个计算阶段在遇到action函数的时候会生成一个作业job
RDD里面的每个数据分片Spark都会创建一个计算任务去处理所以一个计算阶段会包含很多个计算任务task
关于作业、计算阶段、任务的依赖和时间先后关系你可以通过下图看到。
<img src="https://static001.geekbang.org/resource/image/2b/d0/2bf9e431bbd543165588a111513567d0.png" alt="">
图中横轴方向是时间,纵轴方向是任务。两条粗黑线之间是一个作业,两条细线之间是一个计算阶段。一个作业至少包含一个计算阶段。水平方向红色的线是任务,每个阶段由很多个任务组成,这些任务组成一个任务集合。
DAGScheduler根据代码生成DAG图以后Spark的任务调度就以任务为单位进行分配将任务分配到分布式集群的不同机器上执行。
## Spark的执行过程
Spark支持Standalone、Yarn、Mesos、Kubernetes等多种部署方案几种部署方案原理也都一样只是不同组件角色命名不同但是核心功能和运行流程都差不多。
<img src="https://static001.geekbang.org/resource/image/16/db/164e9460133d7744d0315a876e7b6fdb.png" alt="">
上面这张图是Spark的运行流程我们一步一步来看。
首先Spark应用程序启动在自己的JVM进程里即Driver进程启动后调用SparkContext初始化执行配置和输入数据。SparkContext启动DAGScheduler构造执行的DAG图切分成最小的执行单位也就是计算任务。
然后Driver向Cluster Manager请求计算资源用于DAG的分布式计算。Cluster Manager收到请求以后将Driver的主机地址等信息通知给集群的所有计算节点Worker。
Worker收到信息以后根据Driver的主机地址跟Driver通信并注册然后根据自己的空闲资源向Driver通报自己可以领用的任务数。Driver根据DAG图开始向注册的Worker分配任务。
Worker收到任务后启动Executor进程开始执行任务。Executor先检查自己是否有Driver的执行代码如果没有从Driver下载执行代码通过Java反射加载后开始执行。
## 小结
总结来说Spark有三个主要特性**RDD的编程模型更简单DAG切分的多阶段计算过程更快速使用内存存储中间计算结果更高效**。这三个特性使得Spark相对Hadoop MapReduce可以有更快的执行速度以及更简单的编程实现。
Spark的出现和流行其实也有某种必然性是天时、地利、人和的共同作用。首先Spark在2012年左右开始流行那时内存的容量提升和成本降低已经比MapReduce出现的十年前强了一个数量级Spark优先使用内存的条件已经成熟其次使用大数据进行机器学习的需求越来越强烈不再是早先年那种数据分析的简单计算需求。而机器学习的算法大多需要很多轮迭代Spark的stage划分相比Map和Reduce的简单划分有更加友好的编程体验和更高效的执行效率。于是Spark成为大数据计算新的王者也就不足为奇了。
## 思考题
Spark的流行离不开它成功的开源运作开源并不是把源代码丢到GitHub上公开就万事大吉了一个成功的开源项目需要吸引大量高质量开发者参与其中还需要很多用户使用才能形成影响力。
Spark开发团队为Spark开源运作进行了大量的商业和非商业活动你了解这些活动有哪些吗假如你所在的公司想要开源自己的软件用于提升自己公司的技术竞争力和影响力如果是你负责人你应该如何运作
欢迎你写下自己的思考或疑问,与我和其他同学一起讨论。

View File

@@ -0,0 +1,71 @@
<audio id="audio" title="14 | BigTable的开源实现HBase" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/23/7f/23803069901bf04b31d5d9731bf9407f.mp3"></audio>
我们知道Google发表GFS、MapReduce、BigTable三篇论文号称“三驾马车”开启了大数据的时代。那和这“三驾马车”对应的有哪些开源产品呢我们前面已经讲过了GFS对应的Hadoop分布式文件系统HDFS以及MapReduce对应的Hadoop分布式计算框架MapReduce今天我们就来领略一下BigTable对应的NoSQL系统HBase看看它是如何大规模处理海量数据的。
在计算机数据存储领域一直是关系数据库RDBMS的天下以至于在传统企业的应用领域许多应用系统设计都是面向数据库设计也就是**先设计数据库然后设计程序**,从而导致**关系模型绑架对象模型**,并由此引申出旷日持久的业务对象贫血模型与充血模型之争。
业界为了解决关系数据库的不足提出了诸多方案比较有名的是对象数据库但是这些数据库的出现似乎只是进一步证明关系数据库的优越而已。直到人们遇到了关系数据库难以克服的缺陷——糟糕的海量数据处理能力及僵硬的设计约束局面才有所改善。从Google的BigTable开始一系列的可以进行海量数据存储与访问的数据库被设计出来更进一步说NoSQL这一概念被提了出来。
NoSQL主要指非关系的、分布式的、支持海量数据存储的数据库设计模式。也有许多专家将 NoSQL解读为Not Only SQL表示NoSQL只是关系数据库的补充而不是替代方案。其中HBase是这一类NoSQL系统的杰出代表。
HBase之所以能够具有海量数据处理能力其根本在于和传统关系型数据库设计的不同思路。传统关系型数据库对存储在其上的数据有很多约束学习关系数据库都要学习数据库设计范式事实上是在数据存储中包含了一部分业务逻辑。而NoSQL数据库则简单暴力地认为数据库就是存储数据的业务逻辑应该由应用程序去处理有时候不得不说简单暴力也是一种美。
## HBase可伸缩架构
我们先来看看HBase的架构设计。HBase为可伸缩海量数据储存而设计实现面向在线业务的实时数据访问延迟。HBase的伸缩性主要依赖其可分裂的HRegion及可伸缩的分布式文件系统HDFS实现。
<img src="https://static001.geekbang.org/resource/image/9f/f7/9f4220274ef0a6bcf253e8d012a6d4f7.png" alt="">
HRegion是HBase负责数据存储的主要进程应用程序对数据的读写操作都是通过和HRegion通信完成。上面是HBase架构图我们可以看到在HBase中数据以HRegion为单位进行管理也就是说应用程序如果想要访问一个数据必须先找到HRegion然后将数据读写操作提交给HRegion由 HRegion完成存储层面的数据操作。
HRegionServer是物理服务器每个HRegionServer上可以启动多个HRegion实例。当一个 HRegion中写入的数据太多达到配置的阈值时一个HRegion会分裂成两个HRegion并将HRegion在整个集群中进行迁移以使HRegionServer的负载均衡。
每个HRegion中存储一段Key值区间[key1, key2)的数据所有HRegion的信息包括存储的Key值区间、所在HRegionServer地址、访问端口号等都记录在HMaster服务器上。为了保证HMaster的高可用HBase会启动多个HMaster并通过ZooKeeper选举出一个主服务器。
下面是一张调用时序图应用程序通过ZooKeeper获得主HMaster的地址输入Key值获得这个Key所在的HRegionServer地址然后请求HRegionServer上的HRegion获得所需要的数据。
<img src="https://static001.geekbang.org/resource/image/9f/ab/9fd982205b06ecd43053202da2ae08ab.png" alt="">
数据写入过程也是一样需要先得到HRegion才能继续操作。HRegion会把数据存储在若干个HFile格式的文件中这些文件使用HDFS分布式文件系统存储在整个集群内分布并高可用。当一个HRegion中数据量太多时这个HRegion连同HFile会分裂成两个HRegion并根据集群中服务器负载进行迁移。如果集群中有新加入的服务器也就是说有了新的HRegionServer由于其负载较低也会把HRegion迁移过去并记录到HMaster从而实现HBase的线性伸缩。
先小结一下上面的内容HBase的核心设计目标是解决海量数据的分布式存储和Memcached这类分布式缓存的路由算法不同HBase的做法是按Key的区域进行分片这个分片也就是HRegion。应用程序通过HMaster查找分片得到HRegion所在的服务器HRegionServer然后和该服务器通信就得到了需要访问的数据。
## HBase可扩展数据模型
传统的关系数据库为了保证关系运算通过SQL语句的正确性在设计数据库表结构的时候需要指定表的schema也就是字段名称、数据类型等并要遵循特定的设计范式。这些规范带来了一个问题就是僵硬的数据结构难以面对需求变更带来的挑战有些应用系统设计者通过预先设计一些冗余字段来应对但显然这种设计也很糟糕。
那有没有办法能够做到可扩展的数据结构设计呢不用修改表结构就可以新增字段呢当然有的许多NoSQL数据库使用的列族ColumnFamily设计就是其中一个解决方案。列族最早在Google的BigTable中使用这是一种面向列族的稀疏矩阵存储格式如下图所示。
<img src="https://static001.geekbang.org/resource/image/74/6f/74b3aac940abae8a571cc94f2226656f.png" alt="">
这是一个学生的基本信息表,表中不同学生的联系方式各不相同,选修的课程也不同,而且将来还会有更多联系方式和课程加入到这张表里,如果按照传统的关系数据库设计,无论提前预设多少冗余字段都会捉襟见肘、疲于应付。
而使用支持列族结构的NoSQL数据库在创建表的时候只需要指定列族的名字无需指定字段Column。那什么时候指定字段呢可以在数据写入时再指定。通过这种方式数据表可以包含数百万的字段这样就可以随意扩展应用程序的数据结构了。并且这种数据库在查询时也很方便可以通过指定任意字段名称和值进行查询。
HBase这种列族的数据结构设计实际上是把字段的名称和字段的值以Key-Value的方式一起存储在HBase中。实际写入的时候可以随意指定字段名称即使有几百万个字段也能轻松应对。
## HBase的高性能存储
还记得专栏第5期讲RAID时我留给你的思考题吗当时很多同学答得都很棒。传统的机械式磁盘的访问特性是**连续读写很快,随机读写很慢**。这是因为机械磁盘靠电机驱动访问磁盘上的数据,电机要将磁头落到数据所在的磁道上,这个过程需要较长的寻址时间。如果数据不连续存储,磁头就要不停地移动,浪费了大量的时间。
为了提高数据写入速度HBase使用了一种叫作**LSM树**的数据结构进行数据存储。LSM树的全名是Log Structed Merge Tree翻译过来就是Log结构合并树。数据写入的时候以Log方式连续写入然后异步对磁盘上的多个LSM树进行合并。
<img src="https://static001.geekbang.org/resource/image/5f/3b/5fbd17a9c0b9f1a10347a4473d00ad3b.jpg" alt="">
LSM树可以看作是一个N阶合并树。数据写操作包括插入、修改、删除都在内存中进行并且都会创建一个新记录修改会记录新的数据值而删除会记录一个删除标志。这些数据在内存中仍然还是一棵排序树当数据量超过设定的内存阈值后会将这棵排序树和磁盘上最新的排序树合并。当这棵排序树的数据量也超过设定阈值后会和磁盘上下一级的排序树合并。合并过程中会用最新更新的数据覆盖旧的数据或者记录为不同版本
在需要进行读操作时,总是从内存中的排序树开始搜索,如果没有找到,就从磁盘 上的排序树顺序查找。
在LSM树上进行一次数据更新不需要磁盘访问在内存即可完成。当数据访问以写操作为主而读操作则集中在最近写入的数据上时使用LSM树可以极大程度地减少磁盘的访问次数加快访问速度。
## 小结
最后总结一下我们今天讲的内容。HBase作为Google BigTable的开源实现完整地继承了BigTable的优良设计。架构上通过数据分片的设计配合HDFS实现了数据的分布式海量存储数据结构上通过列族的设计实现了数据表结构可以在运行期自定义存储上通过LSM树的方式使数据可以通过连续写磁盘的方式保存数据极大地提高了数据写入性能。
这些优良的设计结合Apache开源社区的高质量开发使得HBase在NoSQL众多竞争产品中保持领先优势逐步成为NoSQL领域最具影响力的产品。
## 思考题
HBase的列族数据结构虽然有灵活的优势但是也有缺点。请你思考一下列族结构的缺点有哪些如何在应用开发的时候克服这些缺点哪些场景最好还是使用MySQL这类关系数据库呢
欢迎你写下自己的思考或疑问,与我和其他同学一起讨论。如果你学完今天的内容有所收获的话,也欢迎你点击“请朋友读”,把今天的文章分享给你的朋友。

View File

@@ -0,0 +1,123 @@
<audio id="audio" title="15 | 流式计算的代表Storm、Flink、Spark Streaming" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/57/d8/57d4ada50c492dd7ccf8e372d0b784d8.mp3"></audio>
我前面介绍的大数据技术主要是处理、计算存储介质上的大规模数据这类计算也叫大数据批处理计算。顾名思义数据是以批为单位进行计算比如一天的访问日志、历史上所有的订单数据等。这些数据通常通过HDFS存储在磁盘上使用MapReduce或者Spark这样的批处理大数据计算框架进行计算一般完成一次计算需要花费几分钟到几小时的时间。
此外还有一种大数据技术针对实时产生的大规模数据进行即时计算处理我们比较熟悉的有摄像头采集的实时视频数据、淘宝实时产生的订单数据等。像上海这样的一线城市公共场所的摄像头规模在数百万级即使只有重要场所的视频数据需要即时处理可能也会涉及几十万个摄像头如果想实时发现视频中出现的通缉犯或者违章车辆就需要对这些摄像头产生的数据进行实时处理。实时处理最大的不同就是这类数据跟存储在HDFS上的数据不同是实时传输过来的或者形象地说是流过来的所以针对这类大数据的实时处理系统也叫大数据流计算系统。
目前业内比较知名的大数据流计算框架有Storm、Spark Streaming、Flink接下来我们逐一看看它们的架构原理与使用方法。
## Storm
其实大数据实时处理的需求早已有之,最早的时候,我们用消息队列实现大数据实时处理,如果处理起来比较复杂,那么就需要很多个消息队列,将实现不同业务逻辑的生产者和消费者串起来。这个处理过程类似下面图里的样子。
<img src="https://static001.geekbang.org/resource/image/19/31/199c65da1a9dfae48f42c32f6a82c831.png" alt="">
图中的消息队列负责完成数据的流转;处理逻辑既是消费者也是生产者,也就是既消费前面消息队列的数据,也为下个消息队列产生数据。这样的系统只能是根据不同需求开发出来,并且每次新的需求都需要重新开发类似的系统。因为不同应用的生产者、消费者的处理逻辑不同,所以处理流程也不同,因此这个系统也就无法复用。
之后我们很自然地就会想到能不能开发一个流处理计算系统我们只要定义好处理流程和每一个节点的处理逻辑代码部署到流处理系统后就能按照预定义的处理流程和处理逻辑执行呢Storm就是在这种背景下产生的它也算是一个比较早期的大数据流计算框架。上面的例子如果用Storm来实现过程就变得简单一些了。
<img src="https://static001.geekbang.org/resource/image/78/5b/780899b3fda0ea39acbdfb9545fbc55b.png" alt="">
有了Storm后开发者无需再关注数据的流转、消息的处理和消费只要编程开发好数据处理的逻辑bolt和数据源的逻辑spout以及它们之间的拓扑逻辑关系toplogy提交到Storm上运行就可以了。
在了解了Storm的运行机制后我们来看一下它的架构。Storm跟Hadoop一样也是主从架构。
<img src="https://static001.geekbang.org/resource/image/d3/8a/d33aa8765ad381824fd9818f93074a8a.png" alt="">
nimbus是集群的Master负责集群管理、任务分配等。supervisor是Slave是真正完成计算的地方每个supervisor启动多个worker进程每个worker上运行多个task而task就是spout或者bolt。supervisor和nimbus通过ZooKeeper完成任务分配、心跳检测等操作。
Hadoop、Storm的设计理念其实是一样的就是把和具体业务逻辑无关的东西抽离出来形成一个框架比如大数据的分片处理、数据的流转、任务的部署与执行等开发者只需要按照框架的约束开发业务逻辑代码提交给框架执行就可以了。
而这也正是所有框架的开发理念就是将业务逻辑和处理过程分离开来使开发者只需关注业务开发即可比如Java开发者都很熟悉的Tomcat、Spring等框架全部都是基于这种理念开发出来的。
## Spark Streaming
我们知道Spark是一个批处理大数据计算引擎主要针对大批量历史数据进行计算。前面我在讲Spark架构原理时介绍过Spark是一个快速计算的大数据引擎它将原始数据分片后装载到集群中计算对于数据量不是很大、过程不是很复杂的计算可以在秒级甚至毫秒级完成处理。
Spark Streaming巧妙地利用了Spark的**分片**和**快速计算**的特性将实时传输进来的数据按照时间进行分段把一段时间传输进来的数据合并在一起当作一批数据再去交给Spark去处理。下图这张图描述了Spark Streaming将数据分段、分批的过程。
<img src="https://static001.geekbang.org/resource/image/fb/c3/fb535e9dc1813dbacfa03c7cb65d17c3.png" alt="">
如果时间段分得足够小每一段的数据量就会比较小再加上Spark引擎的处理速度又足够快这样看起来好像数据是被实时处理的一样这就是Spark Streaming实时流计算的奥妙。
这里要注意的是在初始化Spark Streaming实例的时候需要指定分段的时间间隔。下面代码示例中间隔是1秒。
```
val ssc = new StreamingContext(conf, Seconds(1))
```
当然你也可以指定更小的时间间隔比如500ms这样处理的速度就会更快。时间间隔的设定通常要考虑业务场景比如你希望统计每分钟高速公路的车流量那么时间间隔可以设为1分钟。
Spark Streaming主要负责将流数据转换成小的批数据剩下的就可以交给Spark去做了。
## Flink
前面说Spark Streaming是将实时数据流按时间分段后当作小的批处理数据去计算。那么Flink则相反一开始就是按照流处理计算去设计的。当把从文件系统HDFS中读入的数据也当做数据流看待他就变成批处理系统了。
为什么Flink既可以流处理又可以批处理呢
如果要进行流计算Flink会初始化一个流执行环境StreamExecutionEnvironment然后利用这个执行环境构建数据流DataStream。
```
StreamExecutionEnvironment see = StreamExecutionEnvironment.getExecutionEnvironment();
DataStream&lt;WikipediaEditEvent&gt; edits = see.addSource(new WikipediaEditsSource());
```
如果要进行批处理计算Flink会初始化一个批处理执行环境ExecutionEnvironment然后利用这个环境构建数据集DataSet。
```
ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
DataSet&lt;String&gt; text = env.readTextFile(&quot;/path/to/file&quot;);
```
然后在DataStream或者DataSet上执行各种数据转换操作transformation这点很像Spark。不管是流处理还是批处理Flink运行时的执行引擎是相同的只是数据源不同而已。
Flink处理实时数据流的方式跟Spark Streaming也很相似也是将流数据分段后一小批一小批地处理。流处理算是Flink里的“一等公民”Flink对流处理的支持也更加完善它可以对数据流执行window操作将数据流切分到一个一个的window里进而进行计算。
在数据流上执行
```
.timeWindow(Time.seconds(10))
```
可以将数据切分到一个10秒的时间窗口进一步对这个窗口里的一批数据进行统计汇总。
Flink的架构和Hadoop 1或者Yarn看起来也很像JobManager是Flink集群的管理者Flink程序提交给JobManager后JobManager检查集群中所有TaskManager的资源利用状况如果有空闲TaskSlot任务槽就将计算任务分配给它执行。
<img src="https://static001.geekbang.org/resource/image/92/9f/92584744442b15d541a355eb7997029f.png" alt="">
## 小结
大数据技术最开始出现的时候仅仅针对批处理计算也就是离线计算。相对说来大数据实时计算可以复用互联网实时在线业务的处理技术方案毕竟对于Google而言每天几十亿的用户搜索访问请求也是大数据而互联网应用处理实时高并发请求已经有一套完整的解决方案了详见我写的《大型网站技术架构核心原理与案例分析》一书大数据流计算的需求当时并不强烈。
但是我们纵观计算机软件发展史,发现这部历史堪称一部**技术和业务不断分离**的历史。人们不断将业务逻辑从技术实现上分离出来,各种技术和架构方案的出现,也基本都是为这一目标服务。
最早的时候我们用机器语言和汇编语言编程直接将业务逻辑用CPU指令实现计算机软件就是CPU指令的集合此时技术和业务完全耦合软件编程就是面向机器编程用机器指令完成业务逻辑当时我们在编程的时候思维方式是面向机器的需要熟记机器指令。
后来我们有了操作系统和高级编程语言将软件和CPU指令分离开来我们使用Pascal、Cobal这样的高级编程语言进行编程并将程序运行在操作系统上。这时我们不再面向机器编程而是面向业务逻辑和过程编程这是业务逻辑与计算机技术的一次重要分离。
再后来出现了面向对象的编程语言,这是人类编程史上的里程碑。我们编程的时候关注的重心,从机器、业务过程转移到业务对象本身,分析客观世界业务对象的关系和协作是怎样的,如何通过编程映射到软件上,这是编程思维的一次革命,业务和技术实现从思想上分离了。
再后来出现各种编程框架一方面使业务和技术分离得更加彻底想象一下如果不用这些框架你自己编程监听80通信端口从获取HTTP二进制流开始到开发一个Web应用会是什么感觉。另一方面这些框架也把复杂的业务流程本身解耦合视图、业务、服务、存储各个层次模块独立开发、部署通过框架整合成一个系统。
回到流计算,固然我们可以用各种分布式技术实现大规模数据的实时流处理,但是我们更希望只要针对小数据量进行业务开发,然后丢到一个大规模服务器集群上,就可以对大规模实时数据进行流计算处理。也就是业务实现和大数据流处理技术分离,业务不需要关注技术,于是各种大数据流计算技术应运而生。
其实我们再看看互联网应用开发也是逐渐向业务和技术分离的方向发展。比如云计算以云服务的方式将各种分布式解决方案提供给开发者使开发者无需关注分布式基础设施的部署和维护。目前比较热门的微服务、容器、服务编排、Serverless等技术方案它们则更进一步使开发者只关注业务开发将业务流程、资源调度和服务管理等技术方案分离开来。而物联网领域时髦的FaaS意思是函数即服务就是开发者只要开发好函数提交后就可以自动部署到整个物联网集群运行起来。
总之,流计算就是将大规模实时计算的资源管理和数据流转都统一管理起来,开发者只要开发针对小数据量的数据处理逻辑,然后部署到流计算平台上,就可以对大规模数据进行流式计算了。
## 思考题
流计算架构方案也逐渐对互联网在线业务开发产生影响,目前流行的微服务架构虽然将业务逻辑拆分得很细,但是服务之间的调用还是依赖接口,这依然是一种比较强的耦合关系。淘宝等互联网企业已经在尝试一种类似流计算的、异步的、基于消息的服务调用与依赖架构,据说淘宝的部分核心业务功能已经使用这种架构方案进行系统重构,并应用到今年的“双十一”大促,利用这种架构特有的回压设计对高并发系统进行自动限流与降级,取得很好的效果。
我也邀请了淘宝负责这次架构重构的高级技术专家李鼎,请他在留言区分享一下这次架构重构的心得体会。
你对这种架构方案有什么想法,是否认为这样的架构方案代表了未来的互联网应用开发的方向?
欢迎你写下自己的思考或疑问,与我和其他同学一起讨论。

View File

@@ -0,0 +1,81 @@
<audio id="audio" title="16 | ZooKeeper是如何保证数据一致性的" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/7e/ef/7e2d9bd920765fc14fd39499e8ac1aef.mp3"></audio>
你可能还记得我们在讲HDFS和HBase架构分析时都提到了ZooKeeper。在分布式系统里的多台服务器要对数据状态达成一致其实是一件很有难度和挑战的事情因为服务器集群环境的软硬件故障随时会发生多台服务器对一个数据的记录保持一致需要一些技巧和设计。
这也就是我们今天要讨论的分布式系统一致性与ZooKeeper的架构。
在讲分布式系统一致性前我们先回顾一下HDFS。HDFS为了保证整个集群的高可用需要部署两台NameNode服务器一台作为主服务器一台作为从服务器。当主服务器不可用的时候就切换到从服务器上访问。但是如果不同的应用程序Client或者DataNode做出的关于主服务器是否可用的判断不同那么就会导致HDFS集群混乱。
比如两个应用程序都需要对一个文件路径进行写操作但是如果两个应用程序对于哪台服务器是主服务器的判断不同就会分别连接到两个不同的NameNode上并都得到了对同一个文件路径的写操作权限这样就会引起文件数据冲突同一个文件指向了两份不同的数据。
这种不同主服务器做出不同的响应,在分布式系统中被称作“脑裂”。光看这个词你也可以看出问题的严重性,这时候集群处于混乱状态,根本无法使用。那我们引入一个专门进行判断的服务器当“裁判”,让“裁判”决定哪个服务器是主服务器不就完事了吗?
但是这个做出判断决策的服务器也有可能会出现故障不可访问,同样整个服务器集群也不能正常运行。所以这个做出判断决策的服务器必须由多台服务器组成,来保证高可用,任意一台服务器宕机都不会影响系统的可用性。
那么问题又来了,这几台做出判断决策的服务器又如何防止“脑裂”,自己不会出现混乱状态呢?有时候真的很无奈,分布式系统设计就像是一个追着自己尾巴咬的喵喵,兜兜转转回到开头。
但是问题必须还要解决我们比较常用的多台服务器状态一致性的解决方案就是ZooKeeper。
## 分布式一致性原理
讲分布式一致性时相信你肯定多少听过著名的CAP原理。CAP原理认为一个提供数据服务的分布式系统无法同时满足数据**一致性**Consistency、**可用性**Availibility、**分区耐受性**Patition Tolerance这三个条件如下图所示。
<img src="https://static001.geekbang.org/resource/image/71/c0/711e087cf895cc58e02d19dcaafb44c0.jpg" alt="">
一致性是说每次读取的数据都应该是最近写入的数据或者返回一个错误Every read receives the most recent write or an error而不是过期数据也就是说数据是一致的。
可用性是说每次请求都应该得到一个响应而不是返回一个错误或者失去响应不过这个响应不需要保证数据是最近写入的Every request receives a (non-error) response, without the guarantee that it contains the most recent write也就是说系统需要一直都是可以正常使用的不会引起调用者的异常但是并不保证响应的数据是最新的。
分区耐受性是说即使因为网络原因部分服务器节点之间消息丢失或者延迟了系统依然应该是可以操作的The system continues to operate despite an arbitrary number of messages being dropped (or delayed) by the network between nodes
当网络分区失效发生的时候,我们要么取消操作,这样数据就是一致的,但是系统却不可用;要么我们继续写入数据,但是数据的一致性就得不到保证。
对于一个分布式系统而言,网络失效一定会发生,也就是说,分区耐受性是必须要保证的,那么在可用性和一致性上就必须二选一。当网络分区失效,也就是网络不可用的时候,如果选择了一致性,系统就可能返回一个错误码或者干脆超时,即系统不可用。如果选择了可用性,那么系统总是可以返回一个数据,但是并不能保证这个数据是最新的。
所以关于CAP原理更准确的说法是在分布式系统必须要满足分区耐受性的前提下可用性和一致性无法同时满足。
## Paxos算法与ZooKeeper架构
ZooKeeper主要提供数据的一致性服务其实现分布式系统的状态一致性依赖一个叫Paxos的算法。Paxos算法在多台服务器通过内部的投票表决机制决定一个数据的更新与写入。Paxos的基本思路请看下面的图。
<img src="https://static001.geekbang.org/resource/image/96/2f/96c6615c359f79922b9b087f6be4172f.png" alt="">
应用程序连接到任意一台服务器后提起状态修改请求也可以是获得某个状态锁的请求从图上看也就是服务器1会将这个请求发送给集群中其他服务器进行表决。如果某个服务器同时收到了另一个应用程序同样的修改请求它可能会拒绝服务器1的表决并且自己也发起一个同样的表决请求那么其他服务器就会根据时间戳和服务器排序规则进行表决。
表决结果会发送给其他所有服务器最终发起表决的服务器也就是服务器1会根据收到的表决结果决定该修改请求是否可以执行事实上只有在收到多数表决同意的情况下才会决定执行。当有多个请求同时修改某个数据的情况下服务器的表决机制保证只有一个请求会通过执行从而保证了数据的一致性。
ZooKeeper作为一个数据一致性解决方案产品事实上是牺牲了部分可用性换来的数据一致性。在Paxos算法中如果某个应用程序连接到一台服务器但是这台服务器和其他服务器的网络连接出现问题那么这台服务器将返回一个错误要求应用程序重新请求。
ZooKeeper通过一种树状结构记录数据如下图所示。
<img src="https://static001.geekbang.org/resource/image/76/5f/76526be77b0026a0c3b2d661d362665f.png" alt="">
应用程序可以通过路径的方式访问ZooKeeper中的数据比如/services/YaView/services/stupidname这样的路径方式修改、读取数据。ZooKeeper还支持监听模式当数据发生改变的时候通知应用程序。
因为大数据系统通常都是主从架构主服务器管理集群的状态和元信息meta-info为了保证集群状态一致防止“脑裂”所以运行期只能有一个主服务器工作active master但是为了保证高可用必须有另一个主服务器保持热备standby master。那么应用程序和集群其他服务器如何才能知道当前哪个服务器是实际工作的主服务器呢
所以很多大数据系统都依赖ZooKeeper提供的一致性数据服务用于选举集群当前工作的主服务器。一台主服务器启动后向ZooKeeper注册自己为当前工作的主服务器因此另一台服务器就只能注册为热备主服务器应用程序运行期都和当前工作的主服务器通信。
如果当前工作的主服务器宕机在ZooKeeper上记录的心跳数据不再更新热备主服务器通过ZooKeeper的监控机制发现当前工作的主服务器宕机就向ZooKeeper注册自己成为当前工作的主服务器。应用程序和集群其他服务器跟新的主服务器通信保证系统正常运行。
因为ZooKeeper系统的多台服务器存储相同数据并且每次数据更新都要所有服务器投票表决所以和一般的分布式系统相反ZooKeeper集群的性能会随着服务器数量的增加而下降。
<img src="https://static001.geekbang.org/resource/image/0f/53/0f9b13ac86458209f2755bda76e0c653.png" alt="">
ZooKeeper通过Paxos选举算法实现数据强一致性并为各种大数据系统提供主服务器选举服务。虽然ZooKeeper并没有什么特别强大的功能但是在各类分布式系统和大数据系统中ZooKeeper的出镜率非常高因此也是很多系统的基础设施。
## 小结
如果我们单独看大数据和分布式系统的很多解决方案如果不把它们放在大规模数据和大规模服务器集群的场景下思考可能会觉得很多问题和方案都很莫名其妙。比如要保证分布式系统中数据的一致性才诞生了Paxos这样专门的算法和ZooKeeper这样的产品。
Paxos算法只考虑所有服务器都是可信任的情况。但在分布式系统中还有一类场景需要考虑当集群中的服务器存在恶意服务器的情况。当这些恶意服务器企图篡改伪造数据或者传递虚假信息的时候如何保证系统继续有效运行呢比如目前非常火的区块链就需要考虑这种场景。
区块链采取的解决方案是工作量证明。一台服务器要想在分布式集群中记录数据即所谓分布式记账必须进行一个规模庞大的计算比如计算一个256 Bit的hash值这个值的前若干位必须为0。比特币区块链就是采用类似这样的工作量证明算法为了进行这样的hash计算目前比特币区块链消耗的电量相当于一个中等规模国家的用电量。
通过这种工作量证明方式保证了恶意服务器要想伪造篡改数据必须拥有强大的计算能力占整个集群服务器计算能力的51%以上),而只要我们认为大多数服务器是善意的,那么这样的区块链分布式集群就是可靠的。
## 思考题
除了工作量证明,还有什么方法可以保证分布式系统中不可信任的机器无法篡改或伪造数据?
欢迎你写下自己的思考或疑问,与我和其他同学一起讨论。

View File

@@ -0,0 +1,72 @@
<audio id="audio" title="17 | 模块答疑:这么多技术,到底都能用在什么场景里?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ce/bc/ce3778b52bbb7f8d0b73cdc6f87a84bc.mp3"></audio>
你好,我是李智慧。
经过前面两个模块,我们学习了大数据最经典、最主流的一些技术和产品,今天我们再回过头来梳理一下这些技术和产品。
<img src="https://static001.geekbang.org/resource/image/84/4c/844d8d462cf27203727db554abaf5c4c.png" alt="">
从上面这张图来看大数据技术的分类,我们可以分为存储、计算、资源管理三大类。
最基本的存储技术是HDFS。比如在企业应用中会把通过各种渠道得到的数据比如关系数据库的数据、日志数据、应用程序埋点采集的数据、爬虫从外部获取的数据统统存储到HDFS上供后续的统一使用。
HBase作为NoSQL类非关系数据库的代表性产品从分类上可以划分到存储类别它的底层存储也用到了HDFS。HBase的主要用途是在某些场景下代替MySQL之类的关系数据库的数据存储访问利用自己可伸缩的特性存储比MySQL多得多的数据量。比如滴滴的司机每隔几秒就会将当前的GPS数据上传而滴滴上的司机数量号称有上千万每天会产生数百亿的GPS数据滴滴选择将这样海量的数据存储在HBase中当订单行程结束的时候会从HBase读取订单行程期间的GPS轨迹数据计算路程和车费。
大数据计算框架最早是MapReduce目前看来用的最多的是Spark。但从应用角度讲我们直接编写MapReduce或者Spark程序的机会并不多通常我们会用Hive或者Spark SQL这样的大数据仓库工具进行大数据分析和计算。
MapReduce、Spark、Hive、Spark SQL这些技术主要用来解决离线大数据的计算也就是针对历史数据进行计算分析比如针对一天的历史数据计算一天的数据是一批数据所以也叫批处理计算。而Storm、Spark Streaming、Flink这类的大数据技术是针对实时的数据进行计算比如摄像头实时采集的数据、实时的订单数据等数据实时流动进来所以也叫流处理大数据技术。
不管是批处理计算还是流处理计算都需要庞大的计算资源需要将计算任务分布到一个大规模的服务器集群上。那么如何管理这些服务器集群的计算资源如何对一个计算请求进行资源分配这就是大数据集群资源管理框架Yarn的主要作用。各种大数据计算引擎不管是批处理还是流处理都可以通过Yarn进行资源分配运行在一个集群中。
所以上面所有这些技术在实际部署的时候通常会部署在同一个集群中也就是说在由很多台服务器组成的服务器集群中某台服务器可能运行着HDFS的DataNode进程负责HDFS的数据存储同时也运行着Yarn的NodeManager负责计算资源的调度管理而MapReduce、Spark、Storm、Flink这些批处理或者流处理大数据计算引擎则通过Yarn的调度运行在NodeManager的容器container里面。至于Hive、Spark SQL这些运行在MapReduce或者Spark基础上的大数据仓库引擎在经过自身的执行引擎将SQL语句解析成MapReduce或者Spark的执行计划以后一样提交给Yarn去调度执行。
这里相对比较特殊的是HBase作为一个NoSQL存储系统HBase的应用场景是满足在线业务数据存储访问需求通常是OLTP在线事务处理系统的一部分为了保证在线业务的高可用性和资源独占性一般是独立部署自己的集群和前面的Hadoop大数据集群分离部署。
今天我帮你把专栏前面讲过的大数据技术串联起来,并放到了比较具体的应用场景中,后面在专栏模块四,我们还会讨论如何将这些技术产品集成为一个大数据平台,希望能帮你更进一步了解大数据技术的应用方法和使用场景,请你一定坚持把专栏学完。
在专栏文章里,“蜗牛”同学问了我这样一个问题,我回顾了下自己的过往,也想和更多同学分享一下我的人生观。
<img src="https://static001.geekbang.org/resource/image/b8/76/b8d270f84f5e8c764d5612e368346a76.jpeg" alt="">
我在评论回复里,讲到王小波的《我的精神家园》。读这本书,大概是在我大学快毕业的时候,当时面临种种困惑,努力思考自己这一生应该如何度过,自己应该做一个什么样的人。
当时我就读于一所不入流的大学,在大学里面我又是个不入流的学生,从当时的趋势看,我未来的人生大概率也是不入流的人生,浑浑噩噩、蝇营狗苟度过一生。
虽然有的时候踌躇满志,也想要改变自己,将来有一天能出人头地。但是更多的时候想想自己的天分、背景,就不由得万念俱灰。进一步想,如果自己注定要平庸一生,活着又有什么意义。现在想来,当时可能是有一点抑郁的。
也就在那个时候读到了王小波的杂文集,关于人应该如何度过自己的一生,有了不一样的理解。王小波说:“**我活在世上,无非想要明白些道理,遇见些有趣的人,做一些有趣的事。倘能如我所愿,我的一生就算成功。**”
王小波的书当时给了我一种全新的认知世界的视角,我不一定要出人头地才叫成功,我能把自己的一生过得有趣、好玩,我也就算没白活一生。
但是如果简单的把好玩、有趣理解成自得其乐、不思进取,这样的生活肯定是有问题的。看王小波的其他文章就会明白,这个好玩、有趣也不是一件容易的事,没有一定的知识、见识,没有有深度的思考,没有经历过足够的困难、挫折,就根本不能理解哪些是真正好玩、有趣的人和事。
所以你必须还是要努力拼搏、锐意进取,然后在这个过程中才能真正明白一些道理,并且会遇到一些有趣的人。“**我只愿蓬勃生活在此时此刻,无所谓去哪,无所谓见谁。那些我将要去的地方,都是我从未谋面的故乡。以前是以前,现在是现在。我不能选择怎么生,怎么死;但我能决定怎么爱,怎么活。**”
想通了这一点后我就不再纠结自己是不是足够的优秀能够成就什么样的事业。我只要每天都有一点点进步明白一点点道理生活就是值得的。所以毕业以后我觉得编程好玩就去自学编程觉得自己学得差不多了就去找了一份程序员的工作觉得缺乏创造、不断重复的程序员工作并不好玩就去考计算机专业的研究生后来又去北京、杭州、上海不同的城市生活去阿里巴巴、Intel、创业公司等不同的公司去工作期间遇到过很多有趣的人跟很多聪明的人合作明白了一些道理也做过一些有趣的事。
再说几本对我影响比较大的技术书籍。我大学读的不是计算机专业后来偶尔在图书馆里看到一本C语言编程的书讲图形编程和游戏编程的当时觉得特别好玩就开始自学编程。但是后来做了程序员以后却发现天天按着需求写代码特别无聊既没有挑战也没有创新偶然看了一本名为[《Effective Java》](time://mall?url=http%3A%2F%2Fh5.youzan.com%2Fv2%2Fgoods%2F3epbuh9vlhry0)的书,发现这些常规的程序也有很多有意思的写法。
这本书讲了很多有趣的编程技巧,当时我在北京的中关村上班,每天上下班的地铁上,刚好可以看完一个技巧,很多技巧可以直接用在工作中,特别好玩。同时我也意识到,很多时候不是事情本身无趣,而是做事情的方式无趣。循规蹈矩、反复重复可以把事情做完,但是这样就会很无聊,如果去寻找更巧妙的解决之道,事情就变得有趣多了。
后来我就想,能不能把这些技巧提炼出来,让大家一起用,所以在看到《敏捷软件开发:原则、模式与实践》这本书的时候,我非常激动。在读这本书之前,我也看过设计模式的书,不过感觉这些书都把设计模式当做编程技巧来讲,虽然也有意思,但是技巧嘛,看得多了也就那么回事。
但是《敏捷软件开发》这本书把设计模式上升到设计思想的高度,书中说“**软件设计不应该是面向需求设计,而应该是面向需求变更设计**”,也就是说在设计的时候,主要要考虑的是当需求变更的时候,如何用最小的代价实现变更。**优秀的工程师不应该害怕需求变更,而应该欢迎需求变革,因为优秀的工程师已经为需求变更做好了设计,如果没有需求变更,那就显示不出自己和只会重复的平庸工程师的区别**。这就非常有意思了不是吗。
因为比较关注设计,并且后来又做了一些架构设计、框架和工具开发的工作,也因此我看到[《企业应用架构模式》](time://mall?url=http%3A%2F%2Fh5.youzan.com%2Fv2%2Fgoods%2F3eqi41hqyk0bc)这本书的时候特别震撼。当时自己觉得能够做框架、工具的开发很了不起,能够做架构设计、指导其他工程师开发挺厉害,但是看了《企业应用架构模式》这本书,才发现我做的这些事情只不过是在更大的领域解决方案、架构模式里非常小的一个部分,同类的东西还有很多。当时我感觉自己真的是坐井观天、夜郎自大,也非常羞愧。
如果感觉《敏捷软件开发》的作者Bob大叔、《企业应用架构模式》的作者Martin Fowler比自己牛太多还没有太多感觉因为毕竟隔得太远、没有交集所以触动还不是特别大那么后来在工作中遇到被高手全方位碾压的时候就真正的感受到生活还真是有意思呢。
如果你也有和我一样有过类似的困惑,不知该如何面对理想和现实之间的差距,希望我的经验可以给你一些启发。人类的进步是因为人们对美的不懈追求,而有趣也是美的一种,追逐有趣就是在追求进步,而一点一滴的进步终会引领我们实现自己的人生价值和目标。
在[专栏第15期](http://time.geekbang.org/column/article/70619)我邀请了淘宝的高级技术专家李鼎来聊聊流式业务架构重构的心得体会我把他的留言也贴在下面感兴趣的同学可以返回第15期也和我们聊聊你对这种架构的思考与理解。
<img src="https://static001.geekbang.org/resource/image/30/68/305c47d313a4697ffa4a062b43c51568.jpeg" alt="">
最后还是老规矩我精选了三木子、Lambda、hunterlodge、老男孩这几位同学的留言贴在今天的文稿里分享给你希望同学们的思考也能对你有所启发。
<img src="https://static001.geekbang.org/resource/image/04/7c/049f60b53907704bdcd2b7d8df8c9f7c.png" alt=""><br>
<img src="https://static001.geekbang.org/resource/image/e1/ff/e1bf9904b12b761fce31273079ea52ff.png" alt=""><br>
<img src="https://static001.geekbang.org/resource/image/2f/2d/2f685fac45b5edfed2ad53109664402d.png" alt=""><br>
<img src="https://static001.geekbang.org/resource/image/90/25/9009d8141f5fb2d06a80c0e21176f225.png" alt="">
如果你身边也有感到迷茫困惑的朋友,欢迎你点击“请朋友读”,把今天的文章分享给好友。也欢迎你写下自己的思考或疑问,与我和其他同学一起讨论。