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()
}
}
输出结果: