sparkStreaming对kafka topic数据进行处理后再重新写入kafka

12 篇文章 0 订阅
11 篇文章 2 订阅

1、需求:

  • 使用sparkStreaming对kafka中某topic数据进行数据处理后再重新写入kafka

2、知识点:

  • 广播变量
  • SparkStreaming连接kafka进行消费
  • rdd算子写入kafka
  • 懒加载
  • 伴生类与伴生对象的使用
  • producerRecord手动序列化

3、方法1:

  • KafkaProducer不可序列化,只能在foreachPartition内部创建
  • 对于每个KafkaProducer都创建一次连接,不灵活且低效,不建议使用

4、方法2:

4.1、伴生类创建KafkaProducer包装器

  • 使用伴生类创建KafkaProducer包装器,构造参数为生产者对象(懒加载,遇到action算子才真正创建),方法为生产者对象KafkaProducer发送一条生产记录producerRecord;然后在伴生对象的apply方法中new对应伴生类的对象(因为该类构造器需要传入参数,所以顺便使用匿名函数创建一个生产者作为对象的参数),这样在其他地方就可以函数式调用该类下的方法了(可以理解为调用时已经创建好该类的匿名对象了)
class KafkaSink[K, V](producer: () => KafkaProducer[K, V]) extends Serializable {
    //懒汉模式,定义一个producer
    lazy val prod: KafkaProducer[K, V] = producer()

    def send(topic: String, key: K, value: V) = {
        prod.send(new ProducerRecord[K, V](topic, key, value))
    }

    def send(topic: String, value: V) = {
        prod.send(new ProducerRecord[K, V](topic, value))
    }
}

object KafkaSink {

    import scala.collection.JavaConversions._

    def apply[K, V](config: Map[String, Object]) = {
        val createKafkaProducer = () => {
            val produ = new KafkaProducer[K, V](config)
            //      //销毁数据时做检查,删了的话,出现垃圾时无法回收
            //      sys.addShutdownHook{
            //        produ.close()
            //      }
            produ
        }
        new KafkaSink[K, V](createKafkaProducer)
    }

    def apply[K, V](config: Properties): KafkaSink[K, V] = apply(config.toMap)
}

4.2、SparkStreaming消费kafka并写入kafka

  • 配置消费者参数:SparkStreaming使用直连的方式连接kafkatopic(策略为:消费本地数据且按照topic订阅)
  • SparkStreaming在消费时,会同时进行数据处理(根据用户需求),然后再将清洗后的数据写入一个新的topic
  • 处理后的数据,把它封装后通过生产者写入到对应分区内即可。这里我们采用广播变量的方式,把封装好的KafkaSink对象(传入生产者的参数到伴生对象KafkaSink里,自动调用apply方法执行,apply方法返回的是KafkaSink(producer)对象)传到每个executor上,每个executor创建一次kafka消费者的连接,提高效率
  • 在进行数据处理时,调用广播变量的方法把结果数据封装写入到topic中
  • 最后,懒加载需要行动算子才会触发执行,可以加个foreach(x=>x)触发
  • 广播变量里连接consumer的变量KafkaSink[String, String](producerParams)因为是伴生对象,直接省略apply方法用伴生类型代替,如果不好理解的话,可以换成KafkaSink.apply(producerParams)
object ReadKafkaTopic_event_attendees_raw {
    def main(args: Array[String]): Unit = {
        val conf = new SparkConf().setMaster("local[*]")
        .setAppName(this.getClass.getSimpleName)
        .set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
        .set("spark.streaming.kafka.consumer.poll.ms", "10000") //解决:Exception:after polling for 512,服务器不稳定
        val ssc = new StreamingContext(conf, Seconds(1))
        ssc.checkpoint("checkpoint")
        //Kafka消费者参数
        val kafkaParams = Map(
            ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG -> "single:9092",
            ConsumerConfig.GROUP_ID_CONFIG -> "ea1",
            ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG -> classOf[StringDeserializer],
            ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG -> classOf[StringDeserializer],
            ConsumerConfig.AUTO_OFFSET_RESET_CONFIG -> "earliest",
            ConsumerConfig.MAX_POLL_RECORDS_CONFIG -> "500" //一次拉取条数
        )
        val ea: InputDStream[ConsumerRecord[String, String]] = KafkaUtils
        .createDirectStream(
            ssc,
            LocationStrategies.PreferConsistent,
            ConsumerStrategies.Subscribe[String, String](Set("event_attendees_raw")
            , kafkaParams)
        )
        //kafka生产者参数
        val producerParams = Map (
            ProducerConfig.BOOTSTRAP_SERVERS_CONFIG->"single:9092",
            ProducerConfig.ACKS_CONFIG->"1",
            ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG->classOf[StringSerializer],
            ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG->classOf[StringSerializer]
        )
        //用kafkaSink作为广播的对象
        val ks = ssc.sparkContext.broadcast(KafkaSink[String,String](producerParams))
        ea.map(x => {
            val info = x.value().split(",", -1)
            Array(
                (info(0), info(1).split(" "), "yes"),
                (info(0), info(2).split(" "), "maybe"),
                (info(0), info(3).split(" "), "invited"),
                (info(0), info(4).split(" "), "no")
            )
        }).flatMap(x=>x).flatMap(x => x._2.map(y => (x._1, y, x._3)))
        .filter(_._2!="").foreachRDD(rdd=>rdd.foreachPartition(iter=>{
            iter.map(msg=>{
                ks.value.send("event_attendees_ss",msg.productIterator.mkString(","))
            }).foreach(x=>x)//懒执行需要行动算子,否则会一直卡着不动
        }))
        //.toStream.foreach(_.get())迭代器转化为流,仅触发行动操作。foreach也是行动算子,就够了
        ssc.start()
        ssc.awaitTermination()
    }
}

输出结果:

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值