《使用Python和Dask实现分布式并行计算》1. Why scalable computing matters(为什么可伸缩计算很重要)


楔子

开新坑啦,最新本人发现了一本书叫《Data Science with Python and Dask》,或许你还不知道它是什么,或许你已经对它有所了解、但是了解的不够深入。如果是这样的话,那么让我们从头开始一起学习吧。

这本书是英文版本的,所以个人决定将它从头到尾翻译一遍,当然我也会加入一些个人的想法进去,以及示例所使用的代码和书中也不一定是一样的。不过我相信这些都不是问题,你在学习的时候编写的代码也不一定要和我一样。

我当前使用操作系统是Windows,Python版本是3.8.1。

废话不多说,下面就开始吧。


欢迎来到《Data Science with Python and Dask》,既然你选择了这本书,那么毫无疑问你肯定对数据科学和机器学习感兴趣,也许你已经是一名数据科学家、分析师,或者机器学习工程师。然而,我猜测你现在正面临(或者曾经面临过)一个巨大的挑战,没错,就是当你在处理大型数据集时所面临的那些众所周知的挑战。比如:即使是执行非常简单的计算,也需要花费漫长的时间;数据集过大使得单机无法全量读取,进而导致无法像平常那样编写代码,需要思考很多额外的东西,比如:数据集的切分等等。当然还有其它的挑战,就不一一列举了。

但是不要绝望,随着大型数据集在收集和存储方面所需要的费用和工作量的显著下降,这些挑战早已变得司空见惯。作为回应,计算机科学社区已经投入了大量的精力去创建一个更好、更容易使用的编程框架,来降低处理海量数据的复杂性。目前已经出现了很多不同的技术和框架,都在致力于解决这些问题,比如:Dask。在功能和灵活性方面,Dask可以说是出类拔萃,在众多框架中处于领先水平。而这本书的目的就是教你如何使用Dask来对大型数据集进行分析和建模,从而提升你的数据科学水平,引领你走向数据科学中的下一个高峰。

谁适合这本书?谁不适合这本书?

值得注意的是,Dask可以解决的问题范围很广,其中包括但不限于:数据的结构化分析、科学计算中的大型仿真,以及通用分布式计算。所以Dask的能力是独一无二的,它可以处理许多种类的问题,如果我们试图覆盖Dask的每一个方方面面,那么这本书将会灰常灰常的厚。因此在本书中,我们只将目光聚焦于使用Dask进行数据分析和机器学习,尽管也会涉及Dask的一些其它方面,但这不是我们的重点。

这本书是为初级数据科学家、数据工程师,以及分析人员编写的,特别是那些还不知如何处理超过单机极限数据量的人。我们将会覆盖数据科学的绝大部分范围,从数据准备到使用Dask进行分析和模型构建,深刻体会分布式计算的基本原理。

如果你使用过诸如spark之类的分布式计算框架,或者你已经熟练掌握numpy、scipy、pandas技术栈,那么你会发现这本书不太适合你,因为Dask设计之初就是为了更轻松地扩展numpy和pandas,所以你可以更好的利用其它资源,比如:API文档。不过即便如此,这本书还是会给你带来一些其它的惊喜的。(突然间不想翻译了,怎么破?)

这本书主要围绕着作为一名数据科学家或数据分析师在工作中都会遇到的典型任务,以及相关的处理方式,但是在这一章我们会先介绍一些基础知识,来理解Dask的工作原理。首先我们会研究像Dask这样的工具为什么在你的数据科学工具箱中是必不可少的,以及它的独特之处;然后我们将介绍有向无环图,因为Dask广泛使用这个概念来控制代码的并行执行;有了这些知识,我们将会更好地理解Dask在处理大型数据集时是如何工作的。当你不断深入Dask时,这些知识将会很好的为你服务,并且在后续章节中我们还会讨论如何在云端搭建自己的集群,那时我们还会回顾这些知识。那么现在就让我们将目光转向Dask的独特之处,并分析它为什么是数据科学中的一个有力工具。

为什么是Dask?

