一、Map:数据转换
1.1 核心功能
- 元素级1:1转换:单输入单输出
- 无状态转换:处理不依赖上下文
1.2 实战代码
// 场景:物联网传感器原始数据解析
DataStream<SensorReading> sensorStream = rawStream
.map(new MapFunction<String, SensorReading>() {
@Override
public SensorReading map(String value) {
String[] parts = value.split(",");
return new SensorReading(
parts[0],
Long.parseLong(parts[1]),
Double.parseDouble(parts[2])
);
}
});
// Lambda简写
DataStream<SensorReading> lambdaStream = rawStream
.map(line -> {
String[] arr = line.split(",");
return new SensorReading(arr[0], Long.parseLong(arr[1]), Double.parseDouble(arr[2]));
});
1.3 典型场景
- 数据格式转换(CSV → POJO)
- 字段提取(从日志中抽离error_code)
- 数据脱敏(手机号中间四位变*)
二、Filter:过滤器
2.1 核心特性
- 布尔过滤器:返回true保留数据
- 轻量级筛选:适合前置数据清洗
2.2 示例
// 场景:金融交易风控过滤
DataStream<Transaction> filtered = transactions
.filter(tx -> {
// 规则1:单笔金额不超过100万
boolean rule1 = tx.amount < 1_000_000;
// 规则2:非高危国家列表
boolean rule2 = !riskCountries.contains(tx.countryCode);
return rule1 && rule2;
});
// 诊断日志过滤(保留ERROR级别)
DataStream<LogEvent> errorLogs = logStream
.filter(log -> "ERROR".equals(log.getLevel()));
2.3 应用场景
- 异常数据拦截(非法值、测试数据)
- 敏感操作审计(管理员操作日志)
- 业务规则过滤(黑名单用户过滤)
三、KeyBy:数据分组
3.1 核心机制
- 逻辑分区:相同key的数据路由到同一分区
- 物理重分区:可能引发网络shuffle
3.2 生产案例
// 场景:电商实时订单统计
DataStream<Order> orders = ...;
KeyedStream<Order, String> keyedOrders = orders
.keyBy(Order::getUserId); // 按用户ID分组
// 多字段组合key(使用Tuple)
.keyBy(order -> Tuple2.of(order.getRegion(), order.getProductCategory()))
3.3 高级用法
// 自定义KeySelector(实现复杂分组逻辑)
public class CustomKeySelector implements KeySelector<LogEvent, String> {
@Override
public String getKey(LogEvent value) {
return value.getHost() + "_" + value.getAppId();
}
}
keyedStream = logStream.keyBy(new CustomKeySelector());
3.4 应用场景
- 用户行为分析(按用户ID分组)
- 设备状态监控(按设备ID分组)
- 地域统计(按省份/城市分组)
四、Window:窗口
4.1 窗口类型矩阵
窗口类型 | 特点 | 适用场景 |
---|---|---|
Tumbling | 固定大小,不重叠 | 每5分钟销售额统计 |
Sliding | 固定大小,可重叠 | 每1分钟计算最近5分钟热度 |
Session | 动态间隙窗口 | 用户访问会话分析 |
Global | 全局单窗口 | 全量数据TopN计算 |
4.2 生产级代码示例
滚动窗口(订单统计)
DataStream<Order> orders = ...;
orders.keyBy(Order::getProductId)
.window(TumblingEventTimeWindows.of(Time.minutes(5)))
.aggregate(new AggregateFunction<Order, Tuple2<Long, Double>, OrderStats>() {
@Override
public Tuple2<Long, Double> createAccumulator() {
return Tuple2.of(0L, 0.0);
}
@Override
public Tuple2<Long, Double> add(Order value, Tuple2<Long, Double> acc) {
return Tuple2.of(acc.f0 + 1, acc.f1 + value.getAmount());
}
@Override
public OrderStats getResult(Tuple2<Long, Double> acc) {
return new OrderStats(acc.f0, acc.f1);
}
// ... merge方法省略
});
滑动窗口(异常检测)
keyedSensorData
.window(SlidingProcessingTimeWindows.of(Time.seconds(30), Time.seconds(10)))
.process(new ProcessWindowFunction<SensorReading, String, String, TimeWindow>() {
@Override
public void process(String key, Context ctx, Iterable<SensorReading> elements,
Collector<String> out) {
long overCount = elements.stream()
.filter(r -> r.getValue() > 100)
.count();
if (overCount > 5) {
out.collect("传感器" + key + "在窗口" + ctx.window() + "触发异常阈值");
}
}
});
4.3 窗口三要素
.window(TumblingEventTimeWindows.of(Time.hours(1))) // 分配器
.trigger(EventTimeTrigger.create()) // 触发器
.evictor(TimeEvictor.of(Time.minutes(5))) // 驱逐器
五、复合操作案例:实时风控引擎
5.1 业务需求
- 实时检测同一设备在10分钟内超过5次密码错误
- 统计同一IP每小时异常登录次数
- 输出风险设备名单
5.2 实现代码
DataStream<LoginEvent> loginStream = ...;
loginStream
// 清洗无效数据
.filter(event -> event.getTimestamp() > 0)
// 按设备ID分组
.keyBy(LoginEvent::getDeviceId)
// 10分钟滚动窗口
.window(TumblingEventTimeWindows.of(Time.minutes(10)))
// 自定义聚合(统计失败次数)
.aggregate(new AggregateFunction<LoginEvent, Integer, Integer>() {
public Integer createAccumulator() { return 0; }
public Integer add(LoginEvent event, Integer acc) {
return "FAIL".equals(event.getStatus()) ? acc + 1 : acc;
}
public Integer getResult(Integer acc) { return acc; }
public Integer merge(Integer a, Integer b) { return a + b; }
})
// 过滤阈值
.filter(count -> count >= 5)
// 输出预警信息
.addSink(new RiskDeviceSink());
六、性能优化要点
-
KeyBy倾斜处理
// 添加随机后缀打散热点 .keyBy(event -> event.getUserId() + "_" + ThreadLocalRandom.current().nextInt(10))
-
Window调优技巧
.window(...) .allowedLateness(Time.minutes(1)) // 允许延迟数据 .sideOutputLateData(lateDataTag) // 侧输出延迟数据
-
状态后端选择
env.setStateBackend(new RocksDBStateBackend("hdfs://checkpoints/"));
七、注意项
-
窗口内容检查
.process(new ProcessWindowFunction<...>() { public void process(...) { System.out.println("Window内容: " + elements.toString()); } })
-
事件时间可视化
.assignTimestampsAndWatermarks( WatermarkStrategy .<Event>forBoundedOutOfOrderness(Duration.ofSeconds(5)) .withTimestampAssigner((event, ts) -> event.getTimestamp()) )
整体上,Flink的转换操作通过组合可以实现,具体情况根据实际场景来判断:
- 实时ETL:数据清洗 → 转换 → 加载
- 复杂事件处理:模式检测 → 关联分析
- 动态决策:实时规则引擎 → 风险控制