自定义博客皮肤VIP专享

*博客头图:

格式为PNG、JPG,宽度*高度大于1920*100像素,不超过2MB,主视觉建议放在右侧,请参照线上博客头图

请上传大于1920*100像素的图片!

博客底图:

图片格式为PNG、JPG,不超过1MB,可上下左右平铺至整个背景

栏目图:

图片格式为PNG、JPG,图片宽度*高度为300*38像素,不超过0.5MB

主标题颜色:

RGB颜色,例如:#AFAFAF

Hover:

RGB颜色,例如:#AFAFAF

副标题颜色:

RGB颜色,例如:#AFAFAF

自定义博客皮肤

-+
  • 博客(100)
  • 收藏
  • 关注

原创 别轻视版本号,软件版本规范背后的工程陷阱

出问题之后,不知道是功能迭代引入的,还是只是微调,回滚决策很难快速下定。比较稳妥的方案是,对外展示语义化版本,内部制品同时携带 git commit hash、构建时间、流水线编号,制品仓库里每一份包都可以反向查到代码提交记录和构建流水线,做到任何版本都可溯源。成熟的做法是锁定依赖版本,使用锁文件,并且定期主动升级依赖,而不是交给构建工具随机拉取。如果团队没有统一的版本约定,前期看起来省事,等到项目变大、多分支并行、依赖变多之后,版本混乱带来的排查成本和发布事故,会远超规范版本管理的成本。

2026-11-08 09:23:44 147

原创 容器资源限制,不是配完 CPU 和内存就万事大吉

以 Java 应用举例,容器内存 limit 是 1G,JVM 堆直接设成 900M,看起来留了余量,但 JVM 之外还有堆外内存、线程栈、元空间、直接内存、socket 缓冲区。容器默认的进程句柄数、最大进程数,如果没有调整,业务服务在创建大量协程、线程或者子进程时,会直接报无法创建进程、打开文件失败。我们团队现在上线容器的检查清单,除了 CPU 内存的 request/limit,还要核对:ulimit 配置、swap 关闭、QoS 等级、OOM 优先级、存储 IO 限制、句柄监控。

2026-11-07 08:38:18 214

原创 别把限流做成简单的 “计数器拦截”

令牌桶允许一定程度的突发,更适合业务接口,可它同样有坑:令牌的生成速度、桶容量配置不合理,要么起不到限流效果,要么限流过于严苛。所以不是所有场景都要上分布式限流,很多时候,单机限流叠加合理的扩容,性价比更高,只有需要全局统一配额的场景,才引入分布式方案。理想的限流应该前置,在网关层就完成初步拦截,只把通过校验的流量放行到后端服务,减少无效消耗。这套简单的计数器逻辑上手快,初期也能挡住突发流量,但在真实业务场景里,粗糙的限流策略经常出现两种极端:要么该拦的没拦住,要么误杀正常用户,限流失去了防护的意义。

2026-11-06 08:29:17 187

原创 不要盲目追求无锁编程

反倒是无锁方案,依赖 CAS、内存屏障、原子指令,在高竞争时会出现大量自旋重试,CPU 空转,CPU 占用飙升,整体吞吐量反而不如一把简单的锁。这类 bug 没有固定复现条件,和 CPU 核心、线程调度、时序强相关,可能测试环境跑千万次都正常,上线之后才随机触发,排查成本极高。在不少开发者的认知里,锁代表阻塞、代表性能损耗,只要能去掉锁,程序就一定更快、吞吐更高。但线上落地久了会发现,无锁不是银弹,它带来的收益和复杂度往往是绑定在一起的,很多场景下,盲目去掉锁反而会埋下更难排查的隐患。

2026-11-05 08:57:30 195

原创 不要把缓存当成数据库的 “替身”

