Flink应用——公交疫情实时流监控

文章目录

  • Flink应用——公交疫情实时流监控
    • 前言
    • 版本信息
    • Mavan 依赖
    • 数据源(刷卡事件信息 + 公交车信息)
      • 流数据源
      • Bean对象与数据
    • 获取公交车辆最新的状态信息(开窗函数)
    • 刷卡事件信息关联车辆状态信息(Join)
    • 后续,下游操作

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.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; } }

Bean对象与数据

  • 刷卡事件实体对象
    /**
    * 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 模式中
    • 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: 公交疫情监控 - 实时流
    * 
    * 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 对应的 warntrue ,那么可通过关联该 carId 的人员信息,通知对应的人员:乘坐过有感染者的公交车
  • 注意:
    • 一般公交卡不强制实名,所以大部分人员信息无法或者,也就没法通知
    • 要做到通知,还需要得到更多的 carId 的人员信息
  • 解决方案-示例:
    1. 已有 carId 人员信息的,直接进行通知
    2. 开展活动,让人主动上报公交卡关联信息,完成 cardId 的人员信息补全
    3. 数据挖掘,分析手机信号与刷卡信息的时间、空间,得到公交卡与手机号的关联性(详细内容请私信)

你可能感兴趣的:(BigData,#,Flink)