工作汇报网 >地图 >

工作总结

工作总结

时间:2026-04-24 作者:工作汇报网

软件开发实习工作个人总结。

三个多月,实际泡在代码里的日子大概七十天出头。我在订单履约中心接了个烂摊子——库存预占模块。说白了下单时锁库存,别卖超了。听起来就加减法,可这平台日活小百万,一到秒杀时段,数据库那张库存表的行锁能排成百米长龙。

先交代第一个坑:Redis扣了库存,DB没记上账。

老代码的逻辑是:收到订单请求,先Redis.decr扣缓存,然后insert流水表,最后update库存表。三个动作没有事务包裹。项目冲刺那周压测,跑着跑着运营半夜来电话:“用户下单成功了,仓库说没货,客服电话已经被打爆了。” Gsi8.com

我当时从床上爬起来连上VPN,拉慢查询日志,发现流水表报了主键冲突。应用层没捕获异常,扣Redis的动作已经提交了。结果就是Redis里库存少了,DB里原封不动。用户以为自己买到了,其实根本没锁住。

这设计简直匪夷所思。更匪夷所思的是,这坨代码已经跑了四个月,前几批实习生居然没踩出来。

我提的方案分两步,但被架构师怼过一次。他认为应该直接用分布式锁套住全部操作。我实测发现,分布式锁在峰值时锁等待超时率接近7%,用户体验崩了。最后拍板的方案是这样的:第一阶段,Redis执行Lua脚本——检查库存,够就扣减,同时在hash里记录一笔预占流水,带上唯一token和过期时间(30分钟)。这一步是原子的,不存在扣成功但记录失败的情况。第二阶段,异步线程去刷DB:先查Redis里这个token对应的预占流水,再写数据库。如果写DB失败(比如网络抖了、死锁了),不重试无限次,而是把这条失败记录推到一个延迟队列里,隔5秒、30秒、2分钟各重试一次。三次全失败,由另一个补偿程序读Redis里的原始流水,执行反向释放——用Lua把库存加回去,同时删除那条预占流水。

为什么不敢无限重试?因为怕DB挂了导致重试把Redis连接池也堵死。这个教训是我从另一套退款系统上学来的,那系统就因为无限重试把整个中间件拖垮过。

上线后,库存负数再没出现过。但两周后的一个凌晨三点,告警又炸了。这次是库存释放出了问题——用户下单后没支付,30分钟到期,Redis的ttl自动删除key,但DB里那笔预占流水还是“已预占”状态,没有回滚。我漏处理了Redis键过期事件。补了个监听__keyevent@0__:expired的订阅服务,拿到key解析出token,异步通知DB把流水状态改成“已超时释放”,同时把库存加回去。这一整套才算闭环。

再来第二个坑:多层库存查得慢,接口动不动三秒。

库存分总仓、区域仓、门店三层,订单要就近匹配,每次预占都得三层都查一遍实时余量。SQL写了五个join,where条件里一堆or和in,扫描行数二十多万。那是一个下雨的周二上午,运维在群里甩了张截图——数据库CPU 85%,慢查询队列堆到两百多条。我一看执行计划,全表扫描,索引完全没走。

这代码是老员工半年前写的,当时数据量三万,现在三百多万。让人憋屈的是,业务还刚提了个新需求:某些爆品要支持区域库存低于阈值时自动切预售。

实时查询这条路堵死了。我提议建一张预聚合表inventory_snapshot,每五分钟(不是每小时,因为业务对实时性要求高)跑一个批任务,把三层库存按(sku_id, region_id)预计算好存入。订单预占时直接读这张表,不走join。但预聚合必然有延迟——用户可能看到页面上有货,点进去刚好被别的单抢完了。折中的办法是:下单页展示用预聚合表的数据,让用户先点“提交订单”;真正扣库存时,再单独查一次具体分配到的那个仓库的实时库存。这个实时查询是主键点查,微秒级响应。

为了解决那个预售需求,我在预聚合表里加了个字段pre_sale_threshold。批任务计算时会同时判断当前库存是否低于阈值,如果是,上游接口返回时直接标记为“预售”,前端展示“可预定”。预售单不扣物理库存,只记到一个预售订单表里,等补货后再统一发货。这个逻辑上线后,产品那边很满意,但我自己知道里面还有个隐患——批任务跑的那几分钟内,如果库存刚好掉到阈值以下,会有少量订单误判为“有货”然后下单失败。我跟产品约定,允许这个误差在千分之一以内,超过就报警。

最终接口rt从2.8秒降到了160毫秒(比之前说的120高一点,因为加了预售判断的几毫秒开销)。数据库CPU降到15%左右。

现在回头想想,教训有几个。

头一个,设计接口时得自己给自己找茬。那个三层库存的SQL,但凡当初写的时候看一眼执行计划,或者提前造个百万级的数据测一测,都不会留这么大雷。我把这事写进了组的代码评审checklist里——新功能必须先提供数据量增长模型,并附上慢查询预估。

第二个,别信“临时方案”。预占代码里的// TODO注释最早的日期是九个月前。我现在自己写的任何临时补丁,都会在注释里加上// EXPIRE: 2026-01-01,并且配一个cron任务每天扫这些过期TODO,扫出来就自动发邮件给责任人。这招挺得罪人的,但管用。

第三个,也是被现实抽过之后才记住的——补偿机制不能只在脑子里推演,得实际跑一遍故障注入。那个键过期遗漏的问题,就是因为我只在单元测试里验证了正常流程,没模拟Redis被动删除的场景。后来我自己写了个混沌测试脚本,随机让某条预占流水“忘记”释放,看系统能不能自动修回来。

临走前那周,组里又分了个新活——退款时的库存回滚逻辑。分布式事务,XA有超时问题,TCC要写一堆补偿代码,还在纠结。但我翻了翻同事留下的设计文档,发现又把“2PC”写成了最终方案。我给他提了个comment:先压测一下Seata的AT模式,如果性能不行,大不了我们自己用本地消息表搓一个。这活儿留给下一任实习生了。

经验这东西,写在文档里都是空话,只有被报警电话叫醒过、被DBA怼过、被产品经理追着改需求过,才能真正长记性。我这三个月记的“故障处置记录”已经十三篇了,篇篇都是血的教训。希望下一篇,是别人踩坑,我看戏。

    我们精彩推荐工作总结专题,静候访问专题:工作总结

本文来源://www.gsi8.com/gongzuozongjie/191568.html