基于Queue与MongoDB的点赞系统数据一致性问题咨询
问题1:引入AWS SQS解决一致性问题是否可行?
可行,但属于成本较高的非最优方案。
你需要对SQS做按帖子ID的分区路由,保证同一帖子的所有点赞/取消点赞操作都进入同一队列分区,且单分区仅由一个消费者串行消费,才能从根源避免并行更新的竞争问题。但该方案会额外引入队列重试、死信处理、计数更新延迟(用户点赞后可能数秒才能看到计数变化)等复杂度,仅适合并发极高且可以接受短时间计数不一致的场景。
问题2:队列订阅者扩容后是否还会出现计数不一致?
如果未做分区路由逻辑,直接扩容消费者节点一定会出现计数不一致。
多个消费者会同时拉取到同一帖子的多条点赞操作,并行执行读-改-写流程,和你原本的并发问题完全一致。只有保证同一帖子的所有操作永远由同一个消费者处理、或增加分布式锁保证同一时间仅一个消费者处理同一帖子的操作,扩容后才能保持一致性。
问题3:更合适的解决方案有哪些?
根据你的技术栈,优先推荐以下方案,复杂度从低到高:
- MongoDB原子操作(优先选择):你原本的问题根源是「先读计数再修改写入」的非原子操作,直接用MongoDB原生的
$inc原子操作符即可解决,完全不需要引入额外组件。示例代码:
数据库层面会自动串行执行同一条记录的更新操作,不会出现计数错误,性能足以支撑绝大多数中小规模应用的并发需求。// 点赞 await db.collection('posts').updateOne( { _id: postId }, { $inc: { likeCount: 1 } } ) // 取消点赞把1改为-1即可 - Redis缓存+异步落库:如果单帖子点赞并发超过MongoDB单文档更新性能上限,可以用Redis的
INCR/DECR原子操作先更新缓存中的点赞计数,同时写入用户点赞状态,再异步批量将缓存计数同步到MongoDB。该方案性能极高,同时可以用缓存直接响应计数读请求,降低数据库压力。 - 乐观锁补偿:如果有特殊场景必须先读后写,可以给帖子文档增加
version版本字段,每次更新时携带当前版本号作为查询条件:
若更新返回的匹配行数为0,说明有其他请求先修改了数据,执行重试即可。该方案高并发下重试率较高,仅适合并发较低的场景。const updateRes = await db.collection('posts').updateOne( { _id: postId, version: currentVersion }, { $inc: { likeCount: 1 }, $set: { version: currentVersion + 1 } } )
问题4:主流社交平台如何解决点赞计数不一致问题?
主流平台的核心思路是「分层拦截+原子更新+最终一致」:
- 接入层先做请求过滤,拦截同一用户短时间内对同一帖子的重复点赞请求,减少无效后端请求。
- 计数读写分离:读请求全部走多级缓存(内存缓存+分布式缓存+CDN),不会直接查库;写请求全部走原子操作,避免竞争。
- 热点数据特殊处理:热度极高的帖子的点赞操作会先写入内存队列,批量合并后再更新数据库,比如100次点赞合并为一次计数+100的更新,大幅降低数据库压力。
- 后台定期校验:定时任务会定期用用户点赞关联表的统计值修正帖子的计数字段,解决极端场景下的计数偏差,保证最终一致性。
内容的提问来源于stack exchange,提问作者Raghav Mishra
相关产品推荐
相关产品推荐

