案例:根据订单编号,将两个流数据合并,得到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()
}
}
}