Hey小伙伴们!👋 最近在咱们的后端世界里,发生了一件让人心跳加速的Bug事件。事情是这样的,一个看似普通的发货操作,竟然没有更新数据!😱 是的,你没听错,就是那个每个电商系统里都有的发货操作。
🧐 问题背景
在咱们的系统中,有两个服务小能手:回收
服务和发货服务。它们通过两个接口:发货接口和上报电量接口,来协同工作。但是,这两个接口都在争先恐后地更新 fy_boxes
表,结果就出现了时间上的纳秒级差异,导致更新操作出现了竞态条件。
🐞 Bug现形记
发货接口刚准备把 fy_boxes
的某个字段更新为2,但是电量上报接口就抢先一步读取了旧值1,然后也去更新了这个字段。结果,发货接口的更新被覆盖了,字段值又变回了1。😤
🔍 解决方案大公开
为了解决这个问题,咱们可以采用以下几种策略:
1.乐观锁:给 fy_boxes
表添加一个版本号字段,每次更新都检查版本号是否有变化。
// 假设咱们的实体类是这样的 public class FyBox { private Long id; private int sort; private int version; // 版本号字段 // 省略其他字段和getter/setter方法 } // 更新操作时,先检查版本号 public boolean updateBoxSort(Long boxId, int newSort, int currentVersion) { return fhAudioTemplateMapper.updateSort(boxId, newSort, currentVersion) == 1; }
2. 悲观锁:在读取数据时,直接锁定相关记录。
SELECT * FROM fy_boxes WHERE id = ? FOR UPDATE; // SQL中的悲观锁语法
3.数据库序列号:使用数据库的序列号或时间戳功能来控制更新顺序。
// 假设 `fy_boxes` 表有一个时间戳字段 `updated_at` UPDATE fy_boxes SET sort = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?;
4.分布式锁:使用ZooKeeper或Redis等工具实现分布式锁,确保同一时间只有一个操作可以更新数据。
// 使用Redis分布式锁的伪代码 boolean lockAcquired = redisClient.acquireLock("lock_key", "unique_value"); if (lockAcquired) { try { // 安全地执行更新操作 } finally { redisClient.releaseLock("lock_key", "unique_value"); } }
5.还有一种最简单 的方法,单独new对象更新(争对于对应业务要 更新的字段去set值)
// 因解决并发问题,在另一服务不同纳秒可能会存在重复更新,故此处不要重复更新其他字段 FyBoxes fyBoxes = new FyBoxes(); fyBoxes.setId(box.getId()); fyBoxes.setPower(dto.getPower()); fyBoxes.setPowerTime(RboxCommonUtils.nowTimestampS()); fyBoxes.updateById();
🎉 总结
小伙伴们,通过这次Bug的排查和解决,咱们不仅提升了自己的技术实力,还加深了对并发控制的理解。在多线程和分布式系统中,正确地处理共享资源的更新是非常重要的。希望这次的分享能够帮助到大家,让我们一起成长,一起进步!💪