缓存没有数据库那样严谨的 DDL 变更机制,它不会自动做数据迁移,旧数据会一直待到过期,故障潜伏期很长。一个稳健的设计,必须预设 “缓存随时可能失效、不可用”,在缓存故障时做限流、熔断,而不是把所有流量交给数据库硬扛。更新数据时,无论是先更库再删缓存,还是先删缓存再更库,都不存在绝对零不一致的方案,只能降低窗口时间。缓存几乎是后端系统里的标配方案,很多同学接触缓存之后,很容易形成一个简单的思路:热点数据查得慢,就丢进缓存,请求优先读缓存,命中就直接返回,没命中再查库。,而不是数据的可信源。

2026-11-04 09:20:26 186

原创 静态资源预加载不是越多越好,踩过一次页面加载雪崩

上线之后问题慢慢冒出来,并不是直接打不开,而是部分用户访问页面的时候,网络请求扎堆,出现大量请求排队,反而首屏渲染卡住,白屏时间变长。如果预加载的资源缓存策略很短,或者资源版本号管理混乱,大量预加载请求同时打到 CDN 节点,缓存失效的时候瞬间回源,短时间放大回源压力,严重时会影响整个域名下所有资源访问。第五,做好监控,观察两个指标:资源请求瀑布、重复请求数量,同时对比不同网络档位(4G、3G、弱网)下的 LCP、TTI,不能只看 wifi 环境下的数据。的资源,优先级很低,浏览器空闲的时候才拉;

2026-11-03 08:35:04 139

原创 负载均衡会话亲和不是银弹,很多场景用上反而埋坑

滚动发布的时候,一台实例下线,原本绑定到它上面的用户,会在下次请求时重新调度到别的节点。有些特殊场景确实适合:比如有长连接、流式会话,像 WebSocket 持续推送,或者一些老旧第三方 SDK,本身不支持分布式会话,改造代价极大,短期过渡可以启用。查配置才发现,他们在负载均衡层开了会话亲和,也就是常说的粘性会话,初衷很简单:前端请求要连着访问同一个实例,避免丢临时状态。如果业务用户生命周期很长,这个倾斜会越来越明显,运维就算扩容,新加的实例也只能分到新进来的用户,存量流量几乎不会流过去。

2026-11-02 08:23:33 82

原创 Java 里 CompletableFuture 踩坑:异常没接住,线程直接消失

哪怕后面其他任务正常返回了,只要最先结束的是失败任务,不去处理,整个链路直接异常。想要 “只要有一个成功就返回成功,失败的忽略”,不能简单依赖 anyOf,需要给每一个子任务先做 exceptionally 兜底,把异常转成标记,再在外面做判断。我们那次故障,除了异常静默,还有一个叠加因素:统计任务并发量大,全都抢占 commonPool,正常的接口异步逻辑拿不到线程,响应时间拉长。另外就是线程池的问题。我原先的认知是,任务里面抛异常,最多这个 future 标记成异常完成,其他任务照常跑,不会影响整体。

2026-11-01 11:02:27 164

原创 Java 线程池隐性故障的排查与治理

线程池线程打满之后,新来的任务会进入队列,队列满了就触发拒绝策略。线程池的线程是复用的,上一个任务跑完,没有清理 ThreadLocal,下一个任务拿到同一个线程,就会读到上一个任务残留的上下文。上线之后,大部分时候它安安静静,一旦出问题,往往是突发的接口阻塞、请求堆积、服务卡死,很多故障根源藏在线程池的细节里,GC、CPU、内存指标看着都正常,排查很久才定位到线程池。用好线程池的核心,不是调一个完美的线程数,而是控制风险、做好隔离、补齐监控,让线程池的异常可以被及时发现,而不是默默堆积直到服务崩盘。

2026-10-31 07:47:57 144

原创 聊聊 Prompt 工程里容易被忽略的边界与工程化误区

最开始接触大模型的时候,很多人理解的提示词,就是写一段长长的文字,把需求讲清楚,给模型定好角色、格式、语气。更好的方案是分层防护:prompt 里做格式引导,代码层增加容错解析,用正则提取目标内容,解析失败之后,有限次数重试,重试也失败则直接走兜底逻辑,而不是依赖模型一定听话。比如客服场景,调优的时候都是规范的用户提问,上线之后遇到口语化简写、方言式表达、带情绪的吐槽、分段杂乱的长文本,模型很容易不遵守输出格式,或者触发错误分支。理解这一点,就不会一味把所有稳定性的希望,都寄托在一段精心撰写的文字上。