对于现代组织而言,数据科学的发展前景是普遍具有吸引力的,并且具备足够充分的理由。另一方面,高效的数据科学团队可以将单纯的0和1转换为真正的竞争优势,比如:做出更好的决策、优化业务流程,以及检测战略盲点,这些都是投资数据科学能力所带来的好处。然而我们今天所谈论的数据科学并不是一个真正的新概念,因为在过去的几十年里,世界各地的组织都在努力寻找更好的方法来做出战略和战术决策,尽管使用的手段各不相同,比如有:决策支持、商业智能、分析或简单的运筹学等等,但是目的都是一致的,那就是:密切关注正在发生的事情,做出更明智的选择。然而近年来发生了很大的变化,学习和使用数据科学的障碍已经大大降低,数据科学不再只局限于运筹学期刊或大型咨询集团的数据研究和开发部门。而将数据科学带给大众的一个关键角色便是Python编程语言、以及它的一些被称为"Python开源数据科学技术栈"的第三方模块。可以说Python在数据科学领域的一些第三方库是使得Python在现如今变得如此流行的最大功臣,这些第三库显然我们都是熟知的,比如:numpy、pandas、scipy、scikit-learn等等,这些库已经成为了一个工业标准,社区拥有大量的开发人员以及丰富的学习资料。而其它的语言也有这样的优势,比如:matlab、fortran等等,但是由于学习起来比较困难,没有那么多的社区支持,由于这些原因,Python和它的一些第三方库已经成为学习数据科学、以及进行相关开发的最流行的平台之一。

随着数据科学的发展,计算机也变得越来越强大,这使得生产、收集、存储和处理数据变得容易,并且支持的数据量也更多。但是现在大量的数据出现,已经让许多组织开始质疑,将所有的数据都收集和存储起来究竟有没有价值?这个想法是正确的,因为原始数据没有内在价值,必须对其进行清理、检测和分析,才能从中提取可操作的信息,进而将其变成价值。而这一步便是数据科学家,也就是屏幕前的你发挥作用的地方。通过Python,数据科学家可以很轻松的使用pandas进行数据清理和探索式数据分析,使用numpy和scipy对数据进行统计测试,使用scikit-learn构建预测模型,当然还可以使用matplotlib、pyecharts、plotly、bokeh进行数据可视化。这一切都是那么的完美,真的是从头到尾都有现成的工具让你用,但是显然我们忽略了一个问题。我们能够如此轻松的处理,最大的一个前提就是这些数据可以被单机读取,可在现如今数据量爆炸的时代,单机显然是解决不了问题的。像阿里巴巴等电商,早在十几年前就遇见了这种问题,当时淘宝人数的大量增加以及分布式技术的不成熟使得它们的业务受到了很大的制约。因此当使用超过一定大小的数据集时,这些工具的可行性就出现了上限,而一旦越过了这个阈值,本章所描述的问题便会开始出现。

但是数据量多大才算大呢?我们经常说大数据,多大的数据量才能被称之为大数据呢?有没有相应的门槛规定呢?因此为了避免定义不明确,加之大数据这个术语已经被过度使用,我们在这里有必要声明一下。在本系列中,我们将数据集分为三种,分别是:小型数据集、中型数据集、大型数据集,其标准如下。

小型数据集的特点是可以轻松地读取到内存中,并且还可以留出足够的空间进行操作和转换,它们通常不超过2-4GB,像排序和聚合这样的复杂操作也可以在不分页的情况下完成。提到分页,那么什么是分页呢?分页指的是在计算的过程中,使用计算机持久化存储(机械硬盘或固态硬盘)来保存中间的计算结果。所以当内存不够的时候,使用硬盘来保存临时结果便是分页,因为我们在计算的过程中肯定会产生大量的临时结果,如果内存不够的话,便会将临时结果通过硬盘来进行存储。而且一旦涉及到分页,那么存储和读取的速度会大幅度降低,因为涉及到了磁盘,而spark之所以比Hadoop的MapReduce快那么多,也是由于Hadoop需要落到磁盘、而spark不需要所导致的。但对于小型数据集而言我们则不需要担心,因为内存足够不会涉及到分页,而且numpy、pandas、scikit-learn等工具包也最适合这种小型数据集。因此小型数据集的话,没有什么可说的,直接处理就可以了。

