☰
实时行情系统架构设计:协议选型、数据源与高可用实战
2026/10/9 22:23:03 网站建设 项目流程

做行情系统这行久了,最常被问的一句话就是:"你们那实时行情到底有多实时?"这个问题听起来简单,背后牵出来的链路却很长:从交易所或数据源发出第一笔行情,到用户屏幕上那个数字跳动,中间经过协议解析、数据路由、多级缓存、推送分发,任何一个环节慢了几毫秒,行情就"不实时"了。更麻烦的是,行情系统不只是快就行,它还得稳——不能断、不能错、不能乱,遇到上游抖动还要能扛住。这篇就从协议选择、数据源选型、高可用架构这三个主战场,把我做实时行情系统这些年踩过的坑、沉淀下来的设计思路,完整拆一遍。

这篇内容适合三类人看:一是准备从零搭建行情服务的后端工程师,二是已经在维护行情系统但总觉得"能用但不敢大改"的同学,三是想搞清楚行情数据到底怎么从源头流到业务方的架构师。不论你是做股票、期货还是数字资产的实时行情,底层思路是相通的,我会把通用的部分讲透,再把特定场景下的取舍一并说清。

1. 行情系统到底在解决什么问题

1.1 行情系统的定位与核心指标

行情系统在技术圈里是块"小而精"的硬骨头。小是因为它承接的消息类型很集中——无非是逐笔成交、盘口快照、K线、涨跌停等几种;精是因为它同时踩中三个极端:数据量大、延迟敏感、可用性要求严苛。这三者互相拉扯,设计上稍有不慎就会顾此失彼。

判断一个行情系统好不好,核心指标其实就是三个:

  • 端到端延迟:从数据源产生行情到终端收到行情的时间差,通常用"xx毫秒内送达"来衡量。这个指标直接决定了行情"实时"的程度。
  • 吞吐能力:单位时间内能处理的行情消息条数。行情系统在开盘和收盘时段往往会有瞬时洪峰,吞吐设计不足就直接丢消息。
  • 可用性:系统在一段时间内不中断服务的概率。行情是典型的高频依赖型服务,断个几十秒,下游业务方(比如交易策略、风控系统)就会出大问题。

这三角色里,延迟和吞吐天然冲突,吞吐和可用性互相制约。架构的功夫就在于怎么权衡,而不是盲目追求某个单点极限。

1.2 实时行情链路的基本盘

不管业务多复杂,行情数据从源头到终端基本都要走这样一条链路:

数据源 → 接入网关 → 校验清洗 → 路由分发 → 业务适配 → 终端推送

每个环节再往下拆,都会有一堆细节。接入网关负责和各种外部数据源建立连接,实时收流并解析;校验清洗环节要做完整性检查、时间戳校准和异常值剔除;路由分发决定这条行情消息应该送给哪个业务模块;业务适配则是把统一的内部格式转成各个下游需要的形态;终端推送则是通过WebSocket、TCP长连接等方式,把行情推给最终用户。

我在实际设计中,一直坚持一个原则:内部统一中间格式。不管上游是二进制协议、Protobuf还是JSON,进了系统一律转成统一的内部结构体,下游各取所需。这一步虽然会带来一点点解析延迟,但换来的却是维护性的质变——否则每接一个新数据源,下游每个模块都要跟着改一遍。

1.3 为什么协议、数据源和架构要绑在一起考虑

很多团队做行情系统容易陷入一个误区:协议选型归选型、数据源接入归接入、高可用架构归架构,各自分开做,最后拼起来发现"带不动"。我个人的经验是,这三者必须放到一张图上去设计,因为它们的耦合点非常深。

协议选择直接决定接入层的复杂度,也严重影响高可用架构的容错策略。比如你选了WebSocket,那心跳和断线重连天然就有一套标准解法;如果你用UDP组播做低延迟分发,那乱序、丢包就得在应用层自己想办法,架构里必须多设计一个"状态同步兜底"的环节。数据源选型则决定了接入层的数量、多源切换的复杂度,以及行情数据的质量上限——源头如果经常延迟、缺数,架构再稳也没用。所以题目里说的"从协议到架构再到数据源",并不是想先讲哪个就先讲哪个,而是一环扣一环的依赖关系。

