首页 文章 精选 留言 我的

精选列表

搜索[翻译],共10000篇文章
优秀的个人博客,低调大师

Spark编程指南V1.4.0(翻译)

Spark编程指南V1.4.0 ·简单介绍 ·接入Spark ·Spark初始化 ·使用Shell ·在集群上部署代码 ·弹性分布式数据集 ·并行集合(Parallelized Collections) ·其它数据集 ·RDD的操作 ·基础操作 ·向Spark传递函数 ·处理键值对 ·转换 ·动作 ·RDD的持久化 ·存储级别的选择 ·移除数据 ·共享变量 ·广播变量 ·累加器 ·部署到一个集群上 ·单元測试 ·从1.0之前版本号的Spark迁移 ·下一步该怎么做 简单介绍 总的来说,每个Spark的应用。都是由一个驱动程序(driver program)构成。它执行用户的main函数,在一个集群上执行各种各样的并行操作。Spark提出的最主要抽象概念是弹性分布式数据集(resilientdistributed dataset,RDD)。它是一个元素集合,划分到集群的各个节点上,能够被并行操作。 RDDs的创建能够从HDFS(或者随意其它支持Hadoop文件系统)上的一个文件開始,或者通过转换驱动程序(driver program)中已存在的Scala集合而来。用户也能够让Spark保留一个RDD在内存中,使其能在并行操作中被有效的反复使用。最后,RDD能自己主动从节点故障中恢复。 Spark的第二个抽象概念是共享变量(shared variables),能够在并行操作中使用。在默认情况下,Spark通过不同节点上的一系列任务来执行一个函数,它将每个函数中用到的变量的拷贝传递到每个任务中。有时候,一个变量须要在任务之间,或任务与驱动程序之间被共享。 Spark支持两种类型的共享变量:广播变量,能够在内存的全部的结点上缓存变量;累加器:仅仅能用于做加法的变量。比如计数或求和。 本指南将用每一种Spark支持的语言来展示这些特性。 这都是非常easy来跟着做的假设你启动了Spark的交互式Shell或者Scala的bin/spark-shell或者Python的bin/pyspark。 接入Spark Scala Spark1.2.1须要和Scala2.10一起使用。假设你要用Scala来编写应用,你须要用一个对应版本号的Scala(比如2.10.X)。 要写一个Spark应用程序,你须要在加入Spark的Maven依赖。Spark能够通过Maven中心库来获得: groupId = org.apache.spark artifactId = spark-core_2.10 version = 1.2.1 除此之外,假设你想訪问一个HDFS集群,你须要依据你的HDFS版本号。加入一个hadoop-client的依赖。一些通用的HDFS版本号标签在第三方发行版页面列出。 groupId = org.apache.hadoop artifactId = hadoop-client version = <your-hdfs-version> 最后,你须要将一些Spark的类和隐式转换导入到你的程序中。 通过例如以下语句: import org.apache.spark.SparkContext import org.apache.spark.SparkContext._ import org.apache.spark.SparkConf Java Spark1.2.1须要执行在Java6及更高版本号上。假设你正在使用Java8,Spark支持使用Lambda表达式简洁地编写函数。或者你能够使用在org.apache.spark.api.java.function包中的类。 要使用Java编写Spark应用程序。你须要加入一个Spark的依赖。Spark能够通过Maven中心库获得: groupId = org.apache.spark artifactId = spark-core_2.10 version = 1.2.1 此外,假设你想訪问一个HDFS集群。你须要依据你的HDFS版本号,加入一个hadoop-client的依赖。一些通用的HDFS版本号标签在第三方发行版页面列出。 groupId = org.apache.hadoop artifactId = hadoop-client version = <your-hdfs-version> 最后,你须要将Spark的类导入到你的程序中。加入例如以下行: importorg.apache.spark.api.java.JavaSparkContext import org.apache.spark.api.java.JavaRDD import org.apache.spark.SparkConf Python Spark1.2.1须要和Python2.6或者更高的版本号(但不是Python3)一起使用。它使用标准的CPython解释器,因此像NumPy这类的C语言库能够用。 要用Python的方式执行Spark应用程序。能够使用在Spark文件夹下的bin/spark-submit脚本。 这个脚本会装载Spark的Java和Scala库并同意你将程序提交到集群。 你也能够使用bin/pyspark来启动一个交互式Python Shell。 假设你想要訪问HDFS数据,你须要依据你的HDFS版本号使用一个PySpark的构建。一些通用的HDFS版本号标签在第三方发行版页面列出。针对通用的HDFS版本号的预先构建的包在Spark主页上也是可获得。 最后,你须要导入一些Spark相关的类到你的程序中。加入例如以下的行: from pyspark import SparkContext, SparkConf 初始化Spark Scala Spark程序须要做的第一件事情,就是创建一个SparkContext对象。它将告诉Spark怎样訪问一个集群。要创建一个SparkContext你首先须要建立一个SparkConf对象,这个对象包括你的程序的信息。 每一个JVM仅仅能有一个活动的SparkContext。在创建一个新的SparkContext之前你必须stop()活动的SparkContext。 val conf = newSparkConf().setAppName(appName).setMaster(master) new SparkContext(conf) appName是你的应用的名称。将会在集群的Web监控UI中显示。master參数,是一个用于指定所连接的Spark,Mesos or Mesos集群URL的字符串,也能够是一个如以下所描写叙述的用于在local模式执行的特殊字符串“local”。在实践中,当执行在一个集群上时,你不会想把master硬编码到程序中,而是启动spark-submit来接收它。然而。对于本地測试和单元測试。你能够通过“local”模式执行Spark。 Java Spark程序须要做的第一件事情,就是创建一个JavaSparkContext对象,它将告诉Spark怎样訪问一个集群。要创建一个SparkContext你首先须要建立一个SparkConf对象,这个对象包括你的程序的信息。 SparkConf conf = new SparkConf().setAppName(appName).setMaster(master); JavaSparkContext sc = newJavaSparkContext(conf); appName是你的应用的名称。将会在集群的Web监控UI中显示。 master參数,是一个用于指定所连接的Spark,Mesos or Mesos集群URL的字符串,也能够是一个如以下所描写叙述的用于在local模式执行的特殊字符串“local”。在实践中,当执行在一个集群上时,你不会想把master硬编码到程序中,而是启动spark-submit来接收它。 然而。对于本地測试和单元測试。你能够通过“local”模式执行Spark。 Python Spark程序须要做的第一件事情,就是创建一个JavaSparkContext对象。它将告诉Spark怎样訪问一个集群。 要创建一个SparkContext你首先须要建立一个SparkConf对象,这个对象包括你的程序的信息。 conf =SparkConf().setAppName(appName).setMaster(master) sc = SparkContext(conf=conf) appName是你的应用的名称,将会在集群的Web监控UI中显示。master參数,是一个用于指定所连接的Spark,Mesos or Mesos集群URL的字符串,也能够是一个如以下所描写叙述的用于在local模式执行的特殊字符串“local”。 在实践中。当执行在一个集群上时,你不会想把master硬编码到程序中,而是启动spark-submit来接收它。然而,对于本地測试和单元測试。你能够通过“local”模式执行Spark。 使用Shell Scala 在Spark shell中,一个特殊的解释器感知的SparkContext已经为你创建好了,变量名叫做sc。创建自己的SparkContext将不会生效。 你能够使用-master參数设置context连接到那个master,而且你能够使用-jars參数把用逗号分隔的一个jar包列表加入到classpath中。比如,假设在四核CPU上执行spark-shell,使用: $ ./bin/spark-shell --master local[4] 或者。同一时候在classpath中增加code.jar,使用: $ ./bin/spark-shell --master local[4] --jarscode.jar 想要获得完整的选项列表,执行spark-shell –help。 在背后,spark-shell调用更一般的spark-submit脚本。 Python 在PySpark shell中,一个特殊的解释器感知的SparkContext已经为你创建好了,变量名叫做sc。创建自己的SparkContext将不会生效。 你能够使用-master參数设置context连接到那个master,而且你能够使用—py-files參数把用逗号分隔的一个Python .zip,.egg或者.py文件列表加入到classpath中。比如。假设在四核CPU上执行bin/pyspark,使用: $ ./bin/pyspark --master local[4] 或者。同一时候将code.py加入到搜索路径中(为了以后使用import code),使用: $ ./bin/pyspark --master local[4] --py-filescode.py 想要获得完整的选项列表。执行pyspark –help。在背后。pyspark调用更一般的spark-submit脚本。 也能够在IPython中启动Pyspark shell。一个增强的Python解释器。PySpark要使用IPython1.0.0及其之后的版本号。要使用IPython,当执行bin/pyspark时要设置PYSPARK_DRIVER_PYTHON变量为ipython: $ PYSPARK_DRIVER_PYTHON=ipython ./bin/pyspark 你能够通过设置PYSPARK_DRIVER_PYTHON_OPTS參数来自己定义ipython命令。 比如,启动有PyLab支持的IPython Notebook支持: $PYSPARK_DRIVER_PYTHON=ipython PYSPARK_DRIVER_PYTHON_OPTS="notebook--pylab inline" ./bin/pyspark 弹性分布式数据集(RDDs) Spark环绕的概念是弹性分布式数据集(RDD)。是一个有容错机制并能够被并行操作的元素集合。眼下有两种创建RDDs的方法:并行化一个在你的驱动程序中已经存在的集合,或者引用在外部存储系统上的数据集。比如共享文件系统,HDFS,HBase。或者不论什么以Hadoop输入格式提供的数据源。 并行集合 Scala 并行集合是通过调用SparkContext的parallelize方法,在一个已经存在的集合上创建的(一个Scala Seq对象)。集合的对象将会被拷贝,创建出一个能够被并行操作的分布式数据集。比如,以下展示了如何创建一个含有数字1到5的并行集合: val data = Array(1, 2, 3, 4, 5) val distData = sc.parallelize(data) 一旦创建了分布式数据集(distData),就能够对其运行并行操作。比如,我们能够调用distData.reduce((a,b)=>a+b)来累加数组的元素。兴许我们会进一步地描写叙述对分布式数据集的操作。 并行集合的一个重要參数是分区数(the number of partitions)。表示数据集切分的份数。 Spark将在集群上为每一个分区数据起一个任务。 典型情况下,你希望集群的每一个CPU分布2-4个分区(partitions)。通常,Spark会尝试基于集群状况自己主动设置分区数。然而,你也能够进行手动设置,通过将分区数作为第二个參数传递给parallelize方法来实现。 (比如:sc.parallelize(data,10))。 注意:代码中的一些地方使用属于“分片(分区的近义词)”来保持向后兼容。 Java 并行集合是通过对存在于驱动程序中的集合调用JavaSparkContext的parallelize方法来构建的。构建时会拷贝集合中的元素。创建一个能够被并行操作的分布式数据集。 比如,这里演示了怎样创建一个包括数字1到5的并行集合: List<Integer> data = Arrays.asList(1, 2,3, 4, 5); JavaRDD<Integer> distData =sc.parallelize(data); 一旦创建了分布式数据集(distData)。就能够对其运行并行操作。 比如,我们能够调用distData.reduce((a,b)=>a+b)来累加数组的元素。 兴许我们会进一步地描写叙述对分布式数据集的操作。 注意:在本指南中,我们会常常使用简洁地Java8的lambda语法来指明Java函数,而在Java的旧版本号中,你能够实现org.apache.spark.api.java.function包中的接口。 以下我们将在把函数传递到Spark中描写叙述很多其它的细节。 并行集合的一个重要參数是分区数(the number of partitions)。表示数据集切分的份数。Spark将在集群上为每一个分区数据起一个任务。 典型情况下,你希望集群的每一个CPU分布2-4个分区(partitions)。 通常,Spark会尝试基于集群状况自己主动设置分区数。然而,你也能够进行手动设置。通过将分区数作为第二个參数传递给parallelize方法来实现。(比如:sc.parallelize(data,10))。注意:代码中的一些地方使用属于“分片(分区的近义词)”来保持向后兼容。 Python 并行集合是通过对存在于驱动程序中的迭代器(iterable)或集合(collection),调用SparkContext的parallelize方法来构建的。构建时会拷贝迭代器或集合中的元素,创建一个能够被并行操作的分布式数据集。比如,这里演示了怎样创建一个包括数字1到5的并行集合: data = [1, 2, 3, 4, 5] distData = sc.parallelize(data) 一旦创建了分布式数据集(distData),就能够对其运行并行操作。 比如,我们能够调用distData.reduce(lambda a,b:a+b)来累加列表的元素。兴许我们会进一步地描写叙述对分布式数据集的操作。 并行集合的一个重要參数是分区数(the number of partitions),表示数据集切分的份数。 Spark将在集群上为每一个分区数据起一个任务。典型情况下,你希望集群的每一个CPU分布2-4个分区(partitions)。通常。Spark会尝试基于集群状况自己主动设置分区数。然而,你也能够进行手动设置,通过将分区数作为第二个參数传递给parallelize方法来实现。 (比如:sc.parallelize(data,10))。注意:代码中的一些地方使用属于“分片(分区的近义词)”来保持向后兼容。 外部数据集 Scala Spark能够从Hadoop支持的不论什么存储源中构建出分布式数据集,包含你的本地文件系统,HDFS。Cassandre。HBase,Amazon S3等。 Spark支持text files。Sequence files,以及其它不论什么一种Hadoop InputFormat。 Text file RDDs的创建能够使用SparkContext的textFile方法。该方法接受一个文件的URI地址(或者是机器上的一个本地路径。或者是一个hdfs://,s3n://等URI)作为參数。并读取文件的每一行数据。放入集合中,以下是一个调用样例: scala> val distFile =sc.textFile("data.txt") distFile: RDD[String] = MappedRDD@1d4cee08 一旦创建完毕。就能够在distFile上运行数据集操作。比如,要相对全部行的长度进行求和,我们能够通过例如以下的map和reduce操作来完毕: distFile.map(s => s.length).reduce((a, b)=> a + b) Spark读文件时的一些注意事项: 1.假设文件使用本地文件系统上的路径。那么该文件必须在工作节点的同样路径下也能够訪问。 能够将文件复制到全部的worker节点上,或者使用network-mounted共享文件系统。 2.Spark的全部基于文件的输入方法。包含textFile。支持在文件夹上执行,压缩文件和通配符。比如。你能够使用textFile(”/my/directory”),textFile(“/my/directory/*.txt”),和textFile(“/my/directory/*.gz”)。 3.textFile方法也带有可选的第二个參数,用于控制文件的分区数。默认情况下,Spark会为文件的每个block创建一个分区。可是你也能够通过传入更大的值,来设置更高的分区数。注意,你设置的分区数不能比文件的块数小。 除了text文件,Spark的Scala API也支持其它几种数据格式: 1.SparkContext.wholeTextFiles能够让你读取包括多个小text文件的文件夹,而且每一个文件相应返回一个(filename,content)对。而相应的textFile方法,文件的每一行相应返回一条记录(record)。 2.对于Sequence文件。使用SparkContext的sequenceFile[K,V]方法。当中K和V分别相应文件里key和values的类型。这些类型必须是Hadoop的Writable接口的子类,如IntWritable和Text。另外。Spark同意你使用一些常见的Writables的原生类型;比如,sequenceFile[Int,String]会自己主动的转换为类型IntWritables和Texts。 3.对于其它的Hadoop InputFormats,你能够使用SparkContext.hadoopRDD方法。它能够接受一个随意类型的JobConf和输入格式类。key类和value类。像Hadoop Job设置输入源那样去设置这些參数就可以。对基于“新”的MapReduce API(org.apache.hadoop.mapreduce)的InputFormats,你也能够使用SparkContex.newHadoopRDD。 4.RDD.saveAsObjectFile和SparkContext.objectFile支持由序列化的Java对象组成的简单格式来保存RDD。 尽管这不是一种像Avro那样有效的序列化格式。可是她提供了一种能够存储不论什么RDD的简单方式。 Java Spark能够从Hadoop支持的不论什么存储源中构建出分布式数据集,包含你的本地文件系统。HDFS,Cassandre。HBase,Amazon S3等。 Spark支持text files,Sequence files,以及其它不论什么一种Hadoop InputFormat。 Text file RDDs的创建能够使用SparkContext的textFile方法。该方法接受一个文件的URI地址(或者是机器上的一个本地路径。或者是一个hdfs://,s3n://等URI)作为參数,并读取文件的每一行数据,放入集合中,以下是一个调用样例: JavaRDD<String> distFile =sc.textFile("data.txt"); 一旦创建完毕,就能够在distFile上运行数据集操作。比如。要相对全部行的长度进行求和。我们能够通过例如以下的map和reduce操作来完毕: distFile.map(s -> s.length()).reduce((a, b)-> a + b) Spark读文件时的一些注意事项: 1.假设文件使用本地文件系统上的路径,那么该文件必须在工作节点的同样路径下也能够訪问。能够将文件复制到全部的worker节点上,或者使用network-mounted共享文件系统。 2. Spark的全部基于文件的输入方法,包含textFile,支持在文件夹上执行,压缩文件和通配符。 比如,你能够使用textFile(”/my/directory”),textFile(“/my/directory/*.txt”)。和textFile(“/my/directory/*.gz”)。 3. textFile方法也带有可选的第二个參数,用于控制文件的分区数。默认情况下,Spark会为文件的每个block创建一个分区。可是你也能够通过传入更大的值,来设置更高的分区数。 注意。你设置的分区数不能比文件的块数小。 除了text文件,Spark的Java API也支持其它几种数据格式: 1. JavaSparkContext.wholeTextFiles能够让你读取包括多个小text文件的文件夹,而且每一个文件相应返回一个(filename,content)对。 而相应的textFile方法。文件的每一行相应返回一条记录(record)。 2.对于Sequence文件,使用SparkContext的sequenceFile[K,V]方法,当中K和V分别相应文件里key和values的类型。这些类型必须是Hadoop的Writable接口的子类,如IntWritable和Text。另外,Spark同意你使用一些常见的Writables的原生类型;比如。sequenceFile[Int,String]会自己主动的转换为类型IntWritables和Texts。 3.对于其它的Hadoop InputFormats,你能够使用JavaSparkContext.hadoopRDD方法。它能够接受一个随意类型的JobConf和输入格式类,key类和value类。 像Hadoop Job设置输入源那样去设置这些參数就可以。对基于“新”的MapReduce API(org.apache.hadoop.mapreduce)的InputFormats,你也能够使用JavaSparkContex.newHadoopRDD。 4. JavaRDD.saveAsObjectFile和JavaSparkContext.objectFile支持由序列化的Java对象组成的简单格式来保存RDD。尽管这不是一种像Avro那样有效的序列化格式,可是她提供了一种能够存储不论什么RDD的简单方式。 Python PySpark能够从Hadoop支持的不论什么存储源中构建出分布式数据集。包含你的本地文件系统,HDFS,Cassandre。HBase,Amazon S3等。Spark支持text files,Sequence files,以及其它不论什么一种Hadoop InputFormat。 Text file RDDs的创建能够使用SparkContext的textFile方法。该方法接受一个文件的URI地址(或者是机器上的一个本地路径,或者是一个hdfs://。s3n://等URI)作为參数,并读取文件的每一行数据。放入集合中,以下是一个调用样例: >>> distFile =sc.textFile("data.txt") 一旦创建完毕,就能够在distFile上运行数据集操作。比如。要相对全部行的长度进行求和,我们能够通过例如以下的map和reduce操作来完毕: distFile.map(lambda s: len(s)).reduce(lambda a,b: a + b) Spark读文件时的一些注意事项: 1.假设文件使用本地文件系统上的路径。那么该文件必须在工作节点的同样路径下也能够訪问。 能够将文件复制到全部的worker节点上,或者使用network-mounted共享文件系统。 2. Spark的全部基于文件的输入方法。包含textFile,支持在文件夹上执行,压缩文件和通配符。 比如,你能够使用textFile(”/my/directory”),textFile(“/my/directory/*.txt”),和textFile(“/my/directory/*.gz”)。 3. textFile方法也带有可选的第二个參数。用于控制文件的分区数。 默认情况下,Spark会为文件的每个block创建一个分区,可是你也能够通过传入更大的值。来设置更高的分区数。注意,你设置的分区数不能比文件的块数小。 除了text文件。Spark的Python API也支持其它几种数据格式: 1.JavaSparkContext.wholeTextFiles能够让你读取包括多个小text文件的文件夹,而且每一个文件相应返回一个(filename,content)对。而相应的textFile方法。文件的每一行相应返回一条记录(record)。 2.RDD.saveAsPickleFile和SparkContext.pickleFile支持由pickled Python对象组成的简单格式保存RDD。使用批量的方式处理pickle模块的对象序列化。默认批处理大小为10. 3.SequenceFile和Hadoop输入/输出格式 注意。此功能当前标识为试验性的,是为高级用户而提供的。在将来的版本号中,可能会由于支持基于SparkSQL的读写而被代替。在这样的情况下。SparkSQL是首选的方法。 Writable支持 PySpark的SequenceFile支持载入Java中的键值(key-value)对RDD。能够将Writable转换为主要的Java类型。而且通过Pyrolite。在结果Java对象上运行pickles序列化操作。当将一个键值对的RDD保存为SequenceFIle时。PySpark会对其进行反操作。 它会unpickles Python的对象为Java对象,然后再将它们转换为Writables。 下表中的Writables会被自己主动地转换: Writable Type Python Type Text unicode str IntWritable int FloatWritable float DoubleWritable float BooleanWritable bool BytesWritable bytearray NullWritable None MapWritable dict 数组不支持开箱(out-of-the-box)处理。当读或写数组时,用户须要指定自己定义的ArrayWritable子类。当写数组时,用户也须要指定自己定义的转换器(converters),将数组转换为自己定义的ArrayWritable子类。当读数组时,默认的转换器会将自己定义的ArrayWritable子类转换为Java的Object[]。然后被pickled成Python的元组。假设要获取包括基本数据类型的数组,Python的array.array的话,用户须要为该数组指定自己定义的转换器。 保存和载入SequenFiles 类似于text files,SequenceFiles能够被保存和载入到指定的路径下。 能够指定key和value的类型,但对标准的Writables类型则不须要指定。 >>> rdd = sc.parallelize(range(1,4)).map(lambda x: (x, "a" * x )) >>>rdd.saveAsSequenceFile("path/to/file") >>> sorted(sc.sequenceFile("path/to/file").collect()) [(1, u'a'), (2, u'aa'), (3, u'aaa')] 保存和载入其它的Hadoop输入/输出格式 PySpark也能够读不论什么Hadoop InputFormat或者写不论什么Hadoop OutputFormat,包含“新”和“旧”两个Hadoop MapReduce APIs。假设须要的话,能够将传递进来的一个Hadoop配置当成一个Python字典。这里有一个使用了Elasticsearch ESInputFormat的例子: SPARK_CLASSPATH=/path/to/elasticsearch-hadoop.jar./bin/pyspark >>> conf = {"es.resource" :"index/type"} # assumeElasticsearch is running on localhost defaults >>> rdd =sc.newAPIHadoopRDD("org.elasticsearch.hadoop.mr.EsInputFormat",\ "org.apache.hadoop.io.NullWritable","org.elasticsearch.hadoop.mr.LinkedMapWritable", conf=conf) >>> rdd.first() # the result is a MapWritable that isconverted to a Python dict (u'Elasticsearch ID', {u'field1': True, u'field2': u'Some Text', u'field3': 12345}) 注意。假设这个InputFormat仅仅是简单地依赖于Hadoop配置和/或输入路径。以及key和value的类型。它就能够非常easy地依据上面的表格进行转换,那么这样的方法应该能够非常好地处理这些情况。 假设你有一个定制序列化的二进制数据(比方载入自Cassandra/HBase的数据)。那么你首先要做的,是在Scala/Java側将数据转换为能够用Pyrolite的pickler处理的东西。 Converter特质提供了这一转换功能。简单地extend该特质。然后在convert方法中实现你自己的转换代码。 记住要确保该类,以及訪问你的InputFormat所需的依赖。都须要被打包到你的Spark作业的jar包,而且包括在PySpark的类路径中。 在Python例子和Converter例子上给出了带自己定义转换器的Cassandra/HBase的InputFormat和OutputFormat的使用例子。 RDD操作 RDDs支持两种操作:转换(transformations),能够从已有的数据集创建一个新的数据集;而动作(actions),在数据集上执行计算后,会向驱动程序返回一个值。 比如。map就是一种转换,它将数据集每个元素都传递给函数,并返回一个新的分布数据集来表示结果。 另一方面。reduce是一种动作。通过一些函数将全部的元素聚合起来,并将终于结果返回给驱动程序(只是另一个并行的reduceByKey,能返回一个分布式数据集)。 Spark中的全部转换都是惰性的,也就是说,它们并不会立即计算结果。相反的,它们仅仅是记住应用到基础数据集(比如一个文件)上的这些转换动作。仅仅有当发生一个要求返回结果给驱动程序的动作时。这些转换才会真正执行。这样的设计让Spark更加有效率地执行。 比如,我们对map操作创建的数据集进行reduce操作时,仅仅会向驱动返回reduce操作的结果,而不是返回更大的map操作创建的数据集。 默认情况下,每个转换过的RDD都会在你对它运行一个动作时被又一次计算。 只是,你也能够使用持久化或者缓存方法。把一个RDD持久化到内存中。在这样的情况下。Spark会在集群中保存相关元素。以便你下次查询这个RDD时。能更高速地訪问。 对于把RDDs持久化到磁盘上,或在集群中拷贝到多个节点也是支持的。 基础操作 Scala 为了描写叙述RDD的基础操作,能够考虑以下的简单程序: val lines = sc.textFile("data.txt") val lineLengths = lines.map(s => s.length) val totalLength = lineLengths.reduce((a, b)=> a + b) 第一行通过一个外部文件定义了一个主要的RDD。这个数据集未被载入到内存。也未在上面执行操作:lines只指向这个文件。第二行定义了lineLengths作为map转换结果。此外,因为惰性,不会马上计算lineLengths。最后。我们执行reduce。这是一个动作。这时候,Spark才会将这个计算拆分成不同的task,并执行在独立的机器上,而且每台机器执行它自己的map部分和本地的reducatin。只返回它的结果给驱动程序。 假设我们希望以后能够复用lineLengths,能够加入: lineLengths.persist() 在reduce之前,这将导致lineLengths在第一次被计算之后,被保存在内存中。 Java 为了描写叙述RDD的基础操作,能够考虑以下的简单程序: JavaRDD<String> lines =sc.textFile("data.txt"); JavaRDD<Integer> lineLengths =lines.map(s -> s.length()); int totalLength = lineLengths.reduce((a, b)-> a + b); 第一行通过一个外部文件定义了一个主要的RDD。这个数据集未被载入到内存,也未在上面执行操作:lines只指向这个文件。 第二行定义了lineLengths作为map转换结果。 此外,因为惰性,不会马上计算lineLengths。最后,我们执行reduce,这是一个动作。 这时候。Spark才会将这个计算拆分成不同的task,并执行在独立的机器上。而且每台机器执行它自己的map部分和本地的reducatin,只返回它的结果给驱动程序。 假设我们希望以后能够复用lineLengths,能够加入: lineLengths.persist(); 在reduce之前,这将导致lineLengths在第一次被计算之后。被保存在内存中。 Python 为了描写叙述RDD的基础操作。能够考虑以下的简单程序: lines = sc.textFile("data.txt") lineLengths = lines.map(lambda s: len(s)) totalLength = lineLengths.reduce(lambda a, b: a+ b) 第一行通过一个外部文件定义了一个主要的RDD。 这个数据集未被载入到内存。也未在上面执行操作:lines只指向这个文件。 第二行定义了lineLengths作为map转换结果。 此外,因为惰性。不会马上计算lineLengths。最后。我们执行reduce,这是一个动作。这时候,Spark才会将这个计算拆分成不同的task,并执行在独立的机器上,而且每台机器执行它自己的map部分和本地的reducatin,只返回它的结果给驱动程序。 假设我们希望以后能够复用lineLengths,能够加入: lineLengths.persist() 在reduce之前。这将导致lineLengths在第一次被计算之后,被保存在内存中。 把函数传递到Spark Scala Spark的API,在非常大程度上依赖于把驱动程序中的函数传递到集群上执行。 这有两种推荐的实现方式: ●使用匿名函数的语法,这能够用来替换简短的代码。 ●使用全局单例对象的静态方法。比方,你能够定义函数对象objectMyFunctions。然后传递该对象的方法MyFunction.func1,例如以下所看到的: object MyFunctions { deffunc1(s: String): String = { ... } } myRdd.map(MyFunctions.func1) 注意:因为可能传递的是一个类实例方法的引用(而不是一个单例对象)。在传递方法的时候,应该同一时候传递包括该方法的对象。比方,考虑: class MyClass { deffunc1(s: String): String = { ... } defdoStuff(rdd: RDD[String]): RDD[String] = { rdd.map(func1) } } 这里。假设我们创建了一个类实例new MyClass,而且调用了实例的doStuff方法,该方法中的map处调用了这个MyClass实例的func1方法。所以须要将整个对象传递到集群中。类似于写成:rdd.map(x=>this.func1(x))。 类似地,訪问外部对象的字段时将引用整个对象: class MyClass { valfield = "Hello" defdoStuff(rdd: RDD[String]): RDD[String] = { rdd.map(x => field + x) } } 等同于写成rdd.map(x=>this.field+x),引用了整个this。为了避免这样的问题,最简单的方式是把field复制到本地变量,而不是去外部訪问它: def doStuff(rdd: RDD[String]): RDD[String] = { valfield_ = this.field rdd.map(x => field_ + x) } Java Spark的API,在非常大程度上依赖于把驱动程序中的函数传递到集群上执行。在Java中,函数由那些实现了org.apache.spark.api.java.function包中的接口的类表示。 有两种创建这种函数的方式: ●在你自己的类中实现Function接口,能够是匿名内部类,或者命名类。而且传递类的一个实例到Spark。 ●在Java8中,使用lambda表达式来简明地定义函数的实现。 为了保持简洁性,本指南中大量使用了lambda语法,这在长格式中非常easy使用全部同样的APIs。 比方,我们能够把上面的代码写成: JavaRDD<String> lines =sc.textFile("data.txt"); JavaRDD<Integer> lineLengths =lines.map(new Function<String, Integer>() { publicInteger call(String s) { return s.length(); } }); int totalLength = lineLengths.reduce(newFunction2<Integer, Integer, Integer>() { publicInteger call(Integer a, Integer b) { return a + b; } }); 或者。假设不方便编写内联函数的话,能够写成: class GetLength implements Function<String,Integer> { publicInteger call(String s) { return s.length(); } } class Sum implements Function2<Integer,Integer, Integer> { publicInteger call(Integer a, Integer b) { return a + b; } } JavaRDD<String> lines = sc.textFile("data.txt"); JavaRDD<Integer> lineLengths =lines.map(new GetLength()); int totalLength = lineLengths.reduce(newSum()); 注意,Java中的匿名内部类也能够訪问封闭域中的变量,仅仅要这些变量标识为final就可以。Spark会像处理其它语言一样,将这些变量复制到每一个工作节点上。 Python Spark的API,在非常大程度上依赖于把驱动程序中的函数传递到集群上执行。 有三种推荐方法能够使用: ●使用Lambda表达式来编写能够写成一个表达式的简单函数(Lambdas不支持没有返回值的多语句函数或表达式)。 ●Spark调用的函数中的Local defs,能够用来取代更长的代码。 ●模块中的顶级函数。 比如,假设想传递一个支持使用lambda表达式的更长的函数,能够考虑下面代码: """MyScript.py""" if __name__ == "__main__": defmyFunc(s): words = s.split(" ") return len(words) sc =SparkContext(...) sc.textFile("file.txt").map(myFunc) 注意:因为可能传递的是一个类实例方法的引用(而不是一个单例对象(singleton object)),在传递方法的时候,应该同一时候传递包括该方法的对象。比方,考虑: class MyClass(object): deffunc(self, s): return s defdoStuff(self, rdd): return rdd.map(self.func) 这里。假设我们创建了一个类实例new MyClass,而且调用了实例的doStuff方法。该方法中的map处调用了这个MyClass实例的func1方法,所以须要将整个对象传递到集群中。 类似地,訪问外部对象的字段时将引用整个对象: class MyClass(object): def__init__(self): self.field = "Hello" defdoStuff(self, rdd): return rdd.map(lambda s: self.field + x) 为了避免这样的问题。最简单的方式是把field复制到本地变量。而不是去外部訪问它: def doStuff(self, rdd): field= self.field return rdd.map(lambda s: field + x) 理解闭包 关于Spark的一个更困难的问题是理解当在一个集群上运行代码的时候,变量和方法的范围以及生命周期。改动范围之外变量的RDD操作常常是造成混乱的源头。在以下的样例中我们看一下使用foreach()来添加一个计数器的代码,只是相同的问题也可能有其它的操作引起。 样例 考虑以下的单纯的RDD元素求和,依据是否执行在一个虚拟机上。它们的行为全然不同。一个寻常的样例是在local模式(--master=local[n])下执行Spark对照将Spark程序部署到一个集群上(比如通过spark-submit提交到YARN)。 Scala var counter = 0 var rdd = sc.parallelize(data) // Wrong: Don't do this!! rdd.foreach(x => counter += x) println("Counter value: " + counter) Java int counter = 0; JavaRDD<Integer> rdd =sc.parallelize(data); // Wrong: Don't do this!! rdd.foreach(x -> counter += x); println("Counter value: " + counter); Python counter = 0 rdd = sc.parallelize(data) # Wrong: Don't do this!! rdd.foreach(lambda x: counter += x) print("Counter value: " + counter) 本地模式VS集群模式 基本的挑战是,上述代码的行为是没有定义的。在使用单个JVM的本地模式中。上面的代码会在RDD中计算值的总和并把它存储到计数器中。这是由于RDD和计数器变量在驱动节点的同一个内存空间中。 然而,在集群模式下。发生的事情更为复杂,上面的代码可能不会依照目的工作。 要运行作业,Spark将RDD操作分成任务——每个任务由一个运行器操作。在运行前。Spark计算闭包。闭包是指运行器要在RDD上进行计算时必须对运行节点可见的那些变量和方法(在这里是foreach())。这个闭包被序列化并发送到每个运行器。在local模式下,仅仅有一个运行器因此全部东西都分享同一个闭包。然而在其它的模式中,就不是这个情况了,运行在不同工作节点上的运行器有它们自己的闭包的一份拷贝。 这里发生的事情是闭包中的变量被发送到每一个运行器都是被拷贝的,因此,当计数器在foreach函数中引用时,它不再是驱动节点上的那个计数器了。在驱动节点的内存中仍然有一个计数器。但它对运行器来说不再是可见的了! 运行器仅仅能看到序列化闭包中的拷贝。因此,计数器终于的值仍然是0,由于全部在计数器上的操作都是引用的序列化闭包中的值。 在这样的情况下要确保一个良好定义的行为。应该使用累加器。Spark中的累加器是一个专门用来在运行被分散到一个集群中的各个工作节点上的情况下安全更新变量的机制。 本指南中的累加器部分会做具体讨论。 一般来说。闭包-构造像循环或者本地定义的方法。不应该用来改变一些全局状态。Spark未定义或者是保证改变在闭包之外引用的对象的行为。 一些这样做的代码可能会在local模式下起作用。但那不过个偶然,这种代码在分布式模式下是不会依照期望工作的。假设须要一些全局的參数,能够使用累加器。 打印RDD中的元素 还有一个常见的使用方法是使用rdd.foreach(println)方法或者rdd.map(println)方法试图打印出RDD中的元素。 在一台单一的机器上,这样会产生期望的输出并打印出RDD中的元素。然而。在集群模式中。被运行器调用输出到stdout的输出如今被写到了运行器的stdout,并非在驱动上的这一个,因此驱动上的stdout不会显示这些信息。要在驱动上打印全部的元素,能够使用collect()方法首先把RDD取回到驱动节点如:rdd.collect().foreach(println)。然而,这可能导致驱动内存溢出。由于collect()将整个RDD拿到了单台机器上;假设你仅仅须要打印非常少几个RDD的元素,一个更安全的方法是使用take()方法:rdd.take(100).foreach(println)。 键值对的使用 Scala 尽管,在包括随意类型的对象的RDDs中,能够使用大部分的Spark操作。但也有一些特殊的操作仅仅能在键值对的RDDs上使用。最常见的一个就是分布式的洗牌(shuffle)操作,诸如基于key值对元素进行分组或聚合的操作。 在Scala中。包括二元组(Tuple2)对象(能够通过简单地(a,b)代码,来构建内置于语言中的元组的RDDs支持这些操作)。仅仅要你在程序中导入了org.apache.spark.SparkContext._,就能进行隐式转换。 PairRDDFunction类支持键值对的操作,假设你导入了隐式转换。该类型就能自己主动地对元组RDD的元素进行转换。 比方。下列代码在键值对上使用了reduceByKey操作。来计算在一个文件里每行文本出现的总次数: val lines = sc.textFile("data.txt") val pairs = lines.map(s => (s, 1)) val counts = pairs.reduceByKey((a, b) => a +b) 我们也能够使用counts.sortByKey(),比方,将键值对以字典序进行排序。最后使用counts.collect()转换成对象的数组形式,返回给驱动程序。 注意:在键值对操作中。假设使用了自己定义对象作为建,你必须确保该对象实现了自己定义的equals()和相应的hashCode()方法。很多其它详情请查看Object.hashCode()文档大纲中列出的规定。 Java 尽管。在包括随意类型的对象的RDDs中,能够使用大部分的Spark操作。但也有一些特殊的操作仅仅能在键值对的RDDs上使用。最常见的一个就是分布式的洗牌(shuffle)操作。诸如基于key值对元素进行分组或聚合的操作。 在java中,能够使用Scala标准库中的scala.Tuple2类来表示键值对,你能够简单地调用new Tuple2(a,b)来创建一个元组。然后使用tuple._1()和tuple._2()方法来訪问元组的字段。 使用JavaPairRDD来表示键值对RDDs。你能够使用指定版本号的map操作,从JavaRDDs构建JavaPairRDDs。比方mapToPair和flatMapToPair。 JavaPairRDD支持标准的RDD函数,也支持特殊的键值函数。 比如。以下的代码在键值(key-value)对上使用reduceByKey操作来计算在一个文件里每行文本出现的总次数: JavaRDD<String> lines =sc.textFile("data.txt"); JavaPairRDD<String, Integer> pairs =lines.mapToPair(s -> new Tuple2(s, 1)); JavaPairRDD<String, Integer> counts =pairs.reduceByKey((a, b) -> a + b); 我们也能够使用counts.sortByKey(),比如。将键值对以字典序(alphabetically)进行排序。 后调用counts.collect()转换成对象的数组形式,返回给驱动程序(driverprogram)。 注意:在键值(key-value)对操作中。假设使用了自己定义对象作为键,你必须确保该对象实现了自己定义的equals()和相应的hashCode()方法。很多其它详情请查看Object.hashCode() documentation文档大纲中列出的规定。 Python 尽管在包括随意类型的对象的RDDs中,能够使用大部分的Spark操作。但也有一 些特殊的操作仅仅能在键值(key-value)对的RDDs上使用。 最常见的一个就是分布式的洗牌("shuffle")操作,诸如基于key值对元素进行分组或聚合的操作。 在Python中,RDDs支持的操作包括Python内置的元组(tuples)操作,比方(1, 2)。你能够简单地创建这种元组,然后调用期望的操作。 比如。以下的代码在键值(key-value)对上使用reduceByKey操作来计算在一个文件里每行文本出现的总次数: lines = sc.textFile("data.txt") pairs = lines.map(lambda s: (s, 1)) counts = pairs.reduceByKey(lambda a, b: a + b) 我们也能够使用counts.sortByKey()。比如,依照字典序(alphabetically)排序键值对。最后调用counts.collect()转换成对象的数组形式,返回给驱动程序(driver program)。 转换 下表中列出了Spark支持的一些常见的转换(Transformations)。详情请參考RDDAPI文档(Scala, Java, Python)和pair RDD函数文档(Scala, Java)。 Transformation Meaning map(func) 返回一个新分布式数据集。由每个输入元素经过func函数转换后组成。 filter(func) 返回一个新数据集。由经过func函数计算后返回值为true的输入元素组成。 flatMap(func) 类似于map,可是每个输入元素能够被映射为0或多个输出元素(因此func应该返回一个序列(Seq),而不是单一元素)。 mapPartitions(func) 类似于map。但独立地在RDD的每个分区(partition,相应块(block))上执行,当在类型为T的RDD上执行时,func的函数类型必须是Iterator<T> => Iterator<U>。 mapPartitionsWithIndex(func) 类似于mapPartitions,但func带有一个整数參数表示分区(partition)的索引值。 当在类型为T的RDD上执行时,func的函数类型必须是(Int, Iterator<T>) => Iterator<U>。 sample(withReplacement,fraction,seed) 依据fraction指定的比例。对数据进行採样。能够选择是否用随机数进行替换,seed用于指定随机数生成器种子。 union(otherDataset) 返回一个新的数据集,新数据集由源数据集和參数数据集的元素联合(union)而成。 intersection(otherDataset) 返回一个新的数据集,新数据集由源数据集和參数数据集的元素的交集(intersection)组成。 distinct([numTasks])) 返回一个新的数据集。新数据集由源数据集过滤掉多余的反复元素仅仅保留一个而成。 groupByKey([numTasks]) 在一个(K, V)对的数据集上调用,返回一个(K, Iterable<V>)对的数据集。 注意:假设你想在每一个key上分组运行聚合(如总和或平均值)操作,使用reduceByKey或combineByKey会产生更好的性能。 注意:默认情况下,输出的并行数依赖于父RDD(parent RDD)的分区数(number of partitions)。你能够通过传递可选的第二个參数numTasks来设置不同的任务数。 reduceByKey(func, [numTasks]) 在一个(K, V)对的数据集上调用时。返回一个(K, V)对的数据集。使用指定的reduce函数func将同样key的值聚合到一起,该函数的类型必须是(V,V) => V。类似groupByKey。reduce的任务个数是能够通过第二个可选參数来配置的。 aggregateByKey(zeroValue)(seqOp,combOp, [numTasks]) 在一个(K, V)对的数据集上调用时,返回一个(K, U)对的数据集,对每一个键的值使用给定的组合函数(combine functions)和一个中性的“零”值进行聚合。同意聚合后的值类型不同于输入的值类型。从而避免了不必要的内存分配。如同groupByKey,能够通过设置第二个可选參数来配置reduce任务的个数。 sortByKey([ascending], [numTasks]) 在一个(K, V)对的数据集上调用,当中,K必须实现Ordered,返回一个 依照Key进行排序的(K, V)对数据集。升序或降序由布尔參数ascending决定。 join(otherDataset, [numTasks]) 在类型为(K, V)和(K, W)类型的数据集上调用时,返回一个同样key 相应的全部元素对在一起的(K, (V, W))对的数据集。也支持外联(Outer joins),通过使用leftOuterJoin和rightOuterJoin. cogroup(otherDataset, [numTasks]) 在类型为(K, V)和(K, W)的数据集上调用,返回一个(K, Iterable<V>, Iterable<W>)元组(tuples)的数据集。这个操作也能够称之为groupWith。 cartesian(otherDataset) 笛卡尔积。在类型为T和U类型的数据集上调用时,返回一个(T, U)对的数据集(全部元素交互进行笛卡尔积)。 pipe(command,[envVars]) 以管道(Pipe)方式将RDD的各个分区(partition)传递到shell命令,比方一个Perl或bash脚本中。RDD的元素会被写入进程的标准输入(stdin),而且将作为字符串的RDD(RDD of strings),在进程的标准输出(stdout)上输出一行行数据。 coalesce(numPartitions) 把RDD的分区数减少到指定的numPartitions。 过滤掉一个大数据集 之后再运行操作会更加有效。 repartition(numPartitions) 随机地对RDD的数据又一次洗牌(Reshuffle),以便创建很多其它或更少的分区,对它们进行平衡。总是对网络上的全部数据进行洗牌(shuffles)。 repartitionAndSortWithinPartitions(partitioner) 依据给定的分区器对RDD进行又一次分区。在每一个结果分区中。将记录依照key值进行排序。这在每一个分区中比先调用repartition再排序效率更高。由于它能够推动排序到分牌机器上。 动作 下表中列出了Spark支持的一些常见的动作(actions)。详情请參考RDD API文档 (Scala,Java, Python)和pair RDD函数文档(Scala, Java)。 Action Meaning reduce(func) 通过函数func (接受两个參数。返回一个參数),聚集数据集中的全部元素。 该函数应该是可交换和可结合的,以便它能够正确地并行计算。 collect() 在驱动程序中。以数组的形式。返回数据集的全部元素。这一般会在使用filter或者其他操作,并返回一个足够小的数据子集后再使用会比較实用 count() 返回数据集的元素的个数。 first() 返回数据集的第一个元素。(类似于take(1)). take(n) 返回一个由数据集的前n个元素组成的数组。 注意,这个操作眼下不能并行运行。而是由驱动程序(driver program)计算全部的元素。 takeSample(withReplacement,num, [seed]) 返回一个数组,由数据集中随机採样的num个元素组成。能够选择是否用随机数替换不足的部分。能够指定可选參数seed。预先指定一个随机数生成器的种子。 takeOrdered(n,[ordering]) 返回一个由数据集的前n个元素,并使用自然顺序或定制顺序对这些元素进行排序。 saveAsTextFile(path) 将数据集的元素,以text file (或text file的集合)的形式,保存到本地文件系统的指定文件夹,Spark会对每一个元素调用toString方法,然后转换为文件里的文本行。 saveAsSequenceFile(path) (Java and Scala) 将数据集的元素,以Hadoop sequencefile的格式,保存到各种文件系统的指定路径下,包含本地系统。HDFS或者不论什么其他hadoop支持的文件系统。该方法仅仅能用于键值(key-value)对的RDDs。或者实现了Hadoop的Writable接口的情况下。在Scala中。也能够用于支持隐式转换为Writable的类型。(Spark包含了基本类型的转换。比如Int,Double。String,等等)。 saveAsObjectFile(path) (Java and Scala) 以简单地Java序列化方式将数据集的元素写入指定的路径,相应的能够用SparkContext.objectFile()载入该文件。 countByKey() 仅仅对(K,V)类型的RDD有效。返回一个(K,Int)对的hashmap,当中(K,Int)对表示每个key相应的元素个数。 foreach(func) 在数据集的每个元素上,执行func函数。这通经常使用于副作用(sideeffects)。比如更新一个累加器变量(accumulator variable)(參见下文)。或者和外部存储系统进行交互. 洗牌操作 Spark触发一个事件后进行的一些操作成为洗牌。洗牌是Spark又一次分配数据的机制,这样它就能够跨分区分组。这通常涉及在运行器和机器之间复制数据,这就使得洗牌是一个复杂和高代价的操作。 背景 为了理解在洗牌的时候发生了什么,我们能够考虑reduceByKey操作的样例。 reduceByKey操作产生了一个新的RDD,在这个RDD中,全部的单个的值被组合成了一个元组,key和运行一个reduce函数后的结果中与这个key有关的全部值。面临的挑战是一个key的全部的值并不都是在同一个分区上的,甚至不是一台机器上的,可是他们必须是可连接的以计算结果。 在Spark中。数据通常是不会跨分区分布的,除非是在一个特殊的地方为了某种特定的目的。在计算过程中,单个任务将在单个分区上操作——因此,为了组织全部数据运行单个reduceByKey中的reduce任务,Spark须要运行一个all-to-all操作。 它必须读取全部分区,找到全部key的值,并跨分区把这些值放到一起来计算每一个key的终于结果——这就叫做洗牌。 虽然在每一个分区中新洗牌的元素集合是确定性的,分区本身的顺序也相同如此。这些元素的顺序就不一定是了。 假设期望在洗牌后获得可预測的有序的数据。能够使用: mapPartitions来排序每一个分区。比如使用.sorted repartitionAndSortWithinPartitions在又一次分区的同一时候有效地将分区排序 sortBy来创建一个全局排序的RDD 能够引起洗牌的操作有重分区比如repartition和coalesce,‘ByKey操作(除了计数)像groupByKey和reduceByKey,还有join操作比如cogroup和join。 性能影响 Shuffle是一个代价高昂的操作。由于它调用磁盘I/O。数据序列化和网络I/O。要组织shuffle的数据,Spark生成一个任务集合——map任务来组织数据,并使用一组reduce任务集合来聚合它。它的命名来自与MapReduce,但并不直接和Spark的map和reduce操作相关。 在内部,单个的map任务的结果被保存在内存中。直到他们在内存中存不下为止。 然后,他们基于目标分区进行排序。并写入到一个单个的文件里。 在reduce这边。任务读取相关的已经排序的块。 某些shuffle操作会消耗大量的堆内存。由于他们用在内存中的数据结构在转换操作之前和之后都要对数据进行组织。特别的。reduceByKey和aggregateByKey在map側创建这些结构,‘ByKey操作在reduce側生成这些结构。当数据在内存中存不下时。Spark会将他们存储到磁盘,造成额外的磁盘开销和添加垃圾收集。 Shuffle也会在磁盘上产生大量的中间文件。 在Spark1.3中。这些文件直到Spark停止执行时才会从Spark的暂时存储中清理掉,这意味着长时间执行Spark作业会消耗可观的磁盘空间。这些做了之后假设lineage又一次计算了。那shuffle不须要又一次计算了。 在配置Spark上下文时,暂时存储文件夹由spark.local.dir配置參数指定。 Shuffle的行为能够通过调整各种配置參数来调整。请看Spark配置指南中的Shuffle Behavior部分。 RDD持久化 Spark最重要的一个功能,就是在不同操作间,将一个数据集持久化(persisting) (或缓存(caching))到内存中。 当你持久化(persist)一个RDD。每个节点都会把它计算的全部分区(partitions)存储在内存中,并在对数据集(或者衍生出的数据集)运行其它动作(actioins)时重用。这将使得兴许动作(actions)的运行变得更加迅速(通常快10倍)。缓存(Caching)是用Spark构建迭代算法和高速地交互使用的关键。 你能够使用persist()或cache()方法来持久化一个RDD。 在首次被一个动作(action)触发计算后。它将会被保存到节点的内存中。 Spark的缓存是带有容错机制的,假设RDD丢失不论什么一个分区的话,会自己主动地用原先构建它的转换(transformations)操作来又一次进行计算。 此外。每个被持久化的RDD都能够用不同的存储级别(storage level)进行存储。比方,同意你持久化数据集到硬盘,以序列化的Java对象(节省空间)存储到内存。跨节点复制,或者以off-heap的方式存储在Tachyon。这些级别的选择,是通过将一个StorageLevel对象(Scala Java, Python)传递到persist()方法中进行设置的。cache()方法是使用默认存储级别的快捷方法,也就是StorageLevel.MEMORY_ONLY (将反序列化(deserialized)的对象存入内存)。 完整的可选存储级别例如以下: Storage Level Meaning MEMORY_ONLY 将RDD以反序列化(deserialized)的Java对象存储到JVM。假设RDD不能被内存装下。一些分区将不会被缓存,而且在须要的时候被又一次计算。这是默认的级别。 MEMORY_AND_DISK 将RDD以反序列化(deserialized)的Java对象存储到JVM。 假设RDD不能被内存装下,超出的分区将被保存在硬盘上,而且在须要时被读取。 MEMORY_ONLY_SER 将RDD以序列化(serialized)的Java对象进行存储(每一分区占用一个字节数组)。 通常来说,这比将对象反序列化(deserialized)的空间利用率更高,尤其当使用高速序列化器(fast serializer),但在读取时会比較耗CPU。 MEMORY_AND_DISK_SER 类似于MEMORY_ONLY_SER。可是把超出内存的分区将存储在硬盘上而不是在每次须要的时候又一次计算。 DISK_ONLY 仅仅将RDD分区存储在硬盘上。 MEMORY_ONLY_2, MEMORY_AND_DISK_2, etc. 与上述的存储级别一样,可是将每个分区都拷贝到两个集群节点上。 OFF_HEAP (experimental) 以序列化的格式(serialized format)将RDD存储到Tachyon。相比于MEMORY_ONLY_SER。OFF_HEAP减少了垃圾收集(garbage collection)的开销。并使executors变得更小并且共享内存池,这在大堆(heaps)和多应用并行的环境下是很吸引人的。并且,因为RDDs驻留于Tachyon中,executor的崩溃不会导致内存中的缓存丢失。在这样的模式下,Tachyon中的内存是可丢弃的。因此。Tachyon不会尝试重建一个在内存中被清除的分块。 注意:在Python中。存储对象时总是使用Pickle库来序列化(serialized),而无论你是否选择了一个序列化的级别。 Spark也会自己主动地持久化一些洗牌(shuffle)操作(比方,reduceByKey )的中间数据,即使用户没有调用persist。 这么做是为了避免在一个节点上的洗牌(shuffle)过程失败时,又一次计算整个输入。我们仍然建议用户在结果RDD上调用persist。假设希望重用它的话。 怎样选择存储级别? Spark的存储级别旨在满足内存使用和CPU效率权衡上的不同需求。我们建议通过下面方法进行选择: ●假设你的RDDs能够非常好的与默认的存储级别(MEMORY_ONLY)契合,就不须要做不论什么改动了。 这已经是CPU使用效率最高的选项,它使得RDDs的操作尽可能的快。 ●假设不行,试着使用MEMORY_ONLY_SER,而且选择一个高速序列化库使对象在有比較高的空间使用率(space-efficient)的情况下,依旧能够较快被訪问。 ●尽可能不要存储到硬盘上。除非计算数据集的函数的计算量特别大。或者它们过滤了大量的数据。否则。又一次计算一个分区的速度,可能和从硬盘中读取差点儿相同快。 ●假设你想有高速的故障恢复能力,使用复制存储级别(比如:用Spark来响应web应用的请求)。 全部的存储级别都有通过又一次计算丢失的数据来恢复错误的容错机制。可是复制的存储级别能够让你在RDD上持续地执行任务,而不须要等待丢失的分区被又一次计算。 ●在大量的内存或多个应用程序的环境下。试验性的OFF_HEAP模式具有下面几个长处: o同意多个executors共享Tachyon中同样的内存池。 o极大地减少了垃圾收集器(garbage collection)的开销。 o即使个别的executors崩溃了,缓存的数据也不会丢失。 移除数据 Spark会自己主动监控各个节点上的缓存使用情况,并使用近期最少使用算法(least-recently-used (LRU))删除老的数据分区。假设你想手动移除一个RDD,而不是等它自己主动从缓存中清除。能够使用RDD.unpersist()方法。 共享变量 一般来说。当一个函数被传递给一个在远程集群节点上执行的Spark操作(比如map或reduce)时,它操作的是这个函数用到的全部变量的独立拷贝。这些变量会被复制到每一台机器。并且在远程机器上对变量的全部更新都不会被传播回驱动程序。 通常看来。读-写任务间的共享变量显然不够高效。然而。Spark还是为两种常见的使用模式。提供了两种有限的共享变量:广播变量(broadcast variables)和累加器(accumulators)。 广播变量 广播变量同意程序猿保留一个仅仅读的变量。缓存在每一台机器上,而不是每一个任务保存一份拷贝。它们能够这样被使用,比如,以一种高效的方式给每一个节点一个大的输入数据集。Spark会尝试使用一种高效的广播算法来传播广播变量。从而降低通信的代价。 Spark动作的执行是通过一个阶段的集合,通过分布式的Shuffle操作分离。Spark自己主动广播在每一个阶段里任务须要的共同数据。以这样的方式广播的数据以序列化的形式缓存并在执行每一个任务之前进行反序列化。这意味着显式地创建广播变量仅仅在当多个阶段之间须要同样的数据或者是当用反序列化的形式缓存数据特别重要的时候。 广播变量是通过调用SparkContext.broadcast(v)方法从变量v创建的。广播变量是一个v的封装器,它的值能够通过调用value方法获得。 例如以下代码展示了这个: Scala scala>val broadcastVar = sc.broadcast(Array(1, 2, 3)) broadcastVar:org.apache.spark.broadcast.Broadcast[Array[Int]] = Broadcast(0) scala>broadcastVar.value res0:Array[Int] = Array(1, 2, 3) Java Broadcast<int[]>broadcastVar = sc.broadcast(new int[] {1, 2, 3}); broadcastVar.value(); // returns[1, 2, 3] Python >>>broadcastVar = sc.broadcast([1, 2, 3]) <pyspark.broadcast.Broadcastobject at 0x102789f10> >>>broadcastVar.value [1, 2, 3] 在广播变量被创建后。它应该在集群执行的不论什么函数中,取代v值被调用,从而v值不须要被再次传递到这些节点上。 另外,对象v不能在广播后改动。这样能够保证全部节点具有同样的广播变量的值(比方,兴许假设变量被传递到一个新的节点)。 累加器 累加器是一种仅仅能通过具有结合性的操作(associative operation)进行“加(added)”的变量。因此能够高效地支持并行。它们能够用来实现计数器(如MapReduce中)和求和器。Spark原生就支持数值类型的累加器,开发人员能够自己加入新的支持类型。 假设创建了一个命名的累加器(accumulators)。这些累加器将会显示在Spark UI界面上。这对于了解当前执行阶段(stages)的进展情况是很实用的(注意:这在Python中尚未支持)。 一个累加器能够通过调用SparkContext.accumulator(v)方法从一个初始值v中创建。 执行在集群上的任务,能够通过使用add方法或+=操作符(在Scala和Python)来给它加值。然而。它们不能读取这个值。仅仅有驱动程序能够使用value方法来读取累加器的值。 下面代码展示了怎样利用一个累加器,将一个数组里面的全部元素相加: Scala scala> val accum = sc.accumulator(0,"My Accumulator") accum: spark.Accumulator[Int] = 0 scala> sc.parallelize(Array(1, 2, 3,4)).foreach(x => accum += x) ... 10/09/29 18:41:08 INFO SparkContext: Tasksfinished in 0.317106 s scala> accum.value res2: Int = 10 Java Accumulator<Integer> accum =sc.accumulator(0); sc.parallelize(Arrays.asList(1, 2, 3,4)).foreach(x -> accum.add(x)); // ... // 10/09/29 18:41:08 INFO SparkContext: Tasksfinished in 0.317106 s accum.value(); // returns 10 Python >>> accum = sc.accumulator(0) Accumulator<id=0, value=0> >>> sc.parallelize([1, 2, 3,4]).foreach(lambda x: accum.add(x)) ... 10/09/29 18:41:08 INFO SparkContext: Tasksfinished in 0.317106 s scala> accum.value 10 尽管代码能够使用内置支持的Int类型的累加器。但程序猿也能够通过子类化(subclassing) AccumulatorParam来创建自己的类型。AccumulatorParam接口有两个方法:zero,为你的数据类型提供了一个“零值(zero value)”,以及addInPlace提供了两个值相加的方法。比方,如果我们有一个表示数学上向量的Vector类,我们能够这么写: Scala object VectorAccumulatorParam extendsAccumulatorParam[Vector] { defzero(initialValue: Vector): Vector = { Vector.zeros(initialValue.size) } defaddInPlace(v1: Vector, v2: Vector): Vector = { v1 +=v2 } } // Then, create an Accumulator of this type: val vecAccum = sc.accumulator(newVector(...))(VectorAccumulatorParam) 在Scala中。Spark也支持更通用的Accumulable接口去累加数据。其结果类型和累加的元素不同(比方。构建一个包括全部元素的列表)。而且SparkContext.accumulableCollection方法能够累加普通的Scala集合(collection)类型。 Java class VectorAccumulatorParam implementsAccumulatorParam<Vector> { publicVector zero(Vector initialValue) { return Vector.zeros(initialValue.size()); } publicVector addInPlace(Vector v1, Vector v2) { v1.addInPlace(v2); return v1; } } // Then, create an Accumulator of this type: Accumulator<Vector> vecAccum =sc.accumulator(new Vector(...), new VectorAccumulatorParam()); 在Java中,Spark也支持更通用的Accumulable接口去累加数据。其结果类型和累加的元素不同(比方,构建一个包括全部元素的列表)。 Python class VectorAccumulatorParam(AccumulatorParam): defzero(self, initialValue): return Vector.zeros(initialValue.size) defaddInPlace(self, v1, v2): v1 += v2 return v1 # Then, create an Accumulator of this type: vecAccum = sc.accumulator(Vector(...),VectorAccumulatorParam()) 由于累加器的更新仅仅在action中执行。Spark确保每一个任务对累加器的更新都仅仅会被应用一次,比如,重新启动任务将不会更新这个值。在转换中,用户应该清楚假设任务或者作业阶段是反复执行的,每一个任务的更新可能会应用不止一次。 累加器不会改变Spark的懒惰评价模型。 假设它们在一个RDD的操作中正在被更新。他们的值仅仅会被更新一次,RDD作为动作的一部分被计算。因此。累加器更新当在运行一个懒惰转换。比如map()时,并不保证被运行。 以下的代码段演示了这个属性: Scala val accum = sc.accumulator(0) data.map { x => accum += x; f(x) } // Here, accum is still 0 because no actionshave caused the `map` to be computed. Java Accumulator<Integer> accum =sc.accumulator(0); data.map(x -> { accum.add(x); return f(x);}); // Here, accum is still 0 because no actionshave caused the `map` to be computed. Python accum = sc.accumulator(0) def g(x): accum.add(x) returnf(x) data.map(g) # Here, accum is still 0 because no actionshave caused the `map` to be computed. 把代码部署到集群上 应用程序提交指南(application submission guide)描写叙述了怎样将应用程序提交到一个集群。简单地说,一旦你将你的应用程序打包成一个JAR(对于Java/Scala)或者一组的.py或.zip文件(对于Python),bin/spark-submit脚本能够让你将它提交到支持的不论什么集群管理器中。 从Java/Scala中启动Spark作业 Org.apache.spark.launcher包中提供了相关类来启动Spark作业作为子线程的简单的Java API。 单元測试 Spark对单元測试很友好。能够使用不论什么流行的单元測试框架。在你的測试中简单地创建一个SparkContext。并将master URL设置成local。执行你的各种操作,然后调用SparkContext.stop()结束測试。确保在finally块或測试框架的tearDown方法中调用context的stop方法,由于Spark不支持在一个程序中同一时候执行两个contexts。 Spark1.0之前版本号的迁移 Scala Spark 1.0冻结了1.X系列的Spark核心(Core) API,如今。当中的API,除了标识为“试验性(experimental)”或“开发人员的(developer) API”的。在将来的版本号中都会被支持。 对Scala用户而言,唯一的改变在于组操作(grouping operations),比方,groupByKey,cogroup和join,其返回值已经从(Key, Seq[Value])对改动为(Key,Iterable[Value])。 迁移指南也能够从Spark Streaming。MLlib和GraphX获取。 Java Spark 1.0冻结了1.X系列的Spark核心(Core) API,如今,当中的API。仅仅要不是标识为“试验性(experimental)”或“开发人员的(developer) API”的。在将来的版本号中都会被支持。当中对Java API做了一些改动: •对于org.apache.spark.api.java.function中的类函数(Function classes),在1.0版本号中变成了接口,这意味着旧的代码中extends Function应该须要为implement Function。 •添加了map转换(transformations)的新变体,如mapToPair和mapToDouble,用于创建指定数据类型的RDDs。 •组操作(grouping operations),如groupByKey。cogroup和join的返回值也被改动了,从原先返回(Key, List<Value>)对改为(Key,Iterable<Value>)。 迁移指南也能够从Spark Streaming,MLlib和GraphX获取。 Python Spark 1.0冻结了1.X系列的Spark核心(Core) API。如今。当中的API,仅仅要不是 标识为“试验性(experimental)”或“开发人员的(developer) API”的,在将来的版本号中 都会被支持。 对Python用户而言,唯一的改动在于分组操作(grouping operations),比 如groupByKey,cogroup和join,其返回值从(key, list of values)对改动为(key, iterableof values)。 迁移指南也能够从Spark Streaming。MLlib和GraphX获取。 下一步 你能够在Spark的站点上看到spark程序的例子。另外。Spark在examples文件夹(Scala, Java, Python。R)中也包括了一些例子。你能够通过将类名传递给spark的bin/run-example脚本来执行Java和Scala的例子。比如: <span lang="EN-US" style="font-size: 10.5pt; font-family: Helvetica, sans-serif; color: rgb(87, 87, 87); background-image: initial; background-attachment: initial; background-size: initial; background-origin: initial; background-clip: initial; background-position: initial; background-repeat: initial;">./bin/run-example SparkPi</span> 对于Python例子,要使用spark-submit: ./bin/spark-submitexamples/src/main/python/pi.py 对于R例子,使用spark-submit: ./bin/spark-submitexamples/src/main/r/dataframe.R 为了帮助优化你的程序,在配置(configuration)和调优(tuning)的指南上提供了最佳实践信息。它们在确保将你的数据用一个有效的格式存储在内存上,是很重要的。 对于部署的帮助信息,能够查看集群模式概述(cluster mode overview),描写叙述了分布式操作以及支持集群管理器所涉及的组件。 最后。完整的API文档能够查看Scala, Java。Python和R。 本文转自mfrbuaa博客园博客,原文链接:http://www.cnblogs.com/mfrbuaa/p/5386688.html,如需转载请自行联系原作者

优秀的个人博客,低调大师

Docker 中管理数据 【已翻译100%】

到目前我们介绍了一些Docker的基础概念, 知道了如何使用Docker的image, 也知道了如何在多个container间通过网络通讯. 在这章里我们将介绍如何在docker的container内管理数据以及如何在不同的container间共享数据。 我们将介绍两种主要的在docker中管理数据的方法: Data volumes Data volume container Data volumes 一个 data volume 就是一个在一个或者多个container里的特殊用途的目录。它绕过了 Union File System (译者: 这里不确定, 需要研究)为持久化数据、共享数据提供了下面这一些有用的特性: Data volumes 可以在不同的container之间共享和重用数据 对 Data volume 的修改及时生效(译者:data volumn是一个目录, 多个container都挂载这个目录, 具体的可以通过 docker inspect 看 volumne的信息) 对 data volume 修改内容在升级image的时候不会被包括进去 (译者:在docker的整个设计中image是一个无状态的, 这样对升级重用非常有利。而标记状态的数据, 比如数据库的数据, 生产的log之类的应该放到volume里。volume的持久化和恢复在下面有介绍, 是通过文件的形式的, 而不是通过image) Volumes 的持久化直到没有container使用他们 添加数据卷 你可以在docker run 的时候使用 -v 来添加一个 data volume。这个参数在docker run 的时候可以多次使用来添加多个 data volumes。让我们为我们的web application container挂载一个 volume。 $ sudo docker run -d -P --name web -v /webapp training/webapp python app.py 这里一个新的volume会创建到container里的 /webapp. (译者:如果你通过ssh或者通过 -i 登陆到你的container的一个shell里, 使用 ls /webapp 可以验证挂载成功了) 注意: 你也可以在Dockerfile里添加 VOLUME 字段,这样在创建一个新的image的 container是就会自动的创建新的volume. 安装一个目录作为数据卷 使用 -v 不仅能创建一个新的 volume, 还可以把宿主机一个目录mount到container里。 $ sudo docker run -d -P --name web -v /src/webapp:/opt/webapp training/webapp python app.py 这条命令会把本地目录 /src/webapp mount到container里的 /opt/webapp 目录上。用这个方法来测试程序非常方便, 比如我们可以把我们的源代码通过这个方法mount到container里, 修改本地代码后立即就可以看到修改后的代码是如何在container里工作的了。宿主机的目录必须是绝对路径, 如果这个目录不存在docker会为你自动创建。 注意 这里是没法用 Dockerfile实现的, 因为这样的用法有悖于可移植性和共享. 因为本地目录就像他名字告诉我们的, 是和本地相关的, 不一定可以在所有的宿主机上工作.(译者: 鬼知道你在使用image的时候的host是啥样子的) Docker默认设置volume是可读写的,但是我们也可以mount一个目录为只读: $ sudo docker run -d -P --name web -v /src/webapp:/opt/webapp:ro training/webapp python app.py 这里我们同样mount了 /src/webapp 目录, 但是我们加上了 ro 参数, 告诉docker这个volume是只读的. 创建并安装数据卷容器 如果你有一些持久化的数据, 并且想在不同的container之间共享这些数据, 或者想在一些没有持久化的container中使用, 最好的方法就是使用 Data Volumn Container, 在把数据mount到你的container里.(译者:如开篇译者提到的docker的container是无状态的, 也就是说标记状态的数据,例如:数据库数据, 应用程序的log 等等, 是不应该放到container里的, 而是放到 Data Volume Container里, 这点和funcational programming很像, 所以我喜欢把一般的docker container 叫做 functional container用来区分 data volume container ) 让我们创建一个有名字的 Data Volume Container 来共享数据. $ docker run -d -v /dbdata --name dbdata training/postgres 这样做之后就可以通过 --volumes-from 把 /dbdata mount到其他的container里了 $ docker run -d --volumes-from dbdata --name db1 training/postgres 还可以继续共享到另外一个container里 $ docker run -d --volumes-from dbdata --name db2 training/postgres -volumes-from 可以多次使用来 mount 多个conatainer里的多个volumes。 这个操作是链式的, 我们在db1 中通过 --volumes-from mount进来的 volume可以继续被其他container使用 $ docker run -d --name db3 --volumes-from db1 training/postgres (译者: 这里我们不是直接使用 volume container, 而是使用db1 这个functional container 把volume 挂载到另外一个 funcational container上的,所谓的链式就是 dbdata -> db1 -> db3) 如果你把所有mount volumes的container都移除掉, 包括初始化的那个 dbdata container, volume才会被移除掉。通过这个属性可以方便的升级升级数据或者在不同container间migrate数据. 备份、恢复和移植数据卷 Volume的另外一个用处就是备份、恢复和migrate数据。 具体的做法如下,使用 --volumes-from 来创建一个新的container mount这个volume $ sudo docker run --volumes-from dbdata -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata 这里我们启动了一个新的container, 从 dbdata 挂载了一个volume。同时挂载了一个本地目录到这个container里。最后我们通过一个 tar命令把 dbdata 里的数据备份到了 /backup 里。命令结束并且停止这个container后我们就在本地得到了一个备份的数据. (译者: 这里使用的 ubuntu container, 就是为了把volume中的数据打包备份到host的某一个目录里。) 备份的数据可以恢复到这个container, 或者其他使用这个volume的container。首先创建一个container $ sudo docker run -v /dbdata --name dbdata2 ubuntu 之后un-tar备份文件到 data volume 里 $ sudo docker run --volumes-from dbdata2 -v $(pwd):/backup busybox tar xvf /backup/backup.tar 你可以使用你喜欢的工具加上上面的技术来自动备份,迁移和恢复数据

优秀的个人博客,低调大师

[Android]使用MVP解决技术债务(翻译)

以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5892671.html 使用MVP解决技术债务 原文:https://medium.com/picnic-engineering/tackling-technical-debt-with-mvp-67e805ed5103#.couu0d5i0 免责申明:这篇博客并不是讲关于怎么使用MVP的方式(上帝知道关于这些已经太多了)去写Android代码。而仅仅是我的个人经验,关于怎么转换我们的表现层到MVP架构来帮助我们解决一些累积的技术债务,而且在这个过程中也会帮助我们的app从一个原型转变成一个更具维护性的产品。 任何从事Android工作足够久、项目足够大的开发者最有可能达到一个点,他们面对他们的代码库,觉得应该有更好的实现方案。我们在Picnic也是一样,在Android app开发开始后大约八个月,我们到达了的那一刻,就在我们向公众发布第一个版本的时候。 这一刻正好是在我们app推出的时候这也并不意外。直到那时,我们以一个非常快的速度在前进,不断敲打我们的键盘,从零开始构建一个完整的产品,尝试新的东西,结合用户反馈到我们的app中,在每天的基础上增加和丢弃特性。 为了跟上公司的速度我们砍掉了这里那里的边边角角。这样的工作对我们来说很好,这也是我们能够在这么短的时间内构建这个app的原因之一。但是正如预期那样,最后这些决定的影响开始以技术债务的形式显示出来。幸运的是这些技术债务是在数月之内建立的,在app的性能和稳定性上面并没有任何真正的影响。反而我们是在其它领域开始注意到它: 新功能迭代时间的增加。 新入职的开发者遇到困难 它被证实难以实现自动化测试 整体功能的复杂性在增加 我们已经有了一个很好的想法和一个易于理解的架构,用于网络层、错误处理和app内部模块通信。但是像大多数Android开发者,我们会对把太多的逻辑放进Activity和Fragment中会产生内疚。 旁注:这是Android开发者的共同的问题,而作为开发者需要在黑暗中摸索,因为Google对这个话题保持沉默。我们从它们那里得到的第一个(算是)官方回复是来自Android团队的一个开发者在 Google+ post,说明我们应该把核心的Android API作为一个‘系统框架’,意味着他们会带我们手把手地到达Android核心的组件(Activity, BroadcastReceiver, Service 和 ContentProvider)。之后我们做什么都是看我们自己了。而且就在最近,Google终于提供了一系列的例子用来解决关于怎么构建一个Android app的共同问题,它着重于MVP。尽管只是beta,但是它可以在这里查看:Android Architecture Blueprints。 无论如何,这其实是一件好事,因为这意味着我们可以自由地去实验任何我们喜欢的方式,而不是被强制在一个平台遵循一个特定的模式。 现在讲回我们的故事… 除非你处在Android开发世界的远古时期,你应该会注意到表现层架构是现在的热门。关于最好的方式是什么,每个人甚至连他妈妈似乎都有自己的观点。工作中标准的Android方式(类似MVC),到MVP,到通过data-binding的MVVM,所有的方式都沿用了 Uncle Bob 的 clean architecture。每一种方式围绕赞成或者反对的意见都有一些有趣的讨论,但是有一件事我们要明确知道,那就是我们应该避免喝Kool-Aid(译者注:这里是比喻,表示非常愚昧地接受信奉某种观点或者思想)和期望其中一种是银色子弹(译者注:这里是比喻为具有极端有效性的解决方法)然后永远解决所有问题。 当在考虑怎么去重构我们的表现层时,我们已经有近一年的代码库的积累,我们很清楚我们的缺陷在哪里,然后我们需要使用一个新的实现(以上主要表示一些能够解决我们的技术债务的点)来达到我们的目标。我们在虚拟的项目中试玩了一些,体验了各种方法的不同之处,然后最终决定使用MVP。从它的核心来说,MVP本身仅仅是一个概念,而Android框架,根据设计,并不强制任何模式,我们可以自由地选择实际的实现细节。 在Android团队中,首先我们是不过度工程的信徒,让代码随着时间的推移自然地发展,而不是过早地在试图为自己不可预知的未来做准备的抽象之上增加抽象。正因为这个原因,我们选择另一风味的MVP,使得可以最低限度地保持我们的抽象层次。在代码级别,这意味着有一个单独的接口来表示View。所有其它的组件都是具体的类。你可能会问自己,怎么会只有View使用接口?考虑到我们迫切的需要,这是真正受益于这样的接口的唯一的组件,因为我们实际上有不同的具体的Views来共享相同的接口。所以在我们的案例中,这里的一个接口将被允许我们去重用Presenters。一些MVP实现建议给所有组件(M,V和P)设置接口。尽管这样会工作得很完美,但是我们在较早的阶段并不提倡,因为添加之后的成本是代码可读性和维护性,尤其是当我们考虑到新入职对MVP陌生的初级开发者的时候,好处超过面向接口编程的方式。 相比其他,MVP实现是非常标准的。View(Activity,Fragment或者一个自定义View)负责创造和维护Presenter,而Presenter处理各种业务相关的逻辑(数据获取,存储,格式化等等),然后根据需要通过更新UI回调到View。在我们的案例中,数据层已经是相当模块化了,构造用于表示数据模型的POJOs,以及一个预先存在的控制层用于处理网络通信。 这是一个非常标准的MVP设置,也因为它很简单,我们可以在几周的时间内替换几乎我们的所有的UI代码。因为我们已经存在独立的数据层来处理所有与后端的API交互,所以真正需要重构的只是Views和Presenters的交互。 在重构的过程中,我们也学习了一些可能会派得上用场的东西: 生命周期:因为Presenter是View创建的,我们需要确保完全地理解View的生命周期,特别是因为它将最有可能去处理状态更新和异步数据。举个例子,每一个Presenter应该在View destroyed的情况下有一个取消异步任务的方式,或者应该在用户暂停或者恢复视图事件时重置到原始状态等等。最后但同样重要的是,当View已经被销毁,试图从Presenter去更新View元素,始终需要注意可怕的NPEs。 保持Views尽可能地愚蠢:我们的Views应该不再包含任何业务相关的逻辑。它应该只包含Android框架inflate和设置View的这些最低限度的东西。任何用户交互应该派发到Presenter。根据经验,如果你的views有任何其它方法去更新UI元素或者响应用户触发的事件,那么你可能应该去检查它们的实现。 保持Presenter尽可能地纯粹:这一点,我们的意思时你应该尽可能地避免有Android相关的代码在你的presenters中。为这些组件编写纯粹的单元测试,而不需要使用其它如Robolectric等测试框架,这明显地得到了简化。这明显说起来比做起来容易得多,因为你终归会在某些地方遇到这种情况,举个例子,你将需要有一个Context的引用用来比如数据加载、访问strings文件等等。 结论 那么,说了那么多,最终的结论是什么呢?总的来说,我很高兴使用了MVP。它一定程度上帮我们解决了我们快速开发所累积的技术债务,然后,我们准备了更多来针对第二阶段的开发。 一些值得一提的事情: 测试数:在重构之前,测试的数量用两只手都可以数得过来。这是一个巨大的任务来针对包含了所有逻辑如执行数据解析、格式化、网络请求、错误处理和管理自己的生命周期的Activity编写测试。仅思考如果在这些条件下编写测试就足以让我们去寻找其它的方式了。一旦转换我们的第一份代码到MVP,对此编写测试就变得碎片化了。通过一个清晰的合同明确什么View能够处理,我们可以把自己的代码与Android UI框架隔离开,然后仅仅测试实际调用的是否是正确的方法,并给出每个测试场景。现在实际的业务相关逻辑被放置在Presenters中,因为它们绝大多数都不需要有Android OS相关的认知(或者小部分相关的可以被mocked),我们也可以针对它们编写非常有效率的单元测试,因此,在过去几个月里,我们的测试用例从原来的10增加到900,而且还在增长中。 可预见性:这个是有一点软度量,但是非常强大的一点。针对UI,我们选择并坚持一个通用的模式,我可以在代码库中获得可预见的好处。这意味着,无论是哪种开发者眼里的UI元素(Activity,Dialog,Fragment等等),如果理解其中一个怎么工作,那也就能理解所有怎么工作。打开一个就算不是你写的文件也不再会遇到让你觉得惊喜的东西了。明确规定职责,每一单个的UI组件都遵循相同的明确的模式。让新入职的新开发者从第一天起就是高效的,这是非常宝贵的。 我们别忘记MVP并不只是用于表现层,但是作为前端开发人员,这里花费了我们太多的时间。所以努力去寻找一个解决方案来给我们带来更好的可预见性和在新的开发者加入我们的时候也能让我们快速迭代是值得的。经过全面的考虑,我们可以有把握地说MVP是可以帮助我们达到这个目标的一个重要的里程碑。 P.S. 如果你仍然渴望看到一些源代码,这里有一个我们MVP实现‘忘记密码’用例的剥离下来的版本,展示MVP组件与用户的交互,用户点击‘重置密码’按钮进入他们的邮件地址(为保持代码的简洁,Android模版代码已经移除): // BasePresenter.java (Base class for all our Presenters) public abstract class BasePresenter<V> { private WeakReference<V> mView; public void bindView(@NonNull V view) { mView = new WeakReference<>(view); } public void unbindView() { mView = null; } public V getView() { if (mView == null) { return null; } else { return mView.get(); } } protected final boolean isViewAttached() { return mView != null && mView.get() != null; } } // IForgotPasswordView.java (view interface) public interface IForgotPasswordView { void showLoading(); void hideLoading(); void setEmailText(String email); void showEmailNotValidError(); void showPasswordRequestOk(String message); void showPasswordRequestFail(); } // ForgotPasswordFragment.java (view implementation) public class ForgotPasswordFragment implements IForgotPasswordView, View.OnClickListener { // Triggered by the user clicking a button public void onResetPasswordClick() { String email = mEmailEditText.getText().toString(); // Forward all logic to the Presenter mPresenter.requestPasswordChange(email); } } // ForgotPasswordPresenter.java public class ForgotPasswordPresenter extends BasePresenter<IForgotPasswordView> { public void requestPasswordChange(String email) { if (!Utils.isEmailValid(email)) { // Make sure the view is still alive before trying to access it if(isViewAttached()) { getView().showEmailNotValidError(); } } else { requestPasswordChangeAsync(email); } } private void requestPasswordChangeAsync(String email) { // Update the view's UI elements if(isViewAttached()) { getView().hideKeyboard(); getView().showLoading(); // Call our API (results are posted back on an EventBus) api.forgotPassword(email); } } // Subscription to the event bus @Subscribe public void onEvent(final Event event) { if (isViewAttached()) { // Update the view's UI elements getView().hideLoading(); switch (event.getType()) { case FORGOT_PASSWORD_OK: getView().showPasswordRequestOk((String) event.getData()); break; case FORGOT_PASSWORD_FAILED: getView().showPasswordRequestFail(); break; } } } }

优秀的个人博客,低调大师

[翻译]Hello, wasm-pack - cargo.toml

Cargo.toml cargo.toml是Rust 包管理器 cargo 的清单文件。这个文件包 name、 version 和包的依赖,在 Rust 中,我们一般称之为 crate。 在示例中给出了一系列信息,但是我们主要讨论如下三点: crate-type wasm-bindgen 依赖 [features] 和 wee_alloc、console_error_panic_hook 依赖 1.crate-type [lib] crate-type = ["cdylib", "rlib"] Rust-wasm 包与通常的 crate 有一点不同,作为 WebAssembly 项目, 我们需要在 cargo.toml 中加入该说明。 如果你熟悉其他的 Rust crate,那么你肯定知道,大多的 crate 的类型是 rlib(默认), 或者是二进制形式的 bin(这种形式不需要 crate-type 注解), 并且 [lib] 注解在普通的 Cargo 项目中并不需要指定。 crate-type = ["cdylib"] 指示你的工程将会被编译为动态系统库 [dynamic system library], 但是对于 WebAssembly,他将会编译为一个没有启动函数的 .wasm 文件,在 Linux 平台上,他将会创建.so 文件,在macOS 上将会创建.dylib文件,在 windows 平台上将会创建 *.dylib 文件。 我们通常指定 crate-type = ["rlib"] 来确保我们的库可以用 wasm-pack 来做单元测试(稍后会看到)。如果没有这个配置,我们将不能测试我们的库,因为 cdylib 包类型和 wasm-pack 的单元测试类型相冲突。 你可以使用此链接获取更多关于包类型的知识。 2. wasm-bindgen 依赖 wasm-bindgen 在WebAssembly 中是一个重要的依赖。 这个包允许我们使用 [wasm-bindgen] 为在 JavaScript 和 Rust 生成的 wasm 之间的代码打标签。以使我们使用它的属性可以导入 JS 并且导出 Rust。 wasm-bindgen = "0.2" 当我们讨论 lib.rs 生成什么内容的时候,将会看到更多关于怎么使用这个库。如果你从 JavaScript 技术栈过来,你可能注意到了当我们添加依赖的时候并没有加 ^ 或者 ~ ,看起来像是我们只要 0.2 这个版本。然而,事实并非如此!在 Rust 里, ^ 是默认的,你可使用这个 链接查看更多信息 3. [features] 和 wee_alloc, console_error_panic_hook dependencies 作为我们设计模板的工作的一部分,该模板可帮助人们发现针对特定用例的有用包,该模板包括两个依赖项,这对于开发Rust-wasm包的人们可能非常有用:console_error_panic_hook 和 wee_alloc。 因为这些依赖关系主要在 Rust-wasm 包开发工作流程的特定部分中有用,所以我们还设置了一些粘合代码,使我们既可以将它们都包含为依赖关系,又可以选择将它们包含在内。 [features] default = ["console_error_panic_hook"] [dependencies] wasm-bindgen = "0.2" # The `console_error_panic_hook` crate provides better debugging of panics by # logging them with `console.error`. This is great for development, but requires # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for # code size when deploying. console_error_panic_hook = { version = "0.1.1", optional = true } # `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size # compared to the default allocator's ~10K. It is slower than the default # allocator, however. # # Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now. wee_alloc = { version = "0.4.2", optional = true } 在我们的代码中,只有在启用某些 [features] 的情况下,我们才会将代码的某些部分标记为正在运行,特别是 console_error_panic_hook 和 wee_alloc。默认情况下,仅启用 console_error_panic_hook。要禁用或启用任一功能,默认情况下,我们可以在 [features] 下编辑 default 数组。 要了解有关这些功能的更多信息,我们将在 src/lib.rs 和 src/utils.rs 部分中深入讨论它们。 简要地,它们包括: console_error_panic_hook ,用于将奔溃消息记录到开发人员控制台的功能。 wee_alloc,一个使代码量更小而优化的分配器。

优秀的个人博客,低调大师

组复制官方翻译二、Group Replication Background

https://dev.mysql.com/doc/refman/8.0/en/group-replication-background.html 这一章主要描述一些组复制的背景 构建一个容错系统最常用的方法就是让组件冗余,换句话说就是组件即便被移除掉,整个系统还是能够正常对外提供服务这无疑在不同层面上提出了更多的挑战需要注意的是,复制结构的数据库系统必须思考的一个事实就是:他们需要维护和管理一堆不同的sever此外,他们还必须解决分布式系统所面临的问题:比如 脑裂、网络分区等等 因此,最大的挑战就是去融合这种逻辑数据库,保证数据复制的一致性换句话说,为了让不同server都同意这个系统的状态,他们每一台server的数据修改都必须验证一致这就意味着他们需要运作的想一个状态机一样(分布式) MySQL Group Replication提供了一套分布式状态机制复制管理方法 对于要提交的事务,这个group采取大部分原则来投票,让事务全局有序决定commit还是拒绝这个事务都是由server自行判断,但是所有servers都会做出一样的决定如果网络产生了分区,脑裂产生导致成员之间无法达成一致投票决定,那么这个系统会停止运行直到这个问题被解决所以,他有一个内置、自动的脑裂包含机制在运行 以上所有的功能都是由Group Communication System (GCS) 协议来保证它有错误检测机制、组成员通信服务、安全可靠的顺序一致消息分发所有这些特性是搭建一个 数据完全一致性的系统 的关键要素在一些非常核心重要的技术点上 罗列了Paxos 算法的实现,它扮演着组复制通信引擎的角色,至关重要 18.1.1 Replication Technologies 在了解MGR内幕之前,这里先主要介绍下相关的背景概念、以及概述这章主要告诉我们,MGR需要什么,以及传统的异步复制和MGR直接的一些区别 18.1.1.1 Primary-Secondary Replication 传统的复制提供了一个简单的主从复制架构(Primary-Secondary)primary就是master,secondary就是slaves,可以有多个slavesmaster执行事务、commit事务,然后异步的将这些事务发送到slaves,让他们re-executed一遍(statement模式)或者 重新applied (ROW模式)它是share-nothing架构,即所有server都有一份完整的数据copy 还有一种传统复制叫:半同步复制它意味着:在commit的之前,master等待,直到slaves给master一个确认接收到事务的ack,master才恢复commit的操作 在上面的两幅图中,你能看到异步传统复制协议的基本架构,箭头代表client消息的流动和转变 18.1.1.2 Group Replication 组复制是一个实现了容错系统的技术组复制集群就是一堆机器,他们之间通过消息进行沟通communication 层:提供了一系列的保障机制,atomic message(原子广播) , total order message delivery(全局序列消息分发机制) MGR在此基础上构建并实现了一个multi-master的复制协议,它可以在任何server上写数据集群的本质就是多server,每个server可以独立的处理事务但是所有的读写(RW)事务都必须经过集群的审核所有的只读(RO)事务不受任何影响换句话说,对于RW事务,group只需要决定它应该commit还是拒绝commit,因此事务操作并不是单方面(origi server)的决定确切的说,当origin server准备进行事务commit的时候,这个server会自动广播这个写集然后,一个全局排序的事务产生了这意味着,所有的server都接收同样顺序的事务集由于是有序的,所有server应用相同顺序,相同数据的写集,因此他们的数据也是一致的 然而,如果是并发写在不同server的场景会遇到冲突因此,对应这种情况需要进行冲突检测,这个过程叫做认证 certification如果两个并发事务在不同server同时执行,并且更新了相同的row,那么他们就是冲突的那么它的解决方案就是,排在前面的事务会被标记commit,排在后面的会被拒绝(这个事务在origin server会回滚,其他server会被丢弃) 最后,MGR也是一种share-nothing架构,每个server都有一份完整的数据copy 18.1.2 Group Replication Use Cases 组复制提供了一个高容错性的系统,即使一些机器宕机,只要不是所有或者大多数机器不可用,那么整个系统还是可用状态总结下来,MGR保证数据库持续可用 18.1.2.1 Examples of Use Case Scenarios 以下就是典型的MGR使用案例 Elastic Replication 可伸缩的复制 Highly Available Shards 高可用的分片 Alternative to Master-Slave replication 可选择master-slave架构 Autonomic Systems 完全自动化的系统 18.1.3 Group Replication Details 18.1.3.1 Failure Detection 它提供一个错误检测机制,可以找到或报告出哪些servers没有回应,哪些server挂了在高一层次来将,错误检测机制就是一个分布式服务,用于提供哪些server挂掉或可能挂掉的情报信息之后,如果组成员通过某种协议认证了这个嫌疑犯(可能挂掉的家伙)已经真的挂了,那么集群就会决定这个嫌疑犯真的的确挂了这意味着,组的其他成员一致决定将这个嫌疑犯踢出集群 当Server A 在指定time-out时间内没有收到来自server B的回应,那么B就会被提升为嫌疑犯如果一个Server被其他group成员隔离,那么它就会怀疑所有其他的成员都挂了由于它不能达成投票的一致性认可(没有达到法定人数的确认),所以它认为的嫌疑犯就不能被确认为failed如果一个Server在这种情况下被隔离,那么他是不能执行local事务的 18.1.3.2 Group Membership MGR依赖组会员服务(Group Membership Service,简称GMS),它是内置的它定义了哪些servers是online并加入了这个group,这些online servers经常被称为view因此,这个组里面的任一online成员都有一个一致的view 如果servers同意让一个新server加入到这个group中来,那么这个group就好重新自动将其配置上,且重新触发形成一个新的view如果一个server非自愿的离开了group,那么错误检测机制就开始识别,也会重新配置上一个新的view上面提到的这些都需要一个协议,并且需要大多数人参与并认可的协议如果这个group没有满足达到这个协议认可的要求,那么自动配置将不会起作用,并且该系统会被阻塞来防止脑裂的产生最后,这意味着管理员需要手动介入来解决这个问题 18.1.3.3 Fault-tolerance MGR 是在Paxos分布式算法构建实现的,以此它需要满足大多数活跃成员进行投票选举的策略。有一个公式:n = 2 x f + 1 , n代表group的成员数,f代表允许挂掉的成员数 ,在这个公式下,整个集群是安全的 如果n=3,那么允许挂掉的server是1,也能满足要求,但是如果再挂一个呢, 其实就问题非常大了 集群成员数量n majority 可允许挂掉的server数量 1 1 0 2 2 0 3 2 1 4 3 1 5 3 2 6 4 2 7 4 3

优秀的个人博客,低调大师

eval()不是魔鬼,只是被误解了(翻译)

原文来自:https://www.nczonline.net/blog/2013/06/25/eval-isnt-evil-just-misunderstood/ 作者:Nicholas C.Zakas 在JavaScript中,我不确定是否有比eval()受到更多诽谤的。它就是个简单的函数被设计用来将字符串转换为可被执行的JavaScript代码。在我的早期的职业生涯里,它比任何其他的东西更受关注和误解。 大多数人认为‘eval()是魔鬼’这句话是Douglas Crockford说的。他说:“eval函数(及其亲属,Function,setTimeout,和setInterval)提供对JavaScript编译器的访问。这有时是必要的,但大多数情况,它证明这个存在是极其糟糕的代码。因为eval()这个特性常常被误用”。 因为道格拉斯的大多数作品并没有注明日期,所以,我不确定他是否是在2002年创造了这个术语。不管怎么说吧,不管是否真正理解了eval()的使用,他都成为了一个热门的短语。 尽管这种理论流行开来了(道格拉斯的坚持),但这并不意味着eval()的存在就有问题。使用eval()不会自动触发XSS攻击,或者你没有意识到的但存在的安全漏洞。就像工具一样,你要知道如何使用它,但即使你使用不正确,但潜在风险依然很低,并且是可容忍的。 误用(滥用) eval()之所以成为了魔鬼,是因为那些对JavaScript语言理解不够深的人误用的原因。你可能会奇怪,误用跟安全和性能好型没有关系吧。误用是不理解如何在JavaScript中构造和使用引用。假设你有几个表单input的名字包含数字,如:option1,option2,常见的代码实现如下: function isChecked(optionNumber) { return eval("forms[0].option" + optionNumber + ".checked"); } var result = isChecked(1); 在这种情况下,开发人员在尽力尝试写forms[0].option1.checked,而没有想我不用eval()该如何做。这样的情况出现在很多10年左右工作经验的开发者,它们不明白如何更好地使用。这里也不是说eval()在这里不合适,而是因为没有必要,你完全可以更简单的实现,用如下的代码: function isChecked(optionNumber) { return forms[0]["option" + optionNumber].checked; } var result = isChecked(1); 在很多情况下,你可以使用[]表示法来替换eval()的使用去构造属性名,这也是 [] 存在的一个原因。包括道格拉斯在内的早期博主们都是在讨论这个问题。 可调式性 不使用eval()的一个重要理由是为了达到调试的目的。以前,如果出现问题,不可能进入eval()代码。这就意味着你的代码运行在一个黑盒中,然后从中取出。现在Chrome开发工具可以调试eval()内的代码,但是有一个问题是,你必须等代码执行一次后才出现在源面板中。 不使用eval()可以令我们的代码调试起来更容易,跟方便的查看源代码。但这并不能说明eval()是魔鬼,这只是开发工作流程中的一点问题。 性能 对eval()的另一个重要影响是它的性能。在旧的浏览器中,你遇到了双重解释惩罚,也就是说,你的代码被解释,而eval()中的代码被解释。在没有编译JavaScript引擎的浏览器中,结果可能会慢十倍(甚至更糟)。 在现代编译JavaScript的引擎中,eval()仍然是一个问题。大多数引擎可以用两种方式运行代码:快速路径或慢路径。快速路径代码是一种稳定且可预测的代码,因此可以为更快的执行而编译。缓慢的路径代码是不可预测的,这使得编译很难,并且可能仍然使用一个解释程序运行。在你的代码中仅仅存在eval()意味着它是不可预测的,因此将在解释器中运行它以“旧浏览器”的速度运行,而不是“新浏览器”的速度(10倍的差异)。 同样的,eval()使YUI压缩器不可能在调用eval()的范围内munge变量名。由于eval()可以直接访问任何这些变量,重命名它们会引入错误(其他工具如闭包编译器和UglifyJS可能仍然会蒙混这些变量——最终导致错误)。 因此,在使用eval()时,性能仍然是一个大问题。但这很难让它成为邪恶,但这是一个需要注意的警告。 安全 在说到eval()的安全时,这是大部分人认为eval是魔鬼的有力佐证。大多数情况下,就是说XSS攻击,以及eval()如何向它们打开代码。在表面上,这种混淆是可以理解的,因为eval()在页面上下文中可执行任意代码。如果你接受用户输入并通过eval()运行它,这将是危险的。但是,如果你的输入不是来自用户,是否存在真正的危险? 我收到了不止一个的抱怨,在我的CSS解析器中使用eval()的一段代码中使用的代码使用eval()将字符串标记从CSS转换为JavaScript字符串值。除了创建自己的字符串解析器外,这是获得token的正确字符串值的最简单方法。到目前为止,还没有人能够或愿意提出一种攻击方案,在这种情况下,这段代码会引起麻烦,因为: eval()的值来自于tokenizer tokenizer已经验证了它是一个有效的字符串 代码最常在命令行上运行。 即使在浏览器中运行,该代码也被封闭在闭包中,不能直接调用。 当然,由于这段代码的主要目标是命令行,所以这个故事有点不同。 设计用于浏览器的代码面临不同的问题,但是eval()的安全性通常不是其中之一。同样,如果你以某种方式接收用户输入并将其传递给eval(),那么你就是在自找麻烦,不要这样做。但是,如果你使用eval()的输入,只有你控制并且不能被用户修改,那么就没有安全风险。 最常见的攻击是来自服务器的eval()代码。这一模式以引入JSON而著名,它之所以流行,是因为它可以通过eval()快速转换成JavaScript。实际上,Douglas Crockford自己在他的原始JSON实用程序中使用eval(),因为它可以转换速度。他确实添加了检查以确保没有真正的可执行代码,但是实现从根本上是eval()。 现在,大多数人都使用浏览器内置的JSON解析功能来实现这一目的,尽管有些人仍然通过eval()来获取任意的JavaScript,作为延迟加载策略的一部分。一些人认为,这才是真正的安全漏洞。如果正在进行中间人攻击,那么你将在页面上执行任意攻击代码。 中间人攻击被认为是eval()的永远存在的危险,会受到蠕虫的的攻击。但是,这是一个与我无关的场景,因为任何时候你不能相信你正在联系的服务器,就意味着有可能出现很多不好的事情。中间人攻击可以通过多种方式向页面注入代码: 返回通过 <script></script> 加载的JavaScript代码。 通过返回攻击者控制的JSON-P请求代码。 通过从一个Ajax请求返回attacker控制的代码,然后eval()。 此外,这样的攻击可以很容易地窃取cookie和用户数据,而不会改变任何东西,更不用说通过返回攻击者控制的HTML和CSS来进行网络钓鱼的可能性了。 简单地说,eval()不会像加载外部JavaScript那样打开中间人攻击。如果你不能信任服务器上的代码,那么你将遇到比eval()调用更大的问题。 结论 我不是说你应该跑出去,开始使用eval()。实际上,很少有好的用例来运行eval()。对于代码清晰性、调试性,以及不应该忽略的性能,确实存在一些问题。但是在eval()有意义的情况下,你不应该害怕使用它。不要第一次使用它,但是不要让任何人吓到你,认为你的代码在使用eval()时更加脆弱或不安全。 文章稍后可能还会继续修改,也欢迎各位批评指正。有问题或者有其他想法的可以在我的GitHub上pr。

优秀的个人博客,低调大师

Openstack 安装部署指南翻译系列 之 概况

1.1.1.1.Nova服务安装(Compute) 1.1.1.1.1.计算服务概述 使用OpenStack Compute来托管和管理云计算系统。OpenStack Compute是基础架构即服务(IaaS)系统的主要部分。主要模块是用Python实现的。 OpenStack Compute与OpenStack Identity进行交互以进行身份验证; OpenStack镜像服务用于磁盘和服务器镜像; OpenStack仪表板用于用户和管理界面。镜像访问受到项目和用户的限制;配额对于每个项目是有限的(例如数量)。OpenStack Compute可以在标准硬件上水平扩展,并下载镜像以启动实例。 OpenStack Compute由以下几个服务组成: 2nova-api服务 接受并回复最终用户的计算API调用。该服务支持OpenStack Compute API,Amazon EC2 API和特殊的Admin API,用于特权用户执行管理操作。它执行一些策略并启动大多数业务流程活动,例如运行一个实例。 2nova-api-metadata服务 接受来自实例的元数据请求。nova-api-metadata当您在多主机模式下运行nova-network安装时,通常会使用该服务 。有关详细信息,请参阅 “OpenStack管理员指南”中的元数据服务。 2nova-compute服务 通过虚拟机管理程序API创建和终止虚拟机实例的工作程序守护程序。例如: 适用于XenServer / XCP的XenAPI KVM或QEMU的libvirt 适用于VMware的VMwareAPI 处理相当复杂。基本上,守护程序接受队列中的操作,并执行一系列系统命令,例如启动KVM实例并更新数据库中的状态。 2nova-placement-api服务 跟踪每个提供商的库存和使用情况。有关详细信息,请参阅Placement API。 2nova-scheduler服务 从队列获取虚拟机实例请求,并确定运行哪个计算服务器主机。 2nova-conductor模块 中介nova-compute服务和数据库之间的交互。它消除了对nova-compute服务器所做的云数据库的直接访问。该nova-conductor模块水平缩放。但是,请勿在nova-compute服务运行的节点上部署它 。有关详细信息,请参阅配置参考指南。 2nova-cert模块 服务器守护进程为Nova Cert服务提供X509证书。用于生成证书euca-bundle-image。只需要EC2 API。 2nova-consoleauth守护进程 授权控制台代理提供的用户的令牌。参考nova-novncproxy和nova-xvpvncproxy。此服务必须运行才能使控制台代理工作。您可以在集群配置中针对单个nova-consoleauth服务运行任一类型的代理。有关信息,请参阅关于nova-consoleauth。 2nova-novncproxy守护进程 提供通过VNC连接访问运行实例的代理。支持基于浏览器的novnc客户端。 2nova-spicehtml5proxy守护进程 提供通过SPICE连接访问运行实例的代理。支持基于浏览器的HTML5客户端。 2nova-xvpvncproxy守护进程 提供通过VNC连接访问运行实例的代理。支持OpenStack特定Java客户机。 2队列 在守护进程之间传递消息的中心枢纽。通常用RabbitMQ实现,也可以用另一个AMQP消息队列来实现,如ZeroMQ。 2SQL数据库 存储云基础设施的大部分构建时间和运行时状态,包括: 可用的实例类型 正在使用的实例 可用网络 项目 理论上,OpenStack Compute可以支持SQLAlchemy支持的任何数据库。公共数据库是用于测试和开发工作的SQLite3,MySQL,MariaDB和PostgreSQL。 1.1.1.1.2.安装和配置控制器节点 本节介绍如何在控制器节点上安装和配置代号为Nova的Compute服务。 1.1.1.1.2.1.先决条件 在安装和配置Compute服务之前,必须创建数据库,服务凭据和API端点。 1、要创建数据库,请完成以下步骤: 使用数据库访问客户端作为root用户连接到数据库服务器: $ mysql -u root -p 创建nova_api,nova和nova_cell0数据库: MariaDB [(none)]> CREATE DATABASE nova_api; MariaDB [(none)]> CREATE DATABASE nova; MariaDB [(none)]> CREATE DATABASE nova_cell0; 授予对数据库的正确访问权限: MariaDB [(none)]> GRANT ALL PRIVILEGES ON nova_api.* TO 'nova'@'localhost' \ IDENTIFIED BY 'NOVA_DBPASS'; MariaDB [(none)]> GRANT ALL PRIVILEGES ON nova_api.* TO 'nova'@'%' \ IDENTIFIED BY 'NOVA_DBPASS'; MariaDB [(none)]> GRANT ALL PRIVILEGES ON nova.* TO 'nova'@'localhost' \ IDENTIFIED BY 'NOVA_DBPASS'; MariaDB [(none)]> GRANT ALL PRIVILEGES ON nova.* TO 'nova'@'%' \ IDENTIFIED BY 'NOVA_DBPASS'; MariaDB [(none)]> GRANT ALL PRIVILEGES ON nova_cell0.* TO 'nova'@'localhost' \ IDENTIFIED BY 'NOVA_DBPASS'; MariaDB [(none)]> GRANT ALL PRIVILEGES ON nova_cell0.* TO 'nova'@'%' \ IDENTIFIED BY 'NOVA_DBPASS'; 更换NOVA_DBPASS一个合适的密码。 退出数据库访问客户端。 2、输入admin凭据以访问仅管理CLI命令: $ . admin-openrc 3、创建计算服务凭据: 创建nova用户: $ openstack user create --domain default --password-prompt nova User Password: Repeat User Password: 将admin角色添加到nova用户: $ openstack role add --project service --user nova admin 创建nova服务实体: $ openstack service create --name nova \ --description "OpenStack Compute" compute 4、创建Compute API服务端点: $ openstack endpoint create --region RegionOne \ compute public http://controller:8774/v2.1 $ openstack endpoint create --region RegionOne \ compute internal http://controller:8774/v2.1 $ openstack endpoint create --region RegionOne \ compute admin http://controller:8774/v2.1 5、使用选择的PLACEMENT_PASS创建Placement服务: $ openstack user create --domain default --password-prompt placement User Password: Repeat User Password: 6、将Placement用户添加到具有管理角色的服务项目中: $ openstack role add --project service --user placement admin 7、在服务目录中创建Placement API条目: $ openstack service create --name placement --description "Placement API" placement 8、创建Placement API服务端点: $ openstack endpoint create --region RegionOne placement public http://controller:8778 $ openstack endpoint create --region RegionOne placement internal http://controller:8778 $ openstack endpoint create --region RegionOne placement adminhttp://controller:8778 1.1.1.1.2.2.安装和配置组件 1、安装软件包: # yum install openstack-nova-api openstack-nova-conductor \ openstack-nova-console openstack-nova-novncproxy \ openstack-nova-scheduler openstack-nova-placement-api 2、编辑/etc/nova/nova.conf文件并完成以下操作: 在该[DEFAULT]部分中,仅启用计算和元数据API: [DEFAULT] # ... enabled_apis = osapi_compute,metadata 在[api_database]和[database]部分中配置数据库访问: [api_database] # ... connection = mysql+pymysql://nova:NOVA_DBPASS@controller/nova_api [database] # ... connection = mysql+pymysql://nova:NOVA_DBPASS@controller/nova 替换NOVA_DBPASS为您选择的计算数据库的密码。 在本[DEFAULT]节中,配置RabbitMQ消息队列访问: [DEFAULT] # ... transport_url = rabbit://openstack:RABBIT_PASS@controller 替换RABBIT_PASS为您为该openstack帐户选择的密码RabbitMQ。 在[api]和[keystone_authtoken]部分中,配置身份服务访问: [api] # ... auth_strategy = keystone [keystone_authtoken] # ... auth_uri = http://controller:5000 auth_url = http://controller:35357 memcached_servers = controller:11211 auth_type = password project_domain_name = default user_domain_name = default project_name = service username = nova password = NOVA_PASS 替换NOVA_PASS为nova身份服务中为用户选择的密码 。 在该[DEFAULT]部分中,配置该my_ip选项以使用控制器节点的管理接口IP地址: [DEFAULT] # ... my_ip = 10.0.0.11 在本[DEFAULT]节中,启用对网络服务的支持: [DEFAULT] # ... use_neutron = True firewall_driver = nova.virt.firewall.NoopFirewallDriver 注意:默认情况下,Compute使用内部防火墙驱动程序。由于网络服务包含防火墙驱动程序,因此您必须使用nova.virt.firewall.NoopFirewallDriver防火墙驱动程序禁用Compute防火墙驱动 程序。 在本[vnc]节中,配置VNC代理以使用控制器节点的管理接口IP地址: [vnc] enabled = true # ... vncserver_listen = $my_ip vncserver_proxyclient_address = $my_ip 在该[glance]部分中,配置Image Service API的位置: [glance] # ... api_servers = http://controller:9292 在该[oslo_concurrency]部分中,配置锁定路径: [oslo_concurrency] # ... lock_path = /var/lib/nova/tmp 在本[placement]节中,配置Placement API: [placement] # ... os_region_name = RegionOne project_domain_name = Default project_name = service auth_type = password user_domain_name = Default auth_url = http://controller:35357/v3 username = placement password = PLACEMENT_PASS 替换PLACEMENT_PASS为placement身份服务中为用户选择的密码 。注释[placement]节中的任何其他选项。 启用对Placement API的访问,方法是将以下配置添加到/etc/httpd/conf.d/00-nova-placement-api.conf: <Directory /usr/bin> <IfVersion >= 2.4> Require all granted </IfVersion> <IfVersion < 2.4> Order allow,deny Allow from all </IfVersion> </Directory> 重新启动httpd服务: # systemctl restart httpd 3、填充nova-api数据库: # su -s /bin/sh -c "nova-manage api_db sync" nova 4、注册cell0数据库: # su -s /bin/sh -c "nova-manage cell_v2 map_cell0" nova 5、创建cell1单元格: # su -s /bin/sh -c "nova-manage cell_v2 create_cell --name=cell1 --verbose" nova 6、导入新星数据库: # su -s /bin/sh -c "nova-manage db sync" nova 7、验证nova cell0和cell1是否正确注册: # nova-manage cell_v2 list_cells 1.1.1.1.2.3.完成安装 启动Compute服务并将其配置为在系统启动时启动: # systemctl enable openstack-nova-api.service \ openstack-nova-consoleauth.service openstack-nova-scheduler.service \ openstack-nova-conductor.service openstack-nova-novncproxy.service # systemctl start openstack-nova-api.service \ openstack-nova-consoleauth.service openstack-nova-scheduler.service \ openstack-nova-conductor.service openstack-nova-novncproxy.service 本文转自yuweibing51CTO博客,原文链接:http://blog.51cto.com/yuweibing/1981178,如需转载请自行联系原作者

优秀的个人博客,低调大师

Openstack 安装部署指南翻译系列 之 网络

网络包括两种类型,网络选项1:提供商网络(Provider networks)和网络选项2:自助网络(Self-service networks),其中网络选项2:自助网络(Self-service networks)能够实现更加高级的网络功能,能够实现网络选项1的所有功能,因此我们的项目选择网络选项2:自助网络(Self-service networks)。以下是虚拟网络选项的说明: 1.1.1.1.网络选项1:提供商网络(Providernetworks) 提供商网络选项以最简单的方式部署OpenStack网络服务,主要是第2层(桥接/交换)服务和网络的VLAN分段。本质上,它将虚拟网络与物理网络相结合,并依赖物理网络基础设施进行第3层(路由)服务。另外,DHCP服务为实例提供IP地址信息。 OpenStack用户需要有关底层网络基础设施的更多信息来创建一个完全匹配基础架构的虚拟网络。 此选项不支持自助服务(私有)网络,第3层(路由)服务以及高级服务,如 LBaaS和 FWaaS。如果希望这些功能,需要自助服务网络选项(Self-service networks)。 1.1.1.1.网络选项2:自助网络(Self-service networks) 自助服务网络选项通过层次3(路由)服务来提供提供商网络选项,使服务网络能够使用覆盖分段方法(如VXLAN)。基本上,它使用NAT将虚拟网络路由到物理网络。此外,此选项为高级服务(如LBaaS和FWaaS)提供了基础。 OpenStack用户可以创建虚拟网络,而无需了解数据网络上的基础架构。如果相应地配置了第二层插件,这也可以包括VLAN网络。 ' 本文转自yuweibing51CTO博客,原文链接:http://blog.51cto.com/yuweibing/1981168 ,如需转载请自行联系原作者

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册