数据源接入:主流交易所API与数据格式解析

做实时行情系统,第一步就是跟交易所打交道。说白了,你得先拿到数据,后面那些花里胡哨的分析才有意义。我见过不少团队,架构设计得天花乱坠,结果数据源接入这块没搞好,整个系统就像没地基的房子——风一吹就倒。

今天咱们就聊聊数据源接入的几个核心问题:REST vs WebSocket怎么选、JSON和Protobuf谁更香、鉴权怎么做、连接池怎么管。嗯,这些都是我踩过坑的地方。

REST vs WebSocket:什么时候该用谁?

先说说REST。这东西大家都很熟,请求-响应模式,简单直接。但用在行情系统里,有个致命问题——你没法实时拿到数据。

举个例子,你想看BTC/USDT的最新价格。用REST的话,你得每隔几秒发一次请求。这就像你每隔几秒给朋友打电话问「现在几点了」,效率低不说,还容易把朋友惹毛。

WebSocket就不一样了。它建立一条长连接,交易所那边数据一变,主动推给你。这才是实时行情该有的样子。

我的建议:

  • 历史数据、查询类操作:用REST。比如拉取K线历史数据、查询账户余额。
  • 实时行情、订单簿、成交记录:用WebSocket。这是实时系统的命脉。

我在项目中遇到过一件事:有个同事图省事,所有数据都用REST轮询。结果行情波动大的时候,服务器CPU直接飙到90%,数据还经常延迟好几秒。后来改成WebSocket,CPU降到20%,延迟控制在100毫秒以内。你看,选对协议能省多少事。

数据格式解析:JSON vs Protobuf

拿到数据了,怎么解析?这又是一个大坑。

JSON:人类可读,调试方便。但体积大,解析慢。行情数据量大的时候,光解析JSON就能吃掉不少CPU。

Protobuf:二进制格式,体积小,解析快。但调试麻烦,得先定义好schema。

对比项 JSON Protobuf
可读性 ★★★★★ ★☆☆☆☆
解析速度 ★★☆☆☆ ★★★★★
数据体积 ★★☆☆☆ ★★★★★
调试便利性 ★★★★★ ★★☆☆☆
跨语言支持 ★★★★★ ★★★★☆

我个人习惯是:开发阶段用JSON,方便调试;生产环境用Protobuf,追求性能。当然,如果交易所只支持JSON,那也没得选。

小技巧:如果必须用JSON,可以考虑用simdjson这类高性能解析库。我在一个项目中把JSON解析从encoding/json换成simdjson,解析速度提升了5倍。

鉴权:别让你的API Key裸奔

说到鉴权,我见过最离谱的事——有人把API Key直接写在代码里,还上传到了GitHub。嗯,结果可想而知,账户被洗劫一空。

主流交易所的鉴权方式大同小异,核心就是API Key + Secret Key + 签名。流程大概是这样的:

  1. 从交易所申请API Key和Secret Key
  2. 构造请求参数,按规则排序
  3. 用Secret Key对参数进行HMAC-SHA256签名
  4. 把签名放在请求头或参数里发出去

代码示例(Go语言):

func signRequest(params map[string]string, secret string) string {
    // 1. 参数按key排序
    keys := make([]string, 0, len(params))
    for k := range params {
        keys = append(keys, k)
    }
    sort.Strings(keys)
    
    // 2. 拼接成 query string
    var buf strings.Builder
    for _, k := range keys {
        buf.WriteString(k)
        buf.WriteString("=")
        buf.WriteString(params[k])
        buf.WriteString("&")
    }
    queryStr := strings.TrimSuffix(buf.String(), "&")
    
    // 3. HMAC-SHA256签名
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write([]byte(queryStr))
    return hex.EncodeToString(mac.Sum(nil))
}

我曾经踩过的坑:

  • 签名时参数顺序没排好,导致签名验证失败。折腾了两小时才发现。
  • 时间戳没用UTC,用了本地时间。交易所那边校验不过。
  • API Key权限没限制,给了提币权限。还好及时发现。

连接池管理:别让连接成为瓶颈

WebSocket连接不是免费的。每个连接都要占用内存、CPU、文件描述符。如果管理不好,系统很容易崩溃。

连接池的核心思想:复用连接,减少创建和销毁的开销

我一般这样设计:

  • 每个交易所维护一个连接池,池子里有若干条WebSocket连接
  • 连接数根据行情订阅量动态调整。订阅的币种多,就多开几条连接
  • 心跳检测:每隔30秒发一次ping,如果60秒没收到pong,就断开重连
  • 自动重连机制:断线后指数退避重试,1秒、2秒、4秒...最多重试5次

连接池配置参考:

type ConnPoolConfig struct {
    MaxConns        int           // 最大连接数,默认10
    MinConns        int           // 最小连接数,默认2
    MaxIdleConns    int           // 最大空闲连接数,默认5
    IdleTimeout     time.Duration // 空闲超时,默认60秒
    DialTimeout     time.Duration // 拨号超时,默认5秒
    HeartbeatInterval time.Duration // 心跳间隔,默认30秒
    ReconnectMaxRetries int       // 最大重试次数,默认5
}

你想想看,如果没有连接池,每次断线都重新创建连接,那得多慢。而且频繁创建连接还可能被交易所当成恶意请求给封了IP。

整体架构图

下面这张图展示了数据源接入的整体流程。从交易所到你的系统,每一步都有讲究。

数据源接入架构图 交易所层 Binance OKX Coinbase Bybit 协议层 REST (历史数据) WebSocket (实时数据) 数据格式层 JSON (开发/调试) Protobuf (生产) 接入层(连接池管理 + 鉴权) 连接复用 | 心跳检测 | 自动重连 | 签名鉴权

从图上你能看到,数据从交易所出来,经过协议选择、格式解析,最后进入我们的接入层。每一步都有对应的技术选型和优化空间。

最后说一句:数据源接入看似简单,但细节决定成败。我见过太多系统因为连接池配置不当、鉴权签名出错、协议选型错误而翻车。希望今天的分享能帮你少走一些弯路。


公众号:蓝海资料掘金营,微信deep3321