资讯动态

高并发采集系统架构设计评审全复盘:从流量预估到调度监控实战

发布时间:2026/10/19 15:00:00 来源:尧图企业网站定制
1. 评审现场还原这是个什么系统为什么要评审前阵子团队内部做了一次高并发采集系统的架构设计评审我作为负责核心链路的开发者把整轮评审过程完整复盘一遍。这次评审不是在会议室里轮流念PPT而是把设计稿、压测数据、流量预估摊开在桌面上逐条过质疑。这种方式的收获是文档里学不到的。先说业务背景。系统要支撑的业务方在做一个多源信息聚合项目需要从互联网公开渠道持续采集商品信息、资讯内容、公开价格数据等数据量级不是几千条而是每天数千万条增量。采集源覆盖数百个不同的数据站点每个站点的接口形态、频率限制、返回结构各不相同。业务方给的目标很朴素稳定跑别崩数据别丢上游峰值来了扛得住。最初团队的演进路径是脚本定时抓取 - 单机多线程采集 - 分布式任务拆分消息队列缓冲。这次评审的标的就是第三阶段的设计方案。但真正推进时我意识到高并发采集系统和其他后端系统的最大差异在于它不只是一套请求处理框架而是一个集调度、抓取、解析、清洗、存储、监控于一体的数据管道。为什么要专门做评审因为踩过太多次坑。上一版单机方案在流量高峰期出现过任务积压、数据库连接被占满、目标站点超时重试形成恶性循环。这些教训告诉我们设计稿阶段多花三天讨论比上线后花三周救火划算得多。评审的讨论范围我提前框定在五条核心主线上任务调度层数千万URL如何拆分、分配、重试抓取执行层并发模型怎么选连接怎么复用消息缓冲与削峰引入中间件到底解决什么问题数据链路完整性如何保证不重、不漏、不阻塞监控与快速定位手段出问题时怎么快速找到病灶下面按评审当天的讨论顺序逐块复盘包括现场提出的问题、我们给出的理由、以及哪些设计后来走了弯路。2. 需求拆解与流量预估先算清楚一小时要扛多少评审的第一个环节不是画架构图而是重新核算需求数字。很多设计失败根源在于连自己要处理多大流量都没算对。这个环节我坚持让团队所有人动手算一遍不允许只看别人给出的结论。2.1 从日增量反推峰值QPS业务方给的目标很粗日新增目标数据量是2000万条左右。但采集任务不等于数据条数一个页面可能包含几十条数据一次详情页请求只能拿到一条商品信息。所以实际的任务量需要先放大若干倍。我要求把链路拆为三层分别估算列表页/索引页请求用于发现新链接假设每日需要遍历50万个列表页详情页请求假设每日需要抓取800万个详情页考虑增量去重后特殊接口调用比如搜索接口、翻页接口每日约200万次三层相加日请求总量约1050万次。一天按10小时有效抓取时间计算需要预留低峰时段给数据源喘息平均QPS大约是300。但采集系统最怕的不是平均而是峰值。评审现场我直接抛出一个问题如果上游数据源集中放量或者业务方临时加急任务瞬时QPS到多少会压垮现有设计我们根据历史数据波动规律取平均值的5倍作为设计目标也就是峰值QPS按1500来定。这个数字不算激进但已经足够暴露很多设计问题——比如单机线程池模型在这个量级下必然会出现线程频繁切换和超时堆积。2.2 带宽与存储成本也要算进架构里详情页平均大小按300KB估算800万详情页一天就是2.4TB原始下载量。如果全部落盘保留一个月就是72TB。这个量级直接否掉了单机加本地磁盘的方案也让“先存原始HTML再慢慢解析”的策略显得不再理所当然——你不可能无限存下去。所以评审中大家达成的共识是原始响应不长期保留只保留结构化字段和必要的元信息原始页面的本地缓存设置过期时间保留7天后自动清理。带宽方面按峰值1500 QPS、平均300KB响应计算瞬时带宽需求约为432MB/s。这是一个很现实的上限约束即时应用层能扛住带宽也可能成为瓶颈。所以设计上加了一层gzip压缩传输和字段裁剪接口目的就是省流量。2.3 需求拆解后浮现的三个关键问题数字摆出来后设计稿中的问题立刻暴露出来。第一原设计想对所有数据源用同一套抓取参数完全不现实。有的源允许每秒5个请求有的源每秒0.5个请求都危险。必须按数据源维度做隔离和差异化配置。第二日增量2000万条写入数据库单表直接写是撑不住的。设计稿里没有说明分库分表方案或写路径优化评审中补上了按来源和日期双维度分表的结构设计。第三原设计用固定频率轮询所有URL忽略了“数据源更新时间规律不同”这件事。有的源每小时更新一次有的每日凌晨更新统一轮询会浪费大量请求配额。后来在调度层加入了按源分类的更新周期配置这项改动带来的请求量下降接近30%。3. 任务调度与抓取执行并发不是越高越好评审讨论最久的就是调度层和执行层的设计。这也是采集系统架构里最容易出问题的地方我见过不少系统把调度做成简单的“一堆线程去拉URL”拉完就扔下一条毫无状态管理可言。3.1 分布式调度把URL变成可跟踪的任务我们的任务来源大致有三类初始导入的历史URL、新发现页面的URL、定时重抓的旧URL。如果设计稿里只写“从数据库取URL分配给worker”评审一定不会通过。因为这样的方案完全无法回答几个问题:哪些URL抓过了哪些失败了哪些在重试队列里哪些源在限流中评审确定的最终方案是引入任务状态机每个URL请求封装为采集任务状态流转为待调度 - 已分配 - 抓取中 - 成功/失败/重试待定。任务状态保存在缓存中加上数据库持久化调度器每轮扫描待调度任务并批量分发。任务拆分粒度上我们不按URL一条条分而是按“批次”分每个批次包含同一数据源下的一组URL批次内串行批次间并发。这个设计的理由很简单同一数据源需要受同一个限流器的控制如果把URL散到不同worker上无法做精确的源级限流。批次大小按源的平均响应速度和单次请求耗时配置快的源批次放大到100条慢的源批次缩到10条。3.2 抓取执行层的并发模型选择评审现场对并发模型做了两轮激烈讨论。第一版设计是用线程池每worker固定开200个线程线程里跑阻塞式HTTP请求。这个方案在平均QPS 300时问题不大但到了1500峰值就会出现明显的线程颠簸大量线程阻塞在等待响应CPU没跑满但线程数爆了切换开销巨大。第二版设计是用异步非阻塞模型基于事件循环和连接池复用。对比测试的数据是同样单机配置线程池方案稳定支撑约200 QPS异步方案可以支撑800 QPS以上且CPU占用更低。最终评审选择的方案是异步非阻塞为主体对少数不支持长连接的源保留线程池兼容。我在这里想提醒一点。选异步模型不等于代码更复杂就一定是好事采集系统的耗时大头在网络IO和解析不是CPU计算。所以异步改造的收益高但前提是必须引入带超时控制的连接池否则一个连接卡死会把事件循环拖垮。3.3 抓取执行层的八项注意清单评审过程中我们整理了一份执行层的设计检查清单这里完整分享给要做类似系统的读者每个数据源一个独立的限流计数器不允许全局共享限流HTTP客户端必须配置连接超时、读取超时、总超时三项缺一不可连接池大小不能超过源允许的并发上限否则池形同虚设重试逻辑必须退避禁止失败后立即重试防止重试风暴响应内容必须做大小上限限制防止异常大响应拖垮内存请求头尽量伪装成正常浏览器形态动态生成User-Agent同一数据源的请求间隔必须恒定随机抖动取正负20%每次请求必须带上业务追踪ID便于回调日志排查第6条是经验之谈。很多新手设计的采集系统一眼就被数据源识别为机器流量因为所有请求的User-Agent一模一样甚至还有明显的Python默认头。虽然不是做恶意对抗但基本的风控意识还是要有的。3.4 任务分配策略的坑什么是“有状态调度”评审中最容易被忽视的一个点是调度器本身的状态。如果调度器是无状态的任务就无法感知worker的实时负载分配出去的请求可能会集中打在已经过载的worker上。我们最终采用了带权重的一致性分配策略每个worker在启动时注册自身信息定时上报当前队列深度和延迟指标调度器根据这些指标动态调整分配权重。这个设计避免了早期版本中“任务都堆在一个worker上其他worker空转”的尴尬局面。另一个偷懒不得的地方是任务的优先级。业务方偶尔会提交加急采集任务这类任务的URL必须比定时任务优先处理。调度器需要额外支持优先级标签调度时优先扫描高优先级队列。这个功能虽然简单但评审前没有设计进去差点漏掉。4. 消息缓冲与削峰什么时候真正需要队列评审初期设计稿里就写了要引入消息队列理由是“高并发必须用队列”。我当场否了这个理由——不能因为大家都在用队列就机械地把队列塞进系统里。先必须明确这里用队列到底要解决什么问题。4.1 削峰填谷还是解耦得先说清楚采集系统的核心链路是调度器发任务 - worker抓取 - 解析 - 写入存储。如果目标源返回速度平稳完全可以不用队列worker直连写入即可。但现实是数据源响应时间波动很大有的源高峰期会突然变慢下游写入如果跟着波动就会打满数据库连接。队列在这里的核心作用有两个。第一把解析后的数据结构化消息暂存下游消费者按自己的节奏入库避免数据库被瞬时写流量冲垮。第二把抓取和解析解耦抓取层不需要等解析完成才能继续抓下一个任务两者并行推进整体吞吐量能提升40%以上。评审结论是队列必须用在解析后到入库之间的链路而不是用在抓取前。把任务先放进队列再取出调度多此一举还会增加链路延迟和出错概率。4.2 队列选型到底选Kafka还是别的队列选型也讨论了很久。我们的场景特点是消息量日均千万级吞吐要求高但每个消费者只处理本数据源的消息不需要复杂的按业务字段路由按数据源分Topic即可消费延迟允许秒级。对比下来Kafka是最合适的。它吞吐量高分区机制天然支持不同数据源独立消费进度而且重放消息方便出了问题可以从最后消费位点拉回重处理。团队内部也有人提出可以用内存队列或Redis列表来当缓冲但考虑到消息落盘、持久化和消费位点管理最终没有采纳。这里有一个成本权衡要说清楚Kafka带来的运维复杂度高于Redis列表但它能保证消息不丢而采集系统最怕的就是数据静默丢失。数据丢了业务方发现时已经晚了相比起多维护一套中间件数据可靠性显然重要得多。4.3 消息格式与后续扩展性消息体不直接存原始响应而是存“已经结构化”的JSON数据附带数据源标识、采集时间、原始URL等元信息。这么设计的好处是消费者只依赖数据格式不依赖具体抓取逻辑。后续即使要加新的数据源只需要在解析层输出同一套消息结构下游不用改动。消息里还额外带了一个 “数据指纹”字段。对采集到的内容做哈希消费者入库前比对指纹如果重复就直接跳过。这个字段是评审时后加的但正是它保证了整个系统的幂等性——重放消息、重复发送都不会导致脏数据。5. 存储设计与数据完整性不重不漏是底线数据完整性是评审的底线话题。采集系统的价值归根结底是数据如果数据丢了、重复了、错乱了前面的高并发设计再漂亮也没意义。5.1 写入路径的分层设计存储层我们设计了写入缓冲区 批量入库 异步补偿三条路径。抓取解析后的结构化消息先写入本地缓冲队列攒够大小或超时后批量提交数据库。批量写比单条写的吞吐量高一个量级这是数据库写入性能差距最明显的一个操作。数据库采用分库分表策略。按数据源ID做水平分表每个表再按日期分区。查询侧主要按数据源和时间范围进行这样分表后能保证绝大多数查询只命中一个分区不需要跨库扫描。某源的数据急增也不会挤占其他源的空间和性能。上线前我们做过一次压测批量入库的TPS可以稳定到每秒3000以上单表而逐条插入只有每秒300多差距接近十倍。这个数据说明设计方向是对的。5.2 重试与补偿失败任务的保险丝采集任务失败不可避免网络抖动、目标站点临时升级、响应格式改版都会碰到。所以重试机制必须放进架构里而不是靠人为干预。我们设计了三级重试。第一级是应用层重试仅针对连接超时和响应超时最多重试两次每次退避1秒和3秒。第二级是任务调度层重试如果一个批次内失败率高于50%视为任务异常转入延迟队列15分钟后重新调度。第三级是离线补偿每天凌晨扫描“长时间未完成”的任务清单重新生成调度任务。三级策略制定时讨论最多的不是技术选型而是“什么情况不该重试”。最终定下的规则是HTTP 4xx状态码不重试目标明确拒绝、数据源返回内容格式异常不重试重试也会一样、被限流明确提示不重试等待下轮调度即可。只有5xx和超时类错误才进入重试逻辑。这条规则避免了一次大面积故障。某目标站点升级了页面结构所有请求都返回200但HTML结构变了解析层全部报错。还好当时格式异常被定义为不重试否则整个队列都会被重试消息占满殃及其他数据源的正常任务。5.3 数据校验与质量监控数据入库后不代表完事了完整性校验必须配套。我们做了三类校验任务数量校验每天统计每个数据源计划采集量与实际入库量偏差超过5%触发告警时效校验记录每个数据源最后一次成功采集的时间超过设定阈值即刻标记异常字段完整性校验抽样检查入库数据的必需字段是否都有值缺失率超过2%即告警这三类校验虽然会给系统增加一些额外开销但带来的收益是决定性的——所有数据问题都能在1小时之内被发现而不是等业务方反馈后才知道数据断了。6. 监控告警与故障排查最后一道防线高并发系统没有监控就等于蒙眼开车。这次评审花了整整一个章节讨论监控指标和告警阈值因为采集系统有个特点多数故障不是瞬间崩溃而是性能劣化逐步逼近临界点。6.1 必须盯住的七类指标评审确定的核心监控指标分为七类这是从故障教训里提炼出来的调度滞后数等待中任务数量持续上涨说明上游生产速度高于消费速度抓取成功率低于98%立即告警低于90%属于严重故障平均响应时间基线值上涨30%就要关注翻倍必须介入队列积压量消息积压超过阈值且持续增长说明下游在告警入库TPS与抓取TPS趋势对比偏差变大说明链路某个环节断裂重试比例超过5%意味着目标源或网络出现异常任务完成延迟从任务生成到数据入库的耗时能直观反映管道健康状况监控不只是看看图表。我还给团队定了告警分级规则P0级数据完全中断、P1级链路部分失效、P2级性能劣化、P3级潜在风险。告警阈值宁可误报也不漏报误报顶多费几分钟查看漏报可能造成数小时数据空洞。6.2 典型故障排查路径评审阶段我们头脑风暴了四个最可能发生的故障场景提前写好排查路径。场景一抓取成功率突然掉到80%以下。排查顺序是先看目标源出口状态再看本地连接池和DNS解析然后是限流配置是否误改。很多次出问题时发现是某个数据源临时封了IP段而不是整个系统故障。场景二队列积压快速上涨但抓取TPS正常。这种情况通常是入库链路卡住第一时间看数据库慢查询和连接数其次看消费者是否被某条坏消息阻塞。曾经遇到过一次一条超大字段数据导致消费者解析卡死整个消费组进度停滞。场景三部分worker频繁超时重启。第一反应不是看应用日志而是看宿主机负载和带宽占用。采集系统是带宽敏感型应用经常出现带宽被打满导致所有worker请求超时的情况。场景四数据入库量比预期少但无告警。这是最难排查的一类最后发现是某个数据源结构改动解析不到数据但HTTP状态码正常都返回200。这也再次印证了前面说的“格式异常不重试 时效校验”的必要性。6.3 线上问题排查实用工具和技巧除了监控系统本身我再分享几个排查中用着顺手的实用技巧。日志必须带上下文。每个请求日志要包含数据源ID、任务ID、批次号、URL哈希、耗时、状态码、错误详情。没有上下文关联的日志在排查时价值很低看不出调用链关系。加一个“链路追踪中间页”。我们给每个数据源都维护了一个最近N次请求的状态记录页直接在管理后台能看到每个worker最近处理的100条请求的状态分布。这个页面看起来简单但排查问题时效率极高——不用再翻海量日志找特定时间点。保留“现场快照”机制。当系统检测到异常波动时自动捕获当前活跃任务、连接池状态、队列长度、最近100条错误日志存入独立存储。事后复盘直接看快照就行不用猜测故障发生时刻的状态。6.4 巡检脚本与日常检查清单监控告警是出了问题才知道巡检是防患于未然。团队每周跑一次巡检脚本检查项包括各数据源近7天抓取成功率变化任务队列积压是否有周期性上涨数据库表增长速率和磁盘空间预估各worker的负载均衡程度确认没有热点重试次数分布是否有个别数据源反复触发重试其中第五项特别重要。某个源反复触发重试往往意味着该源在悄悄改版或者风控收紧等到完全抓不动再处理就晚了。7. 评审会上的分歧与最终决策记录评审过程中不是所有问题都顺利达成一致记录几个有代表性的分歧点这些也是架构设计中最容易反复的地方。7.1 高并发执行框架自研还是基于成熟组件有成员提出直接用成熟的采集框架来省时间理由是社区生态完善、上手快。我们评估后否决了这个方案。核心原因是通用采集框架很难做到“按数据源精细化限流”和“动态优先级调度”强行改造框架的成本远高于在自有轻量框架上补充功能。而且团队对这个框架不熟出了问题排查成本极高。最终选择是自研一个精简执行内核只保留调度、执行、重试三块基础能力外部对接已有的监控和配置体系。自研带来的额外负担是必须自己维护和压测但换来的是对整个链路每个细节都心里有数。这个取舍我认为值得。7.2 采集到的数据先清洗还是先入库常规思维是先清洗再入库保证入库数据质量。但我们反了过来原始数据先入临时表异步清洗后再入正式表。理由是解析和清洗环节可能失败如果清洗永远不通过原始数据就没了。按我们先入临时表的方式即使清洗任务挂掉原始数据还在可以随时重新清洗。这个方案带来了一点小代价多一张临时表需要定期清理过期数据。但换来的数据安全性提升是值得的——至少没有遇到过“解析挂了数据跟着没了”的事故。7.3 是否需要专门建一套采集任务管理平台设计稿里本来只有任务配置和worker状态的基础管理功能。评审时业务方提出需要一套独立的任务管理平台让非技术人员能自助提交加急任务、查看任务执行进度、导出采集结果。这个需求也不是伪需求单独提出来是因为它关系到系统的对外能力。最后我们做了一套轻量的管理界面按角色控制权限技术人员能操作全部功能业务人员只能提交加急任务和查看进度。跑了一段时间后发现这个平台的使用频率远超预期业务方自己就能处理很多以前需要找开发的事。8. 实测压测数据与评审后的改进项评审结束后团队按结论改造了原设计随后做了一轮完整的压测验证。压测目标就是验证之前设计的目标峰值1500 QPS下系统的稳定性和资源消耗。8.1 压测方案与关键数据压测环境没有直接连真实数据源而是搭了一套模拟目标服务按真实响应分布返回数据。三个worker节点每节点分配约500 QPS的任务量持续运行30分钟。压测结果系统稳定支撑峰值1500 QPS抓取成功率99.2%平均响应耗时1.8秒p99响应耗时3.5秒。任务积压数维持在极低水平队列消费与生产保持平衡。数据入库端无积压批量写入TPS稳定在4000以上。期间观察到一个小问题是某一瞬间某worker的内存占用飙到高位定位发现是某个大响应页面超过5MB导致的缓冲占用。之后在抓取执行层加大响应大小上限控制问题未再出现。这类边界情况只在压测中能暴露纯靠代码审查很难发现。8.2 评审结论中仍然保留下来的风险项没有完美的架构。评审结束后我们保留了三个已知风险项没有在本次迭代中解决而是记录在案第一单机房部署如果机房发生整体故障系统不可用。多活改造在下个季度规划目前靠手工切流兜底。第二数据完整性校验的时效性不够目前是小时级发现数据异常。理论上可以做到分钟级但会显著增加校验任务的开销性价比不高。第三部分边界数据源每天请求量极小的低频源在系统里占用了将近三分之一的配置项和维护精力但产出只占很小比例长期看需要做一次清理和合并。这三个风险项在评审记录中明确标注了负责人和时间节点避免风险项躺在文档里不了了之。8.3 评审中最值得借鉴的五条经验评审完整跑下来我整理了五条对任何类似系统都有参考价值的经验先算账再画图。把日增量、峰值QPS、带宽、存储算清楚比先画架构图重要得多没有数字支撑的架构设计都是空中楼阁。限流必须是源级细粒度全局限流等于没有限流。不同数据源的容忍度差异太大必须隔离。队列只放在真正需要缓冲的地方不要为了用队列而用队列。多一层队列就多一层延迟和故障点。幂等设计要融入数据源头而不是靠下游去重。靠下游兜底的结果往往是下游被拖垮。监控指标要在设计阶段就定义好而不是上线后再补。没有监控的高并发系统就像没有仪表盘的飞机。9. 落地上线后的一些补充感悟系统上线运行三个月后回头再看这次评审有些判断被验证正确有些地方则有了新的理解。调度层按源配置更新周期这条改动实际效果比预估还好。不仅请求量下降了30%抓取成功率也提升了3个百分点因为低频源的访问频率降下来以后被风控的概率也随之下降。任务优先级功能本来以为使用频率不高结果业务方大量使用。每逢上游有重要活动需要及时采集数据时加急任务就从队列里插到最前面。这个功能如果在评审时被忽略后面要补就会牵涉不少调度逻辑改动。批量入库策略上线初期遇到一个问题写入缓冲区设置的阈值和超时时间不合理导致低峰期数据迟迟不入库。后来改为“每满1000条或间隔5秒批量提交一次”的双条件触发问题就解决了。这类参数调优就是单纯的经验活压测数据只能给出一个初始值最终还是要通过线上观察来微调。监控告警分级收到过一些“狼来了”的体验。初期P1级别的告警误报率比较高团队慢慢习惯了告警就变得麻木。后来把误报率高的几条规则重新校准了阈值并且对告警加上了“持续时间超过3分钟才真正触发”的延时逻辑误报率大幅下降。这让我重新认识到告警设计不是越灵敏越好在灵敏和准确之间找平衡点才是关键。最后把数据完整性校验单独再强调一遍。采集系统里数据不丢比单次抓得快要重要得多。宁可整体速度稍微慢一点也要保证每条数据都有唯一的轨迹可追踪、可对账。如果你也在设计类似系统这句话值得贴在最显眼的地方提醒自己。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