在现代交易系统中,行情数据量巨大且更新频率极高。如何从数据源头获取行情数据、有效存储,并高效分发给终端用户,是交易所和量化平台架构设计中的核心问题。本文将结合实际经验,从数据获取、存储到分发的全链路进行系统讲解,并给出常见问题及优化策略。
一、行情数据获取
交易所行情数据通常来源于 WebSocket 实时推送,主要类型包括:
-
实时成交明细(Trade):记录每笔成交的价格、数量和时间。
-
实时盘口数据(Order Book / Depth):展示买卖双方挂单深度。
-
实时 K 线(OHLCV):用于技术分析的时间序列数据。
WebSocket 连接示例(Python)
import json
import time
import schedule
import threading
import websocket
from loguru import logger
class WebsocketExample:
def __init__(self):
self.session = None
self.ws_url = "wss://data.infoway.io/ws?business=crypto&apikey=yourApikey"
self.reconnecting = False
self.is_ws_connected = False # 添加连接状态标志
def connect_all(self):
"""建立WebSocket连接并启动自动重连机制"""
try:
self.connect(self.ws_url)
self.start_reconnection(self.ws_url)
except Exception as e:
logger.error(f"Failed to connect to {self.ws_url}: {str(e)}")
def start_reconnection(self, url):
"""启动定时重连检查"""
def check_connection():
if not self.is_connected():
logger.debug("Reconnection attempt...")
self.connect(url)
# 使用线程定期检查连接状态
schedule.every(10).seconds.do(check_connection)
def run_scheduler():
while True:
schedule.run_pending()
time.sleep(1)
threading.Thread(target=run_scheduler, daemon=True).start()
def is_connected(self):
"""检查WebSocket连接状态"""
return self.session and self.is_ws_connected
def connect(self, url):
"""建立WebSocket连接"""
try:
if self.is_connected():
self.session.close()
self.session = websocket.WebSocketApp(
url,
on_open=self.on_open,
on_message=self.on_message,
on_error=self.on_error,
on_close=self.on_close
)
# 启动WebSocket连接(非阻塞模式)
threading.Thread(target=self.session.run_forever, daemon=True).start()
except Exception as e:
logger.error(f"Failed to connect to the server: {str(e)}")
def on_open(self, ws):
"""WebSocket连接建立成功后的回调"""
logger.info(f"Connection opened")
self.is_ws_connected = True # 设置连接状态为True
try:
# 发送实时成交明细订阅请求
trade_send_obj = {
"code": 10000,
"trace": "01213e9d-90a0-426e-a380-ebed633cba7a",
"data": {"codes": "BTCUSDT"}
}
self.send_message(trade_send_obj)
# 不同请求之间间隔一段时间
time.sleep(5)
# 发送实时盘口数据订阅请求
depth_send_obj = {
"code": 10003,
"trace": "01213e9d-90a0-426e-a380-ebed633cba7a",
"data": {"codes": "BTCUSDT"}
}
self.send_message(depth_send_obj)
# 不同请求之间间隔一段时间
time.sleep(5)
# 发送实时K线数据订阅请求
kline_data = {
"arr": [
{
"type": 1,
"codes": "BTCUSDT"
}
]
}
kline_send_obj = {
"code": 10006,
"trace": "01213e9d-90a0-426e-a380-ebed633cba7a",
"data": kline_data
}
self.send_message(kline_send_obj)
# 启动定时心跳任务
schedule.every(30).seconds.do(self.ping)
except Exception as e:
logger.error(f"Error sending initial messages: {str(e)}")
def on_message(self, ws, message):
"""接收消息的回调"""
try:
logger.info(f"Message received: {message}")
except Exception as e:
logger.error(f"Error processing message: {str(e)}")
def on_close(self, ws, close_status_code, close_msg):
"""连接关闭的回调"""
logger.info(f"Connection closed: {close_status_code} - {close_msg}")
self.is_ws_connected = False # 设置连接状态为False
def on_error(self, ws, error):
"""错误处理的回调"""
logger.error(f"WebSocket error: {str(error)}")
self.is_ws_connected = False # 发生错误时设置连接状态为False
def send_message(self, message_obj):
"""发送消息到WebSocket服务器"""
if self.is_connected():
try:
self.session.send(json.dumps(message_obj))
except Exception as e:
logger.error(f"Error sending message: {str(e)}")
else:
logger.warning("Cannot send message: Not connected")
def ping(self):
"""发送心跳包"""
ping_obj = {
"code": 10010,
"trace": "01213e9d-90a0-426e-a380-ebed633cba7a"
}
self.send_message(ping_obj)
# 使用示例
if __name__ == "__main__":
ws_client = WebsocketExample()
ws_client.connect_all()
# 保持主线程运行
try:
while True:
schedule.run_pending()
time.sleep(1)
except KeyboardInterrupt:
logger.info("Exiting...")
if ws_client.is_connected():
ws_client.session.close()
核心注意点
-
保持连接稳定
WebSocket 容易因网络波动断开,需要自动重连机制。常用做法是基于异步事件驱动框架(如asyncio + websockets)管理海量连接,避免阻塞和重复连接。 -
心跳与超时监控
定时发送心跳包并监控每条连接的最后一次数据时间。如果超过阈值,应立即重连,以防数据静默中断。 -
高并发消息处理
为防止 WebSocket 被阻塞,应采用异步解析消息、批量入队缓存,再统一写入数据库或缓存。
二、行情数据存储
高频行情数据具有体量大、写入频繁的特点,存储策略直接影响系统性能。
1. 内存缓存(Redis)
-
用于保存最新盘口和 K 线,支持快速查询与分发。
-
建议仅保留最近 N 条成交明细或滑动窗口数据,避免 OOM。
2. 时间序列数据库(TSDB)
-
高频成交数据和历史 K 线可存入 InfluxDB、TimescaleDB 或 Kdb+。
-
优势:
-
支持按时间索引快速查询
-
提供高效的压缩存储
-
-
优化:
-
批量写入,减少 I/O 压力
-
避免逐条写入磁盘
-
3. 热冷分层存储
-
热数据:最近几天的数据保存在高性能 TSDB,用于实时查询。
-
冷数据:历史数据归档至对象存储(S3、Ceph),降低成本同时保留回溯能力。
三、行情数据分发
将行情数据高效、安全地推送至终端用户,是系统最容易成为瓶颈的环节。
1. 消息中间件
-
使用 Kafka 或 NATS 实现解耦:
数据源 → 消息中间件 → WebSocket 网关 -
优点:
-
支持高并发、多机扩展
-
保证消息顺序性
-
2. 分布式 WebSocket
-
单机 WebSocket 无法支撑上万用户同时在线。
-
解决方案:
-
部署分布式 WebSocket 网关(如 SocketCluster 或自研网关)
-
通过 Redis 或 Kafka 同步数据,保证网关间数据一致性
-
3. 用户订阅与数据裁剪
-
根据用户订阅类型裁剪不必要的数据,例如深度聚合或只发送特定品种。
-
目的:
-
减少网络带宽占用
-
降低客户端处理压力
-
四、常见问题及优化策略
| 问题 | 描述 | 优化方案 |
|---|---|---|
| WebSocket 高并发断线 | 网络波动或服务器压力导致连接掉线 | 异步管理连接 + 心跳 + 超时重连 |
| 消息处理阻塞 | 数据处理慢导致 WebSocket 堵塞 | 异步队列 + 批量处理 |
| 存储 I/O 压力大 | 高频交易写入磁盘 | 批量写入 + TSDB + 热冷分层 |
| 用户端推送瓶颈 | 单机无法支撑大量 WebSocket | 分布式网关 + 消息中间件 |
| 历史数据查询慢 | 直接查询原始交易明细 | TSDB + 索引 + 归档策略 |
五、从源头到终端的完整架构
完整行情数据流向如下:
WebSocket 数据源
↓
异步消息处理
↓
内存缓存(Redis) / TSDB
↓
消息中间件(Kafka / NATS)
↓
分布式 WebSocket 网关
↓
终端用户
特性说明
-
热数据快速可用,支持实时交易和分析。
-
冷数据归档,便于历史回溯。
-
高可用架构,多机热备,保证系统稳定。
-
异常监控与数据回溯机制,确保数据完整性。
1094

被折叠的 条评论
为什么被折叠?



