1.foreach***几个算子的比较
 foreach:作用于DStream中每一个时间间隔的RDD中的每一个元素。
 foreachPartition:作用于每一个时间间隔的RDD。
 foreachRDD:作用于DStream中每一个时间间隔RDD。

val orderInfoWithProvinceDstream: DStream[OrderInfo] = orderInfoWithFirstRealFlagDstream.transform { rdd =>
      //driver  按批次周期性执行
      val sql = "select id,name,area,ise_code,iso_3166_2 from gmall110105_province_info"
      val provinceInfoList: List[JSONObject] = PhoenixUtil.queryList(sql)

      // 封装广播变量
      val provinceMap: Map[String, ProvinceInfo] = provinceInfoList.map { jsonObj =>
        val provinceInfo = ProvinceInfo(
          jsonObj.getString("ID"),
          jsonObj.getString("NAME"),
          jsonObj.getString("AREA_CODE"),
          jsonObj.getString("ISO_CODE"),
          jsonObj.getString("ISO_3166_2"))
        (provinceInfo.id, provinceInfo)
      }.toMap
      val provinceBC: Broadcast[Map[String, ProvinceInfo]] = ssc.sparkContext.broadcast(provinceMap)
      // 在executor中执行
      val orderInfoWithProvinceRDD: RDD[OrderInfo] = rdd.map { orderInfo => // executor中执行
        val provinceMap: Map[String, ProvinceInfo] = provinceBC.value
        val provinceInfo: ProvinceInfo = provinceMap.getOrElse(orderInfo.province_id.toString, null)
        if (provinceInfo != null) {
          orderInfo.province_name = provinceInfo.name
          orderInfo.province_area_code = provinceInfo.area_code
          orderInfo.province_iso_code = provinceInfo.iso_code
          orderInfo.province_iso_3166_2 = provinceInfo.iso_3166_2
        }
        orderInfo
      }
      /*  rdd.mapPartitions{ xxx => //executor中执行
              xxx
            }*/
      orderInfoWithProvinceRDD
    }

2.transfrom与map
 transform是流中特有的算子,将DStream[Entity]转换成RDD[Entity]来使用,在driver中执行。
 map是将DStream[Entity]转换成Entity。
 最后返回的都是DStream[Entity]
 例如获取偏移量

val inputGetOffsetDstream = recordInputStream.transform(rdd => {
      // 所有分区的结束位置,在driver中执行。周期性执行
      offsetRanges = rdd.asInstanceOf[HasOffsetRanges].offsetRanges
      rdd
    })

3.auto.offset.reset设置

val kafkaParm = collection.mutable.Map(
    "bootstarp.servers" -> broker_list,
    "key.deserializer" -> classOf[StringDeserializer],
    "value.deserializer" -> classOf[StringDeserializer],
    "group.id" -> "gmall_consumer_group",
    // 如果没有初始化偏移量或者当前的偏移量不存在任何服务器上,可以使用这个配置属性
    // latest自动重置偏移量为最新的偏移量
    "auto.offset.reset" -> "latest",
    // 如果是true,则这个消费者的偏移量会在后台自动提交,但是kafka宕机容易丢失数据  true是kafka定时提交偏移量 大概5-10秒
    // 因为Zookeeper并不适合大批量的频繁写入操作,新版的kafka已推荐将consumer的位移信息保存在Kafka你内部的topic中。
    // 即__consumer_offsets topic,并且默认提供了kafka_consumer_groups.sh脚本供用户查看consumer信息。   偏移量保存在kafka
    // 如果是false,会需要手动维护kafka偏移量
    "enable.auto.commit" -> (false: java.lang.Boolean)
  )

4.KafkaUtils获取Kafka数据的2种API
1、KafkaUtils.createDstream
构造函数为KafkaUtils.createDstream(ssc, [zk], [consumer group id], [per-topic,partitions] ) 使用了receivers来接收数据,利用的是Kafka高层次的消费者api,对于所有的receivers接收到的数据将会保存在spark executors中,然后通过Spark Streaming启动job来处理这些数据,默认会丢失,可启用WAL日志,该日志存储在HDFS上
A、创建一个receiver来对kafka进行定时拉取数据,ssc的rdd分区和kafka的topic分区不是一个概念,故如果增加特定主体分区数仅仅是增加一个receiver中消费topic的线程数,并不增加spark的并行处理数据数量
B、对于不同的group和topic可以使用多个receivers创建不同的DStream
C、如果启用了WAL,需要设置存储级别,即KafkaUtils.createStream(….,StorageLevel.MEMORY_AND_DISK_SER)

2.KafkaUtils.createDirectStream
区别Receiver接收数据,这种方式定期地从kafka的topic+partition中查询最新的偏移量,再根据偏移量范围在每个batch里面处理数据,使用的是kafka的简单消费者api

优点:
A、 简化并行,不需要多个kafka输入流,该方法将会创建和kafka分区一样的rdd个数,而且会从kafka并行读取。
B、高效,这种方式并不需要WAL,WAL模式需要对数据复制两次,
第一次是被kafka复制,另一次是写到wal中
C、恰好一次语义(Exactly-once-semantics),传统的读取kafka数据是通过kafka高层次api把偏移量写入zookeeper中,存在数据丢失的可能性是zookeeper中和ssc的偏移量不一致。EOS通过实现kafka低层次api,偏移量仅仅被ssc保存在checkpoint中,消除了zk和ssc偏移量不一致的问题。缺点是无法使用基于zookeeper的kafka监控工具

5.KafkaUtils.createDirectStream参数详解

def getKafkaStream(topic: String, ssc: StreamingContext, groupId: String): InputDStream[ConsumerRecord[String, String]] = {
    //LocationStrategies.PreferConsistent任务尽量均匀分布在各个executor节点
    // 创建DStream,返回接收到的输入数据
    // LocationStrategies:根据给定的主题和集群地址创建consumer
    // LocationStrategies.PreferConsistent:持续的在所有Executor之间分配分区
    // ConsumerStrategies:选择如何在Driver和Executor上创建和配置Kafka Consumer
    // ConsumerStrategies.Subscribe:订阅一系列主题
    //createDirectStream[String,String] 指定消费kafka的message的key/value的类型
    //ConsumerStrategies.Subscribe[String,String]指定key/value的类型
    val dStream = KafkaUtils.createDirectStream[String, String](ssc, LocationStrategies.PreferConsistent, ConsumerStrategies.Subscribe[String, String](Array(topic), kafkaParm))
    dStream
  }