2. 协议选择:先别急着上WebSocket,把权衡想清楚

2.1 协议选型的底层逻辑

行情协议选型是我每次做新项目都会重新过一遍的决定。市面上可选的无非是WebSocket、自定义TCP二进制协议、UDP组播、QUIC这几类,但真要按照"实时行情"的场景去套,每一类都有自己的短板。

选协议的时候,我一般只问三个问题:

  • 允许的端到端延迟是多少?如果业务允许100ms以上,那TCP阵营随便选;如果要求到终端20ms以内,TCP的拥塞控制、队头阻塞就成了绕不开的坎。
  • 数据连接的规模多大?千级连接的推送跟百万级连接的推送,协议层面的考量完全不同。
  • 网络环境可控吗?如果终端分散在公网、跨地域、移动网络,和机房内部独立组网的低延迟链路,能用的协议栈也不一样。

把这三个问题回答完,协议选型的大方向基本就出来了。

2.2 WebSocket与自定义TCP二进制协议:通用性 vs 性能

WebSocket是很多团队做实时推送时的第一反应,因为浏览器原生支持、调试方便、周边生态好。但它在行情场景下有个绕不开的痛点——传输效率偏低。WebSocket天然以文本帧为主,实用中很多团队又偷懒直接用JSON传行情数据,我想想都心疼那带宽。行情是高频小消息,单条快照可能就几百字节,但盘口变动时一秒几十笔,折算下来数据量很可观。用WebSocket推行情,最大的代价还不是带宽,而是解析开销——JSON解析本身就是CPU密集操作,在高吞吐场景下会成为瓶颈。

自定义TCP二进制协议则在这一点上优势明显。定一个紧凑的报文结构,头几个字节放消息号、时间戳、序列号,后面按固定格式排列买卖盘口数据。解析就是内存拷贝,连反序列化都不用,性能差距能到数倍。代价是调试麻烦,没法直接在浏览器里看,还得做测试工具。但如果你的行情是服务内部系统而不是浏览器页面,我强烈建议走自定义二进制协议,收益大到值得多写那点工具代码。

2.3 UDP组播与QUIC:低延迟到底怎么压出来

再往上走,就是延迟敏感型机构玩家关注的地带。如果你是要服务于本地机房内的量化策略程序化交易,链路短、网络自治可控,UDP组播几乎是标准答案。UDP没有三次握手、没有拥塞控制、没有队列调度,天然适合一对多的行情广播。但代价也很大:它不可靠、会乱序、会丢包。所以使用UDP组播的行情系统,应用层必须自己补一套可靠传输逻辑——序列号比对、丢包重发、快照同步。

QUIC是近几年新兴的方案,它基于UDP实现可靠传输,同时解决了TCP的队头阻塞问题,还有0-RTT连接建立能力。在我实测的场景里,QUIC对弱网环境下的行情推送提升非常明显,尤其是移动端、跨地域的长链路,延迟抖动比TCP小很多。缺点是目前中间件支持和运维工具相对少,团队要有一定技术积累才玩得转。但如果你做的是公网行情服务,且延迟要求比较高,QUIC值得认真考虑。

2.4 协议选型的实战对照表

为了方便对比,我整理了几种方案的适用场景和注意点:

协议方案延迟量级吞吐上限可靠性适用场景主要坑点
WebSocket + JSON中等中等TCP可靠浏览器端、快速实现解析开销大、带宽浪费
WebSocket + Protobuf中等偏低中等偏高TCP可靠浏览器端+服务端需管理schema兼容
自定义TCP二进制低高TCP可靠服务端内部对接调试成本高
UDP组播极低极高不可靠,需自建恢复局域网内低延迟分发需要精心设计恢复机制
QUIC低高可靠公网、移动端、跨地域运维工具链不成熟