2026-10-30 08:18:24 168

原创 别小看序列化:版本兼容才是长期维护里最头疼的事

比如一个 int 类型字段,老版本不存在,新版本解析出来变成 0,业务里 0 是合法业务值,就分不清是消息里本来就是 0,还是字段缺失自动填充的。新版本服务发给老版本实例的响应,老的序列化框架直接识别到未知字段,有的直接抛异常打断调用,有的直接静默丢弃整条消息,上游一下子收到大量失败。很多人测试兼容的时候,只测新服务发、新服务消费,或者老服务发、新服务消费。缓存:结构不兼容变更,要配套缓存刷新或者版本隔离,新版本读取时识别旧版本缓存,做兼容转换,或者按版本前缀隔离 key,避免新旧结构混读。

2026-10-29 09:07:02 190

原创 定时任务不是写个 cron 就完事,那些容易被忽略的调度陷阱

这种方式非常脆弱,一旦 A 任务执行超时,跑到 1 点 40 才结束,B 已经先跑了,拿到不完整的数据,统计结果出错。某天数据库慢查询导致单次任务跑了 8 分钟,新的调度请求持续进来,任务实例越堆越多,线程池被占满,数据库连接被耗尽,最后连锁影响其他定时任务和同步接口。很多开发的理解很简单,写好业务函数,配一个 cron 表达式,丢进调度框架,就可以不管了。但它和在线接口不一样,接口出问题马上能收到用户反馈,定时任务大多在低峰期跑,问题可以潜伏很久,等到对账的时候才集中爆发,修复和数据回补成本很高。

2026-10-28 08:50:29 119

原创 程序里的 “伪异步”,看着不阻塞,实则没提速

表面上是异步调度,实际上被锁强行串行化。异步这两年用得很多,Go 的 goroutine、Java 的 CompletableFuture、Node.js、Rust 的 async/await,大家遇到慢 IO,第一反应就是改成异步,想把等待时间利用起来。我之前也很信奉这套思路,直到在线上排查一个接口耗时居高不下的问题,扒开代码才发现,写的只是一层外壳像异步的代码,本质上根本没有并发收益,也就是常说的伪异步。但写烂的伪异步,并发上来之后,阻塞、排队、锁竞争集中爆发,故障会比同步代码来得更快,而且排查更难。

2026-10-27 08:37:43 109

原创 消息队列消费堆积,别上来就加消费者实例

RabbitMQ 如果是普通的轮询模式,虽然没有分区的硬限制,但如果消息本身是短任务,且消息生产是批量瞬时爆发,扩容带来的收益也会边际递减。刚扩容完的一小段时间,消费速度确实上来了,监控上消息的消费曲线陡增,大家都松了口气。可没过多久,情况急转直下:数据库的连接瞬间打满,大量慢查询出现,消费开始大面积报错,失败的消息重新入队,堆积量反弹得比之前更高,形成恶性循环。线上碰到 MQ 堆积,很多人的第一反应都很统一:队列消息堆住了,消费速度跟不上,直接扩容消费者,多起几个实例一起跑,应该很快就能消化完。

2026-10-26 08:49:29 382

原创 关于服务健康检查,很多团队只做了 “活着” 的判断

更极端一点,健康接口里面写了没有超时控制的同步调用,一旦某个依赖卡住,健康检查线程阻塞,探针接口超时,实例被反复重启,形成恶性循环。简单的 HTTP 裸探针也好不到哪里去,如果这个接口只是独立写死返回 200,不去触碰任何依赖,本质上和 TCP 探测差别不大,只是多了一层 HTTP 协议封装,同样发现不了内部阻塞。很多团队两个探针写一模一样的逻辑,该重启的时候不重启,不该摘除的时候疯狂摘除。本地跑服务,不会有编排平台持续打探针,很多探针的 bug,比如死锁、慢查询,只有在高频持续探测的时候才会暴露。

