From 334493f7cb8f6c5f6e9594300b6fbb5d0937baec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BD=97=E7=A5=A5?= <1366971433@qq.com> Date: Mon, 27 May 2019 18:01:20 +0800 Subject: [PATCH] modify --- notes/Spark_Streaming基本操作.md | 129 +++++++++++++++++++++ notes/Spark累加器与广播变量.md | 2 +- pictures/spark-streaming-word-count-v3.png | Bin 0 -> 13330 bytes 3 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 pictures/spark-streaming-word-count-v3.png diff --git a/notes/Spark_Streaming基本操作.md b/notes/Spark_Streaming基本操作.md index cd94b5b..9a5686b 100644 --- a/notes/Spark_Streaming基本操作.md +++ b/notes/Spark_Streaming基本操作.md @@ -166,6 +166,8 @@ Deleting hdfs://hadoop001:8020/spark-streaming/checkpoint-1558945265000 ## 三、输出操作 +### 3.1 输出API + | Output Operation | Meaning | | :------------------------------------------ | :----------------------------------------------------------- | | **print**() | 在运行流应用程序的driver节点上打印DStream中每个批次的前十个元素。用于开发调试。 | @@ -175,8 +177,135 @@ Deleting hdfs://hadoop001:8020/spark-streaming/checkpoint-1558945265000 | **foreachRDD**(*func*) | 最通用的输出方式,它将函数func应用于从流生成的每个RDD。此函数应将每个RDD中的数据推送到外部系统,例如将RDD保存到文件,或通过网络将其写入数据库。 | +### 3.1 foreachRDD + +这里我们使用Redis作为客户端,对文章开头示例程序进行改变,把每一次词频统计的结果写入到Redis,利用Redis的`HINCRBY`命令来进行总次数的统计。相关依赖和实现代码如下: + +```xml + + redis.clients + jedis + 2.9.0 + +``` + +实现代码如下: + +```scala +import org.apache.spark.SparkConf +import org.apache.spark.streaming.dstream.DStream +import org.apache.spark.streaming.{Seconds, StreamingContext} +import redis.clients.jedis.Jedis + +object NetworkWordCountToRedis { + + def main(args: Array[String]) { + + val sparkConf = new SparkConf().setAppName("NetworkWordCountToRedis").setMaster("local[2]") + val ssc = new StreamingContext(sparkConf, Seconds(5)) + + /*创建文本输入流,并进行词频统计*/ + val lines = ssc.socketTextStream("hadoop001", 9999) + val pairs: DStream[(String, Int)] = lines.flatMap(_.split(" ")) + .map(x => (x, 1)).reduceByKey(_ + _) + pairs.foreachRDD { rdd => + rdd.foreachPartition { partitionOfRecords => + var jedis: Jedis = null + try { + jedis = JedisPoolUtil.getConnection + partitionOfRecords.foreach(record => jedis.hincrBy("wordCount", record._1, record._2)) + } catch { + case ex: Exception => + ex.printStackTrace() + } finally { + if (jedis != null) jedis.close() + } + } + } + + ssc.start() + ssc.awaitTermination() + } +} + +``` + +其中`JedisPoolUtil`的代码如下: + +```java +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; + +public class JedisPoolUtil { + + /* 声明为volatile防止指令重排序 */ + private static volatile JedisPool jedisPool = null; + private static final String HOST = "localhost"; + private static final int PORT = 6379; + + /* 双重检查锁实现懒汉式单例 */ + public static Jedis getConnection() { + if (jedisPool == null) { + synchronized (JedisPoolUtil.class) { + if (jedisPool == null) { + JedisPoolConfig config = new JedisPoolConfig(); + config.setMaxTotal(30); + config.setMaxIdle(10); + jedisPool = new JedisPool(config, HOST, PORT); + } + } + } + return jedisPool.getResource(); + } +} +``` + +### 3.3 代码说明 + +这里将上面输出操作的代码单独抽取出来,并去除异常判断的部分,精简后的代码如下: + +```scala +pairs.foreachRDD { rdd => + rdd.foreachPartition { partitionOfRecords => + val jedis = JedisPoolUtil.getConnection + partitionOfRecords.foreach(record => jedis.hincrBy("wordCount", record._1, record._2)) + jedis.close() + } +} +``` + +这里可以看到一共使用了三次循环,分别是循环RDD,循环分区,循环每条记录,上面我们的代码是在循环分区的时候获取连接,也就是为每一个分区获取一个连接。但是这里大家可能会有疑问:为什么不在循环RDD的时候,为每一个RDD获取一个连接,这样所需要的连接数更少。实际上这是不可以的,如果按照这种情况进行改写,如下: + +```scala +pairs.foreachRDD { rdd => + val jedis = JedisPoolUtil.getConnection + rdd.foreachPartition { partitionOfRecords => + partitionOfRecords.foreach(record => jedis.hincrBy("wordCount", record._1, record._2)) + } + jedis.close() +} +``` + +此时在执行时候就会抛出`Caused by: java.io.NotSerializableException: redis.clients.jedis.Jedis`,这是因为 + +第二个需要注意的是ConnectionPool最好是一个静态,惰性初始化连接池 。这是因为Spark的转换操作本生就是惰性的,且没有数据流时是不会触发写出操作,故出于性能考虑,连接池应该是惰性静态的,所以上面`JedisPool`在初始化时采用了懒汉式单例进行初始化。 +### 3.3 启动测试 + +```shell +[root@hadoop001 ~]# nc -lk 9999 +hello world hello spark hive hive hadoop +storm storm flink azkaban +hello world hello spark hive hive hadoop +storm storm flink azkaban +``` + +使用Redis Manager查看写入结果(如下图),可以看到与使用`updateStateByKey`算子得到的计算结果相同。 + +![spark-streaming-word-count-v2](D:\BigData-Notes\pictures\spark-streaming-word-count-v3.png) + diff --git a/notes/Spark累加器与广播变量.md b/notes/Spark累加器与广播变量.md index 8336076..6cc3bb8 100644 --- a/notes/Spark累加器与广播变量.md +++ b/notes/Spark累加器与广播变量.md @@ -52,7 +52,7 @@ val addMore = (x: Int) => x + more **2. Spark中的闭包** -在实际计算时,Spark会将对RDD操作分解为Task,Task运行在Worker Noode上。在执行之前,Spark会对任务进行闭包,如果闭包内涉及到自由变量,则程序会进行拷贝,并将副本变量放在闭包中,之后闭包被序列化并发送给每个执行者。因此,当在foreach函数中引用`counter`时,它将不再是Driver节点上的`counter`,而是闭包中的副本`counter`,默认情况下,副本`counter`更新后的值不会回传到Driver,所以计数器的最终值仍然为零。 +在实际计算时,Spark会将对RDD操作分解为Task,Task运行在Worker Node上。在执行之前,Spark会对任务进行闭包,如果闭包内涉及到自由变量,则程序会进行拷贝,并将副本变量放在闭包中,之后闭包被序列化并发送给每个执行者。因此,当在foreach函数中引用`counter`时,它将不再是Driver节点上的`counter`,而是闭包中的副本`counter`,默认情况下,副本`counter`更新后的值不会回传到Driver,所以计数器的最终值仍然为零。 需要注意的是:在Local模式下,**有可能**执行foreach的Worker Node与Diver处在相同的JVM,并引用相同的原始`counter`,这时候更新可能是正确的,但是在集群模式下却不行。所以在遇到此类问题时应优先使用累加器。 diff --git a/pictures/spark-streaming-word-count-v3.png b/pictures/spark-streaming-word-count-v3.png new file mode 100644 index 0000000000000000000000000000000000000000..b46f69d7c4a5097cae244ffcbd60d81da43ea0fa GIT binary patch literal 13330 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