做实时行情系统这几年我最大的感受是它不像普通业务系统那样能跑就行而是把延迟、吞吐、可用性三个指标同时按到极致的一类系统。行情晚到一秒钟交易端的体验就崩了数据源断掉一次风控那边就要出大事。这篇文章我就结合自己做过的行情系统从协议选择、高可用架构到数据源选型把整套设计思路和踩过的坑完整梳理一遍。无论你是刚接手行情项目的后端工程师还是准备自建行情服务的团队这篇文章应该都能给你一份可以直接参考的实操清单。1. 先想清楚实时行情系统到底在解决什么问题1.1 三个核心诉求低延迟、高吞吐、高可用行情系统的技术选型本质上都是在和这三个指标博弈。低延迟指的是从数据源产生行情到客户端收到行情之间的总耗时。我做过一个交易类项目业务上给用户的承诺是行情端到端延迟不超过100毫秒内部设计目标直接压到50毫秒以内。分摊下来数据源推送到采集层大约10毫秒采集层处理和入队约10毫秒推送网关转发约20毫秒剩余的网络传输和客户端渲染也就剩十几毫秒的空间。每一个环节稍有阻塞整条链路就超标。高吞吐更直白。行情高峰期逐笔成交消息每秒能达到几万条甚至几十万条同时在线连接数可能是十万级甚至百万级。服务器要反复执行读消息、解析、路由、推送的操作任何一个环节设计成同步阻塞吞吐就会塌方。实测下来如果推送网关里混入了数据库操作或者大对象序列化单机QPS会从几十万掉到几万完全不是一个量级。高可用是第三个大坑。行情服务停了哪怕几十秒用户就会集体投诉数据丢了哪怕几条量化策略就可能算出错误信号。我们通常用SLA来衡量99.99%的可用性意味着一年只能允许约52分钟不可用听起来挺宽裕但行情系统的不可用往往不是整机宕机而是某条数据链路上出现短暂抖动。所以设计时不但要保证进程不挂还要保证数据链路不出现长毛刺。这三个指标往往是矛盾的想要延迟最低就尽量少加中间件但少了冗余又牺牲可用性想要高可用就要多副本、多集群但数据同步又引入延迟。我的建议是不要在架构上追求单一最优而是把链路分层不同层用不同策略后面会详细展开。1.2 行情系统的数据分层与业务场景在设计之前先把行情数据的类型和消费方盘清楚。数据层面行情系统通常要处理两类消息一类是快照数据比如某个交易标的的当前价、最高价、最低价、成交量这类数据可以定期推送比如每100毫秒或500毫秒推一次另一类是逐笔数据也就是每一笔成交的价、量、时间这类数据是事件流没有固定频率行情越活跃频率越高。消费方也分好几拨。面向普通用户的App需要的是低延迟快照用户体验主要看推送间隔和渲染是否卡顿量化策略系统需要的是逐笔委托和逐笔成交的原始数据每一笔都可能成为触发信号所以要求严格有序、不丢不重风控系统更在意数据完整性哪怕晚一点也不能出现缺口。因为各方的诉求不同我倾向于在系统内部做一次数据分层。底层统一接入数据源经过清洗、校验后进入消息总线往上分两条路一条做实时快照计算输出给推送网关另一条保留全量逐笔流供量化侧订阅。这样既保证了不同场景的数据质量又避免了一个消费方把整体链路拖垮。也可以简单理解成采集层负责把数据搞进来计算层负责把数据加工好推送层负责把数据送出去。每一层各管各的事出了问题也能快速定位。2. 协议选择没有最好的协议只有最合适的协议2.1 主流协议的横向对比很多刚做行情系统的同学第一反应是用WebSocket不就行了吗实际上协议选择要看数据的源头在哪一端、消费方是谁、网络环境怎样。我把常见的几类协议放在一起对比过各有各的适用场景。WebSocket是目前对外推送最主流的方案浏览器和App都能直接用全双工通信天然适合服务端主动推送。它基于TCP顺序有保证但帧格式有额外开销连接数高了以后也需要注意内存占用。TCP私有协议适合源头接入或内部服务间通信。比如交易所或数据源提供的原始行情订阅很多是基于TCP和自定义二进制协议优点是协议头可以做到极其精简延迟低、解析高效缺点是开发和维护成本高字段变更需要双方同步修改。UDP组播常用于证券行情局域网分发。它能把一份行情同时发给多个消费端网络开销极低但UDP本身不保证可靠交付需要自己在应用层做序号和重传补偿。如果网络质量不好组播丢包会非常头疼。MQTT是物联网场景下的常用协议轻量、支持发布订阅也支持QoS级别。不过行情系统对延迟和吞吐的要求一般高于MQTT的日常应用场景我用下来觉得它更适合弱网环境或移动端通知不太适合高吞吐的核心行情链路。FIX/FAST是金融领域的老牌协议。FIX报文可读性尚可但字段多、体积大FAST是针对FIX的压缩编码。这类协议在机构间交易场景很常见如果你的上游数据源就是FIX协议对接是躲不掉的。协议延迟吞吐开发成本可靠性典型场景WebSocket中低中高低TCP可靠对外推送、手机端TCP私有协议低高高TCP可靠上游数据源、内部服务UDP组播极低极高高弱需自研补偿局域网行情分发MQTT中中低支持QoS移动端、弱网场景FIX/FAST中低中中高TCP可靠机构、交易所对接实际项目中我使用的是混合方案也就是上游TCP私有协议接入内部消息总线以二进制消息传递对外统一走WebSocket推送。一句话总结协议没有绝对好坏完全取决于你手里的数据从哪来、要到哪去。2.2 对外推送为什么首选 WebSocket对外推送层我几乎没有犹豫就选了WebSocket。原因其实很现实客户端生态太成熟了浏览器自带WebSocket API移动端也有成熟的库团队不需要为每个客户端折腾一套私有协议栈。WebSocket的全双工能力很关键。行情系统需要服务端主动向客户端推送数据如果用HTTP轮询延迟和连接开销都扛不住如果用TCP长连接私有协议客户端SDK要自己处理粘包拆包、鉴权握手工程成本高出一大截。WebSocket把这些问题都标准化了握手阶段可以带鉴权参数连接建立后服务端随时可以推送。另一个优势是WebSocket天然兼容TLS也就是wss。行情数据虽然不是隐私信息但加解密能防止数据被中间人恶意篡改尤其在公网环境下我建议一律启用wss不要裸用ws。开了TLS之后握手开销有所增加但可以通过连接复用、减少频繁重连来弱化影响。不过WebSocket也有一个容易被忽视的坑就是服务端连接数。每个WebSocket连接都是一个持有TCP连接的对象如果客户端规模有几十万Gateway的内存占用会非常可观。我们压测过一个默认2G堆内存的Java网关单机最多扛几万连接再往上就频繁GC。后来调整了Netty参数减少堆外内存拷贝才把单机连接数提上去。选型时一定要把连接数作为核心容量指标去估算。2.3 消息格式与增量快照设计协议确定后接下来是消息格式。我最早图省事直接用JSON推送行情。结果压测一跑序列化开销就把延迟拉高了。行情消息往往高频小体积JSON的冗余字段和字符串解析成本在这种场景下很吃亏。后来改成了二进制编码消息体只保留必须字段解析耗时降了一个数量级。即便在对外推送层我也建议优先考虑二进制格式。如果团队实在没有跨语言的序列化方案可以选择Protobuf或者FlatBuffers。Protobuf生态号前后端都能直接生成代码解析缺点是序列化后不够直观调试时要转成文本。FlatBuffers支持零拷贝读取对延迟更友好但接入成本稍高。消息结构上我习惯分成基础头、数据体两大部分。基础头包括消息类型、产品代码、发送时间、业务序号数据体根据消息类型不同存放快照或逐笔数据。每一类消息都必须带上业务序号这是断线补数据的生命线。举个例子一个快照消息可以设计成这样{ type: snapshot, symbol: AAPL, seq: 123456, timestamp: 1699999999999, data: { last: 190.25, bid: 190.24, ask: 190.26, volume: 12345678 } }逐笔成交消息则带有独立的tradeId和成交方向。字段设计时我特别强调不要把所有数字都用字符串表示。像价格、成交量这类高频字段能用整型就用整型比如价格放大10000倍后以long型传输既能保留精度又能减少转码开销。增量快照的设计也很关键。全量快照每100毫秒或500毫秒推一次增量消息紧随其后。客户端先收全量建立初始状态再按增量逐条更新。一旦发现序号跳变或本地状态对不上就向服务端请求一次全量快照把状态拉齐。这是行情推送中最常见、也最稳定的可靠性方案。2.4 可靠性处理心跳、超时与断线补偿WebSocket虽然是TCP连接但还是会碰到网络闪断、中间设备回收空闲连接、服务端重启等情况。只靠TCP不一定会触发断开应用层必须做心跳机制。我常用的心跳方案是服务端每15到30秒发送一个Ping帧客户端收到后回Pong服务端如果在两个Ping周期内没收到任何数据就判定连接过期主动关闭并清理会话。客户端侧也要设置超时检测如果连续多个周期没收到服务端心跳就主动重连。心跳间隔要结合网络实际情况调整太短会造成不必要的流量和CPU开销太长又会让断开检测很迟钝。我们最终线上用的是30秒。断线重连只是第一步重连之后的数据补齐才是重点。客户端重连成功后服务端要从会话存储中取出它最后一次收到的业务序号然后把后续消息补推给客户端。如果消息总线已经做了持久化可以按序号范围直接拉取如果没做持久化至少要保证能接收一次全量快照让客户端重新建状态。消息业务序号的连续性要贯通整条链路。如果业务序号是在采集层生成的那么后续所有副本都要沿用这个序号不能内部再生成一套新序号。否则一旦做链路切换客户端会因为序号混乱而无法判断自己缺了多少数据。这一点的坑我在后面的常见问题里还会再提。3. 高可用架构从单点到多活3.1 可用性目标与冗余思路很多小规模行情系统最初都是一个单进程采集线程拉数据内存里存最新快照再用一个WebSocket服务推给客户端。这套方案能撑到几千连接但必然挂。我接手过一个项目一次机房网络抖动导致行情停了五分钟业务方直接炸锅。高可用设计的核心就一个字冗余。进程要有备份数据要有副本链路要有旁路。但冗余不是简单多开几个实例就完事还要考虑多实例之间如何选主、如何同步状态、如何切换。我通常按采集层-计算层-推送层三个层面分别设计冗余策略每一层的故障模型不一样。可用性指标也要提前算清楚。如果业务要求99.99%那么一年停机时间不能超过约52分钟。这52分钟要分摊到计划内维护、故障切换、数据补齐。所以很多实现细节都要为这个预算让路。比如凌晨的版本发布也算停机如果需要做到不停机就必须支持滚动发布和连接优雅迁移。我的经验是先别急着做跨地域多活那是成本极高的事。先把同机房内所有单点干掉做到进程级高可用再考虑同城双活最后才是异地容灾。架构上每前进一步复杂度都成倍增加不是所有业务都值得。3.2 采集层的主备与仲裁采集层是整个系统的入口它一旦故障后续全部断粮。连接数据源的采集模块必须做主备。经典方案是主备两套采集进程同时启动但同一时间只有主进程在接收并转发数据备进程处于热备状态持续检测主进程心跳。当主进程心跳超时备进程自动接管重新订阅数据源并开始推送。这里的难点有两个一是如何判断主进程真的挂了还是只是网络抖动二是如何避免两个进程同时往外推送数据俗称脑裂。为了解决脑裂需要引入一个仲裁机制。小规模团队可以用Redis分布式锁或者ZooKeeper临时节点来做选主。持有锁的节点成为主节点失锁后自动降级。因为Redis会过期网络分区时旧主可能短暂仍在工作所以下游消费端必须对相同序号做幂等去重。另一种更稳的做法是双采集双写。两台采集同时从数据源接收行情同时写入消息总线但各自分配不同的partition范围下游消费者合并时按业务序号去重。这样即使一台采集彻底宕机另一台的数据也不会断切换完全透明。代价是双倍的上游订阅费用和双倍的消息量数据源配额有限时未必走得通。我实际用的方案是主备心跳Redis锁备机平时在待命状态每秒钟检查一次主进程状态。这个方案实现简单切换时最多丢几秒数据配合补数机制可以接受。3.3 推送层的无状态化设计推送层是连接数最多、最容易成为瓶颈的一层。庞大的连接状态如果不设计好想在故障时快速切换几乎不可能。我的原则是推送网关要做到逻辑无状态。所谓逻辑无状态不是说连接不存在而是说每个连接的核心信息比如客户端ID、订阅列表、最后收到序号都能被外部存储重建。当一个节点宕机后客户端重连到另一个节点新节点可以恢复它的订阅关系和补数位点。但严格把所有连接状态都外置到Redis每次消息路都查一次性能会很难看。所以实践中我采用本地缓存为主、外部存储为辅的策略运行中连接状态保存在本地内存以最快速度推送同时把关键位点异步上报到外部存储。节点正常时外部存储只是备份节点宕机时用外部存储恢复会话。为了减小重连后的恢复成本客户端在重连请求里带上自己最后的seq新的接入节点可以快速判断如果本地没有这个连接上下文就从外部存储或消息总线的缓存队列里拉取后续数据。这种方式比全量重建要快很多实测切换时间能控制在几秒内。推送层的容量规划也不能忽略。每增加一万连接至少要多预留1到2GB内存。如果用的技术栈是Java还要给GC留出足够空间否则连接一多JVM就频繁Full GC。3.4 多活集群与数据一致性设计到了更大规模单一机房的吞吐和容灾能力会不够这时候要考虑多活集群。但多活不是简单地部署两套系统真正的难点是怎么让两个机房的数据保持连续一致。行情数据的主链路可以这样设计两个机房各自部署一套采集和推送服务同时从数据源接入行情两套系统产生的消息都进入各自机房的消息总线再通过跨机房协议将消息流双向同步到对方的主题中。消息带上全局唯一的业务序号消费者在处理时主要按序号去重如果发现同一序号出现在两个机房只处理第一次到达的那份。网络抖动会导致跨机房同步延迟所以跨机房链路最好采用数据压缩和批量传输降低专线占用。如果业务允许可以设定一个容忍延迟窗口比如收到消息后最多等20毫秒再处理以等待另一侧消息到达从而减少重复处理。但这个延迟会增加端到端耗时需要业务方共同评估。多活最怕的是网络分区。两个机房之间断连两边都在继续生成行情下游就会看到两份重叠的流。我处理这类问题的思路是强制角色划分即使叫多活在同一时刻仍然只有一个机房承担主生产角色另一个机房处于热备消费状态只在主角色故障时才激活。这样做虽然损失了部分多活意义但换来了最大程度的可控性。3.5 故障切换不能靠玄学可观测与演练高可用不是靠到时候再处理撑起来的。没有一套完整的可观测体系故障发生在哪一层你都说不出来。监控指标至少要覆盖这些数据源连接状态、单位时间收到的行情条数、消息序号缺口、采集层到总线延迟、总线消费积压、推送网关连接数、推送延迟、客户端断线重连次数。每一项都要有阈值和告警。我建议重点盯两个综合指标一是端到端延迟分布特别是P99和P999因为平均延迟永远好看毛刺都藏在尾延迟里二是单条链路上业务序号的连续性只要出现缺口就意味着数据丢失或乱序必须立刻告警。有了监控还要定期做故障演练。我最常做的演练包括杀掉主采集进程、模拟数据源断连、拔掉跨机房专线、把推送网关的机器直接重启。演练不只是验证组件能切换更重要的是验证人的操作手册不依赖当时的记忆。每次演练后都要复盘切换时间、影响范围以及是否存在脚本能跑但没人敢执行的情况。在故障切换这件事上我还有一个体会宁可自动化流程慢一点也要稳。完全自动切换在极端情况下可能反复抖动反而把系统搞乱。我们线上采取的是自动检测、半自动切换策略系统监测到异常后自动告警并生成切换建议值班人员确认后再触发切换脚本。这样既保留了速度也给人留了判断空间。4. 数据源选型决定整个系统上限的环节4.1 数据源类型与优劣对比行情系统里最难替换的往往不是代码而是数据源。代码写得再差还能重构数据源一旦不符合需求整个系统的性能上限就被卡死了。常见的数据源大概分四类。第一类是官方行情接口权威性最高数据质量最有保障但通常存在配额限制、连接数限制且不一定能提供细粒度的逐笔数据。第二类是专业数据服务商能提供全市场、多品种的聚合行情协议也相对友好但费用不低且不同服务商之间的数据质量和延迟差异很大。第三类是第三方聚合接口接入简单适合快速验证但延迟通常偏高数据细节可能被简化不适合高精度场景。第四类是自建的行情采集节点通过合法订阅或公共数据源必须有合法授权自行采集并清洗灵活性最高但自己承担全部运维成本和合规风险。无论选哪一种我都要强调一句必须确认数据授权范围。不要为了省成本去接入来路不明的数据也不要超过授权的使用范围把数据二次分发。这个层面一旦出问题不是技术能解决的。数据源类型延迟数据质量成本接入复杂度适用场景官方接口低高中高中核心交易场景专业服务商中低高高中多市场聚合、机构业务第三方聚合中高中低低快速原型、展示页自建采集低可控中高深度定制、特殊策略我的建议是生产环境至少保证一个官方或专业级别的数据源作为主源再配一个不同来源的备源。两个源不能是同一个上游厂商的同一套接口否则上游一出事两个源一起断。4.2 数据源评估的三个维度数据源选型不能只看一张宣传页必须拿实际数据去测。我评估一个数据源时基本只看三个维度。第一个维度是延迟。要分别测量接入延迟和更新频率。接入延迟指的是从源数据产生时刻到我们的系统收到数据时刻的时间差需要在消息里带上数据源时间戳用本地时间减一下就能估算。更新频率也很关键有些服务商宣称实时推送实际只在秒级聚合后推送完全达不到逐笔标准。测试时要看行情高峰期每秒能推多少条以及单条消息的最大间隔。第二个维度是数据质量。我连续一周按天统计数据源的缺口数、乱序数、异常值数。数据缺口是指业务序号不连续乱序是后到的消息序号反而小异常值是指明显突破市场合理范围的价格或成交量。质量差的数据源即使延迟低也会让下游系统做很多额外的清洗工作整体算下来反而更贵。第三个维度是服务质量。需要关注对方的SLA承诺、工单响应时间、定期维护窗口是否频繁、是否有沙箱环境可以测试。我吃过一次亏某数据源每周三凌晨维护半小时恰好是我做夜间压测的时间一测就断流后来才在文档深处找到维护计划。这三个维度可以量化为一个选型评分表延迟占40%数据质量占40%服务质量占20%。连续测试一周之后把每天的最大延迟、平均缺口数、故障次数加权基本就能筛出靠谱的数据源。4.3 多数据源冗余与交叉校验主源和备源选定之后不能只是主源挂了人工切换那样太慢。更稳的做法是让备源在后台持续接收数据虽然不下发到生产链路但一直运行着这样切换时才能保证数据连续性。交叉校验是双源方案的核心。我日常会校验三组指标同一标的最新成交价偏差是否超过阈值通常设置为合理价格区间的千分之一到千分之二同一时间窗口内的成交量和累计成交笔数是否在合理差异范围内消息序号是否持续增长有没有长时间没有新序号的情况。一旦校验失败不能立刻切换主备。系统要先判断是自己网络的问题还是数据源的问题。常见做法是连续三次采样失败、且持续时间达到预先设定的阈值才触发自动切换。切换后还要持续观察备源的数据质量如果备源数据同样异常就要停止切换并进入全链路告警。切换后如何回切也需要设计。我建议不要自动回切而是由运维人员确认主源恢复稳定后在低峰期手动执行回切。原因很简单主源刚恢复时可能还会抖动自动回切容易造成反复切换比一直用备源更危险。数据源这块我还要提醒一个容易被忽略的细节即使有主备双源也一定要保留一条直连主源的旁路通道。这条路不参与正常生产只用于排障。有一次线上数据异常所有监控都在告警但完全不知道问题出在数据源还是自己的解析层。我直接启动旁路脚本在接入层之前单独打印原始报文一对比就确认是数据源解析方式变了。这个旁路通道关键时刻能救命。5. 实战落地一套可运行的行情系统骨架5.1 核心组件与技术选型讲完概念来看一个实际可落地的系统骨架。我以一个中等规模的行情系统为例目标支撑10万连接、单日处理消息量10亿条左右。接入采集层连接数据源的模块用Go或Java皆可。Go在并发连接和内存控制上更有优势Java的生态和排查工具更成熟。我们这边用的是Java基于Netty处理二进制协议线程模型简单压测表现稳定。消息总线层选用Kafka或Pulsar。Kafka吞吐高、生态好Pulsar的存算分离和多租户特性在多人共享集群时更友好。我们当时Kafka已经在线运行稳定所以继续用Kafka。关键配置是把行情主题的分区数设置得足够大建议至少和下游消费者实例数一致否则会出现某个消费者热点。实时计算层如果涉及快照聚合、多源合并、指标计算可以用流处理框架。简单场景直接写消费者程序也行关键处理好幂等和乱序。不建议为了用框架而上框架行情链路每一跳都会增加延迟。推送网关层独立部署一组无状态服务对外提供WebSocket接入。常见方案是基于Netty或Go原生库做推送网关单机容量取决于连接数和消息量10万连接至少准备5到8个节点。缓存和存储层最新快照放Redis命中率高设置过期时间比如10分钟历史行情按时序数据库存储供查询和复盘。如果查询量不大也可以用ClickHouse做批量导入查询成本更低。监控告警层Prometheus采集指标Grafana做看板配合Alertmanager发告警。日志统一走集中式收集避免故障时逐台机器翻日志。核心组件清单如下数据源接入Netty 自研协议解析消息总线Kafka分区数由下游消费实例决定实时计算轻量消费者程序或流处理框架推送网关Netty WebSocket服务状态存储Redis会话备份、最新快照历史存储时序数据库或ClickHouse监控Prometheus Grafana Alertmanager5.2 关键配置与参数参考很多细节问题不是架构问题而是参数没调对。我整理了线上稳定运行的一组参考配置不一定对所有场景通用但可以作为起点。TCP参数方面服务端建议开启TCP_NODELAY禁用Nagle算法避免小消息被延迟合并。接收和发送缓冲区可以适当加大我一般设置SO_RCVBUF为4MB、SO_SNDBUF为4MB减少网络吞吐瓶颈。Linux内核层面的文件描述符和连接队列参数也要同步调大具体数值取决于压测结果。WebSocket心跳间隔设为30秒服务端在连续60秒内没有收到客户端任何报文时判定超时并断开。客户端如果连续90秒没有收到服务端心跳主动重连。这样重连频率不会太高也能及时发现死连接。Kafka生产端设置acksall保证消息写入多副本后才返回避免单副本故障丢数据。消费端要开启手动提交offset并确保业务处理成功后再提交。行情场景下宁可偶发重复消费也不能丢消息所以消费幂等由业务序号去重兜底。Java服务端重点调GC参数。消息量大的时候对象创建非常频繁我建议使用G1收集器并适当增大年轻代和堆内存。更激进的做法是使用堆外内存、对象池减少GC压力。有一版系统在推送高峰期频繁Full GC我们把Netty消息体改成堆外ByteBuf并启用对象池后GC停顿明显下降。监控阈值参考端到端延迟P99超过100毫秒告警Kafka消费积压超过几千条告警业务序号每分钟缺口大于0告警推送网关单节点CPU使用率超过80%持续5分钟告警。5.3 端到端延迟优化实战延迟优化这件事最怕没有量化目标。我习惯把延迟拆成几段数据源到接入节点、接入节点到消息总线、消息总线到消费端、消费端到推送网关、推送网关到客户端。每一段都单独探针打点用日志或者指标记录下来。优化优先级也很明确。第一优先处理网络传输减少跨机房跳数、减少不必要的网络转发。如果主链路在同机房数据源接入和推送网关尽量放在同一可用区这样RTT可以控制在1毫秒以内。第二优先处理序列化和反序列化用更紧凑的编码替代JSON。第三优先处理线程模型和阻塞点杜绝锁竞争、杜绝在IO线程里做耗时操作。还有一个很容易被忽略的点对象创建和垃圾回收。如果消息处理过程中创建了大量临时对象GC会周期性抢占用CPU导致延迟毛刺。解决方案是使用对象池、复用消息容器、尽量使用基本类型数组代替包装类。批量处理也能明显降低延迟。消息总线到推送网关之间如果每条消息都单独消费、单独推送网络包很小系统调用开销占比会很高。我们采用批量拉取、批量推送的策略单批处理几十到几百条消息后再统一网络发送整体吞吐能提升一个量级。但批量不能无限大否则单条消息的等待时间会拉长需要压测找到平衡点。压测方法上建议先做单机压测再做全链路压测。单机压测时重点观察不同并发连接数下的P99延迟和吞吐全链路压测时重点观察端到端延迟在峰值消息量下是否仍能达标。压测数据一定要接近真实行情不能只推固定频率的模拟消息。5.4 上线前检查清单行情系统上线前的检查比普通业务系统要多得多。我每一条都吃过亏列出来供参考。第一功能验证消息字段解析是否与数据源文档一致快照更新是否及时逐笔成交是否有序客户端重连后补数是否准确。第二性能压测按预估峰值的1.5到2倍做压测持续至少30分钟观察延迟、吞吐、连接数、CPU、内存和GC情况。第三容灾演练至少演练主采集宕机、数据源断连、推送网关宕机三个基础场景确认切换耗时和数据缺口是否符合预期。第四监控告警验证确认告警能正常发出而不是哑弹确认值班人员知道如何响应最好把排查手册和切换脚本放到统一位置。第五回滚方案推送网关升级时老版本是否还能快速恢复如果数据库结构有变更是否有兼容旧版本的回滚脚本。风险控制上我建议上线前三天做一轮小流量灰度。选择一小部分用户连到新集群观察延迟和错误率稳定后再全量切换。行情系统影响面大宁可多花一天灰度也不要上线后所有人一起卡顿。6. 常见问题排查与避坑实录6.1 连接频繁断开客户端一直在重连这个现象很经典。客户端没有主动断但服务端连接几分钟就消失一次全网大量重连。排查时先看网关的连接日志确认是服务端主动关闭还是客户端关闭还是中间网络设备悄悄掐断。最常见的原因是服务端心跳超时判断太短。如果客户端网络环境是弱网或者经过移动网络一个Ping发出去可能要一两秒才能回来心跳超时设置成5秒就会出现大量误判。我们把服务端判定超时的时间从10秒调整到60秒后断开率明显下降。还有一种情况是客户端和服务端之间经过了一些空闲连接回收策略不合理的中间网络设备。连接长时间没有数据就会被认为是空闲连接被清理即使有WebSocket心跳也可能被忽略。解决方式是缩短心跳间隔到15到20秒同时客户端针对连接断开做指数退避重连避免一旦断开所有客户端同时重连压垮网关。6.2 行情数据有缺口和乱序数据缺口通常表现为某个标的的行情突然停更几秒恢复后价格跳变。乱序则表现为新消息的价格落后于之前已收到的消息。这两者都会严重干扰量化策略。排查时先定位缺口出现在哪一段链路。在接入节点、消息总线、推送网关分别检查业务序号看从哪一段开始不连续。如果接入节点收到的序列就缺基本上是数据源或网络抓包环节的问题如果接入节点完整但总线消费端缺失就是消费端处理逻辑或offset提交失误如果总线完整但推送网关缺多半是内存缓存淘汰策略把数据丢了。乱序的产生主要有两个原因一是多数据源同时接入时各源的时间戳和序号体系不一致下游合并时没有按统一业务序号排序二是消息总线内分区分配不合理同一标的的行情被路由到了不同分区消费时无法保证全局有序。解决思路是给每个标的绑定固定分区同时在消费端做序号校验发现乱序先缓存等待超时后再丢弃或补拉。6.3 多数据源相互打架接入双源之后最头疼的问题是主备两个源给的行情不完全一致。价格差几个tick、成交量差一点、快照时间各说各话交叉校验一直报警。这不是系统bug而是不同数据源之间天然存在采样时点和聚合逻辑的差异。有些数据源推送的是最新成交有些推送的是基于订单簿计算的理论价有些成交量按笔数统计有些按股数统计。要解决这个问题不能只比最终数值而要明确各自的字段语义再在做比较前进行口径统一。对于确实应该一致的核心字段比如成交价和成交时间如果偏差超过阈值我建议按来得更快且与历史序列更连续的源作为优先值另一个源进入告警观察。不要试图在逻辑里动态切换每个字段很容易把自己绕晕。6.4 行情高峰期的短暂卡顿每到行情剧烈波动的时刻系统就出现几十毫秒甚至几百毫秒的卡顿但平时完全正常。这种问题往往是某个组件在流量上涨后到达临界点。最典型的元凶是JVM GC。积累了大量临时对象后Full GC会暂停所有业务线程直接表现为推送延迟骤增。排查时看GC日志和JVM监控如果确认是GC问题按前面说的调整堆内存、启用对象池、改用堆外内存通常能得到改善。另一个元凶是消息总线的消费端处理不过来。行情一波动积压立刻上涨消费端如果还在逐条解析和推送延迟自然越来越高。解决方式是提高批量拉取能力、增加消费者实例、把非核心逻辑异步化。还有一种情况是网络入向流量打满。一旦某个数据源在峰值时推高频率的大量行情单机网卡会被占满其他消息也会被堵住。排查时需要看网卡丢包和软中断占用必要时给接入层单独划分机器避免与推送层共用网络资源。问题现象可能原因排查步骤解决方法连接频繁断开心跳超时过短、中间设备回收连接查看双向关闭日志、测试心跳RTT调整心跳间隔、客户端退避重连数据缺口/乱序链路任一环节丢数据、分区路由不合理分段检查业务序号绑定分区、消费端序号校验双源价格不一致字段口径不同、采样时点不同对比原始报文字段语义统一字段口径、按源优先级处理高峰期卡顿JVM GC、消费积压、网卡打满看GC日志、消费积压、网卡丢包调参、增加实例、网络隔离6.5 数据源连接串流导致的重连风暴最后一个值得单独说的坑是数据源偶发断开时如果没有控制重连频率会在短时间内把所有采集节点的连接请求同时砸向数据源。数据源本身可能还处于不健康状态被这一波重连请求一冲直接拒绝服务形成重连风暴。处理方案是给重连加上退避策略。第一次重连等1秒第二次等2秒之后按指数退避上限到60秒。同时把限流逻辑做在采集层无论数据源多着急同一分钟内重连次数不能超过阈值。这样做虽然会让恢复时间变长十几秒但能保证数据源不会因为我们的重连而彻底不可用。另外重连成功后不要立刻全量订阅所有标的可以先订阅一小部分做健康检查确认数据源恢复正常后再订阅全量。这个动作能防止数据源刚恢复时被瞬间顶到高负载再次宕机。7. 一些真实体会与建议行情系统做久了我有一个很深的体会技术方案再漂亮也比不上对真实链路细节的把握。你提前想到了消息切面、双源切换、心跳超时系统就能多一分稳定你漏掉一个GC参数、一个序列号校验、一次维护窗口线上就会在某个深夜教你怎么做人。如果只能给一条建议我建议先把可观测性放到最高优先级。没有完整的指标和日志一切高可用设计都是盲人摸象。我见过太多团队架构图画得很完整线上出问题时却连数据在哪个环节丢了都不知道。最后再分享一个小技巧在行情系统正式上线后保留一个从数据源直连到独立脚本的旁路通道不参与生产链路只用于排障和对比。这个通道看起来浪费资源但在你面对异常数据、怀疑某个环节出了问题时它是最高效的定位工具。我靠这条路解决过至少三次疑难杂症。行情系统是一个持续演进的过程不用急着一步到位。先把数据接进来、推出去再把可靠性做扎实最后根据业务需要逐步完善多源和多活能力。每一步走稳了系统自然会越来越强。