从我个人的项目经验来排优先级:内部链路默认用UDP组播或自定义二进制TCP,外部公网推送优先考虑QUIC,只有交付给浏览器端或对外快速迭代的功能才会落到WebSocket上。这个层级划分省了我大量精力,也避免了反复改协议栈的尴尬。

3. 数据源选型:源头脏了,下游全白干

3.1 行情数据源有哪些类型

行情数据的来源一般来说分几类:

  • 官方交易所/核心撮合引擎直连接口:延迟最低、数据最权威,但接入门槛高、对接成本大。
  • 第三方聚合数据服务商:把多家行情源聚合好再统一输出,接入简单、成本可控,但会有额外一跳延迟。
  • 商业数据终端/代理源:比如做一些落盘、转发的中间商,适合做冷备或者容灾。
  • 爬虫抓取的公开页面数据:这个基本只适合做低频率、非实时类的兜底,延迟高且不稳定。

在我做过的项目里,数据源从来不是越多越好。核心一手源一定保留一个,这是基准;然后根据业务量级,再配置一个或两个备用源,用于容灾和质量比对。

3.2 数据源质量评估的四个维度

选数据源不能只看报价和宣传,一定要自己动手做质量评估。我评估一个数据源通常从四个维度打分:

  • 及时性:从源头事件产生到数据源发出行情的时间差,这个要实际对比两个数据源的到达时间来检验。
  • 完整性:是否频繁缺消息、缺字段、缺K线周期。有些源会在盘中时段故意降级,得盯住。
  • 稳定性:断连率、重连耗时、故障时长。这里不光看服务商给的SLA,更要看真实运行中的表现。
  • 准确性:行情数据有没有错价、乱序、跳跃。这一项要拿两个独立源做交叉验证,单靠一个源根本测不出来。

这四个维度可以用一张评分表来量化,低于及格线的一票否决,不能因为价格便宜就妥协——行情数据一旦错漏,引发的连锁问题远大于省下的那点成本。

3.3 多源融合与主备切换策略

多源不是摆设,而是要真的派上用场。我一般把数据源分为主源和备源,主源负责日常生产,备源持续在线保持热同步。切换策略上,我坚持两个原则:

第一,主备切换不能看单一指标。不能因为主源断连几秒就立刻切换,容易造成抖动误判。要设置一个"判定窗口",比如连续3次心跳失败且超过5秒才触发切换,这样既能在真故障时快速响应,又不会因为偶发抖动而频繁割接。

第二,切换流程要尽量自动化,但要留人工兜底。自动化切换能解决90%的场景,剩下的10%(比如数据源静默出错但心跳正常)必须靠监控告警和人工介入来处理。我之前遇到过一次主源数据延迟了十几秒但连接一直没断,自动化切换完全没触发,最后靠监控比对发现异常才切走的。这个教训让我后来在切换逻辑里额外加了一条"数据新鲜度检测",不只看连接是否活着,还要看数据是否在持续前进。

3.4 数据源接入的实测经验

接入不同数据源的时候,我最常踩的坑有两个。第一个是多源时间戳口径不一致。A源给的是本地网关时间,B源给的是事件时间,C源给的是落盘时间,三个源的数据在比对时就会错位。所以接入时我要求在网关层统一校准为同一时区的UTC时间,并保留原始时间戳和接入时间戳两个字段,后续排查问题会省大事。

第二个坑是数据源限流策略不透明。有些第三方源会在行情洪峰时偷偷降级——比如原来每100ms推一条快照,盘中变成每500ms推一条。这种降级不会体现在连接层面,只有拿另一个源做对比才能发现。我现在的做法是在接入网关层对每个源记录"期望消息数/实际消息数"的比值,一旦这个比值偏离正常范围,立刻触发告警,把隐藏降级打回原形。

4. 高可用架构:从单点到可容灾的演进路径