2026-10-25 08:26:15 222

原创 异步消息消费里,自动 ACK 带来的隐形坑

业务逻辑跑完、数据落库,ack 报文在网络上还在路上,进程宕机,broker 收不到 ack,超时之后就把消息重新投递给别的消费者,于是就发生重复消费。如果业务逻辑跑的很慢,还没处理完,锁超时,消息就会被重新分发,照样重复消费。后面我们改成手动 ACK 的模式,流程调整成:收到消息 → 做幂等校验 → 执行业务逻辑 → 全部处理成功之后,再主动调用 ack。后面才理清整个过程:消费者拿到消息之后,业务逻辑已经执行完成,数据入库成功,但就在准备给 broker 发送 ack 回执的瞬间,进程被重启了。

2026-10-24 09:43:51 145

原创 接口文档这件事,最大的问题不是没写,是滞后

以前是代码驱动文档,现在是契约先行,先约定入参、返回、异常,前后端先对齐,再各自开发。但契约也有门槛,简单小需求还好,复杂嵌套结构、动态返回字段的场景,写契约的成本会明显上去,小团队很难坚持全量落地。之前有一次联调,前端按照文档写好了页面逻辑,调接口的时候发现返回字段名变了,枚举值也改了。曾经第三方对接方反馈报错,我们翻了半天,才想起上周改过一个字段校验规则,旧版本的文档已经被覆盖,对方根本不知道变更。最怕的就是文档和代码变成两套独立的东西,各走各的,等到联调或者线上对接的时候,矛盾集中爆发。

2026-10-23 09:05:45 169

原创 版本回滚,远不是切一下镜像那么简单

直到一次业务迭代,我们改了数据库表结构,新增了一个字段,新版本代码会写入这个字段,旧版本代码不会读它。但另一次类似场景就出了事:新版本修改了字段的存储格式,写入了新格式数据,回滚之后老版本无法解析新格式的数据,报错量反而比上线的时候更高。不可逆的 DDL、缓存结构修改、消息协议改动,都要单独标记,这类变更上线要降低流量比例,灰度放很小的范围,一旦发现异常,优先停止放量,而不是直接全量回滚。踩过几次坑之后才意识到,回滚本身也是一次变更,它有自己的风险,很多故障的二次恶化,恰恰来自草率的回滚操作。

2026-10-22 08:51:24 83

原创 实现简单的请求重试,别直接写递归

手写的时候,别图省事用递归,也别只写一个固定 sleep,把错误判断、退避、超时、幂等这几件事考虑进去,重试逻辑才算安全。我之前遇到过一次线上事故:提交订单接口加了重试,没有幂等 key,下游数据库没做唯一约束,网络偶尔丢包,客户端重试之后生成了两条一模一样的订单,对账的时候才发现。第三,重试和幂等性绑定在一起。查询接口一般天然幂等,但是新增、提交类接口,想做重试,必须让下游支持幂等,带上唯一请求标识。循环版本没有调用栈累积的问题,重试次数完全可控,所有状态都在循环体内,加日志、埋点、调整策略都很方便。

2026-10-21 08:41:58 144

原创 网关限流配置里容易被忽略的优先级问题

有些同维度的规则,比如同一个接口,既配置了 IP 限流,又配置了账号限流,如果不控制好,很容易叠加生效,实际限流效果和预期偏差很大。这个问题很难提前感知,因为测试的时候流量小,全局阈值给得很高,很难触发,等到线上真实流量上来,概率性的异常就出现了。之前调整网关限流规则的时候踩了个挺隐蔽的坑,线上少量用户偶尔触发不该出现的限流提示,日志翻了很久才定位到不是阈值设小了,而是规则匹配顺序乱了。现在的做法是,同维度的限制只保留一条主规则,其他作为备选,或者明确设计成叠加模式并且记录在文档里,不凭感觉配置。

