传送区
[Scala] Flink项目实例系列(零)
[Scala] Flink项目实时热门商品统计(一)
[Scala] Flink项目实时流量统计(二)
[Scala] Flink项目恶意登录监控(三)
[Scala] Flink项目订单支付失效监控(四)
[Scala] Flink项目订单支付实时对账(五)
[Scala] Flink项目小彩蛋(六)
本项目的代码及文件见这这这,友情码是:3n9z。
项目需求
用户下单15分钟后未完成支付,输出监控信息
思路
- 传统方案:当订单创建时启动定时器,在定时器内完成支付便输出到主流,否则输出到侧流
- CEP方案:基于CEP实现
代码实现
数据源结构
orderId | eventType | txId | timestamp |
---|---|---|---|
34729 | pay | sd76f87d6 | 1558430844 |
传统方案
import org.apache.flink.api.common.state.{ValueStateDescriptor, ValueState}
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.KeyedProcessFunction
import org.apache.flink.streaming.api.scala._
import org.apache.flink.util.Collector
object OrderTimeout {
// 给支付时间超时设置侧输出流
val orderTimeoutOutputTag = new OutputTag[OrderResult]("orderTimeout")
def main(args: Array[String]) {
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(1)
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
val resource = getClass.getResource("/OrderLog.csv")
val orderEventStream = env.readTextFile(resource.getPath)
.map(line => {
val lineArray = line.split(",")
OrderEvent(lineArray(0).toLong, lineArray(1).trim, lineArray(2).trim, lineArray(3).toLong)
})
.assignAscendingTimestamps(_.eventTime * 1000)
.keyBy(_.orderId)
// 定义process function进行超时检测
// val timeoutWarningStream = orderEventStream.process( new OrderTimeoutWarning() )
val orderResultStream = orderEventStream.process(new OrderPayMatch())
orderResultStream.print("payed")
orderResultStream.getSideOutput(orderTimeoutOutputTag).print("timeout")
env.execute("order timeout without cep job")
}
class OrderPayMatch() extends KeyedProcessFunction[Long, OrderEvent, OrderResult] {
lazy val isPayedState: ValueState[Boolean] = getRuntimeContext.getState(
new ValueStateDescriptor[Boolean]("ispayed-state", classOf[Boolean])
)
// 保存定时器的时间戳为状态
lazy val timerState: ValueState[Long] = getRuntimeContext.getState(
new ValueStateDescriptor[Long]("timer-state", classOf[Long])
)
override def processElement(value: OrderEvent, ctx: KeyedProcessFunction[Long, OrderEvent, OrderResult]#Context,
out: Collector[OrderResult]): Unit = {
// 先读取状态
val isPayed = isPayedState.value()
val timerTs = timerState.value()
// 根据事件的类型进行分类判断,做不同的处理逻辑
if (value.eventType == "create") {
// 1. 如果是create事件,接下来判断pay是否来过
if (isPayed) {
// 1.1 如果已经pay过,匹配成功,输出主流,清空状态
out.collect(OrderResult(value.orderId, "payed successfully"))
ctx.timerService().deleteEventTimeTimer(timerTs)
isPayedState.clear()
timerState.clear()
} else {
// 1.2 如果没有pay过,注册定时器等待pay的到来
val ts = value.eventTime * 1000L + 15 * 60 * 1000L
ctx.timerService().registerEventTimeTimer(ts)
timerState.update(ts)
}
} else if (value.eventType == "pay") {
// 2. 如果是pay事件,那么判断是否create过,用timer表示
if (timerTs > 0) {
// 2.1 如果有定时器,说明已经有create来过
// 继续判断,是否超过了timeout时间
if (timerTs > value.eventTime * 1000L) {
// 2.1.1 如果定时器时间还没到,那么输出成功匹配
out.collect(OrderResult(value.orderId, "payed successfully"))
} else {
// 2.1.2 如果当前pay的时间已经超时,那么输出到侧输出流
ctx.output(orderTimeoutOutputTag, OrderResult(value.orderId, "payed but already timeout"))
}
// 输出结束,清空状态
ctx.timerService().deleteEventTimeTimer(timerTs)
isPayedState.clear()
timerState.clear()
} else {
// 2.2 pay先到了,更新状态,注册定时器等待create
isPayedState.update(true)
ctx.timerService().registerEventTimeTimer(value.eventTime * 1000L)
timerState.update(value.eventTime * 1000L)
}
}
}
override def onTimer(timestamp: Long, ctx: KeyedProcessFunction[Long, OrderEvent, OrderResult]#OnTimerContext,
out: Collector[OrderResult]): Unit = {
// 根据状态的值,判断哪个数据没来
if (isPayedState.value()) {
// 如果为true,表示pay先到了,没等到create
ctx.output(orderTimeoutOutputTag, OrderResult(ctx.getCurrentKey, "already payed but not found create log"))
} else {
// 表示create到了,没等到pay
ctx.output(orderTimeoutOutputTag, OrderResult(ctx.getCurrentKey, "order timeout"))
}
isPayedState.clear()
timerState.clear()
}
}
}
// 实现自定义的处理函数,不能实时输出需要优化
class OrderTimeoutWarning() extends KeyedProcessFunction[Long, OrderEvent, OrderResult] {
// 保存pay是否来过的状态
lazy val isPayedState: ValueState[Boolean] = getRuntimeContext.getState(
new ValueStateDescriptor[Boolean]("ispayed-state", classOf[Boolean])
)
override def processElement(value: OrderEvent, ctx: KeyedProcessFunction[Long, OrderEvent, OrderResult]#Context,
out: Collector[OrderResult]): Unit = {
// 先取出状态标识位
val isPayed = isPayedState.value()
if (value.eventType == "create" && !isPayed) {
// 如果遇到了create事件,并且pay没有来过,注册定时器开始等待
ctx.timerService().registerEventTimeTimer(value.eventTime * 1000L + 15 * 60 * 1000L)
} else if (value.eventType == "pay") {
// 如果是pay事件,直接把状态改为true
isPayedState.update(true)
}
}
override def onTimer(timestamp: Long, ctx: KeyedProcessFunction[Long, OrderEvent, OrderResult]#OnTimerContext,
out: Collector[OrderResult]): Unit = {
// 判断isPayed是否为true
val isPayed = isPayedState.value()
if (isPayed) {
out.collect(OrderResult(ctx.getCurrentKey, "order payed successfully"))
} else {
out.collect(OrderResult(ctx.getCurrentKey, "order timeout"))
}
// 清空状态
isPayedState.clear()
}
}
CEP方案
import java.util
import org.apache.flink.cep.{PatternSelectFunction, PatternTimeoutFunction}
import org.apache.flink.cep.scala.CEP
import org.apache.flink.cep.scala.pattern.Pattern
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.windowing.time.Time
case class OrderEvent(orderId: Long, eventType: String, txId: String, eventTime: Long)
case class OrderResult(orderId: Long, eventType: String)
object OrderTimeoutWithCEP {
def main(args: Array[String]) {
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(1)
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
val resource = getClass.getResource("/OrderLog.csv")
val orderEventStream = env.readTextFile(resource.getPath)
.map(line => {
val lineArray = line.split(",")
OrderEvent(lineArray(0).toLong, lineArray(1).trim, lineArray(2).trim, lineArray(3).toLong)
})
.assignAscendingTimestamps(_.eventTime * 1000)
.keyBy(_.orderId)
// 2. 定义一个匹配模式
val orderPayPattern = Pattern.begin[OrderEvent]("begin").where(_.eventType == "create")
.followedBy("follow").where(_.eventType == "pay")
.within(Time.minutes(15))
// 3. 把模式应用到stream上,得到一个pattern stream
val patternStream = CEP.pattern(orderEventStream, orderPayPattern)
// 4. 调用select方法,提取事件序列,超时的事件要做报警提示
val orderTimeoutOutputTag = new OutputTag[OrderResult]("orderTimeout")
val resultStream = patternStream.select(orderTimeoutOutputTag,
new OrderTimeoutSelect(), // 支付超时的定单
new OrderPaySelect()) // 正常支付定单
resultStream.print("payed")
resultStream.getSideOutput(orderTimeoutOutputTag).print("timeout")
env.execute("order timeout job")
}
}
// 自定义超时事件序列处理函数
class OrderTimeoutSelect() extends PatternTimeoutFunction[OrderEvent, OrderResult] {
// l是超时的时间戳
override def timeout(map: util.Map[String, util.List[OrderEvent]], l: Long): OrderResult = {
val timeoutOrderId = map.get("begin").iterator().next().orderId
OrderResult(timeoutOrderId, "timeout")
}
}
// 自定义正常支付事件序列处理函数
class OrderPaySelect() extends PatternSelectFunction[OrderEvent, OrderResult] {
override def select(map: util.Map[String, util.List[OrderEvent]]): OrderResult = {
// 此时get的可以begin或follow
val payedOrderId = map.get("follow").iterator().next().orderId
OrderResult(payedOrderId, "payed successfully")
}
}