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

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

在上文中,我们使用sparkStreaming对kafka中某topic数据进行数据处理后再重新写入kafka,其实整套逻辑思路并不复杂,但全都写在一个类里面,只能一次性使用,修改时要修改类主体的内容,扩展性很差,安全性也得不到保障。

因此本文把该类主体内容拆解,抽出各个部分,便于管理和扩展

1、思路分析:

1.1、从逻辑上抽出第一层概念:

  • 用户要读Kafka的数据,用户要对数据进行处理,用户要往kafka里写数据

1.2、通过核心动作抽出第二层概念

  • XXX用户->读XXX库的数据,XXX用户->对数据进行XXX处理,XXX用户把数据->写入XXX库

1.3、把可以替换的属性方法抽出来,得到:

  • 顶层接口:读、写、操作
  • 实现类1:读kafka、写kafka、操作数据
  • 实现类2:执行类,串联上述三者的实现操作,把词组成句子

2、代码实现:

2.1、顶级接口功能设计

2.1.1、读

/**
 * @tparam T:读MySQL时,返回值就会变,所以用泛型T
 */
trait ReadTrait[T] {
  import scala.collection.JavaConversions._
    //kafka配置参数底层默认是Object
  def reader(prop:Map[String,Object],tableName:String):InputDStream[T]
  def reader(prop:Properties,tableName:String):InputDStream[T] = reader(prop.toMap[String,Object],tableName)
}

2.1.2、写

/**
 * @tparam T
 */
trait WriteTrait[T] {
  import scala.collection.JavaConversions._
  def write(prop:Map[String,Object],tableName:String,ds:DStream[T]):Unit
  def write(prop:Properties,tableName:String,ds:DStream[T]):Unit=write(prop.toMap[String,Object],tableName,ds)
}

2.1.3、数据处理

进来一个出去一个,进来是T类型,出去是V类型

/**
 * 对于不同的topic数据,转换变形的方法是不一样的,继承特质重写变化方法
 * @tparam T:进来时的类型是T
 * @tparam V:出去的时类型有可能会变,就用V
 */
trait TransformTrait[T,V] {
  def transform(in:InputDStream[T]):DStream[V]
}

2.2、工具类:

把kafka不可序列化的record,包装为可序列的消息

/**
 * 工具类
 * @param producer
 * @tparam K
 * @tparam V
 */
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)
}

2.3、实现类1:开发人员

2.3.1、KafkaReader

数据库是kafka时,读的操作就是kafka消费者

/**
 * ConsumerRecord[String,String]:读出来的是String
 * InputDStream:
 * @param ssc:在一个executor上只能有一个StreamingContext
 */
class KafkaReader(ssc:StreamingContext) extends ReadTrait[ConsumerRecord[String,String]]{
  override def reader(prop: Map[String, Object],tableName:String): InputDStream[ConsumerRecord[String,String]] = {
    KafkaUtils.createDirectStream(
      ssc,
      LocationStrategies.PreferConsistent,
      ConsumerStrategies.Subscribe[String,String](Set(tableName),prop)
    )
  }
}

object KafkaReader{
  def apply(ssc: StreamingContext): KafkaReader = new KafkaReader(ssc)
}

2.3.2、KafkaWriter

/**
 *
 * @param ssc
 * @tparam T
 */
class KafkaWriter[T](ssc:StreamingContext) extends WriteTrait[String]{
  override def write(prop: Map[String, Object], tableName: String,ds:DStream[String]): Unit = {
    //用KafkaSink作为广播对象
    val bc = ssc.sparkContext.broadcast(KafkaSink[String,String](prop))
    ds.foreachRDD(rdd=>rdd.foreachPartition(iter=>{
      iter.map(record=>{
        bc.value.send(tableName,record)
      }).foreach(x=>x)//动作算子触发懒加载
    }))
  }
}
object KafkaWriter{
  def apply[T](ssc: StreamingContext): KafkaWriter[T] = new KafkaWriter(ssc)
}

2.3.3、实现类2:用户

自定义对数据的处理

/**
 * 对于不同的topic数据,转换变形的方法是不一样的,继承特质重写变化方法
 * 重写TransformTrait方法
 */
trait User_friends_Trait extends TransformTrait[ConsumerRecord[String, String], String] {
  override def transform(in: InputDStream[ConsumerRecord[String, String]]): DStream[String] = {
    in.flatMap(line => {
      val info = line.value().split(",", -1)
      info(1).filter(_ != "").split(" ").map(friendid => {
        (info(0), friendid)
      })
    }).filter(_._2!="").map(tp => tp.productIterator.mkString(","))

  }
}

2.4、执行类:通过上下文环境串联各个抽象概念

这里采用了混用特质的方法

/**
 * 设计模式:模板模式
 */
class KTKExecutor(readConf:Map[String,Object],writeConf:Map[String,Object]){
  tran:TransformTrait[ConsumerRecord[String,String],String]=>

  def worker(intopic:String,outtopic:String)={
    //创建一个streamingContext
    val conf = new SparkConf().setMaster("local[*]").setAppName("readtest")
      .set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
      .set("spark.streaming.kafka.consumer.poll.ms", "10000")
    val ssc = new StreamingContext(conf, Seconds(1))
    ssc.checkpoint("checkpoint")
    //调用kafka数据读取
    val kr = new KafkaReader(ssc).reader(readConf,intopic)
    //调用混入特质进行数据处理
    val ds = tran.transform(kr)
    //调用kafka写入topic
    KafkaWriter(ssc).write(writeConf,outtopic,ds)
    ssc.start()
    ssc.awaitTermination()
  }
}

2.5、用户调用:

输入参数,动态混入,调用方法

object UFtest {
  def main(args: Array[String]): Unit = {
    val inParams = Map(
      ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG->"single:9092",
      ConsumerConfig.GROUP_ID_CONFIG->"uf1",
      ConsumerConfig.MAX_POLL_RECORDS_CONFIG->"1000",
      ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG->classOf[StringDeserializer],
      ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG->classOf[StringDeserializer],
      ConsumerConfig.AUTO_OFFSET_RESET_CONFIG->"earliest"
    )
    val outParams = Map(
      ProducerConfig.BOOTSTRAP_SERVERS_CONFIG->"single:9092",
      ProducerConfig.ACKS_CONFIG->"all",
      ProducerConfig.RETRIES_CONFIG->"0",
      ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG->classOf[StringSerializer],
      ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG->classOf[StringSerializer]
    )
    (new KTKExecutor(inParams,outParams) with User_friends_Trait)
      .worker("user_friends_raw","user_friends_ss")
  }
}

设计结构:
image-20210406204317194

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值