上面提出DA-LT-4BT0007点位离线,但未生成离线异常,请分析原因后生成完整修改后代码。生成package com.tongchuang.realtime.mds;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONObject;
import com.tongchuang.realtime.bean.ULEParamConfig;
import com.tongchuang.realtime.util.KafkaUtils;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.api.common.state.*;
import org.apache.flink.api.common.time.Time;
import org.apache.flink.api.common.typeinfo.BasicTypeInfo;
import org.apache.flink.api.common.typeinfo.TypeHint;
import org.apache.flink.api.common.typeinfo.TypeInformation;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.connector.kafka.source.KafkaSource;
import org.apache.flink.connector.kafka.source.enumerator.initializer.OffsetsInitializer;
import org.apache.flink.streaming.api.datastream.*;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.co.KeyedBroadcastProcessFunction;
import org.apache.flink.streaming.api.functions.source.RichSourceFunction;
import org.apache.flink.util.Collector;
import org.apache.flink.util.OutputTag;
import java.io.Serializable;
import java.sql.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.util.*;
import java.util.Date;
import java.util.concurrent.TimeUnit;
public class ULEDataanomalyanalysis {
public static void main(String[] args) throws Exception {
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
env.getConfig().setAutoWatermarkInterval(1000);
// Kafka消费者配置
KafkaSource<String> kafkaConsumer = KafkaUtils.getKafkaConsumer(
"realdata_minute",
"minutedata_uledataanomalyanalysis",
OffsetsInitializer.latest()
);
// 修改Watermark策略
DataStreamSource<String> kafkaDS = env.fromSource(
kafkaConsumer,
WatermarkStrategy.<String>forBoundedOutOfOrderness(Duration.ofMinutes(1))
.withTimestampAssigner((event, timestamp) -> {
try {
JSONObject json = JSON.parseObject(event);
return new SimpleDateFormat("yyyy-MM-dd HH:mm").parse(json.getString("times")).getTime();
} catch (ParseException e) {
return System.currentTimeMillis();
}
}),
"realdata_uledataanomalyanalysis"
);
kafkaDS.print("分钟数据流");
// 解析JSON并拆分tag
SingleOutputStreamOperator<JSONObject> splitStream = kafkaDS
.map(JSON::parseObject)
.returns(TypeInformation.of(JSONObject.class))
.flatMap((JSONObject value, Collector<JSONObject> out) -> {
JSONObject data = value.getJSONObject("datas");
String time = value.getString("times");
for (String tag : data.keySet()) {
JSONObject tagData = data.getJSONObject(tag);
JSONObject newObj = new JSONObject();
newObj.put("time", time);
newObj.put("tag", tag);
newObj.put("ontime", tagData.getDouble("ontime"));
newObj.put("avg", tagData.getDouble("avg"));
out.collect(newObj);
}
})
.returns(TypeInformation.of(JSONObject.class))
.name("Split-By-Tag");
// 配置数据源
DataStream<ConfigCollection> configDataStream = env
.addSource(new MysqlConfigSource())
.setParallelism(1)
.filter(Objects::nonNull)
.name("Config-Source");
// 标签值数据流
DataStream<Map<String, Object>> tagValueStream = splitStream
.map(json -> {
Map<String, Object> valueMap = new HashMap<>();
valueMap.put("tag", json.getString("tag"));
valueMap.put("value", "436887485805570949".equals(json.getString("datatype")) ?
json.getDouble("ontime") : json.getDouble("avg"));
return valueMap;
})
.returns(new TypeHint<Map<String, Object>>() {})
.name("Tag-Value-Stream");
// 合并配置流和标签值流
DataStream<Object> broadcastStream = configDataStream
.map(config -> (Object) config)
.returns(TypeInformation.of(Object.class))
.union(
tagValueStream.map(tagValue -> (Object) tagValue)
.returns(TypeInformation.of(Object.class))
);
// 广播流
BroadcastStream<Object> finalBroadcastStream = broadcastStream
.broadcast(Descriptors.configStateDescriptor, Descriptors.tagValuesDescriptor);
// 按tag分组
KeyedStream<JSONObject, String> keyedStream = splitStream
.keyBy(json -> json.getString("tag"));
BroadcastConnectedStream<JSONObject, Object> connectedStream = keyedStream.connect(finalBroadcastStream);
// 异常检测处理
SingleOutputStreamOperator<JSONObject> anomalyStream = connectedStream
.process(new OptimizedAnomalyDetectionFunction())
.name("Anomaly-Detection");
anomalyStream.print("异常检测结果");
// 离线检测结果侧输出
DataStream<String> offlineCheckStream = anomalyStream.getSideOutput(OptimizedAnomalyDetectionFunction.OFFLINE_CHECK_TAG);
offlineCheckStream.print("离线检测结果");
env.execute("uledataanomalyanalysis");
}
// 配置集合类
public static class ConfigCollection implements Serializable {
private static final long serialVersionUID = 1L;
public final Map<String, List<ULEParamConfig>> tagToConfigs;
public final Map<String, ULEParamConfig> encodeToConfig;
public final Set<String> allTags;
public final long checkpointTime;
public ConfigCollection(Map<String, List<ULEParamConfig>> tagToConfigs,
Map<String, ULEParamConfig> encodeToConfig) {
this.tagToConfigs = new HashMap<>(tagToConfigs);
this.encodeToConfig = new HashMap<>(encodeToConfig);
this.allTags = new HashSet<>(tagToConfigs.keySet());
this.checkpointTime = System.currentTimeMillis();
}
}
// MySQL配置源
public static class MysqlConfigSource extends RichSourceFunction<ConfigCollection> {
private volatile boolean isRunning = true;
private final long interval = TimeUnit.MINUTES.toMillis(5);
@Override
public void run(SourceContext<ConfigCollection> ctx) throws Exception {
while (isRunning) {
ConfigCollection newConfig = loadParams();
if (newConfig != null) {
ctx.collect(newConfig);
System.out.println("配置加载完成,检查点时间: " + new Date(newConfig.checkpointTime));
} else {
System.out.println("配置加载失败");
}
Thread.sleep(interval);
}
}
private ConfigCollection loadParams() {
Map<String, List<ULEParamConfig>> tagToConfigs = new HashMap<>(5000);
Map<String, ULEParamConfig> encodeToConfig = new HashMap<>(5000);
String url = "jdbc:mysql://10.51.37.73:3306/eps?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8";
String user = "root";
String password = "6CKIm5jDVsLrahSw";
String query = "SELECT F_tag AS tag, F_enCode AS encode, F_dataTypes AS datatype, "
+ "F_isConstantValue AS constantvalue, F_isOnline AS isonline, "
+ "F_isSync AS issync, F_syncParaEnCode AS syncparaencode, "
+ "F_isZero AS iszero, F_isHigh AS ishigh, F_highThreshold AS highthreshold, "
+ "F_isLow AS islow, F_lowThreshold AS lowthreshold, F_duration AS duration "
+ "FROM t_equipmentparameter "
+ "WHERE F_enabledmark = '1' AND (F_isConstantValue='1' OR F_isZero='1' "
+ "OR F_isHigh='1' OR F_isLow='1' OR F_isOnline='1' OR F_isSync='1')";
try (Connection conn = DriverManager.getConnection(url, user, password);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(query)) {
while (rs.next()) {
ULEParamConfig config = new ULEParamConfig();
config.tag = rs.getString("tag");
config.encode = rs.getString("encode");
config.datatype = rs.getString("datatype");
config.constantvalue = rs.getInt("constantvalue");
config.iszero = rs.getInt("iszero");
config.ishigh = rs.getInt("ishigh");
config.highthreshold = rs.getDouble("highthreshold");
config.islow = rs.getInt("islow");
config.lowthreshold = rs.getDouble("lowthreshold");
config.duration = rs.getLong("duration");
config.isonline = rs.getInt("isonline");
config.issync = rs.getInt("issync");
config.syncparaencode = rs.getString("syncparaencode");
if (config.encode == null || config.encode.isEmpty()) {
System.err.println("忽略无效配置: 空encode");
continue;
}
String tag = config.tag;
tagToConfigs.computeIfAbsent(tag, k -> new ArrayList<>(10)).add(config);
encodeToConfig.put(config.encode, config);
}
System.out.println("加载配置: " + encodeToConfig.size() + " 个参数");
return new ConfigCollection(tagToConfigs, encodeToConfig);
} catch (SQLException e) {
System.err.println("加载参数配置错误:");
e.printStackTrace();
return null;
}
}
@Override
public void cancel() {
isRunning = false;
}
}
// 状态描述符
public static class Descriptors {
public static final MapStateDescriptor<Void, ConfigCollection> configStateDescriptor =
new MapStateDescriptor<>(
"configState",
TypeInformation.of(Void.class),
TypeInformation.of(ConfigCollection.class)
);
public static final MapStateDescriptor<String, Double> tagValuesDescriptor =
new MapStateDescriptor<>(
"tagValuesBroadcastState",
BasicTypeInfo.STRING_TYPE_INFO,
BasicTypeInfo.DOUBLE_TYPE_INFO
);
}
// 优化后的异常检测函数(重点修复离线检测)
public static class OptimizedAnomalyDetectionFunction
extends KeyedBroadcastProcessFunction<String, JSONObject, Object, JSONObject> {
public static final OutputTag<String> OFFLINE_CHECK_TAG = new OutputTag<String>("offline-check"){};
// 状态管理
private transient MapState<String, AnomalyState> stateMap;
private transient MapState<String, Double> lastValuesMap;
private transient MapState<String, Long> lastDataTimeMap;
private transient MapState<String, Long> offlineTimerState;
private transient MapState<String, Long> tagInitTimeState; // 新增:标签初始化时间状态
private transient SimpleDateFormat timeFormat;
private transient long lastSyncLogTime = 0;
@Override
public void open(Configuration parameters) {
StateTtlConfig ttlConfig = StateTtlConfig.newBuilder(Time.days(30))
.setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite)
.setStateVisibility(StateTtlConfig.StateVisibility.NeverReturnExpired)
.cleanupFullSnapshot()
.build();
// 异常状态存储
MapStateDescriptor<String, AnomalyState> stateDesc = new MapStateDescriptor<>(
"anomalyState",
BasicTypeInfo.STRING_TYPE_INFO,
TypeInformation.of(AnomalyState.class)
);
stateDesc.enableTimeToLive(ttlConfig);
stateMap = getRuntimeContext().getMapState(stateDesc);
// 最新值存储
MapStateDescriptor<String, Double> valuesDesc = new MapStateDescriptor<>(
"lastValuesState",
BasicTypeInfo.STRING_TYPE_INFO,
BasicTypeInfo.DOUBLE_TYPE_INFO
);
valuesDesc.enableTimeToLive(ttlConfig);
lastValuesMap = getRuntimeContext().getMapState(valuesDesc);
// 最后数据时间存储
MapStateDescriptor<String, Long> timeDesc = new MapStateDescriptor<>(
"lastDataTimeState",
BasicTypeInfo.STRING_TYPE_INFO,
BasicTypeInfo.LONG_TYPE_INFO
);
timeDesc.enableTimeToLive(ttlConfig);
lastDataTimeMap = getRuntimeContext().getMapState(timeDesc);
// 离线检测定时器状态
MapStateDescriptor<String, Long> timerDesc = new MapStateDescriptor<>(
"offlineTimerState",
BasicTypeInfo.STRING_TYPE_INFO,
BasicTypeInfo.LONG_TYPE_INFO
);
timerDesc.enableTimeToLive(ttlConfig);
offlineTimerState = getRuntimeContext().getMapState(timerDesc);
// 新增:标签初始化时间状态
MapStateDescriptor<String, Long> initTimeDesc = new MapStateDescriptor<>(
"tagInitTimeState",
BasicTypeInfo.STRING_TYPE_INFO,
BasicTypeInfo.LONG_TYPE_INFO
);
initTimeDesc.enableTimeToLive(ttlConfig);
tagInitTimeState = getRuntimeContext().getMapState(initTimeDesc);
timeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
}
@Override
public void processElement(JSONObject data, ReadOnlyContext ctx, Collector<JSONObject> out) throws Exception {
String tag = ctx.getCurrentKey();
String timeStr = data.getString("time");
long eventTime = timeFormat.parse(timeStr).getTime();
// 更新最后数据时间
lastDataTimeMap.put(tag, eventTime);
// 获取广播配置
ConfigCollection configCollection = getBroadcastConfig(ctx);
if (configCollection == null) return;
List<ULEParamConfig> configs = configCollection.tagToConfigs.get(tag);
if (configs == null || configs.isEmpty()) return;
// 清理无效状态
List<String> keysToRemove = new ArrayList<>();
for (String encode : stateMap.keys()) {
boolean found = false;
for (ULEParamConfig cfg : configs) {
if (cfg.encode.equals(encode)) {
found = true;
break;
}
}
if (!found) {
System.out.println("清理过期状态: " + encode);
keysToRemove.add(encode);
}
}
for (String encode : keysToRemove) {
stateMap.remove(encode);
}
double value = 0;
boolean valueSet = false;
// 检查是否需要离线检测
boolean hasOnlineConfig = false;
long minDuration = Long.MAX_VALUE;
for (ULEParamConfig config : configs) {
if (config.isonline == 1) {
hasOnlineConfig = true;
minDuration = Math.min(minDuration, config.duration);
// 初始化标签检测时间(如果尚未初始化)
if (!tagInitTimeState.contains(tag)) {
tagInitTimeState.put(tag, System.currentTimeMillis());
}
}
}
// 管理离线检测定时器
if (hasOnlineConfig) {
// 删除现有定时器
Long currentTimer = offlineTimerState.get(tag);
if (currentTimer != null) {
ctx.timerService().deleteEventTimeTimer(currentTimer);
}
// 注册新定时器(使用最小duration)
long offlineTimeout = eventTime + minDuration * 60 * 1000;
ctx.timerService().registerEventTimeTimer(offlineTimeout);
offlineTimerState.put(tag, offlineTimeout);
// 重置离线状态(数据到达表示恢复)
for (ULEParamConfig config : configs) {
if (config.isonline == 1) {
AnomalyState state = getOrCreateState(config.encode);
AnomalyStatus status = state.getStatus(6);
if (status.reported) {
reportAnomaly(6, 0, 0.0, timeStr, config, out);
status.reset();
stateMap.put(config.encode, state);
System.out.println("离线恢复: tag=" + tag + ", encode=" + config.encode);
}
}
}
}
// 遍历配置项进行异常检测
for (ULEParamConfig config : configs) {
if (!valueSet) {
value = "436887485805570949".equals(config.datatype) ?
data.getDouble("ontime") : data.getDouble("avg");
lastValuesMap.put(tag, value);
valueSet = true;
}
AnomalyState state = getOrCreateState(config.encode);
// 处理异常类型
checkConstantValueAnomaly(config, value, timeStr, state, out); // 1. 恒值检测
checkZeroValueAnomaly(config, value, timeStr, state, out); // 2. 零值检测
checkThresholdAnomaly(config, value, timeStr, state, out); // 3. 上阈值, 4. 下阈值
checkSyncAnomaly(config, value, timeStr, state, configCollection, ctx, out); // 5. 同步检测
stateMap.put(config.encode, state);
}
}
@Override
public void onTimer(long timestamp, OnTimerContext ctx, Collector<JSONObject> out) throws Exception {
String tag = ctx.getCurrentKey();
Long lastEventTime = lastDataTimeMap.get(tag);
ConfigCollection configCollection = getBroadcastConfig(ctx);
if (configCollection == null) return;
List<ULEParamConfig> configs = configCollection.tagToConfigs.get(tag);
if (configs == null) return;
// 检查所有需要离线检测的配置项
boolean hasOnlineConfig = false;
for (ULEParamConfig config : configs) {
if (config.isonline == 1) {
hasOnlineConfig = true;
AnomalyState state = getOrCreateState(config.encode);
AnomalyStatus status = state.getStatus(6);
// 核心修复:处理从未收到数据的情况
if (lastEventTime == null) {
// 获取标签初始化时间
Long initTime = tagInitTimeState.get(tag);
if (initTime == null) {
// 如果状态中不存在,使用配置加载时间
initTime = configCollection.checkpointTime;
tagInitTimeState.put(tag, initTime);
}
// 检查是否超过配置的duration
long elapsed = System.currentTimeMillis() - initTime;
long durationMs = config.duration * 60 * 1000;
if (elapsed >= durationMs) {
if (!status.reported) {
String triggerTime = timeFormat.format(new Date());
reportAnomaly(6, 1, 0.0, triggerTime, config, out);
status.reported = true;
// 输出到侧输出流
ctx.output(OFFLINE_CHECK_TAG,
String.format("离线异常(从未收到数据): tag=%s, encode=%s, 初始化时间=%s, 当前时间=%s, 时间差=%dms (阈值=%dms)",
config.tag, config.encode,
timeFormat.format(new Date(initTime)),
triggerTime,
elapsed,
durationMs)
);
System.out.println("检测到从未接收数据的离线异常: " + config.tag);
}
}
}
// 处理已有数据但超时的情况
else if (timestamp - lastEventTime >= config.duration * 60 * 1000) {
if (!status.reported) {
String triggerTime = timeFormat.format(new Date(timestamp));
reportAnomaly(6, 1, 0.0, triggerTime, config, out);
status.reported = true;
ctx.output(OFFLINE_CHECK_TAG,
String.format("离线异常: tag=%s, encode=%s, 最后数据时间=%s, 超时时间=%s, 时间差=%dms (阈值=%dms)",
config.tag, config.encode,
timeFormat.format(new Date(lastEventTime)),
triggerTime,
timestamp - lastEventTime,
config.duration * 60 * 1000)
);
System.out.println("检测到数据中断的离线异常: " + config.tag);
}
} else {
// 数据已恢复,重置状态
if (status.reported) {
reportAnomaly(6, 0, 0.0, timeFormat.format(new Date()), config, out);
status.reset();
System.out.println("离线状态恢复: " + config.tag);
}
}
stateMap.put(config.encode, state);
}
}
// 重新注册定时器(每分钟检查一次)
if (hasOnlineConfig) {
long newTimeout = timestamp + TimeUnit.MINUTES.toMillis(1);
ctx.timerService().registerEventTimeTimer(newTimeout);
offlineTimerState.put(tag, newTimeout);
} else {
offlineTimerState.remove(tag);
}
}
// 恒值检测 - 异常类型1
private void checkConstantValueAnomaly(ULEParamConfig config, double currentValue, String timeStr,
AnomalyState state, Collector<JSONObject> out) {
if (config.constantvalue != 1) return;
try {
AnomalyStatus status = state.getStatus(1);
long durationThreshold = config.duration * 60 * 1000;
Date timestamp = timeFormat.parse(timeStr);
if (status.lastValue == null) {
status.lastValue = currentValue;
status.lastChangeTime = timestamp;
return;
}
if (Math.abs(currentValue - status.lastValue) > 0.001) {
status.lastValue = currentValue;
status.lastChangeTime = timestamp;
if (status.reported) {
reportAnomaly(1, 0, currentValue, timeStr, config, out);
}
status.reset();
return;
}
long elapsed = timestamp.getTime() - status.lastChangeTime.getTime();
if (elapsed > durationThreshold) {
if (!status.reported) {
reportAnomaly(1, 1, currentValue, timeStr, config, out);
status.reported = true;
}
}
} catch (Exception e) {
System.err.println("恒值检测错误: " + config.encode + " - " + e.getMessage());
}
}
// 零值检测 - 异常类型2
private void checkZeroValueAnomaly(ULEParamConfig config, double currentValue, String timeStr,
AnomalyState state, Collector<JSONObject> out) {
if (config.iszero != 1) return;
try {
AnomalyStatus status = state.getStatus(2);
Date timestamp = timeFormat.parse(timeStr);
boolean isZero = Math.abs(currentValue) < 0.001;
if (isZero) {
if (status.startTime == null) {
status.startTime = timestamp;
} else if (!status.reported) {
long elapsed = timestamp.getTime() - status.startTime.getTime();
if (elapsed >= config.duration * 60 * 1000) {
reportAnomaly(2, 1, currentValue, timeStr, config, out);
status.reported = true;
}
}
} else {
if (status.reported) {
reportAnomaly(2, 0, currentValue, timeStr, config, out);
status.reset();
} else if (status.startTime != null) {
status.startTime = null;
}
}
} catch (Exception e) {
System.err.println("零值检测错误: " + config.encode + " - " + e.getMessage());
}
}
// 阈值检测 - 异常类型3(上阈值)和4(下阈值)
private void checkThresholdAnomaly(ULEParamConfig config, double currentValue, String timeStr,
AnomalyState state, Collector<JSONObject> out) {
try {
if (config.ishigh == 1) {
AnomalyStatus highStatus = state.getStatus(3);
processThresholdAnomaly(highStatus, currentValue, timeStr,
currentValue > config.highthreshold, config, 3, out);
}
if (config.islow == 1) {
AnomalyStatus lowStatus = state.getStatus(4);
processThresholdAnomaly(lowStatus, currentValue, timeStr,
currentValue < config.lowthreshold, config, 4, out);
}
} catch (Exception e) {
System.err.println("阈值检测错误: " + config.encode + " - " + e.getMessage());
}
}
private void processThresholdAnomaly(AnomalyStatus status, double currentValue, String timeStr,
boolean isAnomaly, ULEParamConfig config, int anomalyType,
Collector<JSONObject> out) {
try {
Date timestamp = timeFormat.parse(timeStr);
if (isAnomaly) {
if (status.startTime == null) {
status.startTime = timestamp;
} else if (!status.reported) {
long elapsed = timestamp.getTime() - status.startTime.getTime();
if (elapsed >= config.duration * 60 * 1000) {
reportAnomaly(anomalyType, 1, currentValue, timeStr, config, out);
status.reported = true;
}
}
} else {
if (status.reported) {
reportAnomaly(anomalyType, 0, currentValue, timeStr, config, out);
status.reset();
} else if (status.startTime != null) {
status.startTime = null;
}
}
} catch (Exception e) {
System.err.println("阈值处理错误: " + config.encode + " - " + e.getMessage());
}
}
// 同步检测 - 异常类型5(优化日志输出)
private void checkSyncAnomaly(ULEParamConfig config, double currentValue, String timeStr,
AnomalyState state, ConfigCollection configCollection,
ReadOnlyContext ctx, Collector<JSONObject> out) {
if (config.issync != 1 || config.syncparaencode == null || config.syncparaencode.isEmpty()) {
return;
}
try {
// 通过encode获取关联配置
ULEParamConfig relatedConfig = configCollection.encodeToConfig.get(config.syncparaencode);
if (relatedConfig == null) {
if (System.currentTimeMillis() - lastSyncLogTime > 60000) {
System.out.println("同步检测错误: 未找到关联配置, encode=" + config.syncparaencode);
lastSyncLogTime = System.currentTimeMillis();
}
return;
}
// 获取关联配置的tag
String relatedTag = relatedConfig.tag;
if (relatedTag == null || relatedTag.isEmpty()) {
if (System.currentTimeMillis() - lastSyncLogTime > 60000) {
System.out.println("同步检测错误: 关联配置没有tag, encode=" + config.syncparaencode);
lastSyncLogTime = System.currentTimeMillis();
}
return;
}
// 从广播状态获取关联值
ReadOnlyBroadcastState<String, Double> tagValuesState =
ctx.getBroadcastState(Descriptors.tagValuesDescriptor);
Double relatedValue = tagValuesState.get(relatedTag);
if (relatedValue == null) {
if (System.currentTimeMillis() - lastSyncLogTime > 60000) {
// 优化日志:添加当前标签信息
System.out.printf("同步检测警告: 关联值未初始化 [主标签=%s(%s), 关联标签=%s(%s)]%n",
config.tag, config.encode, relatedTag, config.syncparaencode);
lastSyncLogTime = System.currentTimeMillis();
}
return;
}
// 同步检测逻辑
AnomalyStatus status = state.getStatus(5);
Date timestamp = timeFormat.parse(timeStr);
// 业务逻辑:当前值接近1且关联值接近0时异常
boolean isAnomaly = (currentValue >= 0.99) && (Math.abs(relatedValue) < 0.01);
// 日志记录
if (System.currentTimeMillis() - lastSyncLogTime > 60000) {
System.out.printf("同步检测: %s (%.4f) vs %s (%.4f) -> %b%n",
config.tag, currentValue, relatedTag, relatedValue, isAnomaly);
lastSyncLogTime = System.currentTimeMillis();
}
// 处理异常状态
if (isAnomaly) {
if (status.startTime == null) {
status.startTime = timestamp;
} else if (!status.reported) {
long elapsed = timestamp.getTime() - status.startTime.getTime();
if (elapsed >= config.duration * 60 * 1000) {
reportAnomaly(5, 1, currentValue, timeStr, config, out);
status.reported = true;
}
}
} else {
if (status.reported) {
reportAnomaly(5, 0, currentValue, timeStr, config, out);
status.reset();
} else if (status.startTime != null) {
status.startTime = null;
}
}
} catch (ParseException e) {
System.err.println("同步检测时间解析错误: " + config.encode + " - " + e.getMessage());
} catch (Exception e) {
System.err.println("同步检测错误: " + config.encode + " - " + e.getMessage());
}
}
// 报告异常(添加详细日志)
private void reportAnomaly(int anomalyType, int statusFlag, double value,
String time, ULEParamConfig config, Collector<JSONObject> out) {
JSONObject event = new JSONObject();
event.put("tag", config.tag);
event.put("paracode", config.encode);
event.put("abnormaltype", anomalyType);
event.put("statusflag", statusFlag);
event.put("datavalue", value);
event.put("triggertime", time);
out.collect(event);
// 添加详细日志输出
String statusDesc = statusFlag == 1 ? "异常开始" : "异常结束";
System.out.printf("报告异常: 类型=%d, 状态=%s, 标签=%s, 编码=%s, 时间=%s%n",
anomalyType, statusDesc, config.tag, config.encode, time);
}
@Override
public void processBroadcastElement(Object broadcastElement, Context ctx, Collector<JSONObject> out) throws Exception {
// 处理配置更新
if (broadcastElement instanceof ConfigCollection) {
ConfigCollection newConfig = (ConfigCollection) broadcastElement;
BroadcastState<Void, ConfigCollection> configState =
ctx.getBroadcastState(Descriptors.configStateDescriptor);
// 获取旧配置
ConfigCollection oldConfig = configState.get(null);
// 处理配置变更:清理不再启用的报警
if (oldConfig != null) {
for (Map.Entry<String, ULEParamConfig> entry : oldConfig.encodeToConfig.entrySet()) {
String encode = entry.getKey();
ULEParamConfig oldCfg = entry.getValue();
ULEParamConfig newCfg = newConfig.encodeToConfig.get(encode);
if (newCfg == null || !isAlarmEnabled(newCfg, oldCfg)) {
// 发送恢复事件
sendRecoveryEvents(encode, oldCfg, ctx, out);
}
}
}
// 更新广播状态
configState.put(null, newConfig);
System.out.println("广播配置更新完成, 配置项: " + newConfig.encodeToConfig.size());
// 新增:初始化所有需要在线检测的标签
for (String tag : newConfig.allTags) {
List<ULEParamConfig> configs = newConfig.tagToConfigs.get(tag);
if (configs != null) {
for (ULEParamConfig config : configs) {
if (config.isonline == 1) {
// 初始化标签检测时间
if (!tagInitTimeState.contains(tag)) {
long initTime = System.currentTimeMillis();
tagInitTimeState.put(tag, initTime);
System.out.println("初始化在线检测: tag=" + tag + ", 时间=" + timeFormat.format(new Date(initTime)));
}
}
}
}
}
}
// 处理标签值更新
else if (broadcastElement instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> tagValue = (Map<String, Object>) broadcastElement;
String tag = (String) tagValue.get("tag");
Double value = (Double) tagValue.get("value");
if (tag != null && value != null) {
BroadcastState<String, Double> tagValuesState =
ctx.getBroadcastState(Descriptors.tagValuesDescriptor);
tagValuesState.put(tag, value);
}
}
}
// 检查报警是否启用
private boolean isAlarmEnabled(ULEParamConfig newCfg, ULEParamConfig oldCfg) {
return (oldCfg.constantvalue == 1 && newCfg.constantvalue == 1) ||
(oldCfg.iszero == 1 && newCfg.iszero == 1) ||
(oldCfg.ishigh == 1 && newCfg.ishigh == 1) ||
(oldCfg.islow == 1 && newCfg.islow == 1) ||
(oldCfg.isonline == 1 && newCfg.isonline == 1) ||
(oldCfg.issync == 1 && newCfg.issync == 1);
}
// 发送恢复事件
private void sendRecoveryEvents(String encode, ULEParamConfig config, Context ctx, Collector<JSONObject> out) {
try {
AnomalyState state = stateMap.get(encode);
if (state == null) return;
// 遍历所有可能的报警类型
for (int type = 1; type <= 6; type++) {
AnomalyStatus status = state.getStatus(type);
if (status.reported) {
JSONObject recoveryEvent = new JSONObject();
recoveryEvent.put("tag", config.tag);
recoveryEvent.put("paracode", config.encode);
recoveryEvent.put("abnormaltype", type);
recoveryEvent.put("statusflag", 0); // 恢复事件
recoveryEvent.put("datavalue", 0.0);
recoveryEvent.put("triggertime", timeFormat.format(new Date()));
out.collect(recoveryEvent);
System.out.println("发送恢复事件: 类型=" + type + ", 标签=" + config.tag);
status.reset();
}
}
// 更新状态
stateMap.put(encode, state);
} catch (Exception e) {
System.err.println("发送恢复事件失败: " + e.getMessage());
}
}
// 辅助方法
private ConfigCollection getBroadcastConfig(ReadOnlyContext ctx) throws Exception {
return ctx.getBroadcastState(Descriptors.configStateDescriptor).get(null);
}
private AnomalyState getOrCreateState(String encode) throws Exception {
AnomalyState state = stateMap.get(encode);
if (state == null) {
state = new AnomalyState();
}
return state;
}
}
// 异常状态类
public static class AnomalyState implements Serializable {
private static final long serialVersionUID = 1L;
private final Map<Integer, AnomalyStatus> statusMap = new HashMap<>();
public AnomalyStatus getStatus(int type) {
return statusMap.computeIfAbsent(type, k -> new AnomalyStatus());
}
}
// 异常状态详情
public static class AnomalyStatus implements Serializable {
private static final long serialVersionUID = 1L;
public Date startTime; // 异常开始时间
public Double lastValue; // 用于恒值检测
public Date lastChangeTime; // 值最后变化时间
public boolean reported; // 是否已报告
public void reset() {
startTime = null;
lastValue = null;
lastChangeTime = null;
reported = false;
}
}
}
运行时日志为:"C:\Program Files (x86)\Java\jdk1.8.0_102\bin\java.exe" -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:21096,suspend=y,server=n -javaagent:C:\Users\Administrator\AppData\Local\JetBrains\IntelliJIdea2021.2\captureAgent\debugger-agent.jar=file:/C:/Users/Administrator/AppData/Local/Temp/capture.props -Dfile.encoding=UTF-8 -classpath C:\Users\Administrator\AppData\Local\Temp\classpath849000959.jar com.tongchuang.realtime.mds.ULEDataanomalyanalysis
已连接到目标 VM, 地址: ''127.0.0.1:21096',传输: '套接字''
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/F:/flink/flinkmaven/repository/org/apache/logging/log4j/log4j-slf4j-impl/2.10.0/log4j-slf4j-impl-2.10.0.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/F:/flink/flinkmaven/repository/org/slf4j/slf4j-log4j12/1.7.25/slf4j-log4j12-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.apache.logging.slf4j.Log4jLoggerFactory]
加载配置: 30 个参数
配置加载完成,检查点时间: Wed Aug 06 07:55:06 CST 2025
广播配置更新完成, 配置项: 30
Exception in thread "main" org.apache.flink.runtime.client.JobExecutionException: Job execution failed.
at org.apache.flink.runtime.jobmaster.JobResult.toJobExecutionResult(JobResult.java:144)
at org.apache.flink.runtime.minicluster.MiniClusterJobClient.lambda$getJobExecutionResult$3(MiniClusterJobClient.java:137)
at java.util.concurrent.CompletableFuture.uniApply(CompletableFuture.java:602)
at java.util.concurrent.CompletableFuture$UniApply.tryFire(CompletableFuture.java:577)
at java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:474)
at java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:1962)
at org.apache.flink.runtime.rpc.akka.AkkaInvocationHandler.lambda$invokeRpc$0(AkkaInvocationHandler.java:237)
at java.util.concurrent.CompletableFuture.uniWhenComplete(CompletableFuture.java:760)
at java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(CompletableFuture.java:736)
at java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:474)
at java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:1962)
at org.apache.flink.runtime.concurrent.FutureUtils$1.onComplete(FutureUtils.java:1081)
at akka.dispatch.OnComplete.internal(Future.scala:264)
at akka.dispatch.OnComplete.internal(Future.scala:261)
at akka.dispatch.japi$CallbackBridge.apply(Future.scala:191)
at akka.dispatch.japi$CallbackBridge.apply(Future.scala:188)
at scala.concurrent.impl.CallbackRunnable.run$$$capture(Promise.scala:60)
at scala.concurrent.impl.CallbackRunnable.run(Promise.scala)
at org.apache.flink.runtime.concurrent.Executors$DirectExecutionContext.execute(Executors.java:73)
at scala.concurrent.impl.CallbackRunnable.executeWithValue(Promise.scala:68)
at scala.concurrent.impl.Promise$DefaultPromise.$anonfun$tryComplete$1(Promise.scala:284)
at scala.concurrent.impl.Promise$DefaultPromise.$anonfun$tryComplete$1$adapted(Promise.scala:284)
at scala.concurrent.impl.Promise$DefaultPromise.tryComplete(Promise.scala:284)
at akka.pattern.PromiseActorRef.$bang(AskSupport.scala:573)
at akka.pattern.PipeToSupport$PipeableFuture$$anonfun$pipeTo$1.applyOrElse(PipeToSupport.scala:22)
at akka.pattern.PipeToSupport$PipeableFuture$$anonfun$pipeTo$1.applyOrElse(PipeToSupport.scala:21)
at scala.concurrent.Future.$anonfun$andThen$1(Future.scala:532)
at scala.concurrent.impl.Promise.liftedTree1$1(Promise.scala:29)
at scala.concurrent.impl.Promise.$anonfun$transform$1(Promise.scala:29)
at scala.concurrent.impl.CallbackRunnable.run$$$capture(Promise.scala:60)
at scala.concurrent.impl.CallbackRunnable.run(Promise.scala)
at akka.dispatch.BatchingExecutor$AbstractBatch.processBatch(BatchingExecutor.scala:55)
at akka.dispatch.BatchingExecutor$BlockableBatch.$anonfun$run$1(BatchingExecutor.scala:91)
at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:12)
at scala.concurrent.BlockContext$.withBlockContext(BlockContext.scala:81)
at akka.dispatch.BatchingExecutor$BlockableBatch.run(BatchingExecutor.scala:91)
at akka.dispatch.TaskInvocation.run(AbstractDispatcher.scala:40)
at akka.dispatch.ForkJoinExecutorConfigurator$AkkaForkJoinTask.exec(ForkJoinExecutorConfigurator.scala:44)
at akka.dispatch.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
at akka.dispatch.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)
at akka.dispatch.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
at akka.dispatch.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)
Caused by: org.apache.flink.runtime.JobException: Recovery is suppressed by NoRestartBackoffTimeStrategy
at org.apache.flink.runtime.executiongraph.failover.flip1.ExecutionFailureHandler.handleFailure(ExecutionFailureHandler.java:138)
at org.apache.flink.runtime.executiongraph.failover.flip1.ExecutionFailureHandler.getFailureHandlingResult(ExecutionFailureHandler.java:82)
at org.apache.flink.runtime.scheduler.DefaultScheduler.handleTaskFailure(DefaultScheduler.java:216)
at org.apache.flink.runtime.scheduler.DefaultScheduler.maybeHandleTaskFailure(DefaultScheduler.java:206)
at org.apache.flink.runtime.scheduler.DefaultScheduler.updateTaskExecutionStateInternal(DefaultScheduler.java:197)
at org.apache.flink.runtime.scheduler.SchedulerBase.updateTaskExecutionState(SchedulerBase.java:682)
at org.apache.flink.runtime.scheduler.SchedulerNG.updateTaskExecutionState(SchedulerNG.java:79)
at org.apache.flink.runtime.jobmaster.JobMaster.updateTaskExecutionState(JobMaster.java:435)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.apache.flink.runtime.rpc.akka.AkkaRpcActor.handleRpcInvocation(AkkaRpcActor.java:305)
at org.apache.flink.runtime.rpc.akka.AkkaRpcActor.handleRpcMessage(AkkaRpcActor.java:212)
at org.apache.flink.runtime.rpc.akka.FencedAkkaRpcActor.handleRpcMessage(FencedAkkaRpcActor.java:77)
at org.apache.flink.runtime.rpc.akka.AkkaRpcActor.handleMessage(AkkaRpcActor.java:158)
at akka.japi.pf.UnitCaseStatement.apply(CaseStatements.scala:26)
at akka.japi.pf.UnitCaseStatement.apply(CaseStatements.scala:21)
at scala.PartialFunction.applyOrElse(PartialFunction.scala:123)
at scala.PartialFunction.applyOrElse$(PartialFunction.scala:122)
at akka.japi.pf.UnitCaseStatement.applyOrElse(CaseStatements.scala:21)
at scala.PartialFunction$OrElse.applyOrElse(PartialFunction.scala:171)
at scala.PartialFunction$OrElse.applyOrElse(PartialFunction.scala:172)
at scala.PartialFunction$OrElse.applyOrElse(PartialFunction.scala:172)
at akka.actor.Actor.aroundReceive(Actor.scala:517)
at akka.actor.Actor.aroundReceive$(Actor.scala:515)
at akka.actor.AbstractActor.aroundReceive(AbstractActor.scala:225)
at akka.actor.ActorCell.receiveMessage$$$capture(ActorCell.scala:592)
at akka.actor.ActorCell.receiveMessage(ActorCell.scala)
at akka.actor.ActorCell.invoke(ActorCell.scala:561)
at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:258)
at akka.dispatch.Mailbox.run(Mailbox.scala:225)
at akka.dispatch.Mailbox.exec(Mailbox.scala:235)
... 4 more
Caused by: java.lang.NullPointerException: No key set. This method should not be called outside of a keyed context.
at org.apache.flink.util.Preconditions.checkNotNull(Preconditions.java:76)
at org.apache.flink.runtime.state.heap.StateTable.checkKeyNamespacePreconditions(StateTable.java:270)
at org.apache.flink.runtime.state.heap.StateTable.get(StateTable.java:260)
at org.apache.flink.runtime.state.heap.StateTable.get(StateTable.java:143)
at org.apache.flink.runtime.state.heap.HeapMapState.get(HeapMapState.java:86)
at org.apache.flink.runtime.state.ttl.TtlMapState.lambda$getWrapped$0(TtlMapState.java:64)
at org.apache.flink.runtime.state.ttl.AbstractTtlDecorator.getWrappedWithTtlCheckAndUpdate(AbstractTtlDecorator.java:97)
at org.apache.flink.runtime.state.ttl.TtlMapState.getWrapped(TtlMapState.java:63)
at org.apache.flink.runtime.state.ttl.TtlMapState.contains(TtlMapState.java:96)
at org.apache.flink.runtime.state.UserFacingMapState.contains(UserFacingMapState.java:72)
at com.tongchuang.realtime.mds.ULEDataanomalyanalysis$OptimizedAnomalyDetectionFunction.processBroadcastElement(ULEDataanomalyanalysis.java:767)
at org.apache.flink.streaming.api.operators.co.CoBroadcastWithKeyedOperator.processElement2(CoBroadcastWithKeyedOperator.java:133)
at org.apache.flink.streaming.runtime.io.StreamTwoInputProcessorFactory.processRecord2(StreamTwoInputProcessorFactory.java:221)
at org.apache.flink.streaming.runtime.io.StreamTwoInputProcessorFactory.lambda$create$1(StreamTwoInputProcessorFactory.java:190)
at org.apache.flink.streaming.runtime.io.StreamTwoInputProcessorFactory$StreamTaskNetworkOutput.emitRecord(StreamTwoInputProcessorFactory.java:291)
at org.apache.flink.streaming.runtime.io.AbstractStreamTaskNetworkInput.processElement(AbstractStreamTaskNetworkInput.java:134)
at org.apache.flink.streaming.runtime.io.AbstractStreamTaskNetworkInput.emitNext(AbstractStreamTaskNetworkInput.java:105)
at org.apache.flink.streaming.runtime.io.StreamOneInputProcessor.processInput(StreamOneInputProcessor.java:66)
at org.apache.flink.streaming.runtime.io.StreamTwoInputProcessor.processInput(StreamTwoInputProcessor.java:98)
at org.apache.flink.streaming.runtime.tasks.StreamTask.processInput(StreamTask.java:423)
at org.apache.flink.streaming.runtime.tasks.mailbox.MailboxProcessor.runMailboxLoop(MailboxProcessor.java:204)
at org.apache.flink.streaming.runtime.tasks.StreamTask.runMailboxLoop(StreamTask.java:684)
at org.apache.flink.streaming.runtime.tasks.StreamTask.executeInvoke(StreamTask.java:639)
at org.apache.flink.streaming.runtime.tasks.StreamTask.runWithCleanUpOnFail(StreamTask.java:650)
at org.apache.flink.streaming.runtime.tasks.StreamTask.invoke(StreamTask.java:623)
at org.apache.flink.runtime.taskmanager.Task.doRun(Task.java:779)
at org.apache.flink.runtime.taskmanager.Task.run(Task.java:566)
at java.lang.Thread.run(Thread.java:745)
与目标 VM 断开连接, 地址为: ''127.0.0.1:21096',传输: '套接字''
进程已结束,退出代码为 1
请生成完整修复后代码。