mirror of
https://github.com/cheetahlou/CategoryResourceRepost.git
synced 2025-10-22 18:03:45 +08:00
mod
This commit is contained in:
@@ -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就是左边表中每一行的数据,比如<1, 25>这样。map函数的输出就是以输入的Value作为Key,Value统一设为1,比如<<1, 25>, 1>这样。
|
||||
|
||||
map函数的输出经过shuffle以后,相同的Key及其对应的Value被放在一起组成一个<Key, Value集合>,作为输入交给reduce函数处理。比如<<2, 25>, 1>被map函数输出两次,那么到了reduce这里,就变成输入<<2, 25>, <1, 1>>,这里的Key是<2, 25>,Value集合是<1, 1>。
|
||||
|
||||
在reduce函数内部,Value集合里所有的数字被相加,然后输出。所以reduce的输出就是<<2, 25>, 2>。
|
||||
|
||||
讲起来有点拗口,我把这个过程画成了一张图,看起来就清楚多了。
|
||||
|
||||
<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的Client(Hive的命令行工具,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就记录为<1, X>,这里的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。
|
||||
|
||||
类似这样将两个(或更多个)软件集成(嫁接)到一起,产生巨大创新应用价值的软件产品还有哪些?
|
||||
|
||||
欢迎你写下自己的思考或疑问,与我和其他同学一起讨论。
|
||||
|
||||
|
@@ -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("hdfs://...")
|
||||
val counts = textFile.flatMap(line => line.split(" "))
|
||||
.map(word => (word, 1))
|
||||
.reduceByKey(_ + _)
|
||||
counts.saveAsTextFile("hdfs://...")
|
||||
|
||||
```
|
||||
|
||||
不熟悉Scala语言没关系,我来解释一下上面的代码。
|
||||
|
||||
第1行代码:根据HDFS路径生成一个输入数据RDD。
|
||||
|
||||
第2行代码:在输入数据RDD上执行3个操作,得到一个新的RDD。
|
||||
|
||||
<li>
|
||||
将输入数据的每一行文本用空格拆分成单词。
|
||||
</li>
|
||||
<li>
|
||||
将每个单词进行转换,`word => (word, 1)`,生成<Key, Value>的结构。
|
||||
</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 => line.split(" "))
|
||||
val rdd2 = rdd1.map(word => (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分片时候,才会真的生成一个RDD,Spark的这种特性也被称作**惰性计算**。
|
||||
|
||||
另一种转换操作产生的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>
|
||||
|
||||
## 思考题
|
||||
|
||||
你在工作、生活中通过提问发现问题背后的本质、现象背后的规律的例子有哪些?或者你观察到同事、朋友这样的例子有哪些?
|
||||
|
||||
欢迎你写下自己的思考或疑问,与我和其他同学一起讨论。
|
||||
|
||||
|
101
极客时间专栏/从0开始学大数据/模块二 大数据生态体系主要产品原理与架构/13 | 同样的本质,为何Spark可以更高效?.md
Normal file
101
极客时间专栏/从0开始学大数据/模块二 大数据生态体系主要产品原理与架构/13 | 同样的本质,为何Spark可以更高效?.md
Normal 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),这些计算阶段组成一个有向无环图DAG,Spark任务调度器可以根据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生成和管理的组件是DAGScheduler,DAGScheduler根据程序代码生成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需要进行shuffle,RDD 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函数有两种,一种是转换函数,调用以后得到的还是一个RDD,RDD的计算逻辑主要通过转换函数完成。
|
||||
|
||||
另一种是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开源运作进行了大量的商业和非商业活动,你了解这些活动有哪些吗?假如你所在的公司想要开源自己的软件,用于提升自己公司的技术竞争力和影响力,如果是你负责人,你应该如何运作?
|
||||
|
||||
欢迎你写下自己的思考或疑问,与我和其他同学一起讨论。
|
@@ -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这类关系数据库呢?
|
||||
|
||||
欢迎你写下自己的思考或疑问,与我和其他同学一起讨论。如果你学完今天的内容有所收获的话,也欢迎你点击“请朋友读”,把今天的文章分享给你的朋友。
|
@@ -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<WikipediaEditEvent> edits = see.addSource(new WikipediaEditsSource());
|
||||
|
||||
```
|
||||
|
||||
如果要进行批处理计算,Flink会初始化一个批处理执行环境ExecutionEnvironment,然后利用这个环境构建数据集DataSet。
|
||||
|
||||
```
|
||||
ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
|
||||
|
||||
DataSet<String> text = env.readTextFile("/path/to/file");
|
||||
|
||||
```
|
||||
|
||||
然后在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,意思是函数即服务,就是开发者只要开发好函数,提交后就可以自动部署到整个物联网集群运行起来。
|
||||
|
||||
总之,流计算就是将大规模实时计算的资源管理和数据流转都统一管理起来,开发者只要开发针对小数据量的数据处理逻辑,然后部署到流计算平台上,就可以对大规模数据进行流式计算了。
|
||||
|
||||
## 思考题
|
||||
|
||||
流计算架构方案也逐渐对互联网在线业务开发产生影响,目前流行的微服务架构虽然将业务逻辑拆分得很细,但是服务之间的调用还是依赖接口,这依然是一种比较强的耦合关系。淘宝等互联网企业已经在尝试一种类似流计算的、异步的、基于消息的服务调用与依赖架构,据说淘宝的部分核心业务功能已经使用这种架构方案进行系统重构,并应用到今年的“双十一”大促,利用这种架构特有的回压设计对高并发系统进行自动限流与降级,取得很好的效果。
|
||||
|
||||
我也邀请了淘宝负责这次架构重构的高级技术专家李鼎,请他在留言区分享一下这次架构重构的心得体会。
|
||||
|
||||
你对这种架构方案有什么想法,是否认为这样的架构方案代表了未来的互联网应用开发的方向?
|
||||
|
||||
欢迎你写下自己的思考或疑问,与我和其他同学一起讨论。
|
@@ -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%以上),而只要我们认为大多数服务器是善意的,那么这样的区块链分布式集群就是可靠的。
|
||||
|
||||
## 思考题
|
||||
|
||||
除了工作量证明,还有什么方法可以保证分布式系统中不可信任的机器无法篡改或伪造数据?
|
||||
|
||||
欢迎你写下自己的思考或疑问,与我和其他同学一起讨论。
|
@@ -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="">
|
||||
|
||||
如果你身边也有感到迷茫困惑的朋友,欢迎你点击“请朋友读”,把今天的文章分享给好友。也欢迎你写下自己的思考或疑问,与我和其他同学一起讨论。
|
Reference in New Issue
Block a user