Apache Flink Java 示例:实时温度监控(连接流)

Apache Flink Java 示例:实时温度监控(连接流)

本示例展示如何使用 Apache Flink 的连接流(ConnectedStreams)功能实现实时温度监控系统。该系统将处理两种数据流:温度传感器数据和设备告警规则数据,实现动态阈值监控和异常检测。

应用场景说明

  1. 温度数据流:从传感器接收实时温度读数
  2. 规则数据流:接收设备告警规则的动态更新
  3. 连接处理:将温度数据与最新规则动态关联
  4. 告警输出:当温度超过动态设定的阈值时触发告警

完整实现代码

1. 数据模型定义

import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.api.common.state.*;
import org.apache.flink.api.common.typeinfo.TypeHint;
import org.apache.flink.api.common.typeinfo.TypeInformation;
import org.apache.flink.api.java.tuple.*;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.datastream.*;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.co.*;
import org.apache.flink.util.Collector;

import java.time.Duration;

// 温度传感器事件
public class TemperatureEvent {
    private String sensorId;         // 传感器ID
    private double temperature;      // 温度值
    private long timestamp;          // 事件时间戳
    
    // 构造方法/getters/setters
    public TemperatureEvent(String sensorId, double temperature, long timestamp) {
        this.sensorId = sensorId;
        this.temperature = temperature;
        this.timestamp = timestamp;
    }
    
    public String getSensorId() { return sensorId; }
    public double getTemperature() { return temperature; }
    public long getTimestamp() { return timestamp; }
}

// 设备告警规则
public class AlertRule {
    private String deviceId;         // 设备ID
    private double minTemp;          // 最低温度阈值
    private double maxTemp;          // 最高温度阈值
    private long timestamp;          // 规则更新时间
    
    // 构造方法/getters/setters
    public AlertRule(String deviceId, double minTemp, double maxTemp, long timestamp) {
        this.deviceId = deviceId;
        this.minTemp = minTemp;
        this.maxTemp = maxTemp;
        this.timestamp = timestamp;
    }
    
    public String getDeviceId() { return deviceId; }
    public double getMinTemp() { return minTemp; }
    public double getMaxTemp() { return maxTemp; }
    public long getTimestamp() { return timestamp; }
}

// 温度告警事件
public class TemperatureAlert {
    private String deviceId;         // 设备ID
    private double temperature;      // 实际温度
    private double threshold;        // 超过的阈值
    private String alertType;        // 告警类型(HIGH/LOW)
    private long timestamp;          // 告警时间
    
    public TemperatureAlert(String deviceId, double temperature, 
                            double threshold, String alertType, long timestamp) {
        this.deviceId = deviceId;
        this.temperature = temperature;
        this.threshold = threshold;
        this.alertType = alertType;
        this.timestamp = timestamp;
    }
    
    @Override
    public String toString() {
        return String.format("[ALERT] %s - %s: %.2f°C (Threshold: %.2f°C) @ %s", 
                deviceId, alertType, temperature, threshold, new Date(timestamp));
    }
}

2. 核心处理逻辑 - 连接流处理器

public class TemperatureMonitor {
    
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(4);
        
        // 1. 创建温度数据流(模拟)
        DataStream<TemperatureEvent> tempStream = env
            .addSource(new TemperatureSource())
            .name("Temperature Source")
            .uid("temperature-source");
        
        // 2. 创建规则数据流(模拟)
        DataStream<AlertRule> ruleStream = env
            .addSource(new RuleSource())
            .name("Rule Source")
            .uid("rule-source");
        
        // 3. 连接两条流(基于sensorId/deviceId)
        ConnectedStreams<TemperatureEvent, AlertRule> connectedStreams = tempStream
            .connect(ruleStream)
            .keyBy(TemperatureEvent::getSensorId, AlertRule::getDeviceId);
        
        // 4. 应用连接流处理函数
        DataStream<TemperatureAlert> alerts = connectedStreams
            .process(new TemperatureAlertProcessor())
            .name("Temperature Alert Processor")
            .uid("temperature-alert-processor");
        
        // 5. 输出告警信息
        alerts.print();
        
        env.execute("Real-time Temperature Monitoring");
    }
    
    // 连接流处理函数(核心逻辑)
    public static class TemperatureAlertProcessor 
        extends KeyedCoProcessFunction<String, TemperatureEvent, AlertRule, TemperatureAlert> {
        
        // 状态存储:当前设备的告警规则
        private ValueState<AlertRule> currentRuleState;
        // 状态存储:历史最高温和最低温(用于温度突变检测)
        private ValueState<Tuple2<Double, Double>> historyTempState;
        // 状态存储:最后一次告警时间(用于防止警报洪泛)
        private ValueState<Long> lastAlertTimeState;
        
        @Override
        public void open(Configuration parameters) {
            // 初始化告警规则状态
            ValueStateDescriptor<AlertRule> ruleDescriptor = 
                new ValueStateDescriptor<>("current-rule", AlertRule.class);
            currentRuleState = getRuntimeContext().getState(ruleDescriptor);
            
            // 初始化历史温度状态(max, min)
            ValueStateDescriptor<Tuple2<Double, Double>> tempDescriptor = 
                new ValueStateDescriptor<>("history-temp", 
                    TypeInformation.of(new TypeHint<Tuple2<Double, Double>>() {}));
            historyTempState = getRuntimeContext().getState(tempDescriptor);
            
            // 初始化告警时间状态
            ValueStateDescriptor<Long> timeDescriptor = 
                new ValueStateDescriptor<>("last-alert-time", Long.class);
            lastAlertTimeState = getRuntimeContext().getState(timeDescriptor);
        }
        
        // 处理温度事件
        @Override
        public void processElement1(TemperatureEvent tempEvent,
                                    Context context,
                                    Collector<TemperatureAlert> out) throws Exception {
            // 1. 获取当前设备规则(可能为null)
            AlertRule rule = currentRuleState.value();
            String sensorId = context.getCurrentKey();
            
            // 2. 如果规则存在,检查温度是否超出阈值
            if (rule != null) {
                double currentTemp = tempEvent.getTemperature();
                long currentTime = System.currentTimeMillis();
                
                // 防止警报洪泛(每分钟最多告警一次)
                Long lastAlertTime = lastAlertTimeState.value();
                if (lastAlertTime != null && (currentTime - lastAlertTime) < 60000) {
                    return; // 60秒内已告警过
                }
                
                // 3. 检查高温告警
                if (currentTemp > rule.getMaxTemp()) {
                    lastAlertTimeState.update(currentTime);
                    out.collect(new TemperatureAlert(
                        sensorId, 
                        currentTemp, 
                        rule.getMaxTemp(), 
                        "HIGH",
                        currentTime
                    ));
                }
                // 4. 检查低温告警
                else if (currentTemp < rule.getMinTemp()) {
                    lastAlertTimeState.update(currentTime);
                    out.collect(new TemperatureAlert(
                        sensorId, 
                        currentTemp, 
                        rule.getMinTemp(), 
                        "LOW",
                        currentTime
                    ));
                }
                
                // 5. 检查温度突变(与历史数据比较)
                Tuple2<Double, Double> historyTemp = historyTempState.value();
                if (historyTemp != null) {
                    double maxHistory = historyTemp.f0;
                    double minHistory = historyTemp.f1;
                    
                    // 检测温度骤升(2分钟内升高超过5℃)
                    double deltaUp = currentTemp - maxHistory;
                    if (deltaUp > 5.0) {
                        out.collect(new TemperatureAlert(
                            sensorId, 
                            currentTemp, 
                            maxHistory, 
                            "SPIKE_UP",
                            currentTime
                        ));
                    }
                    
                    // 检测温度骤降(2分钟内下降超过5℃)
                    double deltaDown = minHistory - currentTemp;
                    if (deltaDown > 5.0) {
                        out.collect(new TemperatureAlert(
                            sensorId, 
                            currentTemp, 
                            minHistory, 
                            "SPIKE_DOWN",
                            currentTime
                        ));
                    }
                }
                
                // 6. 更新历史温度状态
                double newMax = Math.max(currentTemp, historyTemp != null ? historyTemp.f0 : currentTemp);
                double newMin = Math.min(currentTemp, historyTemp != null ? historyTemp.f1 : currentTemp);
                historyTempState.update(Tuple2.of(newMax, newMin));
            }
        }
        
        // 处理规则更新事件
        @Override
        public void processElement2(AlertRule rule,
                                     Context context,
                                     Collector<TemperatureAlert> out) throws Exception {
            // 更新当前设备的告警规则
            currentRuleState.update(rule);
            System.out.println("Rule updated for " + rule.getDeviceId() + 
                               ": Min=" + rule.getMinTemp() + ", Max=" + rule.getMaxTemp());
        }
    }
    
    // ========================= 模拟数据源 =========================
    
    // 模拟温度数据源
    private static class TemperatureSource extends RichParallelSourceFunction<TemperatureEvent> {
        private volatile boolean running = true;
        private final Random random = new Random();
        private static final String[] DEVICE_IDS = {"device-001", "device-002", "device-003", "device-004"};
        private static final double[] BASE_TEMPS = {20.0, 22.0, 18.0, 25.0}; // 各设备基础温度
        
        @Override
        public void run(SourceContext<TemperatureEvent> ctx) throws Exception {
            while (running) {
                for (int i = 0; i < DEVICE_IDS.length; i++) {
                    String deviceId = DEVICE_IDS[i];
                    
                    // 模拟温度波动(基础温度±10度)
                    double temp = BASE_TEMPS[i] + (random.nextDouble() * 20 - 10);
                    
                    // 10%概率产生异常值
                    if (random.nextDouble() < 0.1) {
                        temp += (random.nextBoolean() ? 15 : -15);
                    }
                    
                    ctx.collect(new TemperatureEvent(deviceId, temp, System.currentTimeMillis()));
                }
                Thread.sleep(500); // 每0.5秒发送一次所有设备数据
            }
        }
        
        @Override
        public void cancel() {
            running = false;
        }
    }
    
    // 模拟规则数据源
    private static class RuleSource extends RichParallelSourceFunction<AlertRule> {
        private volatile boolean running = true;
        private final Random random = new Random();
        private static final String[] DEVICE_IDS = {"device-001", "device-002", "device-003", "device-004"};
        
        @Override
        public void run(SourceContext<AlertRule> ctx) throws Exception {
            // 初始规则
            ctx.collect(new AlertRule("device-001", 15.0, 30.0, System.currentTimeMillis()));
            ctx.collect(new AlertRule("device-002", 18.0, 35.0, System.currentTimeMillis()));
            ctx.collect(new AlertRule("device-003", 12.0, 28.0, System.currentTimeMillis()));
            ctx.collect(new AlertRule("device-004", 20.0, 40.0, System.currentTimeMillis()));
            
            Thread.sleep(30_000); // 等待30秒
            
            // 模拟规则动态更新
            while (running) {
                for (String deviceId : DEVICE_IDS) {
                    // 随机更新阈值(±3度)
                    double minChange = random.nextDouble() * 6 - 3;
                    double maxChange = random.nextDouble() * 6 - 3;
                    
                    // 新规则范围:15-35度
                    double newMin = Math.max(15.0, 20.0 + minChange);
                    double newMax = Math.min(35.0, 30.0 + maxChange);
                    
                    ctx.collect(new AlertRule(deviceId, newMin, newMax, System.currentTimeMillis()));
                }
                Thread.sleep(60_000); // 每分钟更新一次规则
            }
        }
        
        @Override
        public void cancel() {
            running = false;
        }
    }
}