2026-10-20 10:01:05 236

原创 前端长列表虚拟滚动,别只盯着渲染数量

里面放了过滤、索引计算逻辑,快速滑动的时候,短时间堆一堆计算任务,主线程被占住,新节点的渲染就被卡住,表现出来就是空白屏。我加了防抖,结果又带来新问题:防抖延迟太高,滑动快的时候,视口已经滑过去了,节点才姗姗渲染,空白时间更长。我最开始偷懒给了一个预估高度,差距不大的时候还好,一旦单条实际高度和预估差很多,整个滚动容器的总高度计算就会跑偏,滚动条自然乱跳。点开一条,这条的高度会变大。一开始很自然就想到虚拟滚动,只渲染视口内的 DOM 节点,其余的直接丢弃,网上现成的库也多,想着引入进去就能解决卡顿。

2026-10-19 09:18:49 161

原创 代理转发里的超时坑:不是上游慢,是你没看懂连接复用

代理的连接池还把这条连接当成可用,下一次拿到它去发请求的时候,数据丢进去之后,才发现连接已经断了。这个方案能解决问题,但代价很大,QPS 上来之后,大量新建 TCP 连接,握手开销暴涨,端口占用飙升,高并发场景下很容易打到端口上限,不适合内部代理服务。还有一个场景,是上游返回慢,但代理的读超时设置太大,连接一直挂着,占住连接池资源,慢慢把池子耗尽,新请求拿不到连接排队。,控制连接在池子里最多闲置多久,超过就丢弃。,看上游服务的耗时曲线,响应明明很快,大部分请求几十 ms 就返回了,完全对不上超时报错。

2026-10-18 10:44:43 77

原创 进程内异步队列,别拿来当简单的缓冲筐用

队列堆积是渐进式的,消费慢一点,一点点涨,等到告警的时候已经堆到危险水位。比如核心线程数固定,队列很大,就算设置了 maxPoolSize,任务只会往队列里塞,不会扩容线程。任务对象里面带上了大对象,比如完整的用户信息、大报文,丢进队列之后,这些对象一直被队列持有,GC 没法回收。线程多了之后,又遇到下游推送接口被打限流,大量任务消费失败,在代码里写了重试,失败的任务重新塞回队列。无界队列等于把限流的闸门拆掉,流量洪峰过来的时候,所有来不及消费的任务全部堆在堆内存里,没有兜底,没有持久化,挂了数据直接丢。

2026-10-17 09:25:48 201

原创 缓存雪崩之外,被低估的热点 Key 问题

这里踩过一个小坑,之前本地缓存过期时间和 Redis 一致,热点 key 持续命中本地,Redis 的数据更新之后,本地要很久才能感知,最后改成本地 TTL 远小于 Redis 的 TTL,降低不一致窗口。但探测本身也有开销,不能每一条命令都统计,做了采样,只采集一部分请求做频次统计,牺牲一点探测灵敏度,换更低的性能损耗。还有一类特殊热点,是大 Value 带来的。key 访问频率不一定特别高,但 value 很大,每次读取都要传输几 KB 甚至十几 KB 的数据,带宽打满,表现出来的症状和高频热点很像。

2026-10-16 08:20:42 149

原创 接口超时排查:别总先怀疑下游服务

平时流量低的时候完全没问题,十点这个时间段,大量相同类型的查询请求同时打进来,瞬间把信号量占满,后面进来的请求排队等待,超过超时阈值直接失败。高峰期网关往同一个后端实例发起的连接打满,新请求在网关的连接池队列里排队,等拿到连接的时候,上游的超时时间已经耗尽。同样是网关过来的请求,下游所有业务指标都正常,没有锁阻塞,数据库也没有慢 SQL。再核对上下文超时传递:很多项目里会把上层的 ctx 直接透传,锁等待、连接排队这种非业务耗时,都会吃掉 ctx 的有效期,业务本身执行很快,但留给业务的时间已经不够了。

2026-10-15 09:39:08 103