中型数据集的特点是可以存在本地的磁盘中,但是无法加载至内存,其大小大概是10GB到2TB。当然如果你的机器配置好,加几个内存条完全可以达到32G,可以轻松读取20GB的中型数据集,但这与机器的配置有关,这里我们不讨论机器性能如何。所以尽管我们可以使用分析小型数据集的工具来分析中型数据集,但是这会造成严重的性能损失,因为为了避免内存溢出会不可避免地涉及到分页。而且这些数据集过大,即便能读取到内存中,计算起来也是很耗费时间的,因此需要引入并行计算。相比于将所有执行都限制在单核CPU上,并行计算会将工作划分到所有可用的CPU核心,从而大大地提升计算速度。然而在Python中进程之间的通信并不是那么的容易,因此很难在pandas中使用并行计算。

大型数据集的特点是它既不能读到内存中,也不能存储在单台机器的硬盘中,这些数据集的大小通过超过2TB,并且根据问题的规模不同,甚至可以达到PB级别、甚至更高。而Python的那些第三方开源工具包:pandas、numpy、scikit-learn则完全不适合这种数据集,因为它们天生就不适合操作分布式数据集。

当然就如我们刚才所说的,这些阈值是比较模糊的,它还取决于你当前的机器配置。就拿笔者来说,目前项目组在做数据迁移时所使用的服务器就是配置比较高的那种,运行内存有256GB,硬盘是几十个T。但无论如何,当你的数据集在挑战我们对小型数据集的定义时,寻找可替代的分析工具是有好处的(通常也是必须的)。像pandas,如果内存不够,那么要么换一台内存容量更大的机器,要么通过分块读取来曲线救国,但无论是哪一种情况,显然都是不合适的。因为通过增加机器内存的方式所需要的成本太高,分块读取的话要求你的数据集保证整体处理和分块处理的效果一致。所以最好的办法就是换一个工具,但是这也并不是一件容易的事情,因为这通常会导致数据科学家陷入新的困境,比如:使用自己不一定熟悉的技术或者语言,这通常会拖慢他们正在进行的项目的进度。

而Dask的出现就是为了帮助Python程序猿解决这个问题(你说了这么多,终于进入主题了),它于2014年底推出,其目标就是为Python数据科学技术栈带来原生的可伸缩性,并克服单机限制。随着时间的推移,这个项目已经发展成为Python开发人员可以使用的最好的可伸缩计算框架之一。Dask由几个不同的组件和API组成,它们可以分为三层:调度器、底层API、高层API,框架示意图如下:

我们来分别介绍这些组件,首先Dask的强大之处就在于这些组件和层是具有递进关系的,其核心便是任务调度器(scheduler),它负责协调和监视跨CPU核心和机器的计算执行。而这些计算在代码中则是以Dask Dealyed对象和Dask Future对象的形式存在,这两者区别就是前者是延迟计算(Lazy),这意味着它们只有在需要值的时候计算;而后者则是实时计算,这意味着无论是否需要该值,都是实时计算的;Dask Dealyed对象和Dask Future对象处于Low-level APIs。而Dask的High-level APIs则是在Delayed对象和Future对象的基础上提供了一个抽象层,里面有Array、Bag、DataFrame、ML,当然它们都是并行的。对这些高级对象操作,会产生许多由任务调度器管理的并行的低级操作,从而为用户提供更好的体验,因为不用和低级API打交道。这个设计,为Dask带来了四个关键优势:

  • Dask是由纯Python实现的, 天生可以对numpy、pandas、scikit-learn进行扩展
  • Dask可以很高效的在一台机器上处理中型数据集, 以及在集群上处理大型数据集
  • Dask可以作为一个通用框架, 来对大部分Python对象实现并行操作
  • Dask的配置和维护开销非常低

Dask能够在竞争中脱颖而出的第一件事就是它完全使用Python实现,其API集合可以对numpy、pandas、scikit-learn进行扩展,但这并不意味着Dask仅仅只是numpy、pandas的一个镜像,Dask底层使用的对象依旧来自于numpy、pandas这些库。一个Dask DataFrame由于许多较小的pandas DataFrame组成,Dask Array由许多numpy Array组成,以此类推。每一个小的底层对象(称之为"块"或者"分区")可以在集群中从一台机器传输到另一台机器,或者通过队列的方式在本地进行批次处理。稍后我们将更深入的探讨这一过程,总之将中、大型数据集分解为更小的部分,再加上对函数的并行执行的管理,使得Dask可以很优雅地处理那些过于庞大而常规手段无法解决的数据集。使用这些高级对象来支持Dask分布式计算的结果就是,pandas和numpy中的许多函数、属性,在语法上和Dask是等价的。对于熟悉这些开源第三方库的Python程序猿而言,这种设计会使得他们在处理小型数据集到处理中、大型数据集的转换变得非常容易。与学习新的语法相比,数据科学家可以正价专注于可伸缩计算的最重要的方面:编写健壮、高性能、以及针对并行计算进行优化的代码。