4.1 高可用要防的是哪几类故障

行情系统的高可用设计,本质上是在对抗四类故障:进程级故障(OOM、崩溃、死锁)、节点级故障(宕机、断电)、机房级故障(光缆中断、区域性网络异常)、上游故障(数据源断连、协议异常)。每一类故障的防护手段不同,但整体目标一致:任何一个环节出问题,都不能让用户感受到行情中断。

很多团队做到节点级故障的冗余就觉得高可用完成了,但真实生产中最致命的往往是机房级故障——光缆被挖断这种事虽然概率低,一旦发生就是全量不可用。如果行情服务是交易系统的眼睛,眼瞎哪怕十分钟,都是不可接受的。

4.2 分层集群:接入层、汇聚层、分发层

我常用的高可用架构是三层集群模式:

  • 接入层:负责和外部数据源建立连接、接入解析。这一层必须是多节点无状态的,任意一台挂掉,其他节点都能接管连接。数据源侧要有负载均衡或者抢占机制,避免多台接入节点同时处理同一份数据造成重复。
  • 汇聚层:负责去重、排序、清洗、合成完整订单簿和K线。这一层通常采用主备模式,备节点实时同步状态,主节点故障时秒级切换。这里最难的是保持状态一致——比如当前订单簿的深度快照,主备之间需要持续同步快照和增量日志。
  • 分发层:面向下游业务方,负责把清洗后的行情推送给各个消费端。这一层要有水平扩展能力,连接的消费者多了就加节点。它本身不持有复杂状态,所以扩展是最简单的。

三层分离最大的好处是能独立扩缩容。开盘时用户量暴增,分发层加机器就行;接入的数据源数量多了,接入层单独扩容;汇聚层则保持小而精,保证一致性。

4.3 故障切换与降级策略

故障切换之外,降级策略也是高可用里很重要的一环。行情系统的降级不像普通业务那样直接限流就完了,它有一套典型的降级阶梯:

  • 第一阶段:停止推送不重要的衍生数据(比如逐笔成交明细),保留核心快照。
  • 第二阶段:降低推送频率,比如原本100ms推一次的快照,改成500ms推一次。
  • 第三阶段:停止增量推送,只提供手动查询最新快照的能力。

这个阶梯的好处是,每一级降级都在换取系统的存活能力,让核心用户(比如交易员、风控系统)始终能看到最新行情,只是刷新频率降低了,而不是直接黑屏。降级触发条件和恢复条件要写清楚,并且要经过演练验证——我见过不少系统在故障演练中因为降级恢复逻辑没测过,故障结束后服务一直留在降级状态,那反而更麻烦。

4.4 延迟与可用性的平衡

做高可用架构时最容易犯的错,是过度追求万无一失而牺牲性能。比如每个环节都做同步双写、每次转发都做多副本确认,延迟会直线上升。我的经验是,在实时行情这个场景里,"可靠性"和"低延迟"之间要有一个清晰的分层:热点路径低延迟,冷备路径重保障。

热点路径上,数据直接在主链路上转发,尽量不做多次确认;冷备路径则用异步的方式持续同步状态,保证故障时可以接管。这个设计思路的关键词是"冗余但不阻塞",体现在实际操作中,就是同一份行情数据在主链路上走得很轻快,在备份链路里走得慢一点没关系,只要状态最终对齐即可。这样当主节点故障时,备节点接管后能在一个较短的窗口内补上数据缺口,既保证了可用性,又把主链路的延迟压到了最低。

5. 常见问题与排查技巧实录

5.1 延迟抖动排查:先查链路水位,再查应用逻辑

行情系统出现延迟波动,很多人第一反应是去查代码,其实绝大多数延迟问题出在网络链路和中间件水位上。我通常按这个顺序排查:

先看从数据源到接入网关的链路时延——用两个独立源对比到达时间,如果A源比B源慢了30ms以上,问题大概率在上游,不在自己系统里。