原创 线上 Redis 慢查询,不一定就是命令本身慢

而且 RDB 持久化期间,内存持续有写入,写时复制不断复制内存页,额外带来 CPU 开销,所有命令的响应都会跟着变缓,不只是 HGETALL。理由很简单:它本身依然是不稳定的,如果以后这个哈希慢慢膨胀,或者实例再次抖动,它会是最先爆出来的那个。key 不大,不是超大哈希,没有热 key 打爆的迹象,Redis 集群负载看着也平稳,CPU、内存、网络带宽都没到水位线。,命令在客户端连接池里等着,送到 Redis 的时候已经晚了。本来以为事情到此为止,隔了两天,同一个实例,又冒出慢查询,这次是普通的 GET。

2026-10-14 15:31:34 105

原创 聊聊姓名文化赛道里,如何避开流量陷阱、找到适合自己的思路

除此之外,他还维护了个人博客,有空会写长文,记录自己对姓名文化的理解,拆解取名时的思考过程,文风偏务实,很少堆砌华丽空洞的辞藻。参考生辰信息的时候,重点落在格局本身,不会一味堆砌 “鑫、盛、财” 这类大家很容易想到的字,很多商家取名很容易陷入这个误区,名字堆砌感很重,缺少长期品牌的质感。但一个商业名称,要承担品牌传播的使命。这套思路的优势很清晰:审美统一,文艺氛围感强,每个名字都有对应的诗文源头,之后和孩子解释名字含义的时候,有完整的典故可以讲述,适合单纯看重文字美感、本身不认同生辰参考思路的家庭。

2026-10-14 15:03:05 64

原创 聊一聊 PHP 项目里,那些被低估的日志埋点

我见过别的项目,接口每一行代码都打日志,一次请求几十条记录,磁盘涨得飞快,排查的时候信息太多,有效内容反而被淹没。我自己的感受是,链路追踪擅长看耗时、调用关系,但是业务层面的细节,比如某个字段的值、业务状态的变化,还是要靠业务日志。直到这次排查一个支付回调偶发失败的问题,折腾了整整一下午,才又意识到日志埋点这件事,看着简单,实际很影响排障效率。第一种,只打成功,失败才写日志。这个老项目改造日志的过程,没有动业务主逻辑,只是封装了一个统一的日志方法,强制带上业务标识和分级,加上敏感字段过滤。

2026-10-14 08:40:34 348

原创 记录一次 SpringBoot @Transactional 事务没有回滚的排查过程

这次最终的改动方案:异步线程不再操作数据库,计算出来的结果先放在内存,所有数据统一在主线程里面入库,删掉子线程中的 Mapper 调用,事务方法单独抽到新的 Service。第一反应就是事务没回滚,这个问题之前踩过,但这次排查的时候,连续踩了两个叠加在一起的坑,折腾了挺久。如果用 this 调用,走的是原生对象本身的方法,AOP 完全感知不到,注解相当于写了个摆设。,内层是独立事务,内层异常回滚不会影响外层,外层异常也不能回滚内层已经提交的数据,很多人会误以为是事务失效,其实是对传播行为的预期错了。

2026-10-13 09:47:24 197

原创 灰度发布不是万能护身符,大多数线上事故都出在灰度里 现在的互联网项目,基本都标配灰度发布机制。

响应时间的微小上涨、报错率的微弱抬头、CPU内存的小幅波动、队列堆积的苗头,这些测试环境完全看不到。实际上,灰度的核心价值不是“测bug”,而是平滑观测流量、性能、监控的细微波动。而是灰度前的充分测试、灰度中的指标细查、兼容逻辑的严格把控、异常预案的提前准备。高价值用户、高频操作、极值数据、老数据存量场景,大概率不在灰度样本里。却忽略了已经流入线上的新版本逻辑、已经变更的数据、已经生效的缓存。版本回退了,数据没回退,直接造成版本回滚、数据不回滚的分裂故障。灰度覆盖的,往往是普通用户、常规操作、低并发场景。

