在通过多线程来解决高并发的问题上,线程安全往往是最先需要考虑的问题,其次才是性能。库存超卖问题是有很多种技术解决方案的,比如悲观锁,分布式锁,乐观锁,队列串行化,Redis原子操作等。本篇通过MySQL乐观锁来演示基本实现。
一、Goods和Order
@Data
public class Goods {
private int id;
private String name;
private int stock;
private int version;
}
@Data
public class Order {
private int id;
private int uid;
private int gid;
}
二、OrderDao和GoodsDao
@Mapper
public interface OrderDao {
@Insert("INSERT INTO `order` (uid, gid) VALUES (#{uid}, #{gid})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insertOrder(Order order);
}
@Mapper
public interface GoodsDao {
@Select("SELECT * FROM goods WHERE id = #{id}")
Goods getStock(@Param("id") int id);
@Update("UPDATE goods SET stock = stock - 1, version = version + 1 WHERE id = #{id} AND stock > 0 AND version = #{version}")
int decreaseStockForVersion(@Param("id") int id, @Param("version") int version);
}
三、GoodsService
@Service
@Slf4j
public class GoodsService {
@Autowired
private GoodsDao goodsDao;
@Autowired
private OrderDao orderDao;
public int sellGoods(int gid, int uid) {
int retryCount = 0;
int update = 0;
Goods goods = goodsDao.getStock(gid);
if (goods.getStock() > 0) {
while(retryCount < 3 && update == 0){
update = this.reduceStock(gid);
retryCount++;
}
if(update == 0){
log.error("库存不足");
return 0;
}
Order order = new Order();
order.setUid(uid);
order.setGid(gid);
int result = orderDao.insertOrder(order);
return result;
}
return 0;
}
@Transactional(rollbackFor = Exception.class)
public int reduceStock(int gid){
int result = 0;
Goods goods = goodsDao.getStock(gid);
if(goods.getStock() >= 0){
result = goodsDao.decreaseStockForVersion(gid, goods.getVersion());
}
return result;
}
}
四、单元测试GoodsServiceTest
@SpringBootTest
class GoodsServiceTest {
@Autowired
GoodsService goodsService;
@Test
void seckill() throws InterruptedException {
int threadTotal = 100;
ExecutorService executorService = Executors.newCachedThreadPool();
final CountDownLatch countDownLatch = new CountDownLatch(threadTotal);
for (int i = 0; i < threadTotal ; i++) {
int uid = i;
executorService.execute(() -> {
try {
goodsService.sellGoods(1, uid);
} catch (Exception e) {
e.printStackTrace();
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
}
}
五、测试结果
库存由10减到了0,并且生产了10条订单记录。
参考资料 更新库存数量 - 乐观锁 通过乐观锁解决库存超卖的问题 mysql 乐观锁_使用MySQL乐观锁解决超卖问题 商品超买超卖问题分析及实战
|