刷卡事件信息
:即每个人上公交都会进行一次刷卡。每个人上公交刷一次卡,则会生成一次刷卡事件信息。公交车信息
:即公交车本身的信息,以及最主要的状态信息(用于标识是否处于警戒状态,例如有过感染者乘坐)。一旦发现某个公交上有过感染者,则会生成一条新的公交车信息(带警戒状态)。公交车信息
会随着疫情(感染者)不断更新,因此需要获取到最新的公交车状态信息刷卡事件信息
与 公交车信息
的 公交车牌号
进行关联,即可知道本次刷卡的卡号是否乘坐过带感染者的车辆(注意:车牌号不能唯一确定一辆车,此处只是示例)1.9.1
产品 | 版本 |
---|---|
Flink | 1.9.1 |
Java | 1.8.0_231 |
Scala | 2.11.12 |
<dependency>
<groupId>org.apache.flinkgroupId>
<artifactId>flink-javaartifactId>
<version>1.9.1version>
dependency>
<dependency>
<groupId>org.apache.flinkgroupId>
<artifactId>flink-streaming-java_2.11artifactId>
<version>1.9.1version>
dependency>
<dependency>
<groupId>org.apache.flinkgroupId>
<artifactId>flink-table-planner_2.11artifactId>
<version>1.9.1version>
dependency>
<dependency>
<groupId>org.apache.flinkgroupId>
<artifactId>flink-table-planner-blink_2.11artifactId>
<version>1.9.1version>
dependency>
<dependency>
<groupId>org.apache.flinkgroupId>
<artifactId>flink-table-api-java-bridge_2.11artifactId>
<version>1.9.1version>
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: 刷卡事件数据源
*
* 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: 公交车信息数据源
*
* 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;
}
}
/**
* Description: 公交刷卡信息事件 - 事实
*
* 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: 公交车信息 - 维度
*
* 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: 公交疫情监控 - 实时流
*
* 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
模式中
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: 公交疫情监控 - 实时流
*
* 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
的人员信息补全