java商品如何扣减
商品库存扣减的基本逻辑
商品库存扣减是电商系统中常见的操作,核心目标是保证在高并发场景下数据的一致性。Java中实现库存扣减需要考虑原子性、并发控制和异常处理。
数据库乐观锁实现
通过版本号或时间戳实现乐观锁,适合并发量适中的场景:
// SQL示例
UPDATE product SET stock = stock - #{quantity}, version = version + 1
WHERE id = #{productId} AND version = #{version} AND stock >= #{quantity}
需要在Service层进行重试逻辑:
@Transactional
public boolean deductStock(Long productId, int quantity) {
int retryTimes = 3;
while (retryTimes-- > 0) {
Product product = productMapper.selectById(productId);
if (product.getStock() < quantity) return false;
int affected = productMapper.deductStock(productId, quantity, product.getVersion());
if (affected > 0) return true;
}
return false;
}
Redis原子操作方案
利用Redis的原子特性实现高性能扣减:
// Lua脚本保证原子性
String luaScript = "if redis.call('exists', KEYS[1]) == 1 then " +
"local stock = tonumber(redis.call('get', KEYS[1])) " +
"if stock >= tonumber(ARGV[1]) then " +
"return redis.call('incrby', KEYS[1], -tonumber(ARGV[1])) " +
"end " +
"return -1 " +
"end " +
"return -2";
Long result = redisTemplate.execute(
new DefaultRedisScript<>(luaScript, Long.class),
Collections.singletonList("stock:" + productId),
String.valueOf(quantity)
);
分布式锁解决方案
使用Redisson实现分布式锁:
public boolean deductStockWithLock(Long productId, int quantity) {
RLock lock = redissonClient.getLock("product:" + productId);
try {
lock.lock();
Product product = productMapper.selectById(productId);
if (product.getStock() >= quantity) {
product.setStock(product.getStock() - quantity);
productMapper.updateById(product);
return true;
}
return false;
} finally {
lock.unlock();
}
}
消息队列异步处理
应对超高并发场景可采用异步扣减模式:
@KafkaListener(topics = "stock-deduction")
public void handleStockDeduction(StockMessage message) {
try {
deductStock(message.getProductId(), message.getQuantity());
} catch (Exception e) {
// 记录日志并加入重试队列
}
}
异常处理与补偿机制
需要实现补偿逻辑处理失败场景:
@TransactionalEventListener(phase = AFTER_COMMIT)
public void onOrderCreated(OrderCreatedEvent event) {
try {
if(!stockService.deductStock(event.getProductId(), event.getQuantity())) {
// 触发库存不足补偿流程
}
} catch (Exception e) {
// 记录异常并触发补偿
}
}
数据一致性验证
定期核对Redis与数据库库存:

@Scheduled(cron = "0 0 3 * * ?")
public void stockSyncTask() {
List<Product> products = productMapper.selectList(null);
products.forEach(p -> {
Integer redisStock = redisTemplate.opsForValue().get("stock:" + p.getId());
if(redisStock != null && !redisStock.equals(p.getStock())) {
// 触发库存校正
}
});
}
实际实现时应根据业务场景选择合适方案,超高并发建议采用Redis+Lua+异步消息的组合方案,普通场景可直接使用数据库乐观锁。所有方案都需配合完善的监控和报警机制。






