资讯动态

让一个数据流水线连跑不出错:幂等、自愈、可判读

发布时间:2026/9/30 20:13:14 来源:尧图企业网站定制
这里写自定义目录标题让一个数据流水线连跑不出错幂等、自愈、可判读坑一任务跑了一半被杀数据变成「半成品」解法把「写入」定义成「补空」而不是「赋值」坑二任务死了但你不知道根因机器休眠把进程挂死了判据把「配对」变成一条明确的规则同时把任务参数调成「不容易死」坑三巡检脚本自己说谎根因日志里混进了一个不可见的字符修法教训巡检脚本必须被「反验证」让每一轮输出「可判读的结论」而不是一堆日志清单新的改变功能快捷键合理的创建标题有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能丰富你的文章UML图表流程图FLowchart流程图导出与导入导出导入让一个数据流水线连跑不出错幂等、自愈、可判读我维护了一个每天自动跑十几轮的数据采集任务跑了几个月。它要抓的数据源有个特点页面只保留最近三个批次过时就没了——漏一轮那批数据就永久缺失。这种任务最麻烦的不是写抓取代码而是「跑起来之后」怎么保证它半路挂了不会写出脏数据、挂了之后能自己修、以及你一眼就能看出它有没有出问题。下面是我踩过的三个坑和最终的写法。每个坑都有一个反直觉的地方。坑一任务跑了一半被杀数据变成「半成品」最早我的写法很直白抓一次、覆盖一次、写文件。# 反面教材defsave(records):withopen(data.csv,w)asf:forrinrecords:f.write(r)问题在「半路被打断」这个场景上。任务管理器杀进程、服务器重启、脚本自己抛异常——只要终止发生在写文件的中途你就得到一个残缺的表而且它看起来和正常文件一模一样。更阴险的是重跑。如果你在写之前先把内存里的记录拼好重跑时很容易「用这一轮的抓取结果覆盖掉上一轮的」——而这一轮可能刚好因为限流只抓到一半。于是重跑反而把好数据改坏了。解法把「写入」定义成「补空」而不是「赋值」我后来把唯一键定成(批次, 记录ID)加载成字典更新时只做一件事deffill(row,key,value):只在空的时候写入绝不覆盖已有值 —— 保证重复跑永远安全。ifvaluenotin(None,)andnotstr(row.get(key,)).strip():row[key]valuereturn1return0配合「读进来 → 改 → 整体写回」defupdate(fetched):storeload()# {(批次, ID): {...}}added0forrinfetched:key(r[batch],r[id])ifkeynotinstore:# 新记录才新增一行store[key]{k:forkinFIELDS}added1forfieldin(name,price,stock):fill(store[key],field,r.get(field))save(store)# 全量写回这个改动带来三个好处都不是理论上的重跑免费。同一轮跑十次结果完全一样。多轮累积。想看「某个字段是什么时候第一次出现的」时数据天然带着这个信息——因为第一轮写进去的值后来再也没被动过。可以放心加自动化。不用怕定时任务重复触发我的任务是真会重叠触发的。代价也要说清楚你放弃了「修正历史值」的能力。如果某一轮抓到的值是错的它会永远留在表里。我的处理是另开一列存「后补值」而不是覆盖原值——两个都留着判断留给下游。坑二任务死了但你不知道第二个坑更贵。我的任务由 Windows 计划任务在 WSL 里拉起日志显示某天的 18:00 那轮[18:00:01] START然后就没有了。没有错误、没有结束标记、什么都没有。按正常流程那里应该还有一行WSL_EXIT0。它的缺失才是唯一的线索。根因机器休眠把进程挂死了追下去发现那台机器当天反复睡眠/唤醒。18:00 任务被唤醒执行时wsl.exe卡在「启动虚拟机」这一步——连我自己脚本的第一行[启动]标记都没写出来说明 bash 根本没跑起来。然后计划任务有这么一个设置「如果任务运行超过 X 时间则停止」当时是 2 小时。于是 18:00 被拉起的进程在 20:00 被静默强杀退出码0xC000013A。一句话总结休眠挂住 → 超时上限静默回收 → 日志里只剩一个孤零零的 START。判据把「配对」变成一条明确的规则我现在的巡检只看一件事每一次START后面必须跟一个EXIT。有 START 无 EXIT 卡死或被杀要补跑。patre.compile(r\[([^\]])\]\s*(START|WSL_EXIT(-?\d)))rounds[]forlineinlog_text.splitlines():mpat.search(line)# 注意用 search不用 match原因见坑三ifnotm:continueifm.group(2)START:rounds.append([m.group(1),None])elifroundsandrounds[-1][1]isNone:rounds[-1][1]m.group(3)forstarted_at,exit_codeinrounds:ifexit_codeisNone:print(%s START - 无 EXIT 卡死/被杀要补跑%started_at)同时把任务参数调成「不容易死」光有判据不够还得降低死亡率。三个改动参数改前改后解决什么超时上限2 小时30 分钟挂死时 30 分钟就被回收不再占满两小时触发间隔每 4.5 小时每 1 小时任意一轮挂了下一小时自动补因为任务是幂等的多跑不亏唤醒执行未设开启睡眠中也能被唤醒执行——休眠挂死的直接原因注意这三个改动是互相配套的间隔改密的前提是任务幂等坑一的解法否则重复跑就是灾难。幂等是所有自动化的地基。坑三巡检脚本自己说谎这是我最想讲的一个坑因为它是假阴性——比报错更危险。加了「START/EXIT 配对」检查后巡检输出是这样12 次0 次没有正常收尾「0 次异常」。看起来很好。但我知道那天 18:00 出过事故。根因日志里混进了一个不可见的字符逐行调试发现18:00 那一行确实存在但它的行首被写进了一个字面的^C——不是控制字符就是两个普通字符被写进了重定向的日志文件。而我的解析用的是re.match(r^\[(\d{4})/(\d{2})/(\d{2})[^\]]*\]\s*(START|WSL_EXIT(-?\d)),line)# ^ 这个锚定 —— 就是罪魁祸首^锚定行首 match意味着只要行首多一个字符整行就读不到。于是 18:00 的START从统计里消失了。接下来发生的事很微妙真实日志: [18:00] START - 因为 ^C 前缀读不到 (没有 EXIT) [22:00] START [22:00] WSL_EXIT0程序读到的是22:00 的START然后 22:00 的EXIT——天衣无缝地配上了对。而 18:00 那个真正缺 EXIT 的轮次被彻底顶包。17:00 的事故被 22:00 的正常覆盖掉了。修法patre.compile(r\[(\d{4})/(\d{2})/(\d{2})[^\]]*\]\s*(START|WSL_EXIT(-?\d)))mpat.search(line)# search不锚定行首模式里也去掉 ^改完实测18:00:01 START - 无 WSL_EXIT 卡死/被杀要补跑 22:00:00 START - WSL_EXIT0 13 次1 次没有正常收尾与事实一致了。教训巡检脚本必须被「反验证」这件事给我一个方法论写完巡检拿一个你已知的、真实发生过的事故日志去跑它看它是否真的报警。如果当初我这么测过就不会有那次假阴性。能报错的巡检才是巡检不报错的可能只是装饰。让每一轮输出「可判读的结论」而不是一堆日志最后一个设计原则。日志有两种写法# 写法 A我最初的 INFO 开始处理 INFO 读取到 1234 条记录 DEBUG 跳过重复记录 id88 ... INFO 完成# 写法 B现在的 快照: 本轮新增 7 行 | 补字段 14 个 库存: 共 1523 行完整 1511 / 待补 12 异常: 1 次没有正常收尾18:00区别不在信息量在于判读成本。写法 A 需要你去翻、去猜「1234 条」算多还是少。写法 B 直接给出结论和异常每一行都可以被人或程序直接判定对错。我现在的规则是每轮日志的前 5 行必须能在 10 秒内读完并回答「这轮正常吗」。细节往下堆但结论必须放最前面。这也是为什么我把巡检和采集放在同一份日志里输出——出现问题时结论和原始细节在同一个文件里不需要跨系统拼线索。清单如果你要做一个长期跑的自动化任务这几条按优先级幂等——写入定义为「补空」而非「赋值」让重跑永远安全。这是地基。配对判据——每一轮开始必须有配对的结束标记缺一个就是异常。降低死亡率——超时上限收紧 高频触发 唤醒执行前提是幂等。巡检要反验证——拿已知事故的日志测试它别相信一个从不报警的巡检。结论前置——日志最初几行必须能回答「这轮正常吗」。别删除旧值——需要修正历史时另开一列而不是覆盖。这些做法没什么高深技术都是被三次真实的「数据悄悄错了」逼出来的。如果你的任务也是「漏一轮就永久缺失」的类型建议从第 1 条开始改。完新的改变我们对Markdown编辑器进行了一些功能拓展与语法支持除了标准的Markdown编辑器功能我们增加了如下几点新功能帮助你用它写博客全新的界面设计将会带来全新的写作体验在创作中心设置你喜爱的代码高亮样式Markdown将代码片显示选择的高亮样式进行展示增加了图片拖拽功能你可以将本地的图片直接拖拽到编辑区域直接展示全新的KaTeX数学公式语法增加了支持甘特图的mermaid语法1功能增加了多屏幕编辑Markdown文章功能增加了焦点写作模式、预览模式、简洁写作模式、左右区域同步滚轮设置等功能功能按钮位于编辑区域与预览区域中间增加了检查列表功能。功能快捷键撤销Ctrl/CommandZ重做Ctrl/CommandY加粗Ctrl/CommandB斜体Ctrl/CommandI标题Ctrl/CommandShiftH无序列表Ctrl/CommandShiftU有序列表Ctrl/CommandShiftO检查列表Ctrl/CommandShiftC插入代码Ctrl/CommandShiftK插入链接Ctrl/CommandShiftL插入图片Ctrl/CommandShiftG查找Ctrl/CommandF替换Ctrl/CommandG合理的创建标题有助于目录的生成直接输入1次#并按下space后将生成1级标题。输入2次#并按下space后将生成2级标题。以此类推我们支持6级标题。有助于使用TOC语法后生成一个完美的目录。如何改变文本的样式强调文本强调文本加粗文本加粗文本标记文本删除文本引用文本H2O is是液体。210运算结果是 1024.插入链接与图片链接: link.图片:带尺寸的图片:居中的图片:居中并且带尺寸的图片:当然我们为了让用户更加便捷我们增加了图片拖拽功能。如何插入一段漂亮的代码片去博客设置页面选择一款你喜欢的代码片高亮样式下面展示同样高亮的代码片.// An highlighted blockvarfoobar;生成一个适合你的列表项目项目项目项目1项目2项目3计划任务完成任务创建一个表格一个简单的表格是这么创建的项目Value电脑$1600手机$12导管$1设定内容居中、居左、居右使用:---------:居中使用:----------居左使用----------:居右第一列第二列第三列第一列文本居中第二列文本居右第三列文本居左SmartyPantsSmartyPants 是一个文本转换工具主要功能是将普通的 ASCII 标点符号自动转换为更美观的印刷体标点符号。例如原始符号转换后说明引号“引号”直引号变弯引号单引号‘单引号’直单引号变弯单引号--–两个连字符变短破折号---—三个连字符变长破折号...…三个点变省略号创建一个自定义列表MarkdownText-to-HTMLconversion toolAuthorsJohnLuke如何创建一个注脚一个具有注脚的文本。2注释也是必不可少的Markdown将文本转换为HTML。KaTeX数学公式您可以使用渲染LaTeX数学表达式 KaTeX:Gamma公式展示Γ ( n ) ( n − 1 ) ! ∀ n ∈ N \Gamma(n) (n-1)!\quad\forall n\in\mathbb NΓ(n)(n−1)!∀n∈N是通过欧拉积分Γ ( z ) ∫ 0 ∞ t z − 1 e − t d t . \Gamma(z) \int_0^\infty t^{z-1}e^{-t}dt\,.Γ(z)∫0∞​tz−1e−tdt.你可以找到更多关于的信息LaTeX数学表达式here.新的甘特图功能丰富你的文章2014-01-072014-01-092014-01-112014-01-132014-01-152014-01-172014-01-192014-01-21已完成进行中计划一计划二现有任务Adding GANTT diagram functionality to mermaid关于甘特图语法参考 这儿,UML图表可以使用UML图表进行渲染例如下面产生的一个序列图王五李四张三王五李四张三李四想了很长时间, 文字太长了不适合放在一行.你好李四, 最近怎么样?你最近怎么样王五我很好谢谢!我很好谢谢!打量着王五...很好... 王五, 你怎么样?关于UML图表语法参考 这儿,流程图链接长方形圆圆角长方形菱形关于Mermaid语法参考 这儿,FLowchart流程图我们依旧会支持flowchart.js的流程图语法Created with Raphaël 2.3.0开始我的操作确认结束yesno关于Flowchart流程图语法参考 这儿.导出与导入导出如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到文章导出生成一个.md文件或者.html文件进行本地保存。导入如果你想加载一篇你写过的.md文件在上方工具栏可以选择导入功能进行对应扩展名的文件导入继续你的创作。mermaid语法说明 ↩︎注脚的解释 ↩︎

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

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

免费获取报价 →
↑