Flink双流合并案例之coGroup、join、connect算子

案例:根据订单编号,将两个流数据合并,得到userId所用的支付方式是什么

1、使用到的数据:OrderLog.csv、ReceiptLog.csv,存放在百度网盘,可自取
链接:https://pan.baidu.com/s/1A16bSdOjSXYNlmi-R7lxgw?pwd=6au4 
提取码:6au4

OrderLog.csv字段:(userId,订单状态,订单号,创建时间)
ReceiptLog.csv字段:(订单号,支付方式,支付时间)
*其中订单编号orderId可对应

2、代码步骤
① 读两个文本数据,变成两条流数据,切记两条数据都要封装样例类
② 告诉代码那个字段是事件时间
③ 利用算子进行合流操作

创建样例类

//OrderLog.csv  样例类
case class Order(userId:String,state:String,orderId:String,time:Long)
//ReceiptLog.csv 样例类
case class Receipt(orderId:String,channel:String,time:Long)

读数据

def main(args: Array[String]): Unit = {
    val env: StreamExecutionEnvironment = StreamExecutionEnvironment
      .getExecutionEnvironment
    env.setParallelism(1)
//    OrderLog.csv
    val order_data: DataStream[Order] = env.readTextFile("data/OrderLog.csv")
      .map(x => {
        val strings: Array[String] = x.split(",")
        Order(strings(0), strings(1), strings(2), strings(3).toLong)
      })
      .filter(_.state == "pay")
      .assignAscendingTimestamps(_.time*1000)

//    ReceiptLog.csv
val receipt_data: DataStream[Receipt] = env.readTextFile("data/ReceiptLog.csv")
  .map(x => {
    val strings: Array[String] = x.split(",")
    Receipt(strings(0), strings(1), strings(2).toLong)
  })
  .assignAscendingTimestamps(_.time * 1000)

    order_data.print()
    receipt_data.print()
    env.execute()
  }
}

coGroup 算子

val value: CoGroupedStreams[Order, Receipt] = order_data.coGroup(receipt_data)
    //where()和equalTo()中两个字段的值要想等,才能合并
    value.where(_.orderId)
      .equalTo(_.orderId)
      .window(TumblingEventTimeWindows.of(Time.seconds(1000)))
      .apply(new cogroupF)

继承CoGroupFunction抽象类
CoProcessFunction 提供了操作每一个输入流的方法: processElement1() 和 processElement2()。类似于 ProcessFunction,这两种方法都通过 Context 对象来调用。这个 Context 对象可以访问事件数据,定时器时间戳,TimerService,以及 side outputs。CoProcessFunction 也提供了 onTimer() 回调函数。
原文链接:https://blog.csdn.net/weixin_43923463/article/details/128043877

class cogroupF extends CoGroupFunction[Order,Receipt,String]{
  override def coGroup(iterable: lang.Iterable[Order],
                       iterable1: lang.Iterable[Receipt],
                       collector: Collector[String]): Unit = {  //Unit表示可以没有输出
  //userId匹配到or匹配不到都能有输出
    //获取正常订单
    if(iterable.iterator().hasNext && iterable1.iterator().hasNext){
      println(s"正常订单:${iterable.iterator().next().userId},${iterable1.iterator().next().channel}")
    //获取异常订单
      // 情况1:用户支付,但商家没到账 (Order有数据,Receipt未匹配)
    }else if(iterable.iterator().hasNext && !iterable1.iterator().hasNext){
      println(s"商家没到账:${iterable.iterator().next()}")
    }else{
      // 情况2:用户没支付,但商家表示到账了 (Receipt有数据)
      println(s"用户没付款:${iterable1.iterator().next()}")
    }

  }
}

**为什么userId匹配到或者匹配不到都能有输出?
是因为coGroup()输入的是迭代器,里面可以有空值,所以没有匹配到的数据也能输出。

join算子

可以直接apply输出,也可以通过传参输出

//利用join进行合流操作
    //*谁写前面谁是第一条流,此处是order_data
    val value: JoinedStreams[Order, Receipt] = order_data.join(receipt_data)
    value.where(_.orderId)
      .equalTo(_.orderId)
      .window(TumblingEventTimeWindows.of(Time.seconds(1000)))
      //方法1:传参
//      .apply(new joinF)
      //方法2:打印两个匹配到的样例类
//      .apply((Order,Receipt) => (Order,Receipt))
      //方法3:打印匹配userId的支付方式
      .apply((Order,Receipt) => (Order.userId,Receipt.channel))
      .print()
    env.execute()
  }
}

class joinF extends JoinFunction[Order,Receipt,String]{
  override def join(in1: Order, in2: Receipt): String = {
    println(s"${in1.userId},${in2.channel}")
    ""  //输出必须是String,不能是Unit
  }
}

**从输入的角度看,为什么join无法打印异常订单?
 join输入是样例类类型,一定有数据,只有匹配到才会进到这个join函数中,所以无法打印异常订单。 
而输入是迭代器,里面可以有空值,故可以打印没有匹配到的异常订单 。

connect算子

    //用connect算子合并双流
    val value: ConnectedStreams[Order, Receipt] = order_data.connect(receipt_data)
    //先合并,再分组
    value.keyBy("orderId","orderId")
      .process(new proF)

继承KeyedCoProcessFunction,四个参数,(key,input1,input2,输出类型)

class proF extends KeyedCoProcessFunction[String,Order,Receipt,String]{
  lazy val o: ValueState[Order] = getRuntimeContext
    .getState(new ValueStateDescriptor[Order]("o", classOf[Order]))
  lazy val r: ValueState[Receipt] = getRuntimeContext
    .getState(new ValueStateDescriptor[Receipt]("r", classOf[Receipt]))

  override def processElement1(in1: Order,
                               context: KeyedCoProcessFunction[String, Order, Receipt, String]#Context,
                               collector: Collector[String]): Unit = {
    if(r.value() != null){
      //2条流都来了、存了,直接输出
      println(s"正常订单:${in1.userId},${r.value().channel}")
      r.clear()   //都为空
    }else{
      //代表order流先来,要等一下receipt流
      o.update(in1)
      //到底要等多久,等5s
      context.timerService().registerEventTimeTimer((in1.time+5) * 1000)
    }
  }

  override def processElement2(in2: Receipt,
                               context: KeyedCoProcessFunction[String, Order, Receipt, String]#Context,
                               collector: Collector[String]): Unit = {
    if(o.value() != null){
      println(s"正常订单:${o.value().userId},${in2.channel}")
      o.clear()
    }else{
      r.update(in2) //有可能等到o,等不到的话o为空r不为空
      context.timerService().registerEventTimeTimer((in2.time+5)*1000)
    }
  }

  //侧:ctx、context;主:out
  override def onTimer(timestamp: Long,
                       ctx: KeyedCoProcessFunction[String, Order, Receipt, String]#OnTimerContext,
                       out: Collector[String]): Unit = {
    //定时器触发了要判断有没有等到数据  定时器出发了,但此时o可能已经清楚
    if(o.value() != null && r.value()==null){
      println(s"用户已付款:${o.value()}")

    }else if(o.value()==null && r.value() != null){
      println(s"商家已收款:${r.value()}")
      r.clear()
    }
  }
}

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值