文章目录
Flink应用——公交疫情实时流监控
前言
- 此部分是Flink的场景应用示例
- 本篇主要结合近期的疫情热点做应用(公交疫情实时流监控)
- 流数据源
刷卡事件信息
:即每个人上公交都会进行一次刷卡。每个人上公交刷一次卡,则会生成一次刷卡事件信息。公交车信息
:即公交车本身的信息,以及最主要的状态信息(用于标识是否处于警戒状态,例如有过感染者乘坐)。一旦发现某个公交上有过感染者,则会生成一条新的公交车信息(带警戒状态)。
- 处理逻辑
公交车信息
会随着疫情(感染者)不断更新,因此需要获取到最新的公交车状态信息- 将
刷卡事件信息
与公交车信息
的公交车牌号
进行关联,即可知道本次刷卡的卡号是否乘坐过带感染者的车辆(注意:车牌号不能唯一确定一辆车,此处只是示例) - 接着,关联公交卡卡号的人员信息(不一定有,解决方案见文末),即可实施后续手段(例如短信通知对应人员)
- 流数据源
- 需要注意的是
- 本次应用主要以SQL为主
- Flink版本和之前不同,采用了
1.9.1
版本信息
产品 | 版本 |
---|---|
Flink | 1.9.1 |
Java | 1.8.0_231 |
Scala | 2.11.12 |
Mavan 依赖
- pom.xml 依赖部分
<dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-java</artifactId> <version>1.9.1</version> </dependency> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-streaming-java_2.11</artifactId> <version>1.9.1</version> </dependency> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-table-planner_2.11</artifactId> <version>1.9.1</version> </dependency> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-table-planner-blink_2.11</artifactId> <version>1.9.1</version> </dependency> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-table-api-java-bridge_2.11</artifactId> <version>1.9.1</version> </dependency>
数据源(刷卡事件信息 + 公交车信息)
流数据源
- 刷卡事件,源源不断的生成
import com.skey.flinkdemo.bean.CardEvent; import com.skey.flinkdemo.global.Constants; import org.apache.flink.streaming.api.functions.source.RichSourceFunction; import java.util.List; import java.util.Random; /** * Description: 刷卡事件数据源 * <br/> * Date: 2020/7/27 12:21 * * @author ALion */ public class CardEventSourceFunc extends RichSourceFunction<CardEvent> { private volatile boolean isRunning = true; @Override public void run(SourceContext<CardEvent> ctx) throws Exception { while (isRunning) { CardEvent cardEvent = genCardEvent(); ctx.collect(cardEvent); // 每1秒生产一条数据 Thread.sleep(1000); } } @Override public void cancel() { isRunning = false; } // 事件id private long id = 0; /** * 生成刷卡事件信息 */ private CardEvent genCardEvent() { // 随机生成卡片id long cardId = new Random().nextInt(10000); // 生成车牌 List<String> plates = Constants.PLATE_LIST; String plate = plates.get(new Random().nextInt(plates.size())); // 事件时间,时间乱序(网络延迟等问题导致) // 此部分时间无所谓,不影响操作 long r = new Random().nextInt(5000); long updateTime = System.currentTimeMillis() - r; CardEvent cardEvent = new CardEvent(id++, cardId, plate, updateTime); System.out.println("生成数据: " + cardEvent); return cardEvent; } }
- 模拟车辆数据源,动态更新
import com.skey.flinkdemo.bean.BusInfo; import com.skey.flinkdemo.global.Constants; import org.apache.flink.streaming.api.functions.source.RichSourceFunction; import java.util.List; import java.util.Random; /** * Description: 公交车信息数据源 * <br/> * Date: 2020/7/27 12:31 * * @author ALion */ public class BusInfoSourceFunc extends RichSourceFunction<BusInfo> { private volatile boolean isRunning = true; @Override public void run(SourceContext<BusInfo> ctx) throws Exception { while (isRunning) { BusInfo busInfo = genBusInfo(); ctx.collect(busInfo); // 每5秒生产一条数据 Thread.sleep(5000); } } @Override public void cancel() { isRunning = false; } /** * 生成公交车信息 */ private BusInfo genBusInfo() { // 随机生成疫情警告 boolean warn = new Random().nextInt(10) % 2 == 0; // 生成车牌 List<String> plates = Constants.PLATE_LIST; String plate = plates.get(new Random().nextInt(plates.size())); // 事件时间,时间乱序(网络延迟等问题导致) // 后续会取流中最新的公交车信息 long r = new Random().nextInt(3000); long updateTime = System.currentTimeMillis() - r; BusInfo busInfo = new BusInfo(plate, warn, updateTime); System.out.println("生成数据: " + busInfo); return busInfo; } }
Bean对象与数据
- 刷卡事件实体对象
/** * Description: 公交刷卡信息事件 - 事实 * <br/> * Date: 2020/7/27 12:14 * * @author ALion */ public class CardEvent { // 事件id public long id; // 公交卡id public long cardId; // 刷卡的车辆的车牌 public String plate; // 事件时间 public long updateTime; public CardEvent() { } public CardEvent(long id, long cardId, String plate, long updateTime) { this.id = id; this.cardId = cardId; this.plate = plate; this.updateTime = updateTime; } @Override public String toString() { return "CardEvent{" + "id=" + id + ", cardId=" + cardId + ", plate='" + plate + '\'' + ", updateTime=" + updateTime + '}'; } }
- 公交车信息实体对象
/** * Description: 公交车信息 - 维度 * <br/> * Date: 2020/7/27 12:15 * * @author ALion */ public class BusInfo { // 车牌号 public String plate; // 是否处于警戒状态 // 如果为true,表示有感染者乘坐过 // 当然,你还可以按严重程度分级,修改一下数据类型即可 public boolean warn; // 事件时间 public long updateTime; public BusInfo() { } public BusInfo(String plate, boolean warn, long updateTime) { this.plate = plate; this.warn = warn; this.updateTime = updateTime; } @Override public String toString() { return "BusInfo{" + "plate='" + plate + '\'' + ", warn=" + warn + ", updateTime=" + updateTime + '}'; } }
获取公交车辆最新的状态信息(开窗函数)
- 利用
MAX(updateTime)
+GROUP BY plate
的方式,无法方便地获取到告警状态 - 我们需利用
ROW_NUMBER()
+开窗函数
,获取车辆最新的状态信息import com.skey.flinkdemo.bean.BusInfo; import com.skey.flinkdemo.bean.CardEvent; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.Table; import org.apache.flink.table.api.java.StreamTableEnvironment; import org.apache.flink.types.Row; /** * Description: 公交疫情监控 - 实时流 * <br/> * Date: 2020/7/27 12:09 * * @author ALion */ public class BusMonitorDemo { public static void main(String[] args) { // 构建环境 EnvironmentSettings envSettings = EnvironmentSettings.newInstance() .useBlinkPlanner() // Blink .inStreamingMode() // 实时流模式 .build(); StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(4); StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env, envSettings); // 数据源 // 公交车信息 DataStreamSource<BusInfo> busInfoDS = env.addSource(new BusInfoSourceFunc()); // 注册表 tableEnv.registerDataStream("tb_bus_info", busInfoDS); // ROW_NUMBER() + 开窗函数 => 获取公交车辆最新的状态信息 Table table = tableEnv.sqlQuery( "SELECT plate, warn, updateTime " + "FROM (" + "SELECT plate, warn, updateTime, ROW_NUMBER() OVER(PARTITION BY plate ORDER BY updateTime DESC) AS rn " + "FROM tb_bus_info) t " + "WHERE t.rn = 1"); table.printSchema(); // 缩进模式 tableEnv.toRetractStream(table, Row.class) .print(); try { env.execute("BusMonitorDemo"); } catch (Exception e) { e.printStackTrace(); } } }
- 打印的部分示例,如下
root |-- plate: STRING |-- warn: BOOLEAN |-- updateTime: BIGINT 生成数据: BusInfo{plate='DDD444', warn=false, updateTime=1595828143291} 1> (true,DDD444,false,1595828143291) 生成数据: BusInfo{plate='FFF666', warn=false, updateTime=1595828148208} 4> (true,FFF666,false,1595828148208) 生成数据: BusInfo{plate='FFF666', warn=true, updateTime=1595828153704} 4> (false,FFF666,false,1595828148208) 4> (true,FFF666,true,1595828153704) 生成数据: BusInfo{plate='HHH888', warn=true, updateTime=1595828158710} 2> (true,HHH888,true,1595828158710) 生成数据: BusInfo{plate='FFF666', warn=true, updateTime=1595828164795} 4> (false,FFF666,true,1595828153704) 4> (true,FFF666,true,1595828164795) 生成数据: BusInfo{plate='DDD444', warn=false, updateTime=1595828168636} 1> (false,DDD444,false,1595828143291) 1> (true,DDD444,false,1595828168636) 生成数据: BusInfo{plate='AAA111', warn=false, updateTime=1595828173467} 2> (true,AAA111,false,1595828173467) 生成数据: BusInfo{plate='EEE555', warn=false, updateTime=1595828178545} 1> (true,EEE555,false,1595828178545) 生成数据: BusInfo{plate='HHH888', warn=true, updateTime=1595828183983} 2> (false,HHH888,true,1595828158710) 2> (true,HHH888,true,1595828183983) 生成数据: BusInfo{plate='BBB222', warn=false, updateTime=1595828190001} 4> (true,BBB222,false,1595828190001) 生成数据: BusInfo{plate='HHH888', warn=false, updateTime=1595828193908} 2> (false,HHH888,true,1595828183983) 2> (true,HHH888,false,1595828193908)
- 注意,在
Retract
模式中- false: 当前数据已更新,这是旧值
- true: 当前数据已更新,这是新值
刷卡事件信息关联车辆状态信息(Join)
- 根据 车牌
plate
关联import com.skey.flinkdemo.bean.BusInfo; import com.skey.flinkdemo.bean.CardEvent; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.Table; import org.apache.flink.table.api.java.StreamTableEnvironment; import org.apache.flink.types.Row; /** * Description: 公交疫情监控 - 实时流 * <br/> * Date: 2020/7/27 12:09 * * @author ALion */ public class BusMonitorDemo { public static void main(String[] args) { // 构建环境 EnvironmentSettings envSettings = EnvironmentSettings.newInstance() .useBlinkPlanner() // Blink .inStreamingMode() // 实时流模式 .build(); StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(4); StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env, envSettings); // 数据源 // 公交车信息 DataStreamSource<BusInfo> busInfoDS = env.addSource(new BusInfoSourceFunc()); // 刷卡信息 DataStreamSource<CardEvent> cardEventDS = env.addSource(new CardEventSourceFunc()); // 注册表 tableEnv.registerDataStream("tb_bus_info", busInfoDS); tableEnv.registerDataStream("tb_card_event", cardEventDS); // 获取公交的最新数据 String personSql = "SELECT plate, warn, updateTime " + "FROM (" + "SELECT " + "plate, " + "warn, " + "updateTime, " + "ROW_NUMBER() OVER(PARTITION BY plate ORDER BY updateTime DESC) AS rn " + "FROM tb_bus_info) t " + "WHERE t.rn = 1"; // Join 为刷卡事件补全公交信息 // 可以得到哪些公交卡处于警戒状态 Table table = tableEnv.sqlQuery( "SELECT a.plate, a.cardId, a.updateTime, b.warn, b.updateTime " + "FROM tb_card_event AS a " + "JOIN (" + personSql + ") AS b " + "ON a.plate = b.plate" ); table.printSchema(); // 缩进模式 tableEnv.toRetractStream(table, Row.class) .print(); try { env.execute("BusMonitorDemo"); } catch (Exception e) { e.printStackTrace(); } } }
- 部分输出打印的示例
root |-- plate: STRING |-- cardId: BIGINT |-- updateTime: BIGINT |-- warn: BOOLEAN |-- updateTime0: BIGINT 生成数据: BusInfo{plate='EEE555', warn=true, updateTime=1595828810255} 生成数据: CardEvent{id=0, cardId=7198, plate='EEE555', updateTime=1595828808914} 1> (true,EEE555,7198,1595828808914,true,1595828810255) 生成数据: CardEvent{id=1, cardId=5865, plate='EEE555', updateTime=1595828808178} 1> (true,EEE555,5865,1595828808178,true,1595828810255) 生成数据: CardEvent{id=2, cardId=5301, plate='BBB222', updateTime=1595828812077} 生成数据: CardEvent{id=3, cardId=7023, plate='BBB222', updateTime=1595828814567} 生成数据: CardEvent{id=4, cardId=5044, plate='DDD444', updateTime=1595828811764} 生成数据: BusInfo{plate='BBB222', warn=false, updateTime=1595828814318} 生成数据: CardEvent{id=5, cardId=26, plate='GGG777', updateTime=1595828813450} 4> (true,BBB222,7023,1595828814567,false,1595828814318) 4> (true,BBB222,5301,1595828812077,false,1595828814318) 生成数据: CardEvent{id=6, cardId=2255, plate='AAA111', updateTime=1595828813711} 生成数据: CardEvent{id=7, cardId=3836, plate='DDD444', updateTime=1595828816900} 生成数据: CardEvent{id=8, cardId=8224, plate='GGG777', updateTime=1595828815378} 生成数据: CardEvent{id=9, cardId=9189, plate='AAA111', updateTime=1595828818787} 生成数据: BusInfo{plate='AAA111', warn=true, updateTime=1595828820924} 生成数据: CardEvent{id=10, cardId=5713, plate='HHH888', updateTime=1595828818328} 2> (true,AAA111,2255,1595828813711,true,1595828820924) 2> (true,AAA111,9189,1595828818787,true,1595828820924) 生成数据: CardEvent{id=11, cardId=4775, plate='CCC333', updateTime=1595828818310} 生成数据: CardEvent{id=12, cardId=4387, plate='FFF666', updateTime=1595828820758} 生成数据: CardEvent{id=13, cardId=2014, plate='GGG777', updateTime=1595828821539} 生成数据: CardEvent{id=14, cardId=6866, plate='III999', updateTime=1595828821484} 生成数据: BusInfo{plate='III999', warn=false, updateTime=1595828824370} 生成数据: CardEvent{id=15, cardId=1640, plate='III999', updateTime=1595828823027} 3> (true,III999,1640,1595828823027,false,1595828824370) 3> (true,III999,6866,1595828821484,false,1595828824370) 生成数据: CardEvent{id=16, cardId=3509, plate='III999', updateTime=1595828823271} 3> (true,III999,3509,1595828823271,false,1595828824370) 生成数据: CardEvent{id=17, cardId=7838, plate='GGG777', updateTime=1595828826897} 生成数据: CardEvent{id=18, cardId=1801, plate='BBB222', updateTime=1595828826814} 4> (true,BBB222,1801,1595828826814,false,1595828814318) 生成数据: CardEvent{id=19, cardId=8348, plate='EEE555', updateTime=1595828827010} 1> (true,EEE555,8348,1595828827010,true,1595828810255) 生成数据: BusInfo{plate='BBB222', warn=false, updateTime=1595828829174} 生成数据: CardEvent{id=20, cardId=3834, plate='FFF666', updateTime=1595828828511} 4> (false,BBB222,7023,1595828814567,false,1595828814318) 4> (false,BBB222,5301,1595828812077,false,1595828814318) 4> (false,BBB222,1801,1595828826814,false,1595828814318) 4> (true,BBB222,7023,1595828814567,false,1595828829174) 4> (true,BBB222,5301,1595828812077,false,1595828829174) 4> (true,BBB222,1801,1595828826814,false,1595828829174) 生成数据: CardEvent{id=21, cardId=8494, plate='HHH888', updateTime=1595828832402} 生成数据: CardEvent{id=22, cardId=1578, plate='DDD444', updateTime=1595828830997} 生成数据: CardEvent{id=23, cardId=1285, plate='III999', updateTime=1595828830066} 3> (true,III999,1285,1595828830066,false,1595828824370) 生成数据: CardEvent{id=24, cardId=6569, plate='AAA111', updateTime=1595828833730} 2> (true,AAA111,6569,1595828833730,true,1595828820924) 生成数据: BusInfo{plate='AAA111', warn=true, updateTime=1595828836753} 生成数据: CardEvent{id=25, cardId=6199, plate='FFF666', updateTime=1595828833063} 2> (false,AAA111,6569,1595828833730,true,1595828820924) 2> (false,AAA111,2255,1595828813711,true,1595828820924) 2> (false,AAA111,9189,1595828818787,true,1595828820924) 2> (true,AAA111,6569,1595828833730,true,1595828836753) 2> (true,AAA111,2255,1595828813711,true,1595828836753) 2> (true,AAA111,9189,1595828818787,true,1595828836753) 生成数据: CardEvent{id=26, cardId=6825, plate='DDD444', updateTime=1595828834566} 生成数据: CardEvent{id=27, cardId=1740, plate='CCC333', updateTime=1595828834724} 生成数据: CardEvent{id=28, cardId=8628, plate='FFF666', updateTime=1595828837176} 生成数据: CardEvent{id=29, cardId=9748, plate='FFF666', updateTime=1595828838106} 生成数据: BusInfo{plate='III999', warn=false, updateTime=1595828840092} 生成数据: CardEvent{id=30, cardId=7267, plate='III999', updateTime=1595828841158} 3> (false,III999,1640,1595828823027,false,1595828824370) 3> (false,III999,6866,1595828821484,false,1595828824370) 3> (false,III999,1285,1595828830066,false,1595828824370) 3> (false,III999,3509,1595828823271,false,1595828824370) 3> (true,III999,1640,1595828823027,false,1595828840092) 3> (true,III999,6866,1595828821484,false,1595828840092) 3> (true,III999,1285,1595828830066,false,1595828840092) 3> (true,III999,3509,1595828823271,false,1595828840092) 3> (true,III999,7267,1595828841158,false,1595828840092)
- 注意
- 带
1>
、2>
、3>
、4>
的,是Flink的流输出打印 - 带
生成数据
的,是SourceFunction生成数据时的打印 false
表示该条数据已被更新,这是更新前的旧数据true
表示该条数据已被更新,这是更新后的新数据
- 带
后续,下游操作
- 如果
cardId
对应的warn
为true
,那么可通过关联该carId
的人员信息,通知对应的人员:乘坐过有感染者的公交车
- 注意:
- 一般公交卡不强制实名,所以大部分人员信息无法或者,也就没法通知
- 要做到通知,还需要得到更多的
carId
的人员信息
- 解决方案-示例:
- 已有
carId
人员信息的,直接进行通知 - 开展活动,让人主动上报公交卡关联信息,完成
cardId
的人员信息补全 - 使用手机应用刷卡的,要求应用绑定手机号
- 数据挖掘,分析手机信号与刷卡信息的时间、空间,得到公交卡与手机号的关联性(详细内容请私信)
- 已有