2026-10-12 09:49:26 127

原创 架构随笔:写死的魔法数值,是系统腐烂的开始

状态判断的 0、1、2,超时的 3000、6000,分页的 10、20,状态码的 200、500,业务阈值直接裸写在逻辑中。一部分逻辑用旧数值,一部分用新数值,业务规则分裂,数据统计错乱、判断逻辑失效,线上问题层出不穷。成熟的项目,所有业务状态、阈值、超时时间、固定配置,一定统一抽离为常量、枚举、配置项。一处定义,全局复用,修改只改一处,语义一目了然,从根源规避低级bug。比如数字 0,有的模块代表未启用,有的代表默认值,有的代表异常状态。随意写死的魔法数值,看似无伤大雅,实则是系统慢慢腐烂的起点。

2026-10-11 09:34:06 129

原创 缓存更新的认知误区

等读请求后续执行,把旧数据塞回缓存。延时时间靠经验预估,业务执行耗时波动大,延时设置太短,无法覆盖读请求的执行周期;两条更新请求先后到达,数据库写入顺序和缓存更新顺序不一致,最终缓存保存的是较早版本的数据,数据库却是新值。最省事的方案,是改完数据库立刻删缓存,觉得这样就能保证两边数据同步。数据库更新成功,删除缓存的操作失败了,缓存依旧是旧数据。测试环境一切正常,上线之后隔三差五爆出旧数据,排查日志也找不到明显报错,查数据库记录又是最新的。缓存不是简单的加速工具,一旦引入,就要处理两套存储之间的同步问题。

2026-10-10 08:27:48 230

原创 文件句柄泄露,服务最隐蔽的慢性瘫痪病根

Redis 连接偶尔失败、MySQL 连接建不上、日志写不动、外部接口请求超时、本地文件读取异常。很多开发对这个概念很陌生,只知道句柄不够会报错,但不知道它是怎么一点点泄漏、拖垮整个服务的。线上有一种问题,不属于代码逻辑 bug,不属于数据库卡顿,也不属于并发抢占。它很安静,潜伏期极长,从启动服务开始,慢慢蚕食资源,最后突然让服务彻底废掉。普通文件、日志、网络连接、Socket、管道、设备,全部占用文件句柄。每次新建连接、打开文件、创建流,都会占用一个句柄。正常逻辑是用完就关闭,句柄释放,资源归还系统。

2026-10-09 09:46:16 218

原创 接口参数偶尔丢失、乱码、解析失败,不是前端传参问题

