World Count
https://juejin.im/post/5d02365ce51d45109725fe65
详细的流程为:
World Count
# coding:utf-8
from __future__ import print_function
import sys
from operator import add
# SparkSession:是一个对Spark的编程入口,取代了原本的SQLContext与HiveContext,方便调用Dataset和DataFrame API
# SparkSession可用于创建DataFrame,将DataFrame注册为表,在表上执行SQL,缓存表和读取parquet文件。
from pyspark.sql import SparkSession
if __name__ == "__main__":
# appName 为 Spark 应用设定一个应用名,改名会显示在 Spark Web UI 上
# 假如SparkSession 已经存在就取得已存在的SparkSession,否则创建一个新的。
spark = SparkSession\
.builder\
.appName("PythonWordCount")\
.getOrCreate()
# 读取传入的文件内容,并写入一个新的RDD实例lines中,此条语句所做工作有些多,不适合初学者,可以截成两条语句以便理解。
# map是一种转换函数,将原来RDD的每个数据项通过map中的用户自定义函数f映射转变为一个新的元素。原始RDD中的数据项与新RDD中的数据项是一一对应的关系。
file = "file:///home/dyc/opt/soft/spark/README.md"
lines = spark.read.text(file).rdd.map(lambda r: r[0])
# flatMap与map类似,但每个元素输入项都可以被映射到0个或多个的输出项,最终将结果”扁平化“后输出
counts = lines.flatMap(lambda x: x.split(' ')) \
.map(lambda x: (x, 1)) \
.reduceByKey(add)
# collect() 在驱动程序中将数据集的所有元素作为数组返回。 这在返回足够小的数据子集的过滤器或其他操作之后通常是有用的。由于collect 是将整个RDD汇聚到一台机子上,所以通常需要预估返回数据集的大小以免溢出
output = counts.collect()
for (word, count) in output:
print("%s: %i" % (word, count))
spark.stop()
RDD(Resilient Distributed Dataset) 叫着 弹性分布式数据集 ,是Spark 中最基本的抽象,它代表一个不可变、可分区、里面元素可以并行计算的集合
pyspark
Python 3.6.8 (default, Oct 7 2019, 12:59:55)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
20/01/13 00:25:14 WARN NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
Setting default log level to "WARN".
To adjust logging level use sc.setLogLevel(newLevel). For SparkR, use setLogLevel(newLevel).
Welcome to
____ __
/ __/__ ___ _____/ /__
_\ \/ _ \/ _ `/ __/ '_/
/__ / .__/\_,_/_/ /_/\_\ version 2.3.4
/_/
Using Python version 3.6.8 (default, Oct 7 2019 12:59:55)
SparkSession available as 'spark'.
>>> kv = [('a',7), ('a', 2), ('b', 2), ('b',4), ('c',1), ('c',2), ('c',3), ('c',4)]
>>> rdd2 = sc.parallelize(kv)
>>> rdd3 = rdd2.groupByKey()
>>> rdd3.collect()
[('a', ), ('b', ), ('c', )]
# coding:utf-8
from pyspark import SparkConf, SparkContext
if __name__ == '__main__':
# 连接集群local,本应用名称为Demo
conf = SparkConf().setMaster('local').setAppName('Demo')
sc = SparkContext(conf=conf)
# 统计文件中包含mape的行数,并打印第一行
logFile = "file:///home/dyc/opt/soft/spark/README.md"
lines = sc.textFile(logFile)
plines = lines.filter(lambda lines: 'Spark' in lines)
print(plines.count())
print(plines.first())
注意: 当Spark进行批处理任务遇到当个文件过大时,如果Spark执行器设置的内存太小会出现java.lang.OutOfMemoryError: Java heap space
错误导致任务失败,我们可以在RDD创建之后通过repartition
方法设置合理的分区数量,将每一个分区的数据量减小。
def repartition(self, numPartitions):
"""
Return a new RDD that has exactly numPartitions partitions.
Can increase or decrease the level of parallelism in this RDD.
Internally, this uses a shuffle to redistribute data.
If you are decreasing the number of partitions in this RDD, consider
using `coalesce`, which can avoid performing a shuffle.
>>> rdd = sc.parallelize([1,2,3,4,5,6,7], 4)
>>> sorted(rdd.glom().collect())
[[1], [2, 3], [4, 5], [6, 7]]
>>> len(rdd.repartition(2).glom().collect())
2
>>> len(rdd.repartition(10).glom().collect())
10
"""
return self.coalesce(numPartitions, shuffle=True)
这种 RDD 中的所有转换都是延迟加载的,也就是说,他们并不会直接就计算结果。相反的,他们只是记住这些应用到基础数据集(例如一个文件)上的转换动作。只有当发生一个返回结果的 Driver 的动作时,这些操作才会真正的运行。这种设计会让Spark 更加有效率的运行。
常用的 Transformation 操作:
from pyspark import SparkContext
sc = SparkContext("local", "Map app")
words = sc.parallelize (
["scala",
"java",
"hadoop",
"spark",
"akka",
"spark vs hadoop",
"pyspark",
"pyspark and spark"]
)
words_map = words.map(lambda x: (x, 1))
mapping = words_map.collect()
print (mapping)
[('scala', 1), ('java', 1), ('hadoop', 1), ('spark', 1), ('akka', 1), ('spark vs hadoop', 1), ('pyspark', 1), ('pyspark and spark', 1)]
from pyspark import SparkContext
sc = SparkContext("local", "Filter app")
words = sc.parallelize (
["scala",
"java",
"hadoop",
"spark",
"akka",
"spark vs hadoop",
"pyspark",
"pyspark and spark"]
)
words_filter = words.filter(lambda x: 'spark' in x)
filtered = words_filter.collect()
print (filtered)
['spark', 'spark vs hadoop', 'pyspark', 'pyspark and spark']
from pyspark import SparkContext
sc = SparkContext("local", "Map app")
words = sc.parallelize (
["scala",
"java",
"hadoop",
"spark",
"akka",
"spark vs hadoop",
"pyspark",
"pyspark and spark"]
)
words_map = words.flapMap(lambda x: (x, 1))
mapping = words_map.collect()
print (mapping)
['scala', 1, 'java', 1, 'hadoop', 1, 'spark', 1, 'akka', 1, 'spark vs hadoop', 1, 'pyspark', 1, 'pyspark and spark', 1]
from pyspark import SparkContext
sc = SparkContext("local", "Map app")
words = sc.parallelize (
["scala",
"java",
"hadoop",
"spark",
"akka",
"spark vs hadoop",
"pyspark",
"pyspark and spark"]
)
words_map = words.mapPartitions(lambda x: (x, 1))
words_map.foreach(print)
1
1
1
1
1
1
1
1
>>> rdd = sc.parallelize([1, 2, 3, 4], 4)
>>> def f(splitIndex, iterator): yield splitIndex
>>> rdd.mapPartitionsWithIndex(f).sum()
6
>>> rdd = sc.parallelize(range(100), 4)
>>> n = rdd.sample(False, 0.1, 81)
>>> n.collect()
[4, 26, 39, 41, 42, 52, 63, 76, 80, 86, 97]
>>> rdd = sc.parallelize([1, 1, 2, 3])
>>> rdd.union(rdd).collect()
[1, 1, 2, 3, 1, 1, 2, 3]
>>> rdd1 = sc.parallelize([1, 10, 2, 3, 4, 5])
>>> rdd2 = sc.parallelize([1, 6, 2, 3, 7, 8])
>>> rdd1.intersection(rdd2).collect()
[1, 2, 3]
>>> rdd = sc.parallelize([("a", 1), ("b", 1), ("a", 1)])
>>> sorted(rdd.groupByKey().mapValues(len).collect())
[('a', 2), ('b', 1)]
>>> sorted(rdd.groupByKey().mapValues(list).collect())
[('a', [1, 1]), ('b', [1])]
>>> from operator import add
>>> rdd = sc.parallelize([("a", 1), ("b", 1), ("a", 1)])
>>> sorted(rdd.reduceByKey(add).collect())
[('a', 2), ('b', 1)]
Transformation
操作,可以查看RDD API doc转换 | 含义 |
---|---|
aggregateByKey(zeroValue)(seqOp, combOp, [numTasks]) | 先按分区聚合 再总的聚合每次要跟初始值交流 例如:aggregateByKey(0)(+,+) 对k/y的RDD进行操作 |
sortByKey([ascending], [numTasks]) | 在一个(K,V)的RDD上调用,K必须实现Ordered接口,返回一个按照key进行排序的(K,V)的RDD |
sortBy(func,[ascending], [numTasks]) | 与sortByKey类似,但是更灵活 |
join(otherDataset, [numTasks]) | 在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))的RDD |
cogroup(otherDataset, [numTasks]) | 在类型为(K,V)和(K,W)的RDD上调用,返回一个(K,(Iterable,Iterable))类型的RDD |
cartesian(otherDataset) | 笛卡尔积 |
pipe(command, [envVars]) | 调用外部程序 |
coalesce(numPartitions) | 重新分区 第一个参数是要分多少区,第二个参数是否shuffle 默认false ;少分区变多分区 true ; 多分区变少分区 false |
repartition(numPartitions) | 重新分区 必须shuffle 参数是要分多少区少变多 |
repartitionAndSortWithinPartitions(partitioner) | 重新分区+排序 比先分区再排序效率高 对K/V的RDD进行操作 |
以下是Spark支持的一些常用action算子。详细请参考 RDD API doc
Action算子 | 作用 |
---|---|
reduce(func) | 将RDD中元素按func进行聚合(func是一个 (T,T) => T 的映射函数,其中T为源RDD元素类型,并且func需要满足 交换律 和 结合律 以便支持并行计算 |
collect() | 将数据集中所有元素以数组形式返回驱动器(driver)程序。通常用于,在RDD进行了filter或其他过滤操作后,将一个足够小的数据子集返回到驱动器内存中 |
count() | 返回数据集中元素个数 |
first() | 返回数据集中首个元素(类似于 take(1)) |
take(n) | 返回数据集中前 n 个元素 |
takeSample(withReplacement,num, [seed]) | 返回数据集的随机采样子集,最多包含 num 个元素 |
takeOrdered(n, [ordering]) | 按元素排序(可以通过 ordering 自定义排序规则)后,返回前 n 个元素 |
saveAsTextFile(path) | 将数据集中元素保存到指定目录下的文本文件中(或者多个文本文件),支持本地文件系统、HDFS 或者其他任何Hadoop支持的文件系统。保存过程中,Spark会调用每个元素的toString方法,并将结果保存成文件中的一行 |
countByKey() | 只适用于包含键值对(K, V)的RDD,并返回一个哈希表,包含 (K, Int) 对,表示每个key的个数 |
foreach(func) | 在RDD的每个元素上运行 func 函数。通常被用于累加操作 |
RDD 和它依赖的 父 RDD(可能有多个) 的关系有两种不同的类型,即 窄依赖(narrow dependency)和宽依赖(wide dependency)
DAG(Directed Acyclic Graph)叫做有向无环图,原始的RDD通过一系列的转换就就形成了DAG,根据RDD之间的依赖关系的不同将DAG划分成不同的Stage,对于窄依赖,partition的转换处理在Stage中完成计算。对于宽依赖,由于有Shuffle的存在,只能在parent RDD处理完成后,才能开始接下来的计算,因此宽依赖是划分Stage的依据
Spark中最重要的功能之一是跨操作在内存中持久化(或缓存)数据集。当您持久保存RDD时,每个节点都会存储它在内存中计算的任何分区,并在该数据集(或从中派生的数据集)的其他操作中重用它们。这使得未来的行动更快(通常超过10倍)。缓存是迭代算法和快速交互使用的关键工具。
您可以使用persist()或cache()方法标记要保留的RDD 。第一次在动作中计算它,它将保留在节点的内存中。Spark的缓存是容错的 - 如果丢失了RDD的任何分区,它将使用最初创建它的转换自动重新计算。
cache实际上是调用了self.persist(StorageLevel.MEMORY_ONLY)
,由此可见,cache只有一种缓存级别就是StorageLevel.MEMORY_ONLY
, persist可以有多种选择,默认为StorageLevel.MEMORY_ONLY
见源码:
def cache(self):
"""
Persist this RDD with the default storage level (C{MEMORY_ONLY}).
"""
self.is_cached = True
self.persist(StorageLevel.MEMORY_ONLY)
return self
[docs] def persist(self, storageLevel=StorageLevel.MEMORY_ONLY):
"""
Set this RDD's storage level to persist its values across operations
after the first time it is computed. This can only be used to assign
a new storage level if the RDD does not have a storage level set yet.
If no storage level is specified defaults to (C{MEMORY_ONLY}).
>>> rdd = sc.parallelize(["b", "a", "c"])
>>> rdd.persist().is_cached
True
"""
self.is_cached = True
javaStorageLevel = self.ctx._getJavaStorageLevel(storageLevel)
self._jrdd.persist(javaStorageLevel)
return self
具体的定义:
_all__ = ["StorageLevel"]
[docs]class StorageLevel(object):
"""
Flags for controlling the storage of an RDD. Each StorageLevel records whether to use memory,
whether to drop the RDD to disk if it falls out of memory, whether to keep the data in memory
in a JAVA-specific serialized format, and whether to replicate the RDD partitions on multiple
nodes. Also contains static constants for some commonly used storage levels, MEMORY_ONLY.
Since the data is always serialized on the Python side, all the constants use the serialized
formats.
"""
def __init__(self, useDisk, useMemory, useOffHeap, deserialized, replication=1):
self.useDisk = useDisk
self.useMemory = useMemory
self.useOffHeap = useOffHeap
self.deserialized = deserialized
self.replication = replication
def __repr__(self):
return "StorageLevel(%s, %s, %s, %s, %s)" % (
self.useDisk, self.useMemory, self.useOffHeap, self.deserialized, self.replication)
def __str__(self):
result = ""
result += "Disk " if self.useDisk else ""
result += "Memory " if self.useMemory else ""
result += "OffHeap " if self.useOffHeap else ""
result += "Deserialized " if self.deserialized else "Serialized "
result += "%sx Replicated" % self.replication
return result
StorageLevel.DISK_ONLY = StorageLevel(True, False, False, False)
StorageLevel.DISK_ONLY_2 = StorageLevel(True, False, False, False, 2)
StorageLevel.MEMORY_ONLY = StorageLevel(False, True, False, False)
StorageLevel.MEMORY_ONLY_2 = StorageLevel(False, True, False, False, 2)
StorageLevel.MEMORY_AND_DISK = StorageLevel(True, True, False, False)
StorageLevel.MEMORY_AND_DISK_2 = StorageLevel(True, True, False, False, 2)
StorageLevel.OFF_HEAP = StorageLevel(True, True, True, False, 1)
"""
.. note:: The following four storage level constants are deprecated in 2.0, since the records
will always be serialized in Python.
"""
StorageLevel.MEMORY_ONLY_SER = StorageLevel.MEMORY_ONLY
""".. note:: Deprecated in 2.0, use ``StorageLevel.MEMORY_ONLY`` instead."""
StorageLevel.MEMORY_ONLY_SER_2 = StorageLevel.MEMORY_ONLY_2
""".. note:: Deprecated in 2.0, use ``StorageLevel.MEMORY_ONLY_2`` instead."""
StorageLevel.MEMORY_AND_DISK_SER = StorageLevel.MEMORY_AND_DISK
""".. note:: Deprecated in 2.0, use ``StorageLevel.MEMORY_AND_DISK`` instead."""
StorageLevel.MEMORY_AND_DISK_SER_2 = StorageLevel.MEMORY_AND_DISK_2
""".. note:: Deprecated in 2.0, use ``StorageLevel.MEMORY_AND_DISK_2`` instead."""
我们可以看到: def __init__(self, useDisk, useMemory, useOffHeap, deserialized, replication=1)
, 中指明了是否使用磁盘,是否使用内存,是否使用堆外内存,是否序列化,副本数量
有关Spark内存使用,可以看下面几篇文章
partition是RDD的最小数据处理单元,可以看作是一个数据块,每个partition有个编号index。
一个partition被一个map task处理
>>> rdd = sc.parallelize([1, 2, 3, 4], 2)
>>> rdd.getNumPartitions()
2
累加器
累加器(accumulator)
累加器是仅仅被相关操作累加的变量,因此可以在并行中被有效地支持。它可以被用来实现计数器和总和。
累加器通过对一个初始化了的变量v调用SparkContext.accumulator(v)来创建。在集群上运行的任务可以通过add或者”+=”方法在累加器上进行累加操作。但是,它们不能读取它的值。只有驱动程序能够读取它的值,通过累加器的value方法
广播变量
Spark提供的Broadcast Variable,是只读的。并且在每个节点上只会有一份副本,而不会为每个task都拷贝一份副本。因此其最大作用,就是减少变量到各个节点的网络传输消耗,以及在各个节点上的内存消耗。此外,spark自己内部也使用了高效的广播算法来减少网络消耗
Spark SQL是Spark用来处理结构化数据的一个模块,它提供了两个编程抽象分别叫做DataFrame和DataSet
DataFrame与RDD相同之处,都是不可变分布式弹性数据集。不同之处在于,DataFrame的数据集都是按指定列存储,即结构化数据。类似于传统数据库中的表。 DataFrame的设计是为了让大数据处理起来更容易。DataFrame允许开发者把结构化数据集导入DataFrame,并做了higher-level的抽象; DataFrame提供特定领域的语言(DSL)API来操作你的数据集
最简单的理解我们可以认为DataFrame就是Spark中的数据表(类比传统数据库),DataFrame的结构如下:
DataFrame(表)= Schema(表结构) + Data(表数据)
总结:DataFrame(表)是Spark SQL对结构化数据的抽象。可以将DataFrame看做RDD。
小例子
# coding:utf-8
from pyspark import SparkConf, SparkContext
from pyspark.sql import HiveContext,Row,SparkSession
if __name__ == '__main__':
spark = SparkSession \
.builder \
.appName("Python Spark SQL basic example") \
.config("spark.some.config.option", "some-value") \
.getOrCreate()
logFile = "file:///home/dyc/opt/soft/spark/project/file.json"
df = spark.read.json(logFile)
#
df.show()
df.createOrReplaceTempView("people")
sqlDF = spark.sql("SELECT * FROM people")
# 与df.show()相同
sqlDF.show()
from pyspark.sql import SparkSession
from pyspark.sql.types import *
spark = SparkSession \
.builder \
.appName("Python Spark SQL basic example") \
.config("spark.some.config.option", "some-value") \
.getOrCreate()
sc = spark.sparkContext
# Load a text file and convert each line to a Row.
lines = sc.textFile("file:///home/dyc/opt/soft/spark/examples/src/main/resources/people.txt")
parts = lines.map(lambda l: l.split(","))
# Each line is converted to a tuple. It's a RDD
people = parts.map(lambda p: (p[0], p[1].strip()))
# The schema is encoded in a string.
schemaString = "name age"
fields = [StructField(field_name, StringType(), True) for field_name in schemaString.split()]
schema = StructType(fields)
# Apply the schema to the RDD.
schemaPeople = spark.createDataFrame(people, schema)
# Creates a temporary view using the DataFrame
schemaPeople.createOrReplaceTempView("people")
# SQL can be run over DataFrames that have been registered as a table.
results = spark.sql("SELECT name FROM people")
results.show()
简单的说来,Dataset 提供了对 RDD 的 sql 查询。它可以从 JVM objects 中构建,然后使用转换算子操作,例如 map, flatMap, filter
首先,什么是流(streaming)?数据流是连续到达的无穷序列。流处理将不断流动的输入数据分成独立的单元进行处理。流处理是对流数据的低延迟处理和分析。Spark Streaming是Spark API核心的扩展,可实现实时数据的快速扩展,高吞吐量,高容错处理。Spark Streaming适用于大量数据的快速处理。实时处理用例包括:网站监控,网络监控欺诈识别网页点击广告物联网传感器
Spark Streaming支持如HDFS目录,TCP套接字,Kafka,Flume,Twitter等数据源。数据流可以用Spark 的核心API,DataFrames SQL,或机器学习的API进行处理,并且可以被保存到HDFS,databases或Hadoop OutputFormat提供的任何文件系统中去。
Spark Streaming将数据流每X秒分作一个集合,称为Dstreams,它在内部是一系列RDD。您的Spark应用程序使用Spark API处理RDD,并且批量返回RDD操作的结果
在内部实现上,DStream 由连续的序列化 RDD 来表示。每个 RDD 含有一段时间间隔内的数据,如下图所示
对数据的操作也是按照 RDD 为单位来进行的,如下图所示
与传统流式框架相比,Spark Streaming 最大的不同点在于它对待数据是粗粒度的处理方式,即一次处理一小批数据,而其他框架往往采用细粒度的处理模式,即依次处理一条数据。Spark Streaming 这样的设计实现既为其带来了显而易见的优点,又引入了不可避免的缺点。
优点
Spark Streaming 内部的实现和调度方式高度依赖 Spark 的 DAG 调度器和 RDD,这就决定了 Spark Streaming 的设计初衷必须是粗粒度方式的,同时,由于 Spark 内部调度器足够快速和高效,可以快速地处理小批量数据,这就获得准实时的特性
Spark Streaming 的粗粒度执行方式使其确保“处理且仅处理一次”的特性,同时也可以更方便地实现容错恢复机制
由于 Spark Streaming 的 DStream 本质是 RDD 在流式数据上的抽象,因此基于 RDD 的各种操作也有相应的基于 DStream 的版本,这样就大大降低了用户对于新框架的学习成本,在了解 Spark 的情况下用户将很容易使用 Spark Streaming
由于 DStream 是在 RDD 上的抽象,那么也就更容易与 RDD 进行交互操作,在需要将流式数据和批处理数据结合进行分析的情况下,将会变得非常方便
缺点
总而言之,Spark Streaming 为我们提供了一种崭新的流式处理框架,相信未来随着 Spark Streaming 会在易用性、稳定性以及其他方面有很大的提升。
示例应用程序使用pyspark消费kafak json 数据
# encoding:utf-8
from kafka import KafkaProducer
import json
import time
tp='spark_streaming_test_topic'
producer = KafkaProducer(bootstrap_servers='localhost:9092',value_serializer=lambda v: json.dumps(v).encode('utf-8'))
for i in range(1000):
print i
time.sleep(1)
# 投递的数据是JSON
producer.send(tp, key=bytes(i), value={"key":i})
# encoding:utf-8
from pyspark import SparkConf,SparkContext
from pyspark.streaming import StreamingContext
from pyspark.streaming.kafka import KafkaUtils
import json
def main():
master = "local[*]"
appName = "streaming_test"
brokers="127.0.0.1:9092"
topic='spark_streaming_test_topic'
conf = SparkConf().setAppName(appName).set('spark.io.compression.codec','snappy')
sc = SparkContext(master, conf=conf)
# 构建 Streaming Context 对象
ssc = StreamingContext(sc, 2)
# 创建 Kafka InputDStream
kafkaStreams = KafkaUtils.createDirectStream(ssc,[topic],kafkaParams={"metadata.broker.list": brokers})
# 操作 DStream
result =kafkaStreams.map(lambda line: json.loads(line[1])).map(lambda line: ("key",line.get("key",1)))
result_count = result.reduceByKey(lambda x,y: x+y)
result_count.pprint()
# 启动 Spark Streaming
ssc.start()
ssc.awaitTermination()
if __name__=="__main__":
main()
bin/spark-submit --jars spark-streaming-kafka-0-8-assembly_2.11-2.3.4.jar strema.py
-------------------------------------------
Time: 2020-01-16 00:03:48
-------------------------------------------
('key', 7)