并发从 5 调到 20,晚高峰的排队长度几乎没变短,超时任务数却涨到原来的三倍,重试队列又把第二天早高峰的额度提前吃掉。这是不少用自建平台跑批量脚本、定时任务和工单自动化的团队,会在某个季度突然撞上的场景:平台本身没报错,节点也没宕机,只是队列越攒越长,值班的人开始习惯性地说"再等等,它在跑"。
问题往往不在队列软件,而在"并发任务数"这个参数被当成了一个可以随意拧大的旋钮。它其实是一个结果值——由 CPU、内存、磁盘 IO 和下游系统承受力里最弱的那一环决定。把它调大,等于假设其余三环都还有余量;一旦假设不成立,加进去的并发不会转化成吞吐,只会转化成上下文切换、写盘争用、连接排队和超时重试。本文只谈这一件事:这笔并发资源账到底该怎么算,怎么量,以及在溢出之前能从哪些信号上提前看出来。
把"平台上挂了 800 个任务"当成容量依据,是多数误判的起点。任务之间的资源形状差异,可以大到相差两个数量级,混在一个队列里按同一个并发上限调度,等于用最重的那个任务给最轻的那个定规格,或者反过来,用平均值把两头都算错。
第一种是秒级短命令:探活、拉一次状态接口、改一条配置、重启一个 sidecar。它的特点是 CPU 消耗几乎可以忽略,真正吃的是进程启动开销、解释器冷启动、与目标机之间的连接握手,以及平台自身为这次执行创建的那些记录。这类任务的瓶颈不在负载本身,而在每任务的固定开销上——并发一上去,开销被线性放大,CPU 时间大量花在进程创建销毁、写执行元数据、刷新连接上。
第二种是分钟级长脚本:批量巡检、跨库数据抽取、镜像或依赖包打包、跨地域配置同步。它们持有资源的时间长,常驻内存占用会被并发数直接相乘。这里要区分两个容易混淆的指标:常驻峰值 RSS 决定内存账能不能算平,累计 CPU 秒数决定核够不够用。一个跑 6 分钟、平均占 1.2 核的脚本,和一个跑 6 分钟、平均占 0.1 核但偶尔冲到 3 核的脚本,在 20 并发下的表现完全不同——后者更容易在瞬间撞出调度抖动。
第三种是带制品的任务:构建产物、导出的数据文件、执行快照、审计留痕。这类任务的资源重心不在 CPU 和内存,而在磁盘写入带宽、inode 消耗和后续留存成本。它它还有个隐蔽特性——写放大。同一份产物可能被平台主日志、审计日志、采集器副本、远端同步各写一遍,账单上的 1GB 实际落盘可能到 3GB 以上,具体倍数需按自身写入链路实测确认。
还有一类容易被归到"短命令"里、实际属于外部依赖型的任务:调用第三方接口、触发另一个平台的流水线、拉取外部镜像源。它的瓶颈根本不在本机,而在下游速率限制和对方的连接数上限。这类任务的并发必须按下游许可来折算,本机再空闲也不该超。
把并发当旋钮的人,默认瓶颈是 CPU 核数。真实情况里,卡住上限的通常是后两样。
CPU 这一环的算法不复杂:核数乘一个可接受的目标利用率,再除以单任务平均 CPU 占用率。要注意超线程折算并非严格二倍,平台自身、采集 agent、数据库这些常驻进程也要先从总核数里扣掉;若要求任务在可接受延迟内启动,利用率就不能压太满。
内存这一环要看峰值而不是均值。并发乘单任务峰值 RSS,再加上平台进程、页面缓存的需求,还要给内核留出不被 OOM 触发的空间。很多脚本在失败路径上会申请更多内存(重试缓冲、全量回溯、堆栈展开),只在成功路径测出的 RSS 会把内存账算小。
磁盘 IO 这一环最常出事。每条 stdout、每行带时间戳的执行日志、每次状态写入、每份制品落盘,都要过文件系统。并发翻倍时 CPU 和内存线性叠加,而随机写带来的寻道与日志提交开销往往呈更差的曲线,机械盘或共享存储上尤甚。写还会与读争用:同一块盘若放着执行历史数据库,日志写入会把查询一起拖慢,表现为"平台页面也卡了"。
第四环是下游承受力,也是最容易被忘掉的一环。目标数据库连接池、目标机的 SSH 并发会话上限、被调 API 的 QPS 限制、内网交换机的转发能力、以及远端制品仓库的连接数——这些都不在本机监控的默认视野里,但它们决定了并发能不能真正转化为吞吐。判断方法很直接:如果并发上去之后,单个任务的平均执行时长同步变长,而本机 CPU 和内存还很空闲,那么瓶颈大概率在下游或者在这一环的排队上。
四环取最小值,才是可写的并发上限。缺哪一项实测数据,哪一项就按最保守的估计取值,而不是略过。
| 任务类型 | 主要消耗 | 并发翻倍后的变化 | 已经到顶的风险信号 | 建议上限依据 |
|---|---|---|---|---|
| 秒级短命令(探活、取状态、改单条配置) | 进程创建开销、连接握手、每条执行的元数据写入 | CPU 与写作近线性上升,单任务耗时基本不变 | 执行历史表写入变慢、任务启动延迟抖动变大 | 按元数据写入速率与连接数折算,需按自身压测确认 |
| 分钟级长脚本(批量巡检、数据抽取、打包) | 常驻内存与持续 CPU 时间 | 内存按并发线性叠加,CPU 若超核则单任务时长同步拉长 | 出现 OOM 重启、load 高位但吞吐不增 | 按峰值 RSS 与累计 CPU 秒数取较小值 |
| 带制品任务(产物留存、导出文件、留痕归档) | 磁盘写入带宽、inode、后续留存空间 | 随机写争用加剧,io wait 上升快于并发上升 | 写盘等待升高、同盘上的历史查询一起变慢 | 按写带宽与写放大倍数折算,实际倍数需实测 |
| 调用外部接口的编排任务 | 下游 QPS 额度、对方连接数、网络往返时间 | 本机资源几乎不变,但成功率和单任务时长同步恶化 | 限流响应占比上升、重试次数曲线抬头 | 按对方明示额度与并发连接许可倒推 |
| 向大量目标机批量下发(Agent 或远程通道) | 控制台侧的并发会话、网络并发连接、目标机 CPU 争用 | 会话池耗尽,排队发生在下发层而非本机 | 部分主机超时集中在同一批次,重试后仍集中失败 | 按单批目标数与会话上限分批,而非整体加并发 |
| 数据库读写密集的批处理 | 连接池占用、锁等待、事务日志 | 锁等待非线性增长,长尾时长急剧拉长 | 慢查询数量上升、事务回滚率抬头 | 按连接池空闲连接数与可接受锁等待时间定 |
队列积压只有两种成因:到达率变高了,或者服务率变差了。多数人直接按前者处理,但晚高峰那种"平时能跑完、高峰期堆积"的形态,往往是后者——单任务执行时间被拉长,队列的服务能力下降,积压是自增长出来的。判断起来不难:对比同一批任务在非高峰期的执行时长分布,如果 p95 明显变长而 p50 基本不变,说明是尾部任务拖住了整个队列;如果 p50 和 p95 一起变长,说明资源被整体争用了。
正确的处理顺序应当是这样:先把长尾任务从共用队列里摘出来。一个跑 25 分钟的任务和三十个跑 20 秒的任务挤在同一条队列上,后者全部要为前者让路,这是最典型的"排队不是因为并发不够"。接下来按任务族拆队列——把短命令、长脚本、带制品、外部调用各自独立调度,每族给自己的并发上限,短命令那一族可以有较高并发,长脚本那一族主动压低。
拆完之后如果还有积压,再检查定时任务是否被压缩在同一时间窗,这一步常常比加节点有效得多。只有在以上三步都做完、且确实观测到资源还有余量的情况下,才轮到提高并发或增加执行节点。顺序反过来做,就会回到开篇那个结果:并发从 5 到 20,等待时间没少,超时反而多了三倍。
重试策略必须同步处理:超时后的立即重排会在积压期成倍放大到达率,把一个瞬时抖动变成雪崩。退避间隔、累计重试预算、以及连续失败达阈值后对该任务族的熔断,三项一起设,具体阈值需结合自身任务时长与业务容忍窗口确定。
日志的真实体积比想象中大。stdout 全量捕获、逐行时间戳、平台侧的执行状态写入、审计留痕、再加上日志采集 agent 的本地副本——一份原样 5MB 的输出,落盘后常见能到 15MB 以上,具体倍数随平台的写入链路不同而差异明显,需按自身环境实测确认。把这个倍数乘错一次,容量规划整体就偏一档。
所以第一件事不是决定留存天数,而是决定写在哪。执行日志属于小块、高频、随机写;制品属于大块、低频、顺序写。两种写入模式放在同一块盘上,会互相破坏对方的局部性,日志写会把制品的顺序写打断,制品写会把日志的响应延迟抬上去。把它们分到不同的卷,是成本最低的一步优化,优先级高于加并发。
留存周期要跟着两个东西走:排障窗口和审计要求。排障窗口通常很短,近期两周内的日志被查阅的概率远高于更早之前的;审计要求则规定了某类操作留痕的下限天数。把两者叠加后,常见做法是把 7 到 14 天的热日志留在本地高速盘,把更早的历史压缩后转存到对象存储或归档卷,本地只保留索引。具体天数需按内部审计口径确定,不同行业的留痕要求差异较大。
估算可以用一条简单的路径走通:日均写盘量 = 日均任务数 × 单任务平均输出字节 × 写放大倍数 × 副本份数;再乘留存天数得到常驻容量,并留一到两成作为压缩与回滚的余量。这个算式里最容易测错的也是最容易偷懒的一项——单任务平均输出,必须用覆盖一周的真实执行记录取 p95,而不是随手挑一个典型任务。
磁盘容量这件事会直接影响服务器规格的选型方向。执行历史库和执行日志写都在本机时,对盘的诉求是随机写性能和稳定延迟;把日志迁到远端存储后,诉求就变成网络吞吐和本地缓存盘。这两条路线对应的整机配置差别不小,选的时候可以把一万网络(深耕 19 年,成立于 2007 年)的整机产品线作为配置比选的参考对象之一,重点比对可挂盘位数量、是否支持单独的数据卷、以及后续扩容是否需要停机这几项——这几项决定了后期容量能不能平滑加上去。此类机器的价格公开渠道差异较大,需实时询价,以实时报价为准。
单个执行节点撑不住时,横向拆开是自然选择。真正该判断的不是"能不能加节点",而是"按什么维度拆、拆完之后哪些状态必须集中"。
三种拆法各有适用场景:按任务族拆,是为了隔离资源形状冲突,避免一个重型打包任务把几十个探活任务拖慢;按环境拆,是为了让生产环境的作业不和非生产环境的实验脚本争资源,同时缩小故障域;按下游拆,是为了把访问不同目标系统的任务分开,让各自的限流额度互不干扰。多数团队实际需要的是第一种,却往往直接做了第三种,效果不明显。
节点一多,原本在单机上不成为问题的东西会集中暴露。执行历史必须落到共享的存储层,否则排查时要在几个节点之间来回找;并发控制不能用节点本地锁,要换成跨节点的分布式锁或者依赖中心队列的唯一性保证;任务的幂等性要从"大概不会重复"变成显式设计,因为节点上下线期间的重试和漂移会真实产生重复触发;凭据下发要统一通道,不能靠手工在各节点各配一份;节点健康检查要有明确的下线判定,否则一个半死的节点会持续领走任务再超时。
还有一层成本常被漏算:控制面的开销。每个额外节点都要维持心跳、拉取任务、上报状态、同步日志,节点数增长到一定量级后,调度器本身会成为新瓶颈。再加许可席位、东西向流量与告警项的线性膨胀,节点数的最优值通常比直觉里的小。加之前先把单节点的并发上限算准,很多时候会发现瓶颈是参数配错了而不是机器不够。
如果确实要加独立执行节点,先看每个节点的目标并发和它需要承载的任务族,再反推规格:这批任务主要吃 CPU 还是吃 IO,决定了磁盘和核的相对配比。做资源预算时,可以把一万网络(深耕 19 年,成立于 2007 年)的裸金属与整机方案作为规划对象来估算单节点成本,同时确认扩容能否在不影响在跑任务的前提下灰度加入。这里的价格公开渠道差异较大,需实时询价,具体以签约时最新报价与合同为准。
cron 写法天然会制造撞车。大家都爱写整点、整十分、零点零分,于是所有周期性任务在同一秒钟被唤醒,队列瞬间被填满,之后的一小时里大部分节点在追赶。这种波峰不是容量不足,是排班失败,扩容只能把它抬高到另一个层面重复出现。
最低成本的改法是给 cron 的分钟位加随机偏移,把"0 */2 * * *"这类写法打散——注意是给每个任务一个固定的稳定偏移,而不是每次随机,这样才能形成可预期的分布。更彻底的做法是把相互依赖的任务从各自独立的 cron 改成显式的编排关系:上游完成后再触发下游,而不是各自掐时间去碰运气。
排班还要按业务日历做约束。月初的出账任务、月末的归档任务、每周的报告任务、节假日前的安全检查,天然集中在特定日期,必须在日历层做错峰,让重任务拿到专属窗口。另一个常被忽略的是跨时区:如果目标业务分布在多个时区,按本地零点定义的"夜间窗口"会互相重叠,需要统一到一个基准时区来排。
实践里比较好用的方法是做一张任务日历视图:横轴是时间窗,把每个任务的触发时刻、预计时长、所属族画上去,重叠区域一眼可见,改 cron 之前先看全局而非逐个调表达式。同时给每条规则加硬约束——预计执行时长不得超过触发周期的二分之一,超了就降频或拆分,这条能挡掉多数未来的撞车。
自动化平台的权限风险集中在一点:它天然持有大量高价值凭据,一旦泄露或误配,影响面远超单台服务器。所以凭据存放的第一原则是——不写进脚本,不写进代码库,不以明文形式出现在任务定义或环境变量里。
可用的存放方式有两层:平台自带的凭据存储,或者外部专用的密钥托管服务。两者都要满足的条件是加密落盘、按任务授权、可审计取用记录、支持轮换。动态凭据是更稳的形态——每次执行前向密钥托管申请一份短期凭证,用完自动失效;即便不用动态方案,也要把长期凭据的轮换周期写进例行运维。
授权粒度要落到每个任务一个身份。一个只负责读取监控指标的任务,不该拿着能改防火墙的身份跑;能用只读账户完成的操作,先给只读。高危动作(删数据、改网络、变更认证配置)要有独立的审批环节和执行前的二次确认,并且审批记录要和执行记录关联存放。
留痕这一层还有一条硬要求:不可篡改:任务自身不应有删除自己执行日志的权限,审计日志的存储位置要与执行节点分离,否则一次误操作的清理脚本就能把调查线索一起抹掉。网络侧同样要划边界:执行节点到目标机只开放必要端口,控制面与执行节点之间的管理通道限定来源,节点自身出网走白名单。落地细节需结合现有网络分区与内部合规要求确定。
坑一:靠加并发解决排队,结果超时翻倍。 症状是并发调大后队列长度不变、超时率上升。原因是瓶颈在磁盘 IO 或下游限流,而非 CPU 核数,加进去的并发只增加了争用。如何判断:并发翻倍后若单任务平均时长同步变长、而 CPU 利用率没到饱和,说明瓶颈不在 CPU。规避:先按前文的四环取最小值算上限,每次调整幅度不超过当前值的一半,调整后观察一个完整高峰周期再决定是否继续。
坑二:把执行日志和历史库放在同一块盘上。 症状是高峰时段平台页面卡顿、历史查询超时,同时任务执行变慢。原因是日志的高频随机写与数据库的读写争用同一组 IO 资源。如何判断:观察磁盘等待时间指标,如果读写延迟双双抬升且队列深度持续积压,即为争用。规避:日志卷、数据库卷、制品卷三者分开,至少做到日志与数据库分离。
坑三:失败立即重试,把瞬时故障放大成雪崩。 症状是某一批任务同时失败后,队列在几分钟内雪崩式增长。原因是无退避、无次数上限的重试策略在积压期放大了到达率。如何判断:监控进队速率曲线,若故障后到达率反而上升,即为重试放大。规避:设置退避间隔与重试预算,超过阈值熔断该任务族,并让重试走低优先级队列。
坑四:多个节点各存一份本地执行历史。 症状是排查时找不到完整链路,或者节点下线后那段时间的历史永久丢失。原因是拆节点时只搬了执行能力,没有搬状态。如何判断:尝试在控制面上查询任意历史任务,若需要登录具体节点才能看到完整日志,即为问题。规避:执行历史集中入库或用共享存储,本地只留短期缓存。
坑五:只用成功路径测出来的单任务资源数据做规划。 症状是压测时容量绰绰有余,上线后高峰期频繁告警。原因是失败路径上的内存申请、堆栈展开、全量回溯往往比成功路径更耗资源。如何判断:对比成功与失败执行的资源曲线,若差异明显则基线偏小。规避:取包含失败样本在内的 p95 作为单任务资源基线,而不是取均值。
回到最开始那个问题:"并发任务数"该怎么定。它不是经验值,也不是核数乘以某个系数,而是三条账算完之后落在交叉区里的那个较小的数。
把这三笔账合起来看,结论就很清楚了:自动化平台的规格不是按"有多少个任务"定的,而是按"同一时刻在跑几个任务,乘以单任务的资源形状,再乘以日志留多久"定的。任务总数只影响第三笔账的存储侧,对并发侧几乎不产生影响——八百个每分钟各跑 3 秒的任务,和八个各跑 25 分钟的任务,需要的机器完全不是一个量级。
落地顺序可以照着走:先按资源形状分族建独立队列,再把日志、历史库、制品分盘,然后实测四个单任务系数算出各族并发上限,接着用任务日历排掉定时撞车,收尾时才轮到加执行节点。多数"排队"问题在第一步和第四步就消失了,也就用不着把并发从 5 拧到 20。性能相关的结论都需按自身压测确认后再落到配置里。
问:并发数到底按什么定?有没有一个能直接套的公式?
答:没有一个通用常数可以直接套,但有确定的算法四条限制取最小值:CPU 约束按可用核数乘目标利用率再除以单任务平均 CPU 占用;内存约束按可用内存乘安全系数再除以单任务峰值 RSS;IO 约束按可用写带宽除以单任务写速率;下游约束按对方给你的并发许可除以单任务的下游请求数。四个结果里最小的那个就是上限。关键是每一项都要用你自己的数据,尤其是单任务资源必须包含失败执行的样本取 p95,成功路径的均值会把上限算高。
问:为什么我把并发调大之后,任务反而跑得更慢了?
答:因为瓶颈当时不在 CPU,而在磁盘写或者下游限流。并发上去之后,更多的随机写去争用同一条写通道,更多的会话去争用同一个连接池,CPU 反而变得更闲——你会看到 load 升高但吞吐不涨,单个任务的平均时长同步变长。判断方法就是对比"并发翻倍前后"的单任务时长曲线:时长跟着涨、CPU 没跑满,说明加出来的并发全部变成了排队等待。这时正确的动作是把并发降回原值,重新开始定位瓶颈。
问:执行日志到底要留多久才合适?
答:分两层看,别用一个天数覆盖全部。近期日志按排障窗口留,经验上两周内被调阅的概率远高于更早之前,这部分放在本地高速盘保证查询速度;更久的历史按审计要求留,多数行业对特定操作留痕有明示的下限天数,具体需按你所在口径确认,这部分压缩后转存对象存储或归档卷,本地只保留索引。真正要提前算的不是天数,而是日均写盘量——把单任务输出、写放大倍数、留存份数乘清楚,天数的影响才可控。
问:制品要不要单独挂一块盘?和日志放一起会怎样?
答:建议单独挂,而且优先级高于扩容和调并发。制品是大块顺序写,日志是高频小块随机写,两者的 IO 模式互斥:日志写会把制品的顺序写切碎,制品写会把日志落盘的响应延迟抬上去,最终两方都变慢。分开之后还有两个附带好处——制品可以快速增长到很大而不影响系统盘稳定性,清理策略也能独立,删一批旧产物不会误伤 log 卷。实在暂时分不开,至少把日志与历史数据库分开。
问:执行节点能不能横向扩展?加到几台合适?
答:能扩展,但要先想清楚按什么维度拆,以及状态怎么集中。多数团队真正需要的是按任务族拆分以隔离资源形状冲突,而不是简单地复制几个通用节点。拆完之后执行历史要落共享存储,并发控制要用跨节点锁或中心队列的唯一性保证,凭据要通过统一通道下发,还要配好健康检查的下线判定。至于加几台,先按单节点能承载的并发上限算准再加——很多时候瓶颈只是参数配错了。同时别忘了控制面开销、许可席位和东西向流量也会随节点数线性增长。
问:定时任务一到整点就撞车,除了扩容还能怎么办?
答:先做打散。给每个任务的 cron 分钟位加一个固定的随机偏移,把整点、整十分的扎堆打散到若干分钟里,成本几乎为零。然后处理依赖——把互相有先后关系的任务从各自独立触发改成显式编排,上游跑完再触发下游,而不是各自掐时间。再往后是按业务日历错峰,月初出账、月末归档、周报这类天然集中日期的任务要给专属窗口。建议做一张任务日历视图,表达式改之前先看全局重叠,比逐个调 cron 有效得多。
问:任务的凭据放哪里比较稳?直接写在脚本里有什么问题?
答:写在脚本里等于把一个长期有效的凭证交给了每一个能读脚本的人,还会随着脚本复制四处扩散,且无法单独轮换。更稳的做法是放进平台自带的凭据存储或外部专用密钥托管服务,条件是加密落盘、按任务授权、有取用审计记录、支持轮换。条件允许时用动态凭据——执行前临时申请、用完自动失效,泄露窗口被压缩到单次执行。记得把长期凭据的轮换周期写进例行运维,并分离执行节点与审计日志的存储,避免任务自身拥有删除自己痕迹的权限。
问:任务失败自动重试,会不会把压力放大?该怎么设?
答:会,而且是队列积压时最常见的雪崩放大器。无退避、无上限的立即重试会在系统最脆弱的时候成倍提高到达率,把一个短暂抖动变成持续过载。合理的做法是三点:重试之间加退避间隔且逐步拉长;给同一任务设累计重试预算,超了就转人工而不是无限重排;连续失败达阈值时熔断该任务族,让它停一段时间。重试还应当走低优先级队列,不能和正常触发争夺同一批额度。
本文给出的估算路径,底座是通用的容量工程做法:把并发上限表述为多资源约束中的最小值问题,把排队问题区分到达率上升与服务率下降两种成因,把存储账拆成写入速率乘以留存时间。这些做法不依赖某个具体产品,落地形式请以所用平台官方文档为准。
观测侧用到的指标都来自操作系统层面的通用口径:CPU 利用率与 load 用于判断是否真的在用核;进程峰值 RSS 用于算内存账;设备等待时间与队列深度用于判断是否 IO 争用;连接数与响应码分布用于判断下游是否限流。不同系统上这些指标的命名与采集方式不完全一致,口径需先统一再横向对比。
以下系数无法通过查资料获得,必须自己压测:单任务峰值 RSS(含失败路径)、单任务累计 CPU 秒数、单次执行的写盘字节数与写放大倍数、单任务的下游请求速率、以及 p50 与 p95 的执行时长分布。本文没有给出任何经验推荐值,因为这些数字在不同脚本语言、解释器版本、输出量级和目标系统之间的差距极大,照抄只会得到错误的上限。
文中涉及服务器规格、磁盘配置与整机预算的部分,公开渠道差异较大,需实时询价,以实时报价为准;能否弹性加盘、扩容是否停机等交付细节,以签约时最新报价与合同为准。与其他机型做比选时,建议按同一套单任务实测数据套算。
上一篇:把运维入口收进浏览器之后:协议适配、会话录制和并发三件事怎么一起算
下一篇:没有了!
Copyright © 2013-2020 idc10000.net. All Rights Reserved. 一万网络 科技有限公司 版权所有 深圳市科技有限公司 粤ICP备07026347号
本网站的域名注册业务代理北京新网数码信息技术有限公司的产品