3. 连接流处理流程详解

keyBy sensorId
keyBy deviceId
状态管理
状态管理
突变发生?
温度数据流
连接流处理器
规则数据流
规则状态存储
历史温度状态
处理温度事件
处理规则更新
规则存在?
检查温度阈值
忽略
超出阈值?
生成告警
检查温度突变
生成告警
更新历史温度状态
更新规则状态
输出告警

4. 状态管理策略

  1. 告警规则状态 (currentRuleState)

    • 存储每个设备的最新告警规则
    • 通过规则数据流(processElement2)更新
    • 用于温度数据流的阈值检查
  2. 历史温度状态 (historyTempState)

    • 存储每个设备的最高和最低温度
    • 用于检测温度突变事件
    • 随着新温度数据的到来持续更新
  3. 告警时间状态 (lastAlertTimeState)

    • 防止警报洪泛机制
    • 确保每分钟最多触发一次阈值告警
    • 不影响温度突变告警的实时性

5. 生产环境增强建议

// 配置RocksDB状态后端(处理大规模状态数据)
env.setStateBackend(new EmbeddedRocksDBStateBackend());

// 启用检查点(保证状态一致性)
env.enableCheckpointing(60_000); // 60秒一次

// Kafka数据源配置(实际生产环境使用)
Properties tempProps = new Properties();
tempProps.setProperty("bootstrap.servers", "kafka-broker:9092");
DataStream<TemperatureEvent> kafkaTempStream = env
    .addSource(new FlinkKafkaConsumer<>(
        "temperature-topic", 
        new JsonDeserialization<>(TemperatureEvent.class), 
        tempProps
    ));

