
1) 【一句话结论】采用Kafka + Flink的流处理架构,通过实时数据接入、过滤清洗、聚合计算,满足每秒数千条订单的购买频次统计需求,结果输出至缓存与持久化存储。
2) 【原理/概念讲解】老师,先讲几个核心概念。
3) 【对比与适用场景】
| 特性 | 批处理(如Hadoop MapReduce) | 流处理(Flink) |
|---|---|---|
| 处理模式 | 一次性处理历史数据 | 持续处理实时数据流 |
| 延迟 | 较高(几分钟到几小时) | 低(毫秒级) |
| 适用场景 | 数据量大、对延迟不敏感(如日志分析) | 对延迟敏感、需实时反馈(如用户行为分析、购买频次统计) |
| 状态管理 | 较复杂 | 内置状态管理,支持持久化 |
| 容错 | 需要重算 | 支持exactly-once语义,数据不丢失/重复 |
4) 【示例】(伪代码):
// 1. 从Kafka读取订单流
DataStream<Order> orderStream = env
.addSource(new FlinkKafkaConsumer<>(
"order-topic",
new SimpleStringSchema(),
kafkaProperties));
// 2. 过滤无效订单(状态为"cancelled"或"failed"的跳过)
DataStream<Order> validOrders = orderStream
.filter(order -> !order.getStatus().equals("cancelled") && !order.getStatus().equals("failed"));
// 3. 根据订单时间戳去重(避免同一订单多次计算)
DataStream<Order> dedupOrders = validOrders
.keyBy(order -> order.getOrderId())
.timeWindow(Time.seconds(1)) // 1秒内去重
.process(new ProcessFunction<Order, Order>() {
@Override
public void processElement(Order value, Context ctx, Collector<Order> out) throws Exception {
out.collect(value);
}
});
// 4. 按用户ID分组,5秒滑动窗口聚合购买次数
DataStream<AggregateResult> resultStream = dedupOrders
.keyBy(order -> order.getUserId())
.window(SlidingEventTimeWindows.of(Time.seconds(5), Time.seconds(1)))
.apply(new AggregateFunction<Order, Tuple2<Long, Long>, Tuple2<Long, Long>>() {
@Override
public Tuple2<Long, Long> createAccumulator() {
return new Tuple2<>(0L, 0L); // (用户ID, 购买次数)
}
@Override
public Tuple2<Long, Long> add(Order value, Tuple2<Long, Long> accumulator) {
return new Tuple2<>(accumulator.f0, accumulator.f1 + 1);
}
@Override
public Tuple2<Long, Long> merge(Tuple2<Long, Long> a, Tuple2<Long, Long> b) {
return new Tuple2<>(a.f0, a.f1 + b.f1);
}
@Override
public Tuple2<Long, Long> getResult(Tuple2<Long, Long> accumulator) {
return new Tuple2<>(accumulator.f0, accumulator.f1);
}
});
// 5. 输出到Redis(缓存)和MySQL(持久化)
resultStream.addSink(new RedisSink<AggregateResult>("redis://localhost:6379", "user_purchase_freq"));
resultStream.addSink(new JdbcSink<AggregateResult>("INSERT INTO user_purchase_freq (user_id, freq) VALUES (?, ?)",
(ps, r) -> { ps.setLong(1, r.f0); ps.setLong(2, r.f1); }));
5) 【面试口播版答案】
面试官您好,针对实时计算用户购买频次的需求,我设计了一个基于流处理的方案。首先,数据接入层用Kafka接收订单流,因为Kafka能处理高吞吐且持久化数据。然后,处理层用Flink,它支持实时聚合和状态管理。具体流程是:订单流进入Kafka后,Flink消费数据,先过滤掉无效订单(比如已取消或失败的),接着根据订单时间戳去重(避免同一订单多次计算),然后按用户ID分组,用5秒滑动窗口计算购买次数,最后结果输出到Redis缓存(支持实时查询)和MySQL(持久化存储)。这样能保证每秒处理数千条订单,满足实时性要求,同时保证数据准确性。
6) 【追问清单】
7) 【常见坑/雷区】