再看接入层到汇聚层、汇聚层到分发层之间的队列堆积量。行情系统每个环节都有缓冲队列,正常情况下队列几乎是空的,一旦出现堆积,就说明下游消费速度跟不上上游生产速度,这时候要先扩容下游而不是优化代码。

最后才看应用逻辑——GC停顿、锁竞争、磁盘IO都会造成延迟毛刺。我遇到过最隐蔽的一类问题是JVM的GC日志在高峰期写盘导致停顿,移动日志目录后延迟掉了15ms。

5.2 数据乱序与重复:用序列号兜底

多路数据源同时接入后,乱序和重复几乎是必然的。同一个合约的快照,可能先从B源到了,A源的后到,如果直接把消息按到达顺序推出去,终端就会看到价格来回跳。

我的做法是在汇聚层为每一条行情消息分配一个全局递增序列号,并维护一个"最新已推送序列号"水位。消息来了先比对序列号,小于水位的直接丢弃(重复消息),大于水位但中间有缺号的,就等待一个极短的重排窗口(比如3毫秒),补不上再根据策略选择跳过或者申请重发。这个窗口要选得很谨慎,太短会有乱序遗漏,太长则会增加端到端延迟。我在实际项目中用过2-5毫秒,基本能覆盖绝大多数网络乱序场景。

5.3 重连风暴:给断线重连加个"缓冲垫"

行情系统跟数据源的连接一旦不稳定,最怕的不是断线本身,而是重连风暴——断线后所有接入节点同时重连,把数据源的连接数瞬间打满,导致更严重的拒绝服务。

我采取的方案是给每个接入节点配置不同的重连初始退避值,并叠加随机抖动。比如节点A初始退避200ms,节点B是500ms,节点C是1s,每次重连失败退避加倍,最大到30秒。这样即使上游故障恢复,接入层的重连请求也会错峰,不会形成冲击。这套机制我在一个事故复盘后加上去的,从那以后再没出现过重连风暴的问题。

5.4 多源切换时的"数据跳变"处理

主备数据源切换时最常接到客服反馈的问题是:"价格突然跳了一下,是不是系统出bug了?"这里面的原因多半不是bug,而是两个数据源的快照本来就有细微差异——它们的聚合深度、更新频率、甚至价格精度都可能不同,切换后终端的盘口就会出现跳变。

我的处理办法是在切换时给终端下发一个"数据源切换提示"事件,同时做一次全量快照同步,而不是继续流式地推送增量。这样终端收到提示后,会清空旧的盘口数据,以新的快照重建订单簿,用户看到的效果是"刷新了一次"而不是"价格乱了"。这个设计虽然看起来小而细,但真正决定了一个行情系统在运维层面是不是"老练"。

写在最后的实操心得

行情系统做到后面,拼的往往不是某个炫技的架构,而是对细节的敬畏和管理运维的成熟度。三个体会分享给同行:

第一,协议选型要做"五年后还够不够用"的判断,不要贪图一时的开发速度。协议一旦上线,改造成本极高,宁可前期多花一周做压测和场景推演。

第二,多源设计不是简单的备胎逻辑,要日常就用起来,做实时比对、互为主备,否则关键时刻根本不敢切。我在项目里就是让两个源长期并行跑,任何一方的异常都能在30秒内被监控发现。

第三,高可用不是配置出来的,是演练出来的。每年至少要安排两次故障演练,人为断数据源、杀节点、模拟机房故障,看看系统到底能不能自己扛住。没演练过的高可用配置,都只能算纸上谈兵。

最后分享一个小技巧:对于行情这种高吞吐低延迟的业务,监控系统本身也要做性能优化,不能因为监控产生额外开销。我用过不少方案,最终采用的是边缘节点采集 + 聚合后上报 + 独立监控链路的模式,确保监控只在异常时被高频触发,正常情况下几乎不占用主链路资源。这套手法让我的行情系统在持续运行中一直保持"稳"字当头,希望对你也有参考价值。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询