
同事看到我在排查线上问题时往代码里塞了两行日志半开玩笑地说都什么年代了还原始人打法。我回了一句对我现在的身份就是caveman。这不是认怂是我这几年踩完一堆工具坑之后主动养成的习惯越是在复杂系统里拔不出来越要切换到最原始的思考方式和排查手段。这篇博客想聊聊我理解的caveman式工作法——它为什么好用适合谁用在什么情况下必须果断放弃。内容不算新鲜但每一部分都是我从真实故障里换来的希望能给那些同样被工具链拖得喘不过气的人一点参考。1. Caveman不是骂人我理解的原始人做事三板斧1.1 为什么大家开始把原始人当成一种褒义词先说说这个词为什么会钻进我的脑子。身边越来越多朋友会把caveman挂在嘴边用来形容自己写代码的风格、解决问题的习惯甚至生活方式。仔细观察会发现大家不是在向往茹毛饮血而是在表达一种对复杂工具体系的厌倦。这几年软件领域的工具链膨胀得惊人一个简单需求往往要拉起容器集群、配置中心、监控平台、链路追踪、消息队列工具本身的成本比业务问题还大人被卡在工具链里动弹不得。caveman这个词在这种情绪下变成了一个信号我想回到用手边最小工具也能把事情做成的那一刻。我并不是说工具是敌人。工具解决过很多问题我的意思是当一个人被工具绑架到连第一步都迈不出去时原始人的直接与莽撞反而成了稀缺能力。它至少能保证你先把问题抓住哪怕抓得不好看。这套逻辑不止适用于技术人产品、运营、内容创作者也一样——你手里已有的东西往往比你新找来的东西更接近答案。1.2 第一板斧用手边已有的东西解决问题这是我践行caveman的首要原则。遇到一个新问题不要急着去搜索最佳实践、企业级解决方案先低头看看手边已经有什么。排查网络不通第一反应不是打开分布式追踪平台而是用curl -v、telnet、nc这些从Unix诞生就存在的命令排查服务变慢第一反应也不是立刻上APM而是先看日志、抓线程栈。我第一次意识到这一点是在一次线上事故里某服务偶发无响应链路追踪平台显示一切正常APM上的指标也都正常我在仪表盘上翻了一个多小时。后来同事只说了一句你直接看JVM线程栈不就行了。jstack三秒之后问题一目了然。工具的新旧不重要有效才是硬道理。后来我做小工具、写脚本也坚持这条原则用系统自带的分析命令用已经存在的配置文件用团队已经熟悉的语言。别人问为什么不引入某个更好用的库我的答案通常是因为手边的方案已经在解决问题了切换的收益还不够覆盖成本。这不是守旧是对投入产出比的敏感。1.3 第二板斧直接看第一手现象不隔一层工具看二手数据大多数监控工具本质上是在转发或聚合底层信息这个过程中很容易丢掉关键字段。我见过很多次工具说没有错但用户就是报错的情况最后都是回到原始日志才找到原因。有一次接口偶发异常调用链追踪页面把这次请求标记为正常因为异常分支的数据没有上报上去真正能看出问题的是服务端最朴素的那行访问日志和异常堆栈。这个经历让我养成了一个习惯在证据链最底层的地方看第一手现象而不是先看转述者怎么说。当然这不代表我从不看监控平台。在确认第一手数据方向之后我会用监控平台验证覆盖率、看趋势。关键是顺序不能反——先看现场再看汇总否则很容易被二手数据带着走。如果你经常遇到排查了很久才发现真相在某个不起眼的日志里的情况那就说明你的整个排错姿势该改了。1.4 第三板斧先把主流程跑通再考虑优雅caveman的最后一板斧是针对我自己这种有点完美主义倾向的人。以前写一个模块总想把目录结构、抽象层、设计模式一次到位结果经常是设计文档写了一堆核心功能迟迟没动。后来我强迫自己走最小路径先用最直接的方式把主流程跑通甚至可以允许代码难看一点允许配置先写死。跑通之后再根据真实压力决定要不要调整结构。举个实际例子之前要给夜间任务加上失败重试机制。我一开始想得很复杂独立重试服务、消息队列、延迟队列、死信处理。后来压住自己先在一个服务里用最原始的定时扫描加状态字段实现跑了两个星期验证业务逻辑没问题再逐步替换成正式方案。如果一开始就上重型架构估计三个星期都出不了第一版。很多系统的问题不是不够先进而是主流程还没跑通就背上了一堆先进包袱。2. 现代人为什么把简单的事情搞复杂三个典型的反Caveman陷阱2.1 工具通货膨胀装备先行打猎在后我年轻时也干过这种事。接一个小项目第一周不写业务代码先列基础设施的建设清单容器编排、CI/CD流水线、日志收集、监控告警、网关、配置中心、消息队列。每一样都觉得是刚需每一样都折腾很久。结果一周过去了负责的核心流程一行代码都没动。后来换了一种方式先在一台普通的服务器上把服务跑起来用进程管理器守护它日志直接写到文件里监控用最普通的探活。一个下午就把第一版立起来了先让用户用上基础设施之后再按真实规模慢慢补。问题出在哪里出在我把装备水平当成了项目成熟度。但工具的价值在于解决问题不在于堆数量。如果一个工具不能在你遇到的具体问题上节省时间它就是在消耗时间。这也是我在选择工具时用的一个很粗暴的标尺现在我遇到的最痛的问题是什么这个工具能不能直接解决如果暂时没有痛点就别为了想象中的未来去搭一堆东西。基础设施是可以晚一点再还的债真到了有压力的时候再还往往更符合实际需要。2.2 调试军备竞赛仪表盘很全真正有用的字段一个没有这个坑比工具膨胀更隐蔽。有一次排查一个偶发性能问题我的第一反应是打开APM、链路追踪、日志检索平台折腾了快二十分钟才意识到我感兴趣的参数在平台上根本查不到因为埋点根本没覆盖那些字段。最终帮我定位的还是一段最原始的带时间戳的日志加上两次线程快照。漂亮的仪表盘不能替代关键观测点你追查问题时拼的不是有多少监控项而是在出事的那一刻有没有记录下足够还原现场的信息。所以我现在的做法是排查阶段优先把精力放在观测点是否覆盖要害上用最轻量、最直接的打点方式去补充现场信息。工具平台可以在确认根因后用来长期布防但不要在事故现场抱着一个看起来很全的仪表盘当救命稻草。这就像破案一样监控摄像头再多如果关键的街角没拍到你依然得靠目击者的原始口供。2.3 完美主义瘫痪为了以后好维护先重构三个月这个陷阱几乎每个开发者都掉过。手头需要一个简单功能但看现有代码结构不顺眼于是决定先重构再开发。一上来就酝酿领域驱动设计、事件驱动、新框架结果过去两周交付时间毫无悬念地往后拖。我在这个坑里栽过不止一次后来给自己立下一个规矩改动老代码时第一版本尽可能在尽量小的改动范围内完成功能把结构升级和功能开发拆成两件事。如果结构问题确实影响到新增功能了我可以专门为它开一个独立任务但它不能和交付某个功能混在一起。这并不是说重构不重要。恰好相反我认为重构很重要只是它需要一个明确的触发条件和收益证明。caveman的原则是不要因为未来的优雅拿走当下的可用。先把当前行为搞搞清楚再考虑怎么组织代码。见过太多重构到一半、业务需求却变了的烂尾项目原始的能跑版本反而成了最后的救命收尾。3. 实战复盘一次线上超时问题我用Caveman方式锁定根因3.1 现象监控平均耗时正常用户却在骂有一回线上某核心接口出了很诡异的问题。监控面板显示平均耗时80毫秒看起来一切正常但不断有用户反馈页面卡顿、操作超时。后来我才发现平均值掩盖了真相大量请求只要几十毫秒少数请求却要30秒最后客户端直接报错。这类问题用平均指标永远是看不见的真正能看到的是分位线但即便分位线也不告诉你为什么会慢。那一刻我关掉了面板决定回到最原始的办法。这个案例我印象很深因为它特别典型地展示了监控正常和系统正常之间的鸿沟。对于偶发性能问题平均值是最不可信的指标就像我们人均一只鸡这句话对饿肚子的人毫无意义一样。之前的仪表盘上那些健康指标反而成了误导方向的噪音。3.2 第一轮排查先收集原始日志而不是打开更多工具我先做的三件事没什么技术含量但非常有效。第一在接口入口、数据库查询前后、外部HTTP调用前后、返回出口分别加上带毫秒时间戳和请求ID的日志第二在偶发超时出现时用jstack连续抓三次线程快照第三把异常请求的时间分布拉出来。日志打出来之后立刻发现两个线索超时请求集中在某个新扩容节点的几次重启之后而且总是出现在实例启动后的前几分钟。线程快照初看没有死锁但能看到大量业务线程停在某个加解密组件的方法上。当时的日志大概是这样的[2025-01-15 03:12:01.123][req-8899][entry] receive request, traceIdabc [2025-01-15 03:12:01.125][req-8899][db-before] start query user [2025-01-15 03:12:01.180][req-8899][db-after] query done, cost55ms [2025-01-15 03:12:04.301][req-8899][crypto-before] start encrypt payload [2025-01-15 03:12:34.110][req-8899][crypto-after] encrypt done, cost29809ms [2025-01-15 03:12:34.115][req-8899][exit] response ok, total30992ms上面这段日志一出来问题边界立刻就缩小了数据库很快外部调用也不是瓶颈卡就卡在加密这一步而且一卡就是29秒多。很多线上问题之所以难查就是因为从来没有在这个粒度上记录过耗时所有模块混在一起你根本不知道时间花在哪一段。3.3 二分法沿着嫌疑范围一路砍下去面对一堆可能性我没有直接升级监控体系而是用排除法缩小范围。我画了一张很简陋的表把候选原因一个一个划掉嫌疑对象验证手段结论数据库连接池慢查询日志对比无对应慢SQL排除网关层时间分布对比问题只出现在单一节点网关对所有节点统一排除第三方外部接口外部调用前后耗时差耗时正常排除实例自身初始化/锁线程快照与首请求复现高度吻合锁定这种砍嫌疑范围的做法有点像二分法。每次排除一大半最后留下的就是真的现场。人脑不擅长同时跟踪五六个变量但很适合在清晰的表格里把选项依次划掉。3.4 根因新依赖的懒加载加一个共享锁把第一次请求全堵住了为了验证初始化导致锁等待的假设我用最原始的方式写了一个几十行的压测脚本在测试机上对那个节点连续发请求。第一次请求果然明显慢后面的请求立刻恢复正常。我用启动日志又确认了一次某新引入的加密组件是懒加载的第一次被调用时才触发初始化初始化会持有进程内的共享锁耗时接近3秒。线上所有并发请求在初始化完成前会全部堵在锁上排队直接表现为30秒超时。所谓懒加载就是组件不肯在启动时干活一直拖到第一次真正使用才做初始化。正常情况下这没什么问题但它偏偏用在了一个高并发入口上第一次请求就成了替罪羊。修复非常简单改成在启动阶段就完成预热初始化释放后再放开对外流量// 修复前懒加载 全局锁第一次请求被初始化拖住 Cipher.getInstance(ALGORITHM).init(ENCRYPT_MODE, key); // 修复后应用启动阶段显式执行一次预热 KeyPoolHolder.warmup(); // 在启动流程里强制触发一次完整初始化修完以后我特意在压测环境连续跑了两个晚上新老节点都不再出现启动后首请求变慢的问题。整个修复过程没有用分布式追踪没有写复杂的监控规则核心就是压测脚本加三行日志。3.5 复盘为什么高级工具没帮上忙事后想想这个问题的特征决定了它特别容易被高级工具错过偶发、只出现在新实例、只在第一次请求时触发。链路追踪虽然记录了那次30秒请求但采样率不高初始化逻辑也没有对应的埋点平台里看到的是外部依赖慢或者不确定的抖动。如果没有早期那几行最原始的日志团队很可能在错误的方向上反复验证。这次之后我彻底明白了一个道理工具是帮你放大视野的但它不能替你建立对系统真实行为的认知。在还没有第一手证据之前最应该做的不是搭建更漂亮的看板而是找到能还原现场的那几行原始信息。4. Caveman Debugging随身手册我在排查复杂问题时的固定动作4.1 五步固定流程经过几次类似的排障我把caveman debug整理成一串固定动作每次遇到疑难杂症就按这个顺序走。第一步复现。能复现的问题才谈得上定位复现不了就先记触发条件。第二步最小化。把无关模块、无关请求全去掉保留最可能出问题的链路降低干扰。第三步加观测。在两个模块交界的临界点上打上带时间戳、带参数、带返回值的日志。第四步做对照。控制变量例如拿正常节点和异常节点、旧版本和新版本直接对比。第五步修复并回归。修完不是结束至少要观察一个完整周期确认问题不会再冒出来。这套流程在绝大部分场景里比打开十个平台看指标更接近真相也更节省时间。它本质上是在逼你把问题翻译成一个可以亲手验证的假设而不是在一个黑洞般的系统里做各种猜测。4.2 常见场景对应的原始观测手段我整理了一张自己的侦察表遇到什么症状最先用手边的什么工具看。不追求新只追求快症状原始观测手段为什么先试它网络不通或握手慢curl -v、telnet、nc直接展示TCP握手过程不经过任何聚合进程无响应或线程卡死jstack连续抓2到3次能看到线程等锁的位置直击阻塞点内存异常上涨jstat加GC日志先判断GC是否频繁再决定要不要堆转储接口偶发错误入口与出口打带参数日志锁定真实入参与返回还原请求的完整轨迹慢SQL打开数据库慢日志拿到真实SQL和真实执行计划而不是看聚合报表在没有引入重型系统之前这些原始手段从没让我失望过。它们的好处是简单直接、没有采样率、不被聚合逻辑扭曲呈现的就是系统最原始的状态。对大多数中小团队和中小系统来说这一张表已经能覆盖七八成疑难杂症。4.3 三条踩坑换来的铁律第一条一次只改一个变量。很多人排查时怀疑两三个点于是同时改了两三个地方问题暂时表面上消失但根本无法确认是谁的功劳下回再出现一切又要重来。宁可多跑几轮一次只动一个点。这个道理听起来简单可真到了凌晨三点线上告警的时候很少有人能做到。第二条先怀疑自己再怀疑框架最后才怀疑底层平台。实际排查中绝大多数所谓环境问题最后还是自己的代码或配置的锅。诚实一点能省下很多互相推诿的时间。我见过太多人在评论区争论是不是虚拟机时钟漂移、是不是云厂商网卡问题最后真相只是少配置了一个超时参数。第三条要打日志就趁早。不要去猜哪里可能出错而是在关键路径上先铺好带时间戳的原始日志printf也好console.log也好不丢人。丢人的是等到故障发生以后才发现你从来没有在正确的位置留下任何可以还原现场的信息。5. Caveman的边界什么时候该从原始人进化为现代人5.1 原始方式也有成本别神化它坦白说caveman式做法不是万能的。你在三台服务器上手动执行命令时这一刻的做法就没有可复制性你依赖个人经验记住排查链路时下一次换一个人来这个故事就断了当问题完全复现不了、只能大海捞针般等下一次现场时只靠三个printf根本不够用。原始手段最大的成本是人力和时间它不适合大范围、高频、需要可追溯证据的场景。所以我从来不说以后都别再上工具了而是说先看值不值得。如果一个人把所有排错都寄托在手动日志上遇到大规模分布式系统就会直接崩溃。现代系统里有些问题是并发量触发出来的单机复现不了有些问题是数据分布触发的测试环境根本没有那批脏数据。这时候你需要的恰恰是全量采样、分布式追踪和集中式日志平台。caveman不是目的只是一个阶段性的手段。5.2 我什么时候会果断升级工具有几个信号出现我基本会停止caveman路线。同一类问题在一周内出现三次以上说明它已经是频率问题值得用自动化工具长期观测排查过程需要同时登录两台以上机器手动核对数据说明人工操作拖不动规模需要集中的日志和追踪来兜底结论要让团队其他人共同确认散落在各节点上的几行日志不够用必须落到统一的记录和文档里最典型的是完全无法本地复现的偶发问题没有采样率和全量监控你就是在盲人摸象这时候该上分布式追踪和全量日志采集让系统自己去复现而不是靠人整晚蹲在键盘前。我自己的经验是很多人不是死在用了工具而是死在不知道该在什么时候用工具。不要因为某个方案在讨论帖里被说成企业级最佳实践就默认它适合你的现状。规模、频率、协作人数这三个因素决定一切。5.3 我的决策表从原始到现代的切换时机我自己平时会用一个简单的判断思路来看该用什么排查手段问题场景我的策略什么时候切换成机器或工具本地开发调试日志加调试器最快逻辑简单不需要平台线上偶发问题原始日志重建现场高频重复出现时上自动监控分布式长链路直接使用分布式追踪多服务协作人工拼链路不现实高并发实时系统可观测平台是底线一开始就要全量采样不能靠事后补说到底原始和现代不是对立关系而是工序关系先用最笨的办法把真相抓住再用现代工具把防线固化住。很多团队的问题是顺序颠倒还没搞清楚系统真实行为就先把监控大屏和告警规则堆满结果每天都在被误报告警消耗精力。5.4 我是怎么理解真正的Caveman的写到最后我想把最想说的一句话放在这里真正的caveman不是拒绝工具而是拒绝被工具拖着走。它能清楚地区分我现在需要的和我以后可能需要的。排查问题时先把复杂的系统拆成简单的现象用手边的命令确认系统真实行为确认之后再考虑要不要上监控、上追踪、上平台。我现在遇到线上问题仍然会第一反应去看最底层的时间戳、日志、线程状态而不是先打开十个花花绿绿的看板。这套顺序帮我省下了很多加班时间也让我重新体会到解决问题的原始乐趣。如果你也在工具链里越陷越深不妨也试试这个返祖姿势——先当原始人把问题抓出来再当现代人把防线建起来说不定比想象中管用。