其次,Dask在单机上处理中型数据集和在集群上处理大型数据集一样有用,因为Dask无论是向上扩展还是向下扩展都非常灵活,这使得用户可以很容地在本地机器上创建任务原型,并在需要时可以无缝地将这些任务交给集群。无需对现有代码进行重构,也无需为集群的特定问题(如:资源管理、恢复、数据移动)而单独编写额外的代码,直接就可以完成单机向集群的转换。而且Dask还为用户提供了很大的灵活性,支持选择部署和运行代码的最佳方式。通常使用集群来处理中型数据集是完全没有必要的,相反由于协调多台机器一起工作所花费的开销有时还会让程序变得更慢。Dask在内存占用上做的非常多的优化,因此它可以在单机上优雅地处理中型数据集,即使在性能相对较低的机器上。这种透明的可伸缩性要归功于Dask的内置任务调度器(scheduler),当Dask在一台机器上运行时,可以使用本地调度器,而分布式任务调度器可以用于本地执行和跨集群执行。Dask还支持与流行的集群资源管理器(如:yarn、mesos、k8s)进行交互,允许你使用带有分布式任务调度器的现有集群。无论是跨多少机器,配置任务调度器和使用资源管理器部署Dask都只需要很少的工作。而在整个系列中,我们将讨论不同配置下运行Dask,比如:使用本地任务调度器在本地运行,以及使用带有docker和亚马逊弹性容器服务(这里笔者使用的是阿里云)的分布式任务调度器在云端的集群中运行。

Dask最不寻常的方面之一就是它具有可伸缩大部分Python对象的能力,Dask的低级API,如:Dask Delayed和Dask Future对象,它们是对Dask Array中使用的numpy Array,Dask DataFrame中使用的pandas DataFrame,Dask Bag中使用的Python list进行扩展的共同基础。

最后,Dask非常轻便,易于安装、卸载和维护,它的所有依赖都可以使用pip或者conda来安装。而使用docker构建和部署集群的镜像也非常容易,我们在后续将会做这些工作,而且Dask只需要很少的配置就可以开箱即用。由于这个原因,Dask不仅能够很好的处理重复出现的作业,而且还是构建概念证明和执行特定数据分析的优秀工具。

卧槽,感觉你都快把Dask说上天了,后面我要是发现不好用,我就敢把博客全删了。

初识Dask的所有数据科学家脑海里应该都有一个问题,那就是Dask和其它表面上类似的技术(比如:Apache Spark)相比有什么不同。首先Spark已经成为分析大型数据集的一个非常流行好用的框架,并在这方面做得很好。然而,尽管Spark支持包括Python在内的多种语言,但它的开发语言是基于jvm之上的Scala,这或许会给非java程序猿带来一些专业知识上的挑战。Spark于2010年推出,作为Apache Hadoop的处理引擎MapReduce的替代品,其核心严重依赖于java虚拟机(jvm)。在几个发布周期之后,出现了PySpark,正式宣布Python和Spark的结合。但是PySpark仅仅支持Python和Spark集群进行交互,提交给Spark的任何Python代码都必须使用Py4J库来jvm进行交互,这使得调优和调试PySpark代码非常困难,因为有些执行发生在Python上下文之外,而且Spark的一些数据结构还不支持Python。

所以PySpark的用户最终可能会决定将自己的代码采用Scala或java进行重构,以最大限度地利用Spark。Spark的新特性和性能上的提升都优先被添加到java和Scala api中,通常需要经过几个发布周期之后才能将该功能公开给Python,或者说PySpark。此外,PySpark的学习曲线并不平缓,它的DataFrame api虽然在概念上类似于pandas中的DataFrame,但在语法和结构上有很大的差异。这意味着PySpark的新用户必须重新学习如何遵循"Spark way"的方式做事,而不是基于现有的经验、知识和pandas、scikit-learn一起工作。Spark经过了高度的优化,可以对集合对象进行计算,比如对一个数组进行map改变里面的每一个元素,以及计算数组的和。但是这种优化是以灵活性为代价的,如果不能表示为对集合的映射、或者reduce类型的操作,那么Spark是无法计算的,所以Spark无法像Dask那样优雅地扩展自定义算法。而且Spark也因其配置繁琐而臭名昭著,需要很多依赖项,比如:Apache Zookeeper和Apache Ambari,而且这些依赖项自己也很难配置。所以对于重度依赖Spark和Hadoop的公司或组织,一般来说都会拥有一个专门的团队,主要职责就是配置环境、监控和维护集群。

