做后台系统十多年我自己参与过的架构设计评审比写过的代码还值得回味。上周这场主题是一个高并发采集系统的正式评审过程比预想中更磨人。系统已经在线跑了三个月团队开头还能靠加机器应付流量增长后来发现机器越加越多延迟和重复数据却一起往上走这才意识到需要做一次完整的架构评审来定边界现设计到底能不能撑住两倍流量撑不住的话应该先把刀落在哪一块这套采集系统的业务面并不窄高峰时要支撑两千多个数据源的定时任务与实时触发任务按单日千万级任务量来规划沉淀下来的有效数据在每日一千五百万条上下。团队最初的目标是把采集框架标准化新增数据源只需要写一个解析插件调度、限流、重试、入库全部交给底座。这目标听着清晰但评审一开始就发现调度、限流、重试、入库四个词背后全是细节官司。真正有价值的问题在于多数据源并发时怎么控制节奏任务失败后怎么保证不重不漏以及最终数据以什么形态落库才能让下游用得顺手。文章记录的思路就按评审当天的实际推进顺序来。我不会把评审写成流程复述而是把每个环节争执最激烈的地方、被否掉的设计、以及后来补进去的方案都摊开讲给正在做同类采集平台的人一些参考。1. 评审背景一个日千万级采集任务的系统为什么要现在评审1.1 从业务目标推导技术约束评审会上第一个动作不是看架构图而是把业务指标摊到桌面上。团队负责人写了两个字高并发。但高并发这个词在不同系统里的含义差着十万八千里。有人一天一亿请求叫高并发有人一百路并发也叫高并发。我们花了不少时间把业务诉求翻译成技术指标最后形成了一张任务类型表。任务类型数量占比峰值QPS时效要求典型用途定时全量60%1200小时级可接受每日增量更新、历史数据补齐实时触发30%300分钟级用户手动刷新、事件驱动采集失败重试10%200分钟级补偿调度或网络抖动导致失败的任务这个拆分比想象中重要。因为不同任务类型对并发模型的要求是冲突的定时任务希望吞吐优先出错了重新跑一整批就能解决实时任务则要求在短时间内把单条数据送进链路延迟不能超过一分钟失败重试又必须匹配源站的可用性避免在源站抖动时形成重试雪崩。把三种类型混在一起谈高并发任何方案都说不清楚。评审的高优先话题因此定了案调度分发必须控制在百毫秒级别防止任务堆积在入口执行层要做到无状态任何一台工人机宕掉不影响整体进度存储层不能成为写入瓶颈入库吞吐要预留峰值的两倍以上冗余。业务目标倒推下来的这三条约束成了后面每一轮讨论的判据。1.2 评审范围和议程明确了目标下一步是圈定评审范围。这类系统往往牵涉上下游很多服务如果连业务前台和数仓链路一起评审范围太大必然流于形式。我们把范围收缩到采集底座本身从任务调度的入口开始到执行层把数据送入存储之前为止不覆盖下游分析层的业务逻辑。评审议程按四个维度推进架构主链路调度层、执行层、传输层、存储层之间的数据流是否清晰并发机制限速、熔断、重试、降级策略在极端情况下是否闭环数据可靠性任务不丢失、不重复执行、数据不重复入库的保障手段运营保障扩缩容、监控、链路追踪、故障恢复是否足够支撑长期运行。这四个维度不是并列关系而是层层递进链路决定系统能不能跑并发机制决定跑得稳不稳可靠性决定数据能不能信运营保障决定出了问题能不能快速拉回来。评审纪要最终也按这四条线分列了意见避免后面整改时各执一词。2. 核心架构链路拆解任务从入口到落库经过的每一层2.1 四层主链路架构与各自职责采集系统的架构设计如果要画一张简化图基本是调度层—执行层—传输层—存储层四段式。但真正的复杂度藏在每一层内部。我们评审时没有满足于有几层、用什么组件这种介绍而是逐层确认了职责边界和风险点。层级核心职责关键风险评审关注点调度层读取任务配置、生成待执行任务、推进任务状态调度逻辑与执行逻辑耦合状态机转移是否完整可靠执行层执行抓取与解析、上报执行结果节点扩容影响全局限速是否真正无状态、可水平扩展传输层削峰填谷、异步解耦队列积压与消费速率失衡消息堆积时能否保持有序与不丢存储层数据落库、建立索引、供外部读取写入吞吐和索引膨胀冷热数据策略与容量规划是否匹配执行层是整个链路里最需要较真的部分。它负责把任务配置翻译成实际抓取动作再把抓到的内容解析成标准结构。设计上执行层必须完全无状态否则一旦重启内存里留下任务状态就会和数据库不一致。团队当前已经用容器化部署保证无状态这一点在评审中确认通过。但无状态带来的副作用是节点可以随意扩缩扩展时如果不知道全局配额很容易把源站并发打到超限。这个问题在下一节会重点展开。2.2 状态机与回执设计评审中被追问最多的环节画链路图时大家都顺一到状态机环节整个会议室就安静了。因为任务的调度和执行是两个进程在协作状态推进必须靠回执消息。这里最经典的两个风险是消息丢失和消息重复。事件设想这样调度层把任务发给执行层执行层完成抓取后回执成功。如果回执消息在网络传输中丢失调度层永远不会知道任务已经完成了会把它重新发给另一个执行层造成同样的任务被执行两次。反过来如果调度层在发送前崩溃任务发出去了但数据库里状态没更新恢复后又会重新发一次。要解决这个问题不能只盯回执消息本身而得重新定义状态转移的方式。评审会上的明确结论是数据库状态是唯一可信来源消息只当作变更通知不当作事实记录。执行层完成任务后回调接口先把最终状态落库落库成功后再向后唤起后续环节调度层重建任务时必须去数据库查一次最新状态已经完成的任务一律跳过。同时执行结果需要附带完成摘要。所谓摘要就是把这次执行的关键信息做一次签名比如任务的记录数、内容版本的哈希。调度层收到回执后拿着任务标识和摘要去数据库做条件更新。条件更新能够保证并发冲突时只有正确的状态生效而不是两个节点互相覆盖。这一条后来被写进了设计文档成为任务可靠性的基石。3. 评审第一战并发控制应该在哪里做、做到什么程度3.1 单机限速、全局限速与配额代理的实际差别评审会上第一个真正产生分歧的是并发限速放在哪一层。团队当前的做法是每个执行节点上设置单机并发上限以为只要每台机器的并发数不超过某个值就安全。这个设计有很明显的漏洞如果集群有20个节点每个节点并发上限100那整个系统对某一个源站的实际并发就是2000。源站根本扛不住这么高的突发流量轻则限流重则直接封禁整个出口网段。有人提出干脆做全局并发锁所有节点在执行前统一抢锁抢到锁才准发送。这个方案方向对但实现代价很大每次抓取动作前都要做一轮全局协商高频任务下锁服务会变成瓶颈响应延迟也会显著上升。评审结论是这两种都不是最优解应该采用全局配额服务 节点本地上限的双层结构。全局配额服务按数据源维度维护分桶向执行节点发放短时有效的配额令牌执行节点拿到令牌后在本地再做一个轻量信号量控制瞬时并发所有节点在本地排队不再反复请求全局服务。这样可以同时拿到两个优点全局视角下发给每个源站的配额准确可控本地视角下因为令牌是按批量发放的节点很少需要阻塞等待吞吐也不会被过分牺牲。实测下来这套结构还有一个额外好处当某个源站状态异常需要紧急降速时全局配额服务直接收紧令牌发放速度就行不需要重启任何执行节点。配额的变化在几十秒内自然传导到集群所有节点这在运营调整时体验很好。3.2 限速熔断与降级策略三段式保护并发控制的下一层是异常处理。评审前系统只有失败重试这一招没有熔断也没有降级。这在源站抖动频繁的场景里非常危险。最简单的情况某个源站从下午三点开始响应变慢部分请求已经开始超时此时系统看到失败就重试重试又压上去源站进一步变慢最终整个集群的线程都被这个源站拖住。这个问题在线下压测时不会很显眼一旦真实流量涌入就会加速恶化。评审针对性地设计了三段式保护策略熔断阶段监测每个源站最近一分钟的错误率。如果5xx比例超过30%或者平均响应时间超过阈值立刻停止下发新的任务到该源站把任务切回待调度状态等待恢复。半开阶段熔断不是永久性的。每过一分钟允许一个探测任务进入看看源站是否已经恢复。如果探测成功把允许流量从1%慢慢往上抬直到恢复正常配额。恢复和降级阶段熔断后依然无法在预期时间内恢复的源站启动降级策略。降级按优先级分档第一档只砍低优先级任务保证核心数据源正常采集第二档放弃部分增强字段的采集只保留业务必需的数据项第三档直接暂停非关键任务把机器资源让给核心链路。三段式逻辑本身不难理解但真正的难点在于监控指标要分清楚源站故障和节点故障。如果只是某一台执行节点所在网络抖动其他节点访问同一源站还正常这时不应该对整个源站熔断。所以错误率统计要按源站加运行实例的维度分开记录判定时先看单实例错误率再看全集群错误率两种维度都异常才触发熔断。这个细节我们在评审会上争执了十几分钟后来证明争执是值得的它避免了很多误伤。4. 隐藏的大坑任务幂等与数据去重不是同一个问题4.1 任务级幂等状态机才是唯一可信来源评审进行到中途团队负责人突然提了一句我们有去重应该不会重复入库。我立刻把问题掰开去重解决的是数据层面的重复任务层面的重复执行是另一回事。没有任务级幂等保障数据层去重做得再好也只是把重复执行产生的脏数据挡在最后一道门外面代价却是计算资源的极大浪费。什么叫任务级幂等用一个任务标识把任务提交给系统执行多次最终结果和只执行一次相同。在分布式环境里最干净的做法是状态机加条件更新任务状态只能按照待调度 → 调度中 → 执行中 → 成功/失败的顺序推进任何一次状态变更都必须基于当前状态做条件更新。如果收到一个已经处于执行中状态的任务请求调度层不能把它当作新任务再发一轮而应该返回已有的执行状态如果收到一个已完成任务的重试请求应该直接告知结果不重复触发。这一步通过数据库唯一索引落到了具体实现任务主键加运行批次号联合唯一索引防止同一批次重复执行。执行节点完成工作后回写结果回写SQL永远带状态条件更新行数为0就说明状态已经被别人改过以先到的那次为准。用这个机制重复消息、重复回执都在数据库层消解了不需要依赖消息队列的精确送达语义。4.2 数据级去重从源到库的签名与指纹管理任务级幂等解决的是同一件事做了两遍的问题数据级去重解决的是两条内容长得一样的问题。采集系统的重复数据来源比大家想的要多同一个数据源在分页接口里偶尔会把上一页的最后一条重复返回不同重试批次抓到条目的时间戳版本不同解析插件升级后对同一份原始内容给出的结构化结果也会不同。评审最初的方案是对所有入库数据做全局精确指纹去重。这个方案讲起来漂亮算账之后就很难落实假设每条有效数据拿内容哈希做指纹一个哈希按16字节计再叠加业务维度字段和索引单条指纹大概占用48字节。系统每天入库一千五百万条保留一个月的指纹就是一千五百万乘以三十乘以四十八字节约21.6GB这还只是指纹集合不算业务表本身。如果换成128字节的业务主键索引存储成本会更难看。数据去重应该分级设计。短期窗口内用精确指纹去重把最近24小时的指纹放进高性能存储配合布隆过滤器做快速预判长期窗口用业务主键去重按每个数据源定义的唯一键判断是否更新覆盖而不是做全字段比对。这等于把绝对不重复降级为相对可靠的不重复换来的是吞吐和存储成本的可控。评审最后明确要求团队把这种折衷写进接口文档让下游消费方知道系统对短期重复是强保证对长期重复是概率保证。5. 存储模型评审热路径、冷路径与成本控制5.1 按数据热度设计存储链路采集系统的存储设计容易犯两个方向的错误。一个是把所有原始结果都往关系型数据库里写理由是以后可能用得上另一个是全塞进消息队列以为消息队列能代替存储。评审当天讨论的热点就是这两个。第一个问题是原始结果全量落库。源站返回的原始内容可能是一整页HTML也可能是一份很大的JSON。这些数据对下游分析来说绝大多数没有长期价值真正有价值的是解析出来的结构化字段。评审前团队的做法是先把原始报文存一份再把结构化结果存一份结果单条数据写两次存储膨胀得非常快。评审后调整为按热度分层原始响应只做短期缓冲在缓存中间件里保留一到两个小时保证解析失败时可以重新取原文确认入库成功后就自动淘汰结构化结果写入主业务库承担日常查询落到数据仓库的周期归档层压缩后供分析任务使用。需求真正落在解析后的产品数据而不是原始抓取结果整个链路的存储模型就清爽了很多。存储层存放内容保留周期特点缓存中间件原始响应报文1至2小时高速读写自动淘汰主业务库最新结构化数据实时查询与业务主键对齐归档存储历史数据压缩文件3至6个月成本低读取频率低5.2 容量估算和查询模型之间的取舍评审桌上喜欢用数字说话。我们现场算了一笔账假设单条结构化记录平均200字节一天一千五百万条那一个月的原始体积是一千五百万乘三十乘两百字节约90GB。加上索引膨胀和字段冗余保守估计三倍一到三个月就是约270GB。这还没算临时解析产生的中间数据。这个量级对单机数据库来说已经很有压力但不能简单下结论说就要上分布式数据库。真正的判断依据是查询模型如果下游主要是按数据源加时间维度的批量扫表列式存储归档是性价比更高的选择如果下游需要高频点查主业务库的普通索引就够。评审建议采用关系型主库做热数据、列式存储做批量归档的双轨结构双轨之间靠定时任务同步而不是在每次写入时并行做两遍。容量规划还有一个经常被忽略的因素数据的生命周期。团队最初设计时没有明确数据保留时间导致过期数据长期占用空间。评审一票通过了生命周期管理策略把数据分成7天热期、30天温期和6个月冷期冷期结束后自动清理或者转成统计摘要。这乍看是运维规则实际是架构设计的一部分因为它直接决定了存储层的选型深度和成本上限。6. 部署与流量场景验证评审不能只看设计稿6.1 扩缩容和并发配额的关系一个系统能不能扛住增长不是靠设计稿拍胸脯而是靠场景验证。评审环节里我们把过去三个月的线上运行数据调了出来专门对照扩容记录看指标变化结论让人有些意外集群加了快一倍的机器QPS只提升了30%延迟还偶发升高。原因还是出在并发控制上。执行节点扩容后全局限速配置没有跟着调整每个源站的全局配额其实没有变。新增的节点在排队抢token整体吞吐自然上不去而节点一多网络和调度层的同步开销反而增加了。这就是典型的扩了机器不扩容现象。评审给出的修正方案是扩容动作必须和配额调整绑定执行节点注册到集群时配额服务要根据当前健康节点数动态重新分配各源站允许流量。可以配置自动调整策略也可以走人工确认流程但无论如何扩容一节配额不更新这个Bug必须修掉。6.2 观测性设计是评审点名的短期硬伤另一个被当场点名的问题是观测性。系统各服务有自己的日志但格式不统一任务执行链路跨了调度层、执行层和存储层后没有一个统一标识能贯穿全程。出问题时只能靠人工拼时间戳去各个服务里翻日志定位一个慢任务的耗时可能要花上一个小时。评审要求短期之内完成两项整改。第一所有环节统一引入链路标识任务从进入调度入口开始就生成唯一编号传到执行日志、回执消息、存储写入记录里第二至少上线四组核心指标消息队列积压深度、任务成功率、按源站维度的失败率和限速触发次数、以及入库延迟的延迟分布。这些指标要能通过看板一眼看清而不是等业务方投诉了才开始查。观测性不是锦上添花对于高并发采集系统来说它是运维的最后底线。没有全链路标识任何微小的状态错位都可能在达到一定规模后被放大成不可排查的事故。7. 评审结论与复盘哪些通过了哪些被否了7.1 评审决议汇总整个评审从上午十点开到下午四点最终决议没有走极端没有推翻重来也没有照单全收。我按通过、整改、重设计三类产出了一份结论表这里直接贴出来供参考。评审项结论原因摘要四层主链路架构通过分层合理执行层无状态可扩展任务状态机与条件更新通过并保留数据库状态作为唯一可信源能防重防丢全局配额加本地限速整改需要落地配额服务绑定额度与节点数熔断降级策略整改当前只有重试无熔断必须先补三段式保护数据级指纹去重重设计全量精确去重成本不可控要改分级去重存储冷热分离整改原始报文需淘汰归档层要规划生命周期观测性体系建设整改并设最高优先级没有链路标识任何后续优化都难验证结论是这套架构在稳定支持当前流量上基本合格但距离轻松支撑两倍流量还差一轮关键整改。最主要的差距不在单点性能而在控制面和观测面并发配额与节点数没有联动故障响应没有熔断机制问题定位没有全链路工具。这些恰恰是采集系统从能跑变成好用的分水岭。7.2 我在这轮评审里的三个体会评审结束后团队把整改项排进了两个迭代。我坐在椅子上复盘时总结了三个体会也顺手写在了纪要最后。第一个体会是高并发采集系统的架构评审重点往往不是把并发值调多大而是如何让同一时刻不那么猛烈地打到下游。采集的对象是外部数据源节奏控制比速度更重要。一段把源站打挂的提速方案在评审里只是不合格设计。第二个体会是任务幂等和数据去重一定要分开设计。这两个问题经常被混为一谈结果就是任务重试时数据层已经堆了大堆垃圾再用昂贵的指纹清理去擦屁股。把状态机控制前移比在数据入库时拼命兜底有效得多。第三个体会最实际任何方案都要拿出来做容量细算。一个看起来很合理的存储方案按真实数据量一算成本可能超出预算两倍反过来一个看起来费劲的分级去重设计放在真实量级里反而是唯一现实的选择。评审桌上不能靠感觉投票数字摆出来分歧会自动缩小大半。如果你也在设计或者评审类似的数据采集底座我建议先从这三个位置开始看状态转移怎么保证不重不漏并发配额怎么全局可控存储按什么生命周期在滚动。这三处想透了系统的骨架基本就立住了。至于剩下的细节大多可以在迭代里慢慢补不用太慌张。