背景
在电商、金融、银行、支付等涉及到金钱相关的领域,为了安全起见,一般都有对账的需求。
比如,对于订单支付事件,用户通过某宝付款,虽然用户支付成功,但是用户支付完成后并不算成功,我们得确认平台账户上是否到账了。
针对上述的场景,我们可以采用批处理,或离线计算等技术手段,通过定时任务,每天结束后,扫描数据库中的数据,核对当天的支付数据和交易数据,进行对账。
想要达到实时对账的效果,比如有的用户支付成功但是并没有到账,要及时发出报警,我们必须得依赖实时计算框架。
我们将问题简单化,比如有如下场景,在某电商网站,用户创建订单并支付成功,会将相关信息发给kafka,字段包括,用户uid、动作、订单id、时间等信息
{userId=1, action='create', orId='order01', timestamp=1606549513}
{userId=1, action='pay', orId='order01', timestamp=1606549516}
{userId=2, action='create', orId='order02', timestamp=1606549513}
支付成功并且金额已经进入平台账户,往往也会把相关信息发给kafka,如订单id,支付方式、时间等信息。
{orId='order01', payChannel='wechat', timestamp=1606549536}
{orId='order02', payChannel='alipay', timestamp=1606549537}
只有订单在支付(action=pay)成功后,并且成功到账,这才算一次完整的交易。本案例,就是要实时检测那些不成功的交易,如有不成功的,及时发出报警信息。
上述行为本身会产生两种事件流,一种是订单事件流,另一种是交易事件流,我们通过Flink将两种类型的流进行关联,实时分析没有到账的数据,发出报警。
为了简化,我们从socket读取数据流,代替从kafka消费数据。
代码示例
本案例涉及到的知识点:
首先,我们需要定义订单事件OrderEvents和交易事件ReceiptEvents
// 订单事件
public class OrderEvents {
// 用户id
private Long userId;
// 动作
private String action;
// 订单id
private String orId;
// 时间 单位 s
private Long timestamp;
// get/set
}
// 交易事件
public class ReceiptEvents {
// 订单id
private String orId;
// 支付渠道
private String payChannel;
// 时间 单位 s
private Long timestamp;
// get/set
}
通过Flink程序,联合两条流,实时检测交易失败的数据并输出到侧输出流里。
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
// 定义测输出流,输出只有pay事件没有receipt事件的异常信息
OutputTag payEventTag = new OutputTag<String>("payEventTag-side") {};
// 定义测输出流,输出只有receipt事件没有pay事件的异常信息
OutputTag receiptEventTag = new OutputTag<String>("receiptEventTag-side") {};
// 读取订单数据
KeyedStream<OrderEvents, String> orderStream = env.socketTextStream("localhost", 8888).map(new MapFunction<String, OrderEvents>() {
@Override
public OrderEvents map(String value) throws Exception {
String[] split = value.split(",");
return new OrderEvents(Long.parseLong(split[0]), split[1], split[2], System.currentTimeMillis() / 1000);
}
}).assignTimestampsAndWatermarks(new AscendingTimestampExtractor<OrderEvents>() {
@Override
public long extractAscendingTimestamp(OrderEvents element) {
return element.getTimestamp() * 1000;
}
}).filter(new FilterFunction<OrderEvents>() {
@Override
public boolean filter(OrderEvents value) throws Exception {
return value.getAction().equals("pay");
}
}).keyBy(new KeySelector<OrderEvents, String>() {
@Override
public String getKey(OrderEvents value) throws Exception {
return value.getOrId();
}
});
// 读取交易数据
KeyedStream<ReceiptEvents, String> receiptStream = env.socketTextStream("localhost", 9999).map(new MapFunction<String, ReceiptEvents>() {
@Override
public ReceiptEvents map(String value) throws Exception {
String[] split = value.split(",");
return new ReceiptEvents(split[0], split[1], System.currentTimeMillis() / 1000);
}
}).assignTimestampsAndWatermarks(new AscendingTimestampExtractor<ReceiptEvents>() {
@Override
public long extractAscendingTimestamp(ReceiptEvents element) {
return element.getTimestamp() * 1000;
}
}).keyBy(new KeySelector<ReceiptEvents, String>() {
@Override
public String getKey(ReceiptEvents value) throws Exception {
return value.getOrId();
}
});
// connect两条流
SingleOutputStreamOperator<String> process = orderStream.connect(receiptStream).process(new MyCoProcessFunction());
// 输出正常交易的数据
process.print("success");
// 输出异常交易的数据
process.getSideOutput(payEventTag).print("payEventTag");
process.getSideOutput(receiptEventTag).print("receiptEventTag");
env.execute("Tx Match job");
}
上面代码的主要逻辑是:
从端口为8888和9999的两个socket读取订单事件和交易事件(模拟从kafka消费),然后将事件数据包装成OrderEvents和ReceiptEvents。
提取事件时间。
对于OrderEvents,只需要action=pay的数据,过滤无用的数据。
将两条流根据orId keyby,生成orderStream和receiptStream,并通过connect合并两条流,将合并后的结果,交给CoProcessFunction函数计算。
将正常交易事件输出在success中,异常的交易事件,输出到两个侧输出流中。
所以,我们需要自定义聚合函数,继承CoProcessFunction函数,实现正常交易和异常交易行为的实时计算。
class MyCoProcessFunction
extends CoProcessFunction<OrderEvents, ReceiptEvents, String> {
// 定义测输出流,输出只有pay事件没有receipt事件的异常信息
OutputTag payEventTag = new OutputTag<String>("payEventTag-side") {};
// 定义测输出流,输出只有receipt事件没有pay事件的异常信息
OutputTag receiptEventTag = new OutputTag<String>("receiptEventTag-side") {};
// 定义状态,保存订单pay事件和交易事件
ValueState<OrderEvents> payEventValueState = null;
ValueState<ReceiptEvents> receiptEventValueState = null;
@Override
public void open(Configuration parameters) throws Exception {
ValueStateDescriptor<OrderEvents> descriptor1
= new ValueStateDescriptor<OrderEvents>("payEventValueState", OrderEvents.class);
ValueStateDescriptor<ReceiptEvents> descriptor2
= new ValueStateDescriptor<ReceiptEvents>("receiptEventValueState", ReceiptEvents.class);
payEventValueState = getRuntimeContext().getState(descriptor1);
receiptEventValueState = getRuntimeContext().getState(descriptor2);
}
// 处理OrderEvents事件
@Override
public void processElement1(OrderEvents orderEvents, Context ctx, Collector<String> out) throws Exception {
if (receiptEventValueState.value() != null) {
// 正常输出匹配
out.collect("订单事件:"+orderEvents.toString() + "和交易事件:" + receiptEventValueState.value().toString());
receiptEventValueState.clear();
payEventValueState.clear();
} else {
// 如果没有到账事件,注册定时器等待
payEventValueState.update(orderEvents);
ctx.timerService().registerEventTimeTimer(orderEvents.getTimestamp() * 1000 + 5000L); // 5s
}
}
// 处理receipt事件
@Override
public void processElement2(ReceiptEvents receiptEvents, Context ctx, Collector<String> out) throws Exception {
if (payEventValueState.value() != null) {
// 正常输出
out.collect("订单事件:"+payEventValueState.value().toString() + "和交易事件:" + receiptEvents.toString()+" 属于正常交易");
receiptEventValueState.clear();
payEventValueState.clear();
} else {
// 如果没有订单事件,说明是乱序事件,注册定时器等待
receiptEventValueState.update(receiptEvents);
ctx.timerService().registerEventTimeTimer(receiptEvents.getTimestamp() * 1000 + 3000L); // 3s
}
}
// 定时器
@Override
public void onTimer(long timestamp, OnTimerContext ctx, Collector<String> out) throws Exception {
// 判断哪个状态存在,表示另一个事件没有来
if (payEventValueState.value() != null) {
ctx.output(payEventTag, payEventValueState.value().toString() + " 有pay事件没有receipt事件,属于异常事件");
}
if (receiptEventValueState.value() != null) {
ctx.output(receiptEventTag, receiptEventValueState.value().toString() + " 有receipt事件没有pay事件。属于异常事件");
}
receiptEventValueState.clear();
payEventValueState.clear();
}
}
上述代码是我们自定义的窗口函数,主要的功能是:
继承了CoProcessFunction,分别在processElement1和procossElement2方法中处理orderEvents和receiptEvent。
定义状态,侧输出流,注册定时器,通过一些逻辑计算是是正常交易还是异常交易。
在processElement1方法中,如果只有pay事件没有receipt事件,则注册一个5s后触发的定时器,等待receipt事件的到来,如果5s后receipt事件仍没有到来,则说明是一个异常交易事件,触发timer并将异常事件输出到侧输出流中。
在processElement2方法中,如果只有receipt事件没有pay事件,表明pay事件和receipt事件乱序,则注册一个3s的定时器,等待pay事件。如果3s后还是没有pay事件到达,则触发timer将延迟的乱序数据输出到侧输出流中。
定义定时器timer,对于异常的交易行为,将交易输出输出到侧输出流。异常交易是指,在一定时间范围内,只有pay事件没有receipt事件 或 只有receipt事件没有pay事件。如果在一定时间范围内这两个事件都有,则属于正常交易行为。
打开两个socket,输入数据模拟交易行为。为了输出一些异常信息,我们的输入方式,不光要正常输入数据,还要输入一些乱序的数据,比如只输入payEvent不输入receiptEvent等,使之触发timer。
输入订单事件
nc -lk 8888
1,pay,orderId01
2,pay,orderId02
3,pay,orderId03
4,pay,orderId04
6,pay,orderId06
7,pay,orderId07
8,pay,orderId0
输入交易事件
nc -lk 9999
orderId01,wechat
orderId03,alipay
orderId04,wechat
orderId05,alipay
orderId06,wechat
orderId08,alipa
控制台输出:
success> 订单事件:OrderEvents{userId=1, action='pay', orId='orderId01', timestamp=1606555301}和交易事件:ReceiptEvents{orId='orderId01', payEquipment='wechat', timestamp=1606555307} 属于正常交易
success> 订单事件:OrderEvents{userId=3, action='pay', orId='orderId03', timestamp=1606555318}和交易事件:ReceiptEvents{orId='orderId03', payEquipment='alipay', timestamp=1606555325} 属于正常交易
payEventTag> OrderEvents{userId=2, action='pay', orId='orderId02', timestamp=1606555313} 有pay事件没有receipt事件,属于异常事件
success> 订单事件:OrderEvents{userId=4, action='pay', orId='orderId04', timestamp=1606555332}和交易事件:ReceiptEvents{orId='orderId04', payEquipment='wechat', timestamp=1606555338} 属于正常交易
success> 订单事件:OrderEvents{userId=6, action='pay', orId='orderId06', timestamp=1606555351}和交易事件:ReceiptEvents{orId='orderId06', payEquipment='wechat', timestamp=1606555358} 属于正常交易
receiptEventTag> ReceiptEvents{orId='orderId05', payEquipment='alipay', timestamp=1606555345} 有receipt事件没有pay事件。属于异常事件
success> 订单事件:OrderEvents{userId=8, action='pay', orId='orderId08', timestamp=1606555375}和交易事件:ReceiptEvents{orId='orderId08', payEquipment='alipay', timestamp=1606555382} 属于正常交易
payEventTag> OrderEvents{userId=7, action='pay', orId='orderId07', timestamp=1606555368} 有pay事件没有receipt事件,属于异常事件
参考文章搭建文章gitte源码在线体验可以注册两个号来测试演示图:一.整体介绍 介绍SignalR一种通讯模型Hub(中心模型,或者叫集线器模型),调用这个模型写好的方法,去发送消息。 内容有: ①:Hub模型的方法介绍 ②:服务器端代码介绍 ③:前端vue3安装并调用后端方法 ④:聊天室样例整体流程:1、进入网站->调用连接SignalR的方法2、与好友发送消息->调用SignalR的自定义方法 前端通过,signalR内置方法.invoke() 去请求接口3、监听接受方法(渲染消息)通过new signalR.HubConnectionBuilder().on
在Rails3.x应用程序中,我正在使用net::ssh并向远程pc运行一些命令。我想向用户的浏览器显示实时日志。比如,如果两个命令在net中运行::ssh执行即echo"Hello",echo"Bye"被传递然后"Hello"应该在执行后立即显示在浏览器中。这是代码我在rubyonrails应用程序中使用ssh连接和运行命令Net::SSH.start(@servers['local'],@machine_name,:password=>@machine_pwd,:timeout=>30)do|ssh|ssh.open_channeldo|channel|channel.requ
集成背景我们当前集群使用的是ClouderaCDP,Flink版本为ClouderaVersion1.14,整体Flink安装目录以及配置文件结构与社区版本有较大出入。直接根据Streampark官方文档进行部署,将无法配置FlinkHome,以及后续整体Flink任务提交到集群中,因此需要进行针对化适配集成,在满足使用需求上,尽量提供完整的Streampark使用体验。集成步骤版本匹配问题解决首先解决无法识别Cloudera中的FlinkHome问题,根据报错主要明确到的事情是无法读取到Flink版本、lib下面的jar包名称无法匹配。修改对象:修改源码:(解决无法匹配clouderajar
我想使用Rails3创建一个公共(public)实时聊天应用程序。我在rails2上找到了一些例子。任何人都可以告诉你一个很好的例子/教程来使用rails3开发一个实时聊天应用程序。 最佳答案 当我试图在我的Rails3应用程序中实现一个公共(public)和私有(private)聊天系统时,我遇到了几个障碍。我查看了faye、juggernaut、node.js等。最终在尝试了几种方法之后,我能够实现一个运行良好的系统:1)我开始关注Railscast260中的faye消息传递视频指南。正如DevinM所提到的,我能够快速设置一个
Two-StreamConvolutionalNetworksforActionRecognitioninVideos双流网络论文精读论文:Two-StreamConvolutionalNetworksforActionRecognitioninVideos链接:https://arxiv.org/abs/1406.2199本文是深度学习应用在视频分类领域的开山之作,双流网络的意思就是使用了两个卷积神经网络,一个是SpatialstreamConvNet,一个是TemporalstreamConvNet。此前的研究者在将卷积神经网络直接应用在视频分类中时,效果并不好。作者认为可能是因为卷积神经
文章目录使用flinksqlclientonyarnsession模式Per-JobCluster模式flinkrunflinkrunapplication-tyarn-application配置任务退出时保留Checkpoint从外部checkpoint恢复应用资料使用安装完hadoop3.3.4之后,启动hadoop、yarn将flink1.14.6上传到各个服务器节点,解压flinksqlclientonyarnhttps://nightlies.apache.org/flink/flink-docs-release-1.15/docs/deployment/overview/Appli
一、下载源代码打开终端,输入命令克隆仓库gitclonehttps://github.com/raulmur/DXSLAM.gitDXSLAM二、配置环境WehavetestedthelibraryinUbuntu16.04andUbuntu18.04,butitshouldbeeasytocompileinotherplatforms.C++11orC++0xCompilerPangolinOpenCVEigen3Dbow、Fbowandg2o(IncludedinThirdpartyfolder)tensorflow(1.12)作者提供了一个脚本build.sh来编译Thirdparty目
Flink系列TableAPI和SQL之:表和流的转换一、表和流的转换二、将表(Table)转换成流(DataStream)三、将流转换成表四、支持的数据类型一、表和流的转换从创建表环境开始,历经表的创建、查询转换和输出,已经可以使用TableAPI和SQL进行完整的流处理了。不过在应用的开发过程中,我们测试业务逻辑一般不会直接将结果直接写入到外部系统,而是在本地控制台打印输出。对于DataStream非常容易,直接调用print()方法就可以看到结果数据流的内容了。但对于Table就比较悲剧,没有提供print()方法。在Flink中可以将Table再转换成DataStream,然后进行打印
摘要:本文整理自蚂蚁集团高级技术专家、蚂蚁集团流计算平台负责人李志刚,在FlinkForwardAsia2022平台建设专场的分享。本篇内容主要分为四个部分:主要挑战架构方案核心技术介绍未来规划点击查看直播回放和演讲PPT一、主要挑战1.1金融场景业务特点介绍第一部分是时效性。金融场景追求时效性,特别是一些风控类的业务。首先,无论是宕机还是其他风险情况,对业务的影响需要在秒级以内。其次,业务逻辑经常变更,不能影响时效性。最后,金融业务上下游依赖特别复杂,需要保证时效性不受到影响。第二部分是正确性。金融数据在任何情况下,计算出来数据必须保证100%正确。不能因为出现任何故障或者其他问题导致数据出
我想问一个元素是否会响应实时事件,而不实际触发该事件。HTMLClickme!JS$('#foo').live('mousedown',function(){console.log('triggeredmousedownevent');}if($('#foo').__willRespondToLiveEvent__('mousedown')){console.log('#fooiswiredupproperly');}这是一个有点简单和人为的例子,但我正在寻找一个真正适用于__willRespondToLiveEvent__伪代码的替代品。jQuery是否可以在不实际触发事件的情况下吐