// 告警输出到时序数据库
alerts.addSink(new InfluxDBSink(
    "http://influxdb:8086", 
    "temperature_alerts", 
    "admin", 
    "password"
));

// 添加监控仪表板
alerts.map(alert -> {
    Map<String, Object> metrics = new HashMap<>();
    metrics.put("device", alert.getDeviceId());
    metrics.put("value", alert.getTemperature());
    metrics.put("alertType", alert.getAlertType());
    return metrics;
}).addSink(new ElasticsearchSink<>("monitoring-es:9200"));

6. 高级功能扩展

  1. 设备分组告警

    // 添加设备分组信息到规则
    public class AlertRule {
        private String deviceGroup; // 新增分组字段
        // ...
    }
    
    // 分组级别告警
    alerts.keyBy(alert -> alert.getDeviceGroup())
           .process(new GroupAlertProcessor());
    
  2. 温度预测模型

    // 使用状态存储历史温度序列
    ListState<TemperatureEvent> temperatureHistoryState;
    
    // 在processElement1中
    temperatureHistoryState.add(tempEvent);
    
    // 调用预测模型
    if (needPredict()) {
        List<TemperatureEvent> history = Lists.newArrayList(temperatureHistoryState.get());
        double prediction = predictionModel.predict(history);
        if (prediction > threshold) {
            // 生成预测告警
        }
    }
    
  3. 动态规则传播

    // 广播规则流
    MapStateDescriptor<String, AlertRule> ruleDescriptor = 
        new MapStateDescriptor<>("rule-broadcast", String.class, AlertRule.class);
    
    BroadcastStream<AlertRule> broadcastRuleStream = ruleStream
        .broadcast(ruleDescriptor);
    
    // 连接广播流
    tempStream.connect(broadcastRuleStream)
              .process(new BroadcastRuleProcessor());
    

连接流处理流程全貌

Flink作业
Kafka/传感器
管理端API/Kafka
双流连接
Flink处理集群
规则状态
温度状态
时间状态
实时规则更新
温度阈值检测
温度突变检测
告警输出
温度数据源
规则数据源
时序数据库
告警系统
监控仪表板
邮件/短信通知
系统工单
实时可视化

通过此示例,您可掌握 Flink 连接流的核心应用模式。这种技术特别适用于需要将动态配置与实时数据流结合的监控场景,例如物联网设备监控、实时风控系统和动态定价系统等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值