4. 特征计算框架选型:Flink、Spark Streaming、Dask,轻量级方案对比
做实时特征工程,选框架这件事,我踩过的坑比写过的代码还多。
你想想看,行情数据每秒几百笔,订单簿每毫秒都在变。选错了框架,轻则延迟爆炸,重则直接OOM崩掉。今天我就把几个主流方案掰开揉碎讲清楚。
4.1 三大框架的核心定位
先给个全景图,方便你建立直觉。
一句话总结:
- Flink:真正的流处理之王,事件时间、精确一次语义,天生为实时而生
- Spark Streaming:微批次架构,生态丰富,适合与批处理混用
- Dask:轻量级并行计算库,上手快,适合中小规模策略
4.2 Flink:为实时而生的硬核选手
我个人习惯把Flink叫做「真·流处理」。为什么?因为它处理的是事件流,而不是微批次。
举个例子,你要计算过去5分钟的VWAP(成交量加权均价)。在Flink里,你只需要定义一个滑动窗口,数据来了就更新,延迟在毫秒级。
// Flink SQL 实现5分钟VWAP
CREATE VIEW vwap_5min AS
SELECT
symbol,
SUM(price * volume) / SUM(volume) AS vwap,
TUMBLE_END(event_time, INTERVAL '5' MINUTE) AS window_end
FROM trades
GROUP BY
symbol,
TUMBLE(event_time, INTERVAL '5' MINUTE);
我的经验: 做高频策略时,Flink的状态后端我推荐用RocksDB。默认的HashMap状态后端在数据量大时容易OOM。我曾经有个策略,状态量到了50GB,换成RocksDB后稳如老狗。
Flink的事件时间语义是它的杀手锏。行情数据经常乱序到达,比如A股Level-2数据,不同交易所的推送延迟不一样。Flink的watermark机制能优雅处理这个问题。
避坑指南: 我曾经在Flink里设置watermark延迟为5秒,结果某天行情剧烈波动,数据延迟到了8秒。那天的特征全部算错,回测收益直接腰斩。后来我改成动态watermark,根据实时延迟自适应调整,才解决这个问题。
4.3 Spark Streaming:生态为王
Spark Streaming用的是微批次架构,说白了就是把流切成一个个小批次,比如每2秒一批。这样做的好处是能和Spark批处理无缝衔接。
我见过很多团队,白天用Spark做盘后分析,晚上用Spark Streaming做实时特征。一套代码,两套用法,运维成本确实低。
# PySpark 实现滚动窗口特征
from pyspark.sql import functions as F
from pyspark.sql.window import Window
# 定义5分钟滚动窗口
window_spec = (
Window
.partitionBy("symbol")
.orderBy(F.col("event_time").cast("long"))
.rangeBetween(-300, 0) # 过去300秒
)
# 计算滚动特征
features_df = trades_df.withColumn(
"vwap_5min",
F.sum(F.col("price") * F.col("volume")).over(window_spec) /
F.sum("volume").over(window_spec)
)
关键对比:
- Flink的窗口是数据驱动的,数据到了就触发计算
- Spark Streaming的窗口是时间驱动的,每批次触发一次
- 这意味着Spark Streaming的延迟至少是一个批次间隔(通常1-5秒)
你想想看,如果你的策略需要秒级响应,Spark Streaming可能不太够用。但如果是分钟级的中频策略,它完全够用,而且生态优势很明显。
4.4 Dask:轻量级选手的逆袭
说到Dask,我其实挺喜欢它的。它不像Flink那么重,也不像Spark Streaming那么「批」。它就是一个轻量级的并行计算库,能让你在单机上模拟分布式计算。
我刚开始做量化时,就用Dask做实时特征。那时候策略规模小,一天几百万笔数据,Dask完全扛得住。
import dask.dataframe as dd
from dask.distributed import Client
# 启动本地集群
client = Client(n_workers=4, threads_per_worker=2)
# 读取实时数据流(模拟)
df = dd.read_csv('trades_*.csv', blocksize='100MB')
# 计算滚动特征
features = df.groupby('symbol').apply(
lambda x: x.sort_values('time').rolling('300s').agg({
'price': 'mean',
'volume': 'sum'
}),
meta={'price_mean': 'f8', 'volume_sum': 'f8'}
)
我的建议: Dask最适合的场景是策略研究和回测阶段。你可以在本地快速验证特征逻辑,等确认没问题了,再迁移到Flink或Spark上跑生产。这样开发效率能提升好几倍。
但Dask有个硬伤——没有内置的状态管理和容错机制。一旦任务挂了,你得自己处理恢复逻辑。生产环境我一般不推荐用Dask做核心链路。
4.5 选型决策矩阵
说了这么多,到底怎么选?我整理了一个表格,方便你对照决策。
| 评估维度 | Flink | Spark Streaming | Dask |
|---|---|---|---|
| 延迟 | 毫秒级 | 秒级 | 秒级(受限于调度) |
| 吞吐量 | 极高(百万级/秒) | 高(十万级/秒) | 中等(万级/秒) |
| 状态管理 | ★★★★★ | ★★★☆☆ | ★☆☆☆☆ |
| 运维复杂度 | 高 | 中 | 低 |
| 学习成本 | 高 | 中 | 低 |
| Python生态 | 中(PyFlink) | 高(PySpark) | 极高(原生Python) |
| 适合策略频率 | 高频/中频 | 中频/低频 | 研究/小规模 |
4.6 我的实战建议
嗯,这里要注意,选型不是非黑即白的。我见过不少团队混合使用:
- 研究阶段:用Dask快速验证特征逻辑
- 生产阶段:用Flink跑核心高频特征
- 辅助链路:用Spark Streaming做离线特征和批处理
说白了,工具是死的,人是活的。你只要搞清楚每个框架的核心优势和适用边界,就能做出合理的选择。
最后说一句: 别为了用Flink而用Flink。如果你的策略只需要分钟级特征,Spark Streaming完全够用,何必给自己增加运维负担?我见过太多团队,技术选型时追求「高大上」,结果运维成本把策略收益全吃掉了。