更离谱的是,部分特殊字符(空格、#、&、换行、emoji)编码规则不同,会直接截断参数内容,导致后端接收不全。最迷惑的是:大多数请求都是纯英文、数字、简单符号,不会触发问题。前端大部分场景会自动 encode,但少数动态拼接、自定义请求、老旧代码不会完整编码。大部分时间请求正常,偶尔出现参数为空、中文乱码、特殊字符截断、JSON解析失败。用户输入内容、备注内容、富文本内容里,经常藏着未转义特殊字符。前端页面、客户端、第三方回调、网关,编码格式并不一致。不会直接报错,而是静默丢弃、部分替换、字符截断。

2026-10-08 09:24:13 189

原创 大部分脏数据,都是幂等没做好埋下的坑

线上很多莫名其妙的重复数据、重复扣款、重复订单、多条相同日志,排查下来,既不是前端bug,也不是用户恶意操作,根源基本都是接口不幂等。但写接口、变更接口、资金接口、状态更新接口,一旦不做幂等,就是定时炸弹。两个请求同时查询,都查到空,双双进入新增逻辑,直接产生两条重复数据。而且这类问题极具迷惑性,测试环境很难复现,只会在高并发、弱网、重试机制下集中爆发。还有场景是工单创建、审批提交、库存扣减,重复调用导致状态错乱、数据叠加、流程乱套。接口执行业务前,先查数据库有没有数据,有就返回,没有就新增。

2026-10-07 19:27:44 173

原创 Redis键明明过期了,业务还在读到旧数据

如果你的过期key特别多、冷热数据混杂,大量过期垃圾key会一直残留内存,迟迟清不干净。很多线上数据不一致、缓存滞后、刷新不及时的bug,根本不是业务代码写错,是对Redis底层删除机制认知不足。过期机制本身就有滞后性,再加上Redis删除不实时,结果就是线上长期数据不一致,用户看到的一直是旧内容。如果过期后瞬间有请求进来,刚好卡在删除前,旧数据直接被读取出来,业务读到过期缓存,造成数据延迟更新。当内存紧张时,优先删快要过期的,已经过期的反而可能滞留。所有对数据一致性有要求的缓存,绝对不能靠过期自动刷新。

2026-10-06 10:00:47 200

原创 接口明明返回成功,数据库偶尔就是没写入

业务代码正常走完,事务底层标记回滚,最终结果就是:接口成功、逻辑成功、数据库无数据。主线程还在事务里执行写入,异步线程还没跑完,主线程方法已经走完提交事务。前端调用接口完全正常,返回码正确、业务状态成功、日志打印写入成功,但是去数据库一查,数据偶尔凭空消失,压根没存进去。很多新手包括做了几年的后端,对事务的理解都停留在:加个注解,方法成功就提交,报错就回滚。这种嵌套事务问题,排查难度极高,日志看不出任何毛病,业务链路完全正常,就是不落地数据。时序一旦错开,异步读写数据库的时候,事务已经关闭,或者还没提交。

2026-10-05 10:27:52 228

原创 前端debug踩坑:别再完全相信 console.log 了

最后一点点排查才发现,循环瞬间的旧数据是错的,后续统一赋值修正了数据,log 延迟刷新,欺骗了我。代码执行到 log 的瞬间,数据明明是错的、空的、异常的。你反复核对代码,越看越懵,明明数据是对的,为什么跑出来结果就是错的?关键节点的状态,手动区分打印类型,明确输出 null、undefined、空字符串,不再靠肉眼模糊判断。真正靠谱的 debug,从来不是靠多打几行日志,而是不被假象迷惑,抓到代码真实的执行状态。经常出现后面的逻辑先打印,前面的逻辑后打印,让人完全看错执行链路,排查方向直接走偏。

2026-10-04 11:02:27 147

原创 前端本地缓存用滥了,页面bug真的会越来越多

不管是用户信息、菜单列表、筛选条件、弹窗状态,能存的全都往 localStorage、sessionStorage 里塞。比如A列表的页码存全局缓存,打开B列表,偶尔会被带入旧页码,直接显示空白数据。我见过最夸张的,用户本地缓存太多,JSON.parse 解析超长残缺数据,直接报错白屏,新版本完全加载不出来。线上大部分偶现的页面错乱、状态不更新、切换数据不对、刷新一下又正常的问题,基本都能和本地缓存扯上关系。很多前端完全没这个意识,写缓存只管存,从来不考虑清理时机、版本兼容、用户切换场景。

2026-10-03 09:50:00 184

原创 PHP项目复盘:foreach 引用残留造成的诡异数据错乱

如果后续没有再次使用该变量,数据完全正常。写PHP业务久的人,基本都碰到过一类完全无解的问题:数组循环处理完之后,后续操作数据莫名被篡改,代码看着干干净净,没有赋值、没有覆盖,结果就是不对。很多人为了省事,批量处理数据全部用引用赋值,觉得省去数组重新赋值的步骤,代码更简洁。最迷惑人的是,整个过程没有报错、没有警告、逻辑完全通顺,就是数据悄悄变错,尤其多段循环叠加、公共方法复用变量时,错乱概率极高。PHP的变量作用域和引用机制和其他语言不一样,循环结构不会隔离变量,引用一旦绑定,除非主动销毁,否则一直生效。

2026-10-02 16:18:21 272

空空如也

空空如也

TA创建的收藏夹 TA关注的收藏夹

TA关注的人

提示
确定要删除当前文章?
取消 删除