01-为什么要学习Apache Flink
实时计算,流计算,风控
bounded stream
有始有终的数据集合,这个数据集合是有大小的,会有一个结果
unbounded stream
很难有一个结果或者没有结果,因为数据是不停的进来,要不就不断的产生结果
state
有状态计算
application state is a first-class citizen in Flink
像聚合的计算像count,sum这些复杂的它需要keep之前的数据,比如说要计算最近一小时总的pv或uv,我们都要把这一小时的数据缓存到或者keep到我们的系统,这些数据就是我们所说的状态,还跟exactly once有关
无状态计算
像select操作或者where过滤,对于数据来一条,处理一条,数据不需要留在系统里面
Flink提供了本地的状态存储
我们要对本地的状态进行备份来实现容灾,定期把本地的状态归档到外部的分布式持久化存储系统
构建实时数仓,下游构建实时数仓,上游就会构建一个实时的stream ETL
02-Apache Flink概念介绍:有状态流式处理引擎的基石
持续接收数据
以时间作为划分数个批次档案的依据,5点到6点的数据作为一个批次档案,6点到7点作为一个批次档案,然后用时间作为划分,然后使用Spark,mr做计算
周期性执行批次运算
如果时间转化跨越了所定义的时间划分,将中间运算结果带到下一个批次运算
0
Window的使用场景,一般用来处理有限的数据,比如一天的pv,uv,而flink一般是处理无限的流
time window
tumple window
相邻的元素不会重叠,只属于一个window
count window
evitor trigger
non-keyed window 实际生产中很少用到,可以理解成key by one
在window结束的时候会触发算子,在触发算子之前可以自定义evitor
trigger是决定了一个window要被怎么处理
关于数据延迟,这里有个例子,比如说有个人在山沟沟里,WiFi信号不好,用户操作的日志发送不出去会先保存到本地,等网络好了再发送出去,如果有人有WiFi,那么他的操作日志就能立即发出去了
flink从mq里面读到的数据也是有顺序的,这就有可能涉及到数据的乱序
有时候在mq的数据已经是乱序了
如果watermark=10,意味着后面再也不会收到时间戳少于等于10的数据,实际情况下也是有可能收到
30分钟以后再看
9. Flink SQL编程
每小时每个用户点击数
select user, tumble_end(cTime, interval '1' hours) as endT, count(url) as cnt from clicks group by user,tumble(cTime, interval '1' hours)
window聚合的特点
window的结果输出会等到window的时间到达才会输出,而且输出一次,比如说12点到13点的数据,会等到13点的时候输出
从历史到现在每个用户的点击数
select user, count(url) as cnt from clicks group by user
每来一条数据计算一次并把结果输出出去,结果是不断更新的,所以结果表一般接MySQL,HBASE,一般不接kafka,会有重复的数据,对于非window的聚合,一般选择可更新的,幂等的数据库存储
因为window聚合知道什么时候某个窗口已经结束了,过期了,所以它能及时清理过期数据,保证总状态可以维持稳定的比较小的大小
而group聚合是不知道什么时候清理状态,看你聚合的key是不是无限增长的,不太建议这种无限增长的,因为越到后面,这个作业的稳定性和性能都没办法达到稳定的状态,所以一般建议配上一个ttl,状态的ttl是在精确性和状态大小之间的一个权衡,经验是一天半左右,跟job和业务来配置
如果是运行时的代码使用Java来写
计算搭载每种乘客数量的行车事件数,也就是搭载一个乘客的行车数,搭载两个乘客的行车数
select psgCnt, count(*) as cnt from rides group by psgCnt;
为了持续的监控城市的交通流量,计算每个区域每5分钟的进入的车辆数。我们只关心纽约的区域交通情况,并且只关心至少有5辆车子进入的区域
select toAreaId(lon, lat) as area, isStart, tumble_end(rideTime, interval '5' minute) as window_end, count(*) as cnt from rides where isInNYC(lon, lat) group by toAreaId(lon, lat), isStart, tumble(rideTime, interval '5' minute) having count(*) >= 5;
将每10分钟搭乘的乘客数写入kafka
insert into Sink_TenMinPsgCnts select tumble_start(rideTime, interval '10' minute), tumble_end(rideTime, interval '10' minute), cast(sum(psgCnt) as bigint) as cnt from rides group by tumble(rideTime, interval '10' minute)
从每个区域出发的行车数,写入到es
kafka是不支持upsert模式
这个SQL是无限流上的聚合,所以输出的结果也是update的结果,不断的更新它之前输出的结果,要接的sink也是update sink
insert into Sink_AreaCnts select toAreaId(lon, lat) as areaId, count(*) as cnt from rides group by toAreaId(lon, lat);