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

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

前言

  • 此部分是Flink的场景应用示例
  • 本篇主要结合近期的疫情热点做应用(公交疫情实时流监控)
    • 流数据源
      • 刷卡事件信息:即每个人上公交都会进行一次刷卡。每个人上公交刷一次卡,则会生成一次刷卡事件信息。
      • 公交车信息:即公交车本身的信息,以及最主要的状态信息(用于标识是否处于警戒状态,例如有过感染者乘坐)。一旦发现某个公交上有过感染者,则会生成一条新的公交车信息(带警戒状态)。
    • 处理逻辑
      • 公交车信息 会随着疫情(感染者)不断更新,因此需要获取到最新的公交车状态信息
      • 刷卡事件信息公交车信息公交车牌号 进行关联,即可知道本次刷卡的卡号是否乘坐过带感染者的车辆(注意:车牌号不能唯一确定一辆车,此处只是示例)
      • 接着,关联公交卡卡号的人员信息(不一定有,解决方案见文末),即可实施后续手段(例如短信通知对应人员)
  • 需要注意的是
    • 本次应用主要以SQL为主
    • Flink版本和之前不同,采用了 1.9.1

版本信息

产品版本
Flink1.9.1
Java1.8.0_231
Scala2.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 对应的 warntrue ,那么可通过关联该 carId 的人员信息,通知对应的人员:乘坐过有感染者的公交车
  • 注意:
    • 一般公交卡不强制实名,所以大部分人员信息无法或者,也就没法通知
    • 要做到通知,还需要得到更多的 carId 的人员信息
  • 解决方案-示例:
    1. 已有 carId 人员信息的,直接进行通知
    2. 开展活动,让人主动上报公交卡关联信息,完成 cardId 的人员信息补全
    3. 使用手机应用刷卡的,要求应用绑定手机号
    4. 数据挖掘,分析手机信号与刷卡信息的时间、空间,得到公交卡与手机号的关联性(详细内容请私信)
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值