十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

企业级脚本四支柱:断言、日志、异常与重试机制实战指南

企业级脚本四支柱:断言、日志、异常与重试机制实战指南 1. 整体设计与思路拆解接手过企业级脚本项目的人应该都有这种体会一个脚本能跑起来和能稳定跑半年不出事完全是两个维度的事情。标题里提到的断言、日志、异常、重试就是让脚本从“玩具级”走向“企业级”必须跨过的四道坎也是脚本开发中最容易忽视、却最影响稳定性的四根支柱。先说一个我自己的真实经历。早年做接口自动化测试脚本时首次跑通脚本的兴奋感还没过去就发现脚本隔三差五在深夜挂掉第二天早上看任务平台一片红。最气的是脚本挂了没有任何痕迹既不知道卡在哪一步也不知道是数据问题还是网络问题只能靠猜。后来我专门花了两天时间把断言、日志、异常、重试这套体系从头到尾搭了一遍那一周之后脚本稳定性直接上了一个台阶再出问题也能在五分钟内定位到根因。这就是我想要在这篇博文里分享的核心内容怎么把四个看似基础的技术点组合成一套真正能扛住生产环境考验的脚本基建。这里说的脚本不局限某一种语言Python、Shell、JavaScript写的自动化任务、数据采集、接口巡检、定时作业都在范围内核心思路是通用的。适合谁看正在写脚本但总觉得不够稳的工程师被线上脚本半夜告警折磨过的运维准备把自己的个人脚本升级成团队公共工具的开发者都应该能从这里找到可以直接抄作业的方案。1.1 四个优化点的职责边界先理清楚这四个东西各自管什么。断言管的是“结果对不对”。脚本跑完不等于任务完成跑完得到的结果是不是符合预期这才是关键。没有断言脚本跑完你可以说它“执行了”但不能说它“成功了”。断言就是把不可见的执行结果变成可验证的是非判断。日志管的是“过程看得见”。脚本跑得好好的时候日志好像没什么用可真出了事日志就是唯一的目击者。从“脚本挂了”到“脚本为什么挂”中间隔着一条日志的长河。日志写得好不好直接决定定位问题的时间是从十分钟变成一小时还是从一小时变成十秒钟。异常管的是“出事别崩”。脚本运行环境千变万化网络超时、服务端返回500、数据结构变了、磁盘写满了任何一个意外都有可能导致脚本当场崩溃。异常处理的意义不是消灭错误而是把错误控制在可控范围让脚本在出错时知道自己该做什么。重试管的是“再来一次”。很多故障是瞬时的网络抖一下、服务端临时过载、连接池刚好被占满这些情况下直接判定失败太冤枉稍微等一会儿再试试可能就成功了。重试机制也是四个优化点中对稳定性提升立竿见影、又最容易被做过头的一项。1.2 四个机制的协作关系这四个点不是孤立的它们之间是串联协作的关系。我的理解是断言负责发现问题异常负责处理问题重试负责给异常一次补救机会日志负责把整个过程记录下来给人看。举个例子一个定时拉取第三方接口数据的脚本每天凌晨执行。某天凌晨服务商发布了一个新版本接口响应结构发生了变化脚本里解析返回数据的逻辑拿到了一个不存在的字段。这时候异常处理会让脚本不因为KeyError直接崩溃而是捕获这个异常并打印出堆栈日志会把完整的响应体结构记录到文件里方便第二天对比分析如果脚本集成了健康检查断言还会在解析之前先校验响应体的schema提前发现结构变化而重试机制会根据错误类型判断——数据结构变了属于不可恢复的业务性错误不值得重试但如果是超时、连接被重置这类瞬时错误延迟几秒重试三次大概率能恢复正常。这样一套组合下来脚本对故障的感知、响应、恢复、追溯就形成了一个完整的闭环。这也是我坚定不移地认为这四个技术点必须放在一起讲的原因——单拎出来任何一个效果都要打折扣。2. 断言机制给脚本装上“正确答案”断言是整个体系里我最喜欢的一块因为它直接回答了一个灵魂问题脚本到底怎么算跑成功了。很多脚本失败的场景问题根源恰恰在于编写者从来没明确定义过“成功”。2.1 断言的本质与误区从本质上讲断言就是“将执行结果与预期结果做比较不相等则判失败”。企业级断言和平时写代码时随手加的assert有本质区别。随手加的断言更多是开发期自查断言的表达式经常是“这里不应该为null”“这里大小不应该超过10”企业级断言的关注点则是面向整个任务的验收——从用户视角看这个任务的最终产物是否正确。常见的误区是把断言当成锦上添花甚至完全依赖第三方框架自带的断言。我见过不少团队用JUnit、pytest这类测试框架主要看用例有没有通过但对于测试脚本采集到的数据是否合理、接口响应是否符合业务语义完全没有校验。这样的测试用例跑得再绿也没有实际意义——测试脚本替你在验证但验证的深度太浅浅到等于没验证。真正好的断言应该是“能挡得住需求变化的断言”。这一点很重要需求变了断言如果不变脚本还是会按旧标准校验导致大量误报或者漏报。企业里脚本需要长期维护断言的更新频率和代码本身的迭代频率应该是同步的。2.2 断言的分层设计与写法做断言时我习惯把校验目标分成三层数据完整性断言校验返回的数据集是否完整比如一个接口应该返回100条记录实际只返回了98条这就是数据缺失应该用断言抓住。形如assert_that(response.total).isEqualTo(100)。数据有效性断言校验每条记录里的关键字段是否有效。比如时间字段的格式、ID字段不为空、状态值是否在合法枚举内。这类断言考验编写者对业务的理解深度——你得知道哪些字段是无论如何都不能为空的。业务规则断言校验跨字段组合后是否符合业务规则。比如订单总金额商品金额运费-优惠返回的每个订单都该满足这个恒等式。业务规则断言是最难写、也最有价值的一层它能发现很多“数据没丢但算错了”的隐性问题。写断言时还有一个细节我特别想强调断言语句一定要带上下文。直接写assert result expected挂了之后你只知道不相等不知道具体是哪个字段不相等、期望值是什么、实际值是什么。正确写法是带上可读的提示信息和关键变量值例如assert response[order_status] PAID, \ f订单状态异常: expectedPAID, actual{response[order_status]}, order_id{order_id}这样断言失败时日志里直接能看到订单号和实际状态不用再翻代码去猜。2.3 断言策略中的“可执行化”还有一个实践经验我把它叫做“探针式断言”。普通的断言是一次性的——校验一次过了就过了。但高稳定性脚本的场景是同一段逻辑会被反复执行比如每隔五分钟采集一次数据。我建议在脚本启动时做一次“环境探针”先调用少量接口、解析少量数据跑一遍轻量级断言确认整个链路是通的再正式进入批量处理逻辑。这样做的好处很明显如果环境有问题鉴权失效、网络不通、接口变更探针阶段就会触发告警并中止不会让脚本带着故障跑完整轮任务浪费大量时间和资源。探针式断言的实现在逻辑上并不复杂把启动阶段和数据解析阶段共用的校验逻辑抽成一个方法启动时先在小样本上执行一遍再决定是否继续。别小看了这个设计它能让一批批量的任务省下很多不必要的无效重试。2.4 断言失败后的动作设计断言失败的后续动作也值得专门设计。我的经验是把断言失败分成两类一类是可恢复的——比如数据量偏少可能是上游产线还在写入过几分钟再查可能就齐了另一类是不可恢复的——比如数据结构根本变了、接口字段名都没了这种情况再重试也没有意义。对应的设计是可恢复的断言失败交给重试机制处理不可恢复的断言失败立刻中止脚本并记录详细的失败现场——包括请求参数、返回原始内容、当前时间、触发断言的代码位置。失败现场记录得越全后面人排查问题越省事。这一块的日志设计我会在下一节详细讲。3. 日志体系让每一行执行都留有足迹日志在脚本里经常被当成“print”的近义词这个观念必须改。企业级日志不是给开发者在终端里自己看的而是给未来的环境、未来的排障人员很可能是三个月后的你自己看的。一份合格的日志应该达到“远程看一眼就能定位问题方向”的水准。3.1 日志级别要分但不该滥用日志分级听起来简单实际操作中走样的情况特别多。最常见的问题有两个一是全程只用一个级别所有信息都打INFO导致重要问题被淹没在流水账里二是为了省事把大量中间态数据都打DEBUG线上环境为了排查问题不得不开DEBUG结果日志量爆炸磁盘一夜写满。我推荐的小队级标准是正常执行步骤用INFO每个关键阶段至少一条变量展开、详细数据用DEBUG异常和错误用ERROR并始终带堆栈可预见的边界情况比如某次请求超时但重试后成功用WARN。守住了这个级别约定生产环境的日志量基本可控需要深入排查时再临时开DEBUG。还有一个简单有效的约定日志里必须能看出“这条日志是谁在哪产生的”。至少要包含模块名、函数名、行号。Python的logging模块默认格式化里加%(filename)s:%(lineno)s就能实现成本为零排障价值巨大。Shell脚本也一样用echo打日志时手动加上函数名和行号。3.2 日志规范里最能提效的字段日志格式的具体规范不同语言、不同日志框架会有差异但核心字段是通用的字段作用时间戳到毫秒带时区方便跨系统对齐日志级别INFO / WARN / ERROR / DEBUG模块与函数定位代码位置请求ID / 任务ID串联一次完整调用链关键上下文业务参数、订单号、接口URL等用于快速定位问题对象错误摘要与堆栈异常信息必须完整保留这里最想重点说的是请求ID / 任务ID也就是业界常说的TraceID。脚本往往是一个循环处理几千条数据单条数据处理失败时如果日志里没有这条数据对应的唯一ID排查起来真的要命。正确的做法是循环开始时生成一个trace_id或者直接用数据本身的业务主键这一段的所有日志都带上这个ID。这样日志里grep trace_idAB123整条链路就全部浮出水面了。3.3 落盘策略与轮转配置日志配置里最容易踩坑的是轮转和保留策略。没有轮转日志文件会无限膨胀最终把磁盘写满脚本故障不说还可能拖垮同一台机器上的其他应用。轮转策略按体积和按时间两种都常见我一般组合使用单个文件达到50MB就轮转保留最近10个文件或者最近7天。这个配置在Python logging的hander里几行就搞定Shell下配合logrotate也很方便。还有个很多人忽略的细节日志目录要提前创建好并确认脚本进程有写入权限。很多脚本上线后跑得好好的突然某天日志就不写了一查是运维清理目录时把权限改了或者目录被误删。脚本启动时先检查日志目录是否存在、是否可写不存在就尝试创建这一步硬化能让后面的排障省很多时间。3.4 避免日志拖慢脚本性能日志写得越详细越安全但性能开销也越大。高频率的日志写入会拖慢脚本执行速度尤其在循环里写日志的场景。一个经验是循环内部按需记录不搞每一条都写要记录循环进度时用“每处理100条记一条”的节流策略。异步日志在Python里有QueueHandler配合后台线程消费的方案日志IO从同步变成异步吞吐量能提升不少。但异步日志也有代价——脚本进程非正常退出比如被kill时堆积在队列里的日志可能来不及落盘有丢失风险。权衡之下普通脚本用同步日志加节流就够了没必要强上异步。注意日志内容不要记敏感信息尤其是密钥、Token、密码、个人隐私数据。日志文件本身也是会被拷贝、被备份、被传到日志平台的里面的数据一旦泄露就收不回来了。必须记录凭证时至少做脱敏处理。4. 异常处理不出轨也要有应急预案异常处理是脚本健壮性的地基。没有这层设计前面说的断言、日志、重试全是空谈——脚本一崩后面的机制根本没机会执行。异常处理的设计核心是提前想清楚“哪些错误能继续跑哪些错误必须停下来”。4.1 异常分类三桶模型我习惯把脚本运行中可能出现的异常分成三类分别放进三个“桶”基础设施异常网络连不上、DNS解析失败、磁盘空间不足、数据库连接断开等。这类异常的共同特征是瞬时性强可能与当前运行环境强相关前一秒倒下后一秒可能就正常是重试机制的优先候选。业务规则异常数据校验不通过、业务状态不在预期等。这类异常代表数据和逻辑本身有问题重试大概率无效应当直接记录下来跳出当前数据处理单元比如跳过当前记录进入下一条。未预期异常类型错误、边界溢出、依赖库bug。这类异常说明代码本身存在缺陷或环境出现了意料之外的变化必须完整保留现场通常需要人工介入处理不能默默吞掉。分好了桶异常处理逻辑就非常清晰了基础设施异常进重试业务规则异常跳出当前单元未预期异常记录完整堆栈并置失败标志等整轮结束时汇总报告。4.2 捕获粒度与“圈养”策略异常捕获的粒度是个大学问。一个常见错误是在main函数外面套一个巨大的try...except出了任何错都逮住然后打印个“过程失败”就结束。这种写法把异常处理变成了掩盖问题的工具脚本永远在“看起来挂了但没完全挂”的状态运行失去了告警的意义。推荐的捕获粒度是“按处理单元捕获异常”。以数据处理脚本为例每处理一条数据是一个独立的try块单条数据的异常不应该影响整个批次的执行。块内捕获后先判断异常类型属于哪个桶再走对应分支。中途如果遇到“未预期异常”就把失败数据标记出来等全部处理完后统一打报告。这样处理的好处是最大化脚本的吞吐能力。一个批次1000条数据其中3条格式异常脚本应该处理完其余997条最后告诉你“有3条数据异常详见日志”。而不是跑到第50条就整体崩溃剩下950条全部没处理。4.3 finally与资源回收异常处理里最容易被忽略的是资源回收。脚本打开文件、建立数据库连接、拿锁、申请临时目录这些都是吃系统资源的操作一旦在自己处理异常的逻辑里漏了释放轻则文件句柄泄漏重则数据库连接耗尽故障范围从脚本本身蔓延到整个服务。所有资源型的对象都要用with语法Python或者对应的try/finally结构Java、Go来管理。这里有一个我自己曾经踩过的坑脚本用Python连接了MySQL处理数据时一个字段抛了未预期异常我的异常分支做了记录但忘了关连接。脚本循环一跑就是几个小时连接池里的连接越积越多最后把开发环境数据库的连接数打满了整个组的同事都连不上库。后来排查了半天才定位到是脚本泄漏了连接。所以异常处理代码的书写原则是先用finally保证资源一定回收再在里面思考异常的具体分支。顺序反了经验教训就来了。4.4 兜底异常和退出码虽然按处理单元捕获了异常但整脚本层面也需要一个兜底。最外层try...except捕获所有漏网之鱼记录最完整的运行现场然后带着非零退出码退出。退出码是企业级脚本之间协作的基础契约0表示成功非0表示失败。你写的脚本如果可能被其他系统CI、调度平台、定时任务服务调用退出码就是它们判断脚本结果的唯一依据。我见过太多脚本“成功失败都返回0”的案例CI流程里脚本内部明明报错了阶段还是绿的问题被完美掩盖到发布上线才炸。这些问题在写脚本时多写一行sys.exit(1)就能避免。5. 重试机制弹性处理瞬时故障重试是修复瞬时故障性价比最高的手段但同时又是最容易写崩的一种机制。无脑重试不仅浪费资源还可能放大故障面甚至造成雪崩。重试机制的设计核心是做好两个判断哪些错误值得重试重试的节奏怎么控制。5.1 值得重试的错误清单适合重试的错误类型网络超时、连接被重置、HTTP 502/503/504、数据库连接池暂时无可用连接、分布式锁争抢失败、上游服务返回“正在限流请稍后再试”等。这些错误的共同点是“错不在你”换一个时间窗口去请求大概率能成功。不应该重试的错误类型认证失败401/403、参数校验失败400、数据结构错误、资源不存在404、幂等性无法保证的写操作没有唯一ID无法去重时。对这些错误做重试除了浪费时间和流量外没有任何意义。5.2 退避策略与抖动重试节奏是重试机制最核心的参数。先看一个反面典型固定间隔死循环重试。脚本每2秒重试一次任务一直失败就一直重试直到把日志灌满、把目标服务打挂。这在我见过的很多初版脚本里都有危机感极强。正确的姿势是指数退避。每次重试间隔翻倍比如第一次重试等2秒、第二次等4秒、第三次等8秒、第四次等16秒直到达到最大间隔一般上限60秒。光有指数退避还不够业界还会加一个“抖动”处理在退避计算出的等待时间上加上一个随机偏移量。为什么要加随机性因为如果上百个客户端同时触发瞬时故障大家第一次重试的时间点也完全一致重试请求会在同一秒内打向上游——这就是重试风暴。加抖动可以让重试请求在时间轴上散开降低对上游的瞬时压力。典型代码示例import random import time def retry_with_backoff(func, max_retries3, base_delay1, max_delay60): for attempt in range(max_retries): try: return func() except RetryableError as exc: if attempt max_retries - 1: raise delay min(max_delay, base_delay * (2 ** attempt)) jitter random.uniform(0, delay) # 全抖动也可以是delay的一半 time.sleep(delay jitter) logger.warning(retry attempt%s, delay%s, error%s, attempt 1, round(delay jitter, 2), exc)5.3 重试上限与总耗时的控制重试次数不能无限大。经验值是最多3到5次具体取决于重试成本和总耗时的预算。任何重试设计都要回答一个问题整体重试上去的时间加上单次处理的时间不能超过任务的整体SLA。举一个实际例子一个脚本任务要求整体10分钟内完成单次请求平均耗时1秒那么如果最多重试5次理论上最差情况会有约1分钟的时间消耗5次请求本身加退避等待。这个量级在SLA内没问题。但如果单次请求耗时已经到30秒重试5次就意味着最差情况要超过3分钟如果任务对耗时敏感就必须把重试次数往下调。我的习惯是在重试时把总耗时也作为一个隐含终止条件不管重试次数是否用完只要重试的开始时间距离首次失败已经超过N分钟立即放弃。5.4 重试的幂等与重复副作用重试最隐蔽的风险是重复请求带来的副作用。这个风险在PUT、POST这类可能改变服务端状态的接口上尤其严重。第一次请求其实已经到达服务端并执行成功了但响应在途中丢失客户端超时后出发重试服务端又执行了一遍——最终数据被处理了两次。解决这个问题的标准方案是幂等键每次请求生成唯一的idempotency_key服务端针对这个key做去重处理。如果上游不支持幂等键那就要谨慎决定是否对写操作执行重试。对于脚本内部的处理流程最稳妥的设计是“先查后写”数据写入前先查一下是否已经处理过处理过就跳过。这个机制在很多数据采集脚本里叫“断点续传”配合一个记录处理进度的状态表或文件重试时从断点继续而不是从零开始效率和稳定性都大幅提升。6. 实操案例从一个脆弱脚本到企业级脚本改造全记录理论说了不少下面用一个完整的实战案例来演示整个改造过程。场景设定为一个定时数据采集脚本每10分钟从第三方开放接口拉取当天的订单数据清洗后写入数据库。初始版本跑在个人开发机时没什么问题但部署到服务器上稳定运行后就不断出状况。6.1 初始版本的脆弱之处import requests import time def fetch_orders(): resp requests.get(https://api.example.com/orders, timeout5) data resp.json()[data] for order in data: # 清洗并入库 print(fhandle order {order[id]}) time.sleep(0.5) def main(): while True: fetch_orders() time.sleep(600) main()这段代码的问题列出来非常触目惊心没有断言接口返回的数据结构变了代码会在resp.json()[data]那句直接抛KeyError。日志等于没有只有一个print生产环境没人盯着终端看。没有任何异常处理任何一步出错都会导致整个脚本退出调度平台也没法判断失败原因。没有重试网络抖一下或者服务端临时不可用脚本就当场死亡。而且这个脚本还是单次任务设计一旦中途失败本次数据全部丢失。6.2 改进后完整骨架针对上面四个问题我逐一加固得到下面这个升级版骨架import logging import random import sys import time import requests from logging.handlers import RotatingFileHandler # ---- 日志配置 ---- logger logging.getLogger(order_pipeline) logger.setLevel(logging.INFO) _fh RotatingFileHandler(order_pipeline.log, maxBytes50 * 1024 * 1024, backupCount5) _fh.setFormatter(logging.Formatter( %(asctime)s [%(levelname)s] %(filename)s:%(lineno)s %(trace_id)s - %(message)s )) logger.addHandler(_fh) class RetryableError(Exception): 可重试异常网络、超时、5xx等 class BizError(Exception): 业务异常数据格式不符等跳过当前记录 def fetch_orders(trace_id): resp requests.get(https://api.example.com/orders, timeout10) if resp.status_code ! 200: raise RetryableError(fHTTP {resp.status_code}) data resp.json().get(data) # 断言校验必要字段 assert data is not None, fresponse data is None, raw{resp.text[:500]} assert isinstance(data, list), fdata should be list, actual{type(data)} return data def handle_order(order, trace_id): logger.info(handle order begin, extra{trace_id: trace_id}) # 核心清洗入库逻辑 # ... if not order.get(id): raise BizError(forder missing id: {order}) def run_batch(trace_id, max_retries3, base_delay1): for attempt in range(max_retries): try: orders fetch_orders(trace_id) for order in orders: try: handle_order(order, trace_id) except BizError as exc: logger.warning(skip order due biz error, %s, exc, extra{trace_id: trace_id}) continue return True except RetryableError as exc: if attempt max_retries - 1: logger.error(fetch orders exhausted all retries, %s, exc, extra{trace_id: trace_id}) return False delay min(60, base_delay * (2 ** attempt)) random.uniform(0, 1) logger.warning(transient error, retry in %.2fs, %s, delay, exc, extra{trace_id: trace_id}) time.sleep(delay) def main(): trace_id ftask-{int(time.time())}-{random.randint(1000, 9999)} logger.info(scheduler start, extra{trace_id: trace_id}) try: if not run_batch(trace_id): sys.exit(1) except Exception: logger.exception(unexpected fatal error, extra{trace_id: trace_id}) sys.exit(2) finally: logger.info(scheduler finish, extra{trace_id: trace_id}) main()这段代码虽然不长但四个优化点都落地了断言在fetch_orders里日志全程记录且带trace_id异常按RetryableError/BizError/大兜底三分类处理重试采用指数退避加抖动的3次重试策略。6.3 改造后的效果验证改造上线后我做了几个验证实验构建一个模拟接口每10次请求中随机返回两次503观察改造后脚本在24小时内是否出现过任务失败。结果处理稳定性非常好单次请求的失败都被重试消化了整体任务没有一次失败。而改造前的脚本在这种模拟故障下每次都会跑挂。另一个实验是故意修改接口返回结构把data字段改成items观察脚本行为。断言在第一时间抓住了结构变化打印出原始响应内容任务快速失败退出并带有非零退出码。调度平台收到失败信号后告警整个定位时间不超过1分钟。从这两个实验来看改造后的脚本在“抗瞬时故障”和“快速感知永久故障”上的表现都达到了预期稳定性维度算得上是“稳如磐石”了。7. 常见问题与排查技巧实录最后分享一些在实践中反复出现过的问题和对应的处理思路。这些问题在团队里新人写脚本时几乎都踩过一遍整理成速查表方便随时查阅。问题现象可能原因排查方法解决方案脚本运行产物正确但返回码是1逻辑执行成功但遗漏了退出码设置检查每个分支是否有return/exit明确设置返回码成功分支写exit(0)日志文件为空目录权限不正确或日志级别过滤掉检查日志目录权限临时用--debug参数启动启动时检测目录是否可写把DEBUG级别单独控制断言触发了大量误报断言写成业务规则、数据合法但值与昨天不同查看断言中的上下文信息确认到底校验的是什么划分数据完整性与业务规则避免一刀切重试导致接口被限流重试风暴多个任务同时失败同时重试检查重试日志里是否大量集中在同一秒指数退避加抖动增加全局限流捕获了所有异常但问题仍反复异常被吞掉只打了summary日志查看ERROR日志是否有堆栈在未知异常分支记录完整堆栈shell脚本重试逻辑写的乱用sleepgoto实现的循环不直观理顺逻辑改写成函数式重试多语言下都能用“循环计数器break”实现7.1 三个独家的排障技巧技巧一先看日志再动代码。调试任何脚本问题第一步永远是打开日志文件搜索对应的trace_id把整条执行链路从头到尾过一遍。很多问题在日志里看一眼堆栈就能定位直接翻代码反而会浪费大量时间。技巧二给重试加一个可视化的进度输出。重试过程中如果在日志里打一条从第几次重试、上次失败原因、等待什么时候再试那么在运维侧看这些日志会非常安心——你知道脚本没有死还在处理故障。这几行日志对于建立“脚本可靠”的信任感非常重要。技巧三给核心脚本做一次性的自诊断。脚本启动时用探针式断言检查一遍依赖项数据库连接、目录权限、环境变量、网络连通性一旦不符合预期立即失败并输出诊断报告。这样脚本出问题时第一个提示往往就是根因而不是需要人再去猜。7.2 脚本维护中的长期治理企业级脚本不是写完就完事了。我个人有一条经验每隔一段时间给脚本做一次稳定性回放。把过去两周的日志打开找出所有WARN和ERROR分析这些预警有多少被重试消化、有多少靠人工介入才恢复、有无可以继续优化的点。每做一次回放脚本的健壮性都会提升一个台阶因为你会发现很多之前没意识到的边界情况。另一个长期治理原则是脚本要能“安静地成功”。一个设计良好的脚本在运行一切正常时不应该打扰任何人只在异常时发出告警。日志记录要带好上下文信息告警要有关联的任务标识这样运维者在收到告警的第一时间就能知道是哪个任务出了什么问题而不用再去翻各种平台。8. 写在最后的一些体会做完这套企业级优化之后我最深的一个体感是脚本稳定性的提升靠的不是某一个“大招”而是把断言、日志、异常、重试这四件事都做到位之后产生的水桶效应。缺任何一块板子水桶都装不满。从投入产出比来看这是脚本开发里性价比最高的一组投资。断言和日志的代码量可能只占总代码的10%到15%但这10%决定了剩下85%的代码在出问题时是“可救”还是“不可救”异常和重试的代码量可能有20%但正是这20%决定了脚本在没有人类干预的情况下能坚持跑多久。最后再分享一个具体的小建议。如果此刻你手头有一个已经跑了一段时间的脚本但一直没做这四项加固不妨先别急着写新功能花一个下午专门给脚本加上这套“护甲”。先把重试的逻辑加上效果立竿见影再把日志从print换成结构化logger然后抽零碎时间把关键路径上的断言补齐最后一定要检查一遍异常处理的兜底和退出码。做完之后跑一周看看晚上睡觉时收到告警电话的概率会小一个数量级。这件事真的值。
返回列表