上面的比较并不是为了黑Spark,Spark是一个非常优秀的框架,擅于做它所做的事情,当然是分析和处理大型数据集的可行方案。然而Dask的短学习曲线、灵活性和熟悉的api,使得它在使用Python做数据分析的数据科学家眼中是一个更具有吸引力的解决方案。

我希望你现在明白为什么Dask是一个如此强大和灵活的工具了,而且如果我之前的怀疑是正确的,那么你选择这本书的目的是因为你正在和一个大型数据集做斗争(不,我只是单纯的学习Dask以备后用罢了)。我希望你尝试使用Dask,学习更多使用Dask处理大型真实数据集的知识,并为之感到兴奋。但是在研究Dask代码之前,最好先回顾一下几个核心概念,它们将帮助你理解Dask的任务调度程序是如何"分而治之"计算的。如果你不熟悉分布式计算的概念,那你一定要看下去,因为掌握任务调度机制将会使你更好地理解在执行计算时究竟发生了什么,以及潜在的瓶颈可能在哪里。

什么是DAGs

Dask的任务调度器使用"有向无环图( directed acyclic graph,简称DAG)"来对计算进行组织、控制和表示。DAGs(多个DAG,单词的复数形式)来自于一个被称为图论的更大的数学体系,这里图论和你想象的饼图、柱状图没有任何关系。相反,图论是将一组具有关联的对象使用一张图来表达出来,虽然这个定义非常的抽象,但它意味着图非常善于表达各种各样的信息。我们通过生活中的例子,来更好的理解DAG。

比如我要和蕾姆吃晚饭:

当我们想要完成一个功能的时候,需要执行很多的函数,当然在任务调度框架中我们执行的是task(任务),负责执行task的是worker,比较熟悉的概念了。另外task也是基于某个函数创建的,当执行task的时候,实际上执行的就是里面的函数,只不过task内部除了要执行的函数(步骤)之外,还包含了其它的额外信息,比如task的创建时间、内部函数的执行状态等等,这些都要进行动态更新。

而每一个task都对应图中的一个节点或者步骤,而且这些节点会遵循一定的逻辑顺序,比如:吃饭之前得先回家、然后洗手,这意味着在下一个task执行之前,必须先将前面的一个或多个task执行完毕才可以。

正如图中显示的那样,如果我要和蕾姆吃饭,那么蕾姆必须要先煮饭和炒菜,以及我必须要先回家。至于蕾姆煮饭、蕾姆炒菜、我下班回家这三者本身则没有任何联系。这三个步骤的完成顺序并不重要,但是必须要完成所有的步骤之后才能进行最后的吃饭过程。

另外你可能还发现了,那就是图中的箭头是单向的,后面的节点没有指向前面的节点。因为一个节点一旦完成,就不会重复或者重新执行,所以该图才叫有向无环图。有向指的是箭头代表着方向,无环指的是箭头是单向的,如果是双向则会形成一个环。

而且在"下班回家"之后,如果想吃饭还必须要先洗手换衣,像这种想要完成一个节点之前还需要间接满足的依赖叫做传递依赖。

扩展、并发和恢复

我们知道当内存不够的时候,可以采用多台机器的方式,但是要怎么扩展呢?多个worker之间如何协调,才能更好地协同工作呢?并且执行失败之后如何进行错误处理和故障恢复呢?

横向扩展与纵向扩展

假设有一天蕾姆突然说,自己想要当厨师,想让更多的人来品尝自己的手艺。所以她便开了一家店,结果包括老八在内,吃过的人都说好,就这样越来越多的人来店里吃饭。但这样就出现了一个问题,蕾姆不得不在晚餐高峰时期为一大群饥饿的顾客做饭,如果这样的话,花费的时间会大幅增加。就拿切菜来说吧,以前只需要切两人份,现在要切几十人份,花费的时间大幅度提高,更不要提炒菜所花费的时间了。而解决这个问题有两个办法,一是使用切菜和炒菜更快、更有效的设备来替换现有的设备(纵向扩展);二是雇佣更多的工人(worker),大家一块并行工作(横向扩展)。

