社区发帖接口被刷出负积分那次,我重新理解了"事务"二字的分量

站长杂谈 22 浏览 0 回复 返回上级

上周有个站长朋友私信我,说他的社区刚上线活动,结果有人用脚本狂刷发帖接口,积分没扣成,帖子倒是发了八百多条。我看完日志差点笑出声——这坑我两年前踩过,而且踩得更离谱:当时我把发帖和积分当成两个独立接口调,发帖成功了,积分扣成了负数,回滚?不存在的,因为根本没写事务。

后来我自己折腾社区模块对接,才发现发帖、回帖、积分这三件事,表面上是三个独立功能,实际上是一条绳上的蚂蚱。今天不聊具体代码怎么写,聊聊我在设计这套联动逻辑时,那些"想当然"翻车的瞬间。

第一坑:积分接口当"事后诸葛亮"

我最开始的思路特别朴素:用户点发布,帖子先入库,然后异步发个请求给积分模块,加10分。听起来很解耦对吧?直到某天服务器抽风,帖子写进去了,积分请求超时了。用户刷新页面一看:帖子在,积分没动,再点一次发布——又一条重复帖子。

后来我改成同步调用,发帖和积分在同一个请求里串行执行。新问题又来了:积分接口响应慢,用户点完发布按钮转圈三秒,体验稀烂。更坑的是,如果积分接口挂了,帖子到底发还是不发?发吧,积分对不上;不发吧,用户辛辛苦苦写的长文直接没了。

现在我的做法是:发帖操作本身带一个"待结算"标记,积分接口走消息队列异步消化。队列消费成功再改标记为"已结算",消费失败走补偿机制重试。用户感知到的发帖流程永远是快的,后台慢慢对账。

第二坑:回帖的"连环计"积分

回帖比发帖复杂在"双向性"。A回B的帖子,A得2分,B作为楼主也得1分被互动分。我第一版实现是回帖接口里连续调两次积分接口:先给A加,再给B加。结果某天B的积分接口挂了,A加分成功,B没加上,数据直接不一致。

想过用本地事务套两个远程调用,但积分接口是HTTP,事务根本管不到。最后我的妥协方案是:回帖表加两个字段,记录A和B的积分发放状态,队列消费时分别处理,失败就重试,直到两边都对上。看起来冗余,但排查问题时一眼就能定位哪条回帖的积分没结清。

第三坑:幂等性不是"加个锁"就完事

这个最反直觉。用户手快点了两次发布,或者网络抖动导致前端重试,同一篇内容可能触发两次发帖接口。我一开始在接口入口加了Redis分布式锁,key是用户ID,心想这下总不会重复了吧?

结果测试时发现,锁过期时间设5秒,用户第一篇帖子内容多,入库花了6秒,锁早释放了,第二个请求照样进来。把锁延长到30秒?那用户正常发第二篇帖子时被拦在外面,投诉"你们社区 bug 了"。

现在的方案是锁的key改成"用户ID+内容摘要的短哈希",锁时间只覆盖重复请求的窗口期(比如2秒),同时帖子表对内容和发布时间做联合唯一索引兜底。双重保险,各管一段。

第四坑:积分回滚比加分难十倍

帖子被管理员删除,对应的积分要不要扣回来?要。但怎么扣?直接调积分接口减分?那用户积分明细里会出现一条"帖子删除扣10分",他根本不知道哪条帖子被删了。

我现在的做法是单独一张"积分冻结记录"表,发帖时积分不是直接到账,而是先冻结24小时。期间帖子被删,直接取消冻结;正常存活超过24小时,定时任务把冻结转正式。代价是用户看到积分有延迟,但数据干净得多,审计时能追溯到每一笔积分的生命周期。

最后说点感受

社区模块联动这件事,技术上没多高深,就是CRUD加接口调用。但"看起来简单"和"生产环境扛得住"之间,隔着无数个凌晨两点的日志排查。我现在接任何联动需求,第一反应不是写代码,而是画一张"异常时数据状态"的表:每一步如果挂了,系统里会留下什么烂摊子,怎么补。

你们对接发帖回帖积分这类联动接口时,有没有遇到过更阴间的场景?比如积分接口响应了但网络超时,或者用户删帖后积分回滚触发风控之类的?评论区聊聊,我搬板凳听。

评论0
回复 · 0
还没有回复
微信客服 微信客服