个人项目发版本这件事,很容易一直停留在手工作坊阶段:改完代码本地跑一遍打包脚本、手动改名带版本号、开浏览器传 GitHub Release、版本号在 package.json、标签、产物文件名里各写一遍——忘了哪处就漂移。本文以一个真实项目为例,把发版动作收敛成两条 git 命令:任意分支 git push 自动构建出产物(artifact 下载),git push --tags 推一个 v* 标签自动校验版本、构建并创建 GitHub Release。项目形态是 Node 全栈(Vue 3 前端 + Express 后端,Node 22 SEA 打包成 Windows 单文件 exe),但流水线设计与语言无关,照搬即可。
CentOS 7 离线环境从零部署 MySQL 物理备份体系:XtraBackup 2.4 与自动备份脚本安装教程
备份天天在跑、恢复点却停在33天前:XtraBackup增量备份脚本全面翻修实录
凌晨两点的定时备份,天天在跑、日志天天在写、备份目录天天生成——直到需要恢复的那一刻,才发现最后一个能用的恢复点停在 33 天前。
这是一套跑了很久的 CentOS 7 + MySQL 5.7 + Percona XtraBackup 2.4 组合:每周一次全量、每日增量、qpress 压缩、凌晨 cron 自动执行,看上去是教科书级的配置。但从 8 月 7 日起的每一次备份都失败了,而失败只安静地躺在日志文件里——没有任何推送、任何告警。根因是备份窗口撞上了 MySQL 5.7 免 redo 的在线 DDL,XtraBackup 主动中止;旧备份脚本的几个设计缺陷,又把”一次本可以重试的自中止”放大成了”静默断裂一个月”。
本文完整复盘这次事故,并给出一套翻修后的生产级备份脚本:基准有效性校验、DDL 窗口自动重试、半成品清理、链式维护、钉钉结果推送、本机防呆——外加一个很多人不知道的大坑:xtrabackup 的 --host 参数只是”协调端”,要备份的数据永远从本机磁盘复制。
网关日志零报错、首页接口全线404:一次JRebel引发的Zuul路由乱序排查实录
本地开发环境用一键脚本把整套微服务拉起来:网关、注册中心、认证、门户、几个业务服务。打开首页,能正常出登录页、能登录、主框架渲染出来——然后页面上的数据接口开始报错,字典、多语言资源全部拉取失败。直觉说”看网关日志”,打开一看:
网关日志从启动到现在,零 ERROR、零 Exception,干干净净。
日志无罪、配置没改过、代码没动过,这种”环境性灵异故障”最考验排查方法论。最终根因落在一个完全意料之外的地方:网关进程带着 JRebel agent 启动,导致 Zuul 路由表的绑定顺序被打乱,兜底路由抢走了所有通配子路由。整场排查没有看一行 Zuul 源码仓库、没有重启大法、没有玄学,靠的是判别实验、字节码反编译和一个很多人没用过的 JDK 自带工具——jdb。这篇完整复盘整个过程,并把用到的工具逐一讲透。
APS回写停摆故障排查与修复记录
第三方接口回写无声停摆:一次HTTP无超时引发的线程池耗尽故障复盘
概述
T 日下午,一套在产 MES 系统的第三方排产系统(下称 APS)工单状态回写链路发生静默失效:MES 侧所有业务功能(开工、报工、结单)完全正常,但对 APS 的状态推送全部停止,回写失败记录表自当日起不再产生任何新数据。故障持续约两天后被发现,重启服务即恢复,无业务数据损坏。
本文完整记录该故障的定位过程、根因机制、修复方案与验证方法。该故障的典型性在于:四个各自不算缺陷的设计组合在一起,形成了一个无任何错误输出、无任何自愈可能、且监控系统完全盲区的失效模式。
- 直接根因:HTTP 客户端未配置连接与读取超时,第三方接口瞬时半死(TCP 连接建立但不再回包)后,10 个推送工作线程永久阻塞在 socket 读取上;
- 放大因素:推送经固定大小线程池异步执行、队列无界、异常被静默吞掉;
- 恢复手段:仅能重启进程(无超时的 socketRead 永不返回,线程无法自行释放)。
大文件下载必失败、小文件全正常:一行removeChild引发的网关Broken pipe排查实录
老 MES 系统新上了一套异步导出:后台游标逐行读、流式写 Excel,完成后落下载记录并弹桌面提醒,用户去”下载中心”页面取件。上线当天就收到一条诡异的反馈:
- 几十 KB 的小文件,点击即下,一切正常;
- 72MB 的大文件,一点下载立刻失败,下载栏直接飘红。
与此同时,网关(Spring Cloud Netflix Zuul 1.x)日志里躺着与失败时刻精准对应的 WARN:
1 | o.s.c.n.z.f.post.SendResponseFilter : Error while sending response to client: java.io.IOException: Broken pipe |
报错在网关,现象在前端,最后”凶手”却锁定在页面里一行毫不起眼的”善后”代码上。这次排查横跨浏览器、nginx、网关、后端四层,值得完整复盘——因为每一层的日志都”无罪”,唯一有罪的代码行离报错现场隔了三层。
从选项截断到全局自适应:EasyUI下拉面板通用机制与三重陷阱
服役超过十年的 MES 前端,技术栈是 EasyUI 1.4.3 + jQuery 1.11.3 这对”考古级”组合。它有一个存在了同样十年的体验缺陷:combobox 下拉面板的宽度永远等于输入框宽度——这是框架默认行为。于是所有长选项都逃不过被截断的命运:”SMT贴片车间-第一生产线-回流焊工段-NS-04高端精密贴片机设备”在下拉里只剩前半截,用户靠脑补补全。
这类问题单看是小事,逐页修是不归路:全系统几十个页面、数不清的下拉框,每个页面写一遍 panelWidth 计算?下一个新页面照样忘。正确的解法是在全局扩展层做一个通用机制:改一处,全系统生效,且给页面留逃逸口。本文记录这个机制的完整设计、实现、三重陷阱踩坑实录,以及一套无头浏览器验证矩阵——作为年度复盘,这也是”老系统增强”这类工作方法论的一个样本。
一个后台线程断言异常如何杀掉整个插件功能:read-action线程模型踩坑实录
IntelliJ平台PsiReference双向导航:从isReferenceTo到word-index的架构权衡
需求:让框架式调用也能双向跳转
业务代码里大量存在这种框架式调用——方法名藏在字符串字面量里:
1 | // MyBatis 风格:字符串 = Dao 接口方法名 |
IDE 原生对这种字符串无能为力。插件的诉求是四向导航:
| 位置 | 操作 | 期望 |
|---|---|---|
Java "getXxx" 字符串 |
Ctrl+B | 跳 Dao 方法 / Moc XML |
| Dao 方法声明处 | Ctrl+B / Find Usages | 反查字符串 |
Mapper XML <select id> |
Ctrl+B | 弹「Dao 方法 + 调用点」 |
Moc XML name 属性 |
Ctrl+B | 弹 Java 调用点 |
这篇文章记录实现过程中的三次架构演进——每一次都是被真实现象(选择框不弹、进度条死循环、几万文件搜索)逼出来的重新设计。