决定是横向扩展还是纵向扩展并不是一件容易的事情,因为两者都有利弊。如果是纵向扩展,那么蕾姆仍然要从切菜到炒菜,一个人执行完整个过程,但是她不需要担心沟通的问题,也不用担心别人做的没有自己做的好吃。因此面临的是中型数据集的话,那么纵向扩展是一个正确的选择。

但是随着名气越来越大,来的人也越来越多,即便切菜用的刀再锋利,炒菜用的设备再好,也终有撑不住的那一天。这时候我建议蕾姆多找几个人一起工作,但是人如果多了,就意味着要买额外的刀、砧板以及其它额外工具,并且还需要提供足够的设施,并且还要能够为员工发工资。所以从长远的角度来看,这是一个更具有成本效益的解决方案,它需要投入大量的资金。而且工人还有可能会生病从而耽误工作,或者做一些意想不到的事情让人烦恼。如果厨房里面只有三到四个厨师,那么还可以盯着他们,但是随着厨房规模的扩大,那么可能就需要一个副厨师长了。同样的,这些都和成本有关,在考虑是横向扩展还是纵向扩展时,应该如实考虑这些成本。

但最终蕾姆还是选择了横向扩展,组建了相应的厨师团队,那么现在她必须弄清楚如何向每位厨师传达指示,并且确保食谱按时出炉。所以这个时候便可以使用有向无环图,这是一个非常伟大的工具,它可以用于规划和协调复杂的任务(即使不在一个工作池中)。最重要的是,节点之间的依赖关系有助于确保工作能按照一定顺序有条不紊的进行(注意:一个节点只有当所有指向它的节点全部完成之后,才会开始执行)。另外,一个节点是一个独立的工作单元,因此可以让多个厨师做相同的事情,比如:指定三个厨师都负责切菜,两个厨师都负责做点心等等,而对厨师进行任务划分和监督的副厨师长,便扮演着Dask任务调度器的角色。并且为了让食物更高效的在房间里面流动,副厨师长应该不断地评估每个厨师需要做什么工作,在什么时候做。比如:蒜薹炒肉,当厨师A准备切蒜薹时,副厨师长应该让厨师B准备切肉,而不是等厨师A将蒜薹都切完了再下达指令。这种策略可以让一些顾客能更快地得到服务,同样的Dask任务调度器也在多个任务之间循环工作,从而减少内存负载和更快地发出任务。任务调度器会以一种非常有效的方式将工作单元分配给机器,并且会使工作池的空闲时间达到最小,组织多个worker之间并行执行,并为每个任务分配一定数量的worker。

并发和资源管理

通常情况下,除了数量你还需要考虑更多其它的限制因素,在可伸缩计算中这些被称为并发问题。比如蕾姆的厨房里面只有五把刀,这意味着同时只能有五个厨师切菜。如果已经有五个厨师在切白菜了,但是还有一个厨师需要切洋葱,然而已经没有刀可以供他使用了,那么它此时就要处于等待状态了,直到某一个厨师将白菜切完,或者给他分配一个不需要使用刀的任务。假设切白菜需要的时间很长,那么切洋葱的厨师就需要等待很长的时间,这种资源分配显然是不合理的。

并且当共享资源被使用时,会有一个资源锁,保证锁定该资源的工作人员在执行任务的途中不会被其它工作人员"抢夺"。如果一个厨师从另一个厨师手中夺取资源(刀)的话,那么这是非常危险的,而且也会花费无意义的时间。因此副厨师长必须指定基本规则来化解这些冲突,这些规则包括谁可以使用相应资源,以及当资源可用时能够用它来做什么。所以这也是任务调度器要做的事情,可伸缩框架中的任务调度器必须能够决定如何处理资源的分配和锁定,但如果处理不当,资源争夺可能会对性能造成很大的不利影响。但幸运的是,大多数框架(包括Dask)都非常擅于高效的任务调度,通常不需要手工调优。

故障恢复

最后,还有一个故障恢复策略,如果没有这个,那么可伸缩计算框架就不完整。就将副厨师长无法同时监督所有厨师一样,随着集群中机器数量的增加,对任务分配的协调处理也会变得越来越困难。由于最终结果是由所有单独的操作聚合得到,因此要确保所有部分都是准确的,但机器和人一样也会犯错误,所以必须考虑两种典型的故障:节点故障和数据丢失。举个栗子:如果让厨师连续切三个小时的白菜,它肯定会疯的,再也无法忍受这种单调,从而脱下外套走到门外思考人生。这个时候,便出现了节点故障,因为该节点不工作了。因此副厨师长就需要找到另一个厨师来顶替刚才那个厨师的位置,因为白菜必须要有人切,但是新来的厨师可以从上一个厨师中断的地方开始切,所以这还是一个没有数据丢失的节点故障。不需要对数据重新计算,因此对性能的影响也就没那么严重。

但是当发生数据丢失时,对性能造成的影响就没那么小了。假设此时一道菜已经完成了一半,但是某个厨师脚老眼一花不小心把盐当成了糖撒到菜里面了,这个时候显然不能继续下去了,厨师不得不从头开始。因为节点之间的依赖关系不能满足,必须一步步回退到第一个无依赖的节点,然后从那里重新开始。尽管这是一个非常糟糕的示例,但重要的是,我们需要记住:有向无环图中的任何一个节点发生故障,都可以回退到某个无依赖的节点上从头来过,区别就是回退所迈的步伐多与少罢了。如果倒霉的话,就相当于从头开始了。而任务调度器则负责停止部分工作,并从执行失败的worker那里重新分发相应任务,确定任务量,指定新的worker来执行。

在极少的情况下,任务调度器也可能会遇到问题而失败,就像蕾姆的副厨师长决定挂起帽子、走出房门一样。这种失败当然也是可以恢复的,但是由于只有任务调度程序知道完整的DAG和以及任务完成了多少,所以唯一的选择是用一个全新的任务图从第一步重新开始。但说实话,厨房的类比有点站不住脚,因为事实上厨师们会很了解食谱,自己知道该做什么,不可能每一步都需要副厨师长。但是Dask的情况不是这样,worker只是按照要求去执行,如果没有任务调度器告诉它该做什么,它们是无法自己做决定的。

希望你现在已经很好的了解DAG的强大功能,以及它们与可伸缩框架之间的联系,这些概念肯定会再次出现,因为Dask的任务调度都是基于这里介绍的DAG概念。下面我们来介绍一下后续使用的数据集,以及Dask的操作和功能。

数据集

因为我们要使用Dask进行实际演练,所以我们必须要有相应的数据集,并且这些数据集最好不是那种手动简单生成的"玩具数据集",将我们的实际操作应用在更真实、更混乱的数据集中会更有价值。因为使用适当大的数据集可以获得更多的经验,因为这样未来在面临未知的大型数据集时就会有更多的经验去应对,因此在后续我们将会使用NYC OpenData(https:// opendata.cityofnewyork.us)提供的强大的公共域数据集来学习Dask。

每个月的第三周,纽约市财政部都会记录并发布一组数据,其中包括本财年迄今为止的所有停车罚单,这个城市收集的数据非常丰富,甚至包括一些有趣的地理特征。并且为了让数据更容易获取,有两个人收集纽约市OpenData四年来的数据,并将其发布在了热门的机器学习网站kaggle上。数据集的跨度从2013到2017年6月,未压缩的数据集超过8GB。如果你有一台强大的计算机,那么这个数据集对于你来说可能满足小型数据集的定义,但是对于大多数人来说,它应该是一个大小适中的数据集,数据集在www.kaggle.com/new-york-city/nyc-parking-tickets上面获取,当然我们还可以进入www.kaggle.com/datasets页面下载其它感兴趣的数据集。

总结

  • Dask可以扩展流行的Python数据分析库,比如pandas和numpy,允许你轻松地分析中大型数据集。
  • Dask使用有向无环图(DAG)来协调CPU内核和机器之间并行代码的执行。
  • 有向无环图由多个节点组成,明确定义了开始和结束,单个遍历路径以及没有循环。
  • 一个如果节点想要执行,那么必须先保证指向它的节点都执行完毕。
  • 横向扩展(说白了就是加机器)通常可以提高性能,但它会产生额外的开销,因为通信也是需要耗费资源的。
  • 在出现故障时,可以针对故障节点重新执行,不会影响其它节点。

这一章全尼玛是字,翻的我都快吐了。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM