01学完你能做到
区分有界与无界数据、事件时间与处理时间;解释窗口、水位线、迟到数据、状态和检查点;比较 Flink 与 Spark Structured Streaming,并为实时行情选择处理方案。
02这课解决什么问题
Tick 一直在流入、还会乱序和迟到,程序什么时候才应该输出这一分钟的 K 线?流处理要同时处理“结果尽快出来”“数据尽可能完整”和“程序重启后不丢不重”这几个目标。
03流处理的核心概念
- 有界与无界数据:昨天全天 Tick 有终点,属于有界数据;今天持续到来的行情没有终点,属于无界数据。
- 事件时间与处理时间:事件时间是成交发生的时间,处理时间是程序收到或处理它的时间。行情聚合通常应以事件时间为准。
- 窗口:把无限流切成可计算的小段。滚动窗口互不重叠,滑动窗口按间隔计算最近一段时间,会话窗口按活跃间隔切分。
- 水位线:框架对事件时间推进程度的估计,表示早于某个时间的数据基本到齐,用来触发窗口输出。
- 迟到数据:水位线之后才到的数据。可以丢弃、更新原结果,或进入旁路单独处理。
- 状态:窗口中累积的开高低收、成交量等中间结果,需要持久化和恢复。
- 检查点:定期保存算子状态和输入位置。故障后从检查点恢复并重读消息,可以在上下游配合时实现精确一次效果。
04Flink 与 Spark Structured Streaming
Flink 是原生流处理引擎,事件时间、状态管理和低延迟处理能力突出,适合实时指标、风控和复杂事件检测。Spark Structured Streaming 通常采用微批执行,与 Spark 批处理共用 DataFrame API,适合已有 Spark 技术栈的团队;具体延迟和投递保证取决于查询、源、汇和配置。
没有必要一开始就引入大型流处理集群。如果只是每分钟聚合、允许秒级延迟,Kafka 消费者攒批后写入数据库再聚合,可能更简单。选择框架要以实际乱序分布、延迟目标、状态大小和运维能力为依据。
05事件时间、水位线与 K 线
Kafka Tick -> 按证券分组 -> 事件时间一分钟窗口
-> 水位线越过窗口终点
-> 输出暂定/完成 K 线
-> 迟到数据进入修正或旁路容忍乱序时间越短,输出越快但迟到数据越多;容忍时间越长,结果更完整但延迟和状态开销更大。水位线不是交易所保证“绝对到齐”的证明,只是框架根据策略做出的推进判断。
下面代码用纯 Python 模拟最小语义,不是真实 Flink 作业。
from datetime import datetime, timedelta
def parse(s):
return datetime.fromisoformat("2026-09-15T" + s)
events = [
("09:30:05", 10.00, 500), ("09:30:40", 10.05, 300),
("09:31:02", 10.02, 200), ("09:31:30", 10.08, 100),
("09:30:58", 10.10, 400), ("09:32:10", 10.06, 600),
]
def run(max_out_of_order_seconds):
state, closed = {}, set()
max_event_time = datetime.min
late = []
for ts_text, price, volume in events:
ts = parse(ts_text)
window = ts.replace(second=0)
if window in closed:
late.append(ts_text)
continue
item = state.setdefault(window, {
"open": price, "high": price, "low": price,
"close": price, "volume": 0,
"first": ts, "last": ts,
})
if ts < item["first"]:
item["open"], item["first"] = price, ts
if ts >= item["last"]:
item["close"], item["last"] = price, ts
item["high"] = max(item["high"], price)
item["low"] = min(item["low"], price)
item["volume"] += volume
max_event_time = max(max_event_time, ts)
watermark = max_event_time - timedelta(seconds=max_out_of_order_seconds)
for start in sorted(set(state) - closed):
if start + timedelta(minutes=1) <= watermark:
closed.add(start)
return state, late收盘价必须按事件时间取窗口内最后一笔,而不是按到达顺序取最后一笔。午休、停牌或冷门证券长时间没有新事件时,水位线也可能不前进,需要空闲检测或交易日历驱动。
06常见误区
- 用处理时间聚合,网络抖动和重启会让同一数据产生不同结果。
- 凭经验拍一个乱序容忍时长,没有先统计数据分布。
- 忽略没有事件的窗口,导致窗口不关闭或错误补出成交。
- 看到“精确一次”就认为外部数据库和下游服务绝不会重复;外部系统仍需事务或幂等。
- 把盘中实时 K 线当最终结果;迟到数据和盘后全量重算可能造成修订。
07延伸阅读
08一分钟讲给别人
试着用自己的话讲一遍流处理就是边收数据边算结果。Tick 会乱序、迟到,框架用水位线判断某一分钟是否基本到齐,到齐后输出,晚到数据再修正或旁路。等得越久通常越完整但越慢;Flink 偏低延迟和复杂状态,Spark 便于批流共用一套代码。
6 道自测
先在心里答一遍,再展开对照。能说清理由,比答对数字更重要。
01事件时间和处理时间有什么区别?
02水位线的作用是什么?
03乱序容忍时间过大有什么代价?
04为什么实时 K 线可能需要盘后重算?
05Flink 和 Spark Structured Streaming 的主要取舍是什么?
06检查点解决什么问题?
已看 0/6 题
练动手练习
任务:把模拟程序的乱序容忍时长分别设为 5 秒、35 秒和 60 秒,比较 09:30 窗口的输出时间、成交量和迟到记录;写出一个暂定结果、修订结果和最终结果的状态字段设计。
完成这一课
读完讲义、看完自测答案,这一课就算学完了。
- 读完讲义
- 看完全部自测
本课为投资者教育内容,案例数字均为示意,不构成任何投资建议,也不承诺收益。规则和费率会变,实际操作前请以交易所、监管机构和产品的最新公告为准。