我们以前使用的关系型数据库,比如mysql这种,都是统一个静态表,供我们查询,不管我们有多少数据,最好低于百万级别,都可以对某个表的所有数据进行统一操作,但是这仅仅就是一个静态表而已,也就是说,你的一次操作就会让数据编程另一种状态,比如你在中午表的状态是A状态,到了晚上你修改了一条数据,那么现在就变成了B状态了。我们在某个时间点去看,因为有这么大的时间跨度,就是一种静态表,而且它里面的数据涵盖的时间范围也可能非常广的。
假如,我们现在有两种表,一种是下单的表orderTable,另一种是付款表也就是payTable,那么的话如果要计算在下单后10分钟内付款的总数的话,就需要直接将两个表join了,然后根据时间差来筛选对吧
但是呢,如果是对于美团,饿了吗等等,每时每刻都有人在下单,不可能每时每刻都要针对整个表的数据进行计算。而在Flink SQL的流处理中定义了一种Dynamic Table,数据流不断进来的一种流表,其实静态表也可以看成一种流表,只是我们自己对他的操作时间跨度大而已。
Dynamic Table
这是Stream和Dynamic Table之间的转换关系
如果最先进来了一些订单流,进而被我们转成了一个Dynamic Table,这个Dynamic Table其实也可以看成一个快照静态Table,这个快照应该很容易理解,就是某一瞬间某个时间点的这个Table所拥有的数据。
而对于这个Dynamic Table,可以在上面做一个Continious Query,即翻译过来连续查询,因为和传统静态的不同,在需要的时候去查,对于这种流数据,需要不断的去Query,即连续查询。在经过连续查询后又会生成一个Dynamic Table,然后这个Table又可以转成一些Stream了
下面,就针对这个图来一步一步解析
在流上去定义Dynamic Table
EnvironmentSettings fsSettings = EnvironmentSettings.newInstance().useOldPlanner().inStreamingMode().build();
StreamExecutionEnvironment fsenv = StreamExecutionEnvironment.getExecutionEnvironment();
StreamTableEnvironment fsTabEnv = StreamTableEnvironment.create(fsenv,fsSettings);
fsenv.setParallelism(1);
/****************************/
/*** 开始创建一个表吧 ***/
/*** 应该也可以使用connect等等 ***/
/****************************/
DataStreamSource<order> orderDataStreamSource = fsenv.addSource(new orderSource());
DataStreamSource<payment> paymentDataStreamSource = fsenv.addSource(new paysource());
Table orderTable = fsTabEnv.fromDataStream(orderDataStreamSource).as("orderid,productname,timestampo");
Table payTable = fsTabEnv.fromDataStream(paymentDataStreamSource).as("payid,paytype,timestampp");
这个很容易理解,它是一种以流的形式不断输入到Dynamic Table里面去的,所以是一种insert方式
比如一个点击流和一个点击Dynamic Table
Dynamic Table --Continious Query–>Dynamic Table
Table selectResult = orderTable.select("*");
如果一个点击表的话,就会是这个样子了,在左边,中间经过一个连续查询,看到没有,相应的每个查询会在右边对应一个表
当然,我们有时候的场景不需要那样重新计算所有的数据,可想而知,Stream中的窗口是最常用的对吧。每次查询都会针对某个时间段去查询,这样每次更新的结果表也是对应一个时间段的
样例
ClickSource生产实时流
public class ClickSource implements SourceFunction<click> {
public boolean isRunning = true;
@Override
public void run(SourceContext<click> ctx) throws Exception {
String[] users = {"Mary","Jack","Rose","Peter","Xiong","Zhou","Wang","Li","Tang","Xiao","Yang","He"};
String[] urls = {"url1","url2","url3","url4","url5","url6","url7","url8"};
Random rand = new Random();
while(isRunning){
click c = new click();
c.setCtime(System.currentTimeMillis()+"");
c.setUrl(urls[rand.nextInt(urls.length)]);
c.setUser(users[rand.nextInt(users.length)]);
ctx.collectWithTimestamp(c,Long.valueOf(c.getCtime()));
ctx.emitWatermark(new Watermark(Long.valueOf(c.getCtime())));
Thread.sleep(300);
}
}
@Override
public void cancel() {
isRunning = false;
}
}
然后在flink环境中去处理
EnvironmentSettings environmentSettings = EnvironmentSettings.newInstance().useOldPlanner().inStreamingMode().build();
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
StreamTableEnvironment fsTableEnv = StreamTableEnvironment.create(env,environmentSettings);
env.setParallelism(1);
DataStreamSource<click> clickStream = env.addSource(new ClickSource());
Table clickTable = fsTableEnv.fromDataStream(clickStream);
Table selectResult = clickTable.groupBy("user").select("user,count(url) as cnt");
fsTableEnv.toRetractStream(selectResult, Row.class).print();
// fsTableEnv.toAppendStream(selectResult,Row.class).print();
try {
env.execute("new flink");
} catch (Exception e) {
e.printStackTrace();
}
注意,这里不能将Table转换成Append流了,因为他不是那种需要insert结果表的,这个带有group和count的必须保证每个user在一张表中只有唯一值,不能说来一个user就插入到里面,那样就没了group和count的效果。所以他转换成的是一种delete+insert流
下面这个是运行效果,有true和false,true代表insert一个值,false代表删除一个值。比如前面三个true都分别加入了Zhou,Li,Mary的url个数,然后第四个流是false,代表的意思是将Li删除,然后下一条是一个true,Li,且Li的url个数变成了2了。可见,他用的这种insert+delete的方式达到更新效果