库存保卫战:电商系统防超卖的5把利刃与Java实战

举报
悟空码字 发表于 2026/01/08 15:46:25 2026/01/08
【摘要】 在电商系统中,超卖是指商品库存不足以满足所有购买请求时,系统仍然接受了超过库存数量的订单。这会导致:商家无法正常发货,用户体验受损,平台信誉下降。

大家好,我是小悟。

目录

  • 问题背景
  • 解决方案概览
  • 数据库悲观锁方案
  • 数据库乐观锁方案
  • Redis原子操作方案
  • 分布式锁方案
  • 消息队列方案
  • 总结与对比

问题背景

在电商系统中,超卖是指商品库存不足以满足所有购买请求时,系统仍然接受了超过库存数量的订单。这会导致:

  • 商家无法正常发货
  • 用户体验受损
  • 平台信誉下降

解决方案概览

方案 适用场景 优点 缺点
数据库悲观锁 并发量不高 实现简单,强一致性 性能瓶颈,死锁风险
数据库乐观锁 读多写少 性能较好 需要重试机制
Redis原子操作 高并发场景 高性能 数据一致性需注意
分布式锁 分布式系统 强一致性 实现复杂
消息队列 流量削峰 系统解耦 实时性较差

数据库悲观锁方案

实现原理

使用数据库的排他锁(SELECT … FOR UPDATE)锁定库存记录,确保同一时间只有一个事务可以修改库存。

Java代码实现

@Service
@Transactional
public class PessimisticLockInventoryService {
    
    @Autowired
    private ProductRepository productRepository;
    
    @Autowired
    private OrderRepository orderRepository;
    
    public boolean purchaseWithPessimisticLock(Long productId, Integer quantity) {
        // 1. 查询商品并加锁
        Product product = productRepository.findWithLock(productId);
        
        if (product == null) {
            throw new RuntimeException("商品不存在");
        }
        
        // 2. 检查库存
        if (product.getStock() < quantity) {
            throw new RuntimeException("库存不足");
        }
        
        // 3. 扣减库存
        int updatedRows = productRepository.decreaseStock(productId, quantity);
        
        if (updatedRows == 0) {
            throw new RuntimeException("库存扣减失败");
        }
        
        // 4. 创建订单
        Order order = new Order();
        order.setProductId(productId);
        order.setQuantity(quantity);
        order.setStatus(1);
        orderRepository.save(order);
        
        return true;
    }
}

// Repository 层实现
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
    
    @Query(value = "SELECT * FROM product WHERE id = ?1 FOR UPDATE", nativeQuery = true)
    Product findWithLock(Long id);
    
    @Modifying
    @Query("UPDATE Product p SET p.stock = p.stock - ?2 WHERE p.id = ?1 AND p.stock >= ?2")
    int decreaseStock(Long productId, Integer quantity);
}

数据库乐观锁方案

实现原理

通过版本号机制,在更新时检查数据是否被其他事务修改过,如果版本号不匹配则更新失败。

Java代码实现

@Service
public class OptimisticLockInventoryService {
    
    @Autowired
    private ProductRepository productRepository;
    
    @Autowired
    private OrderRepository orderRepository;
    
    private static final int MAX_RETRY_TIMES = 3;
    
    public boolean purchaseWithOptimisticLock(Long productId, Integer quantity) {
        int retryTimes = 0;
        
        while (retryTimes < MAX_RETRY_TIMES) {
            // 1. 查询商品(不加锁)
            Product product = productRepository.findById(productId)
                    .orElseThrow(() -> new RuntimeException("商品不存在"));
            
            // 2. 检查库存
            if (product.getStock() < quantity) {
                throw new RuntimeException("库存不足");
            }
            
            // 3. 扣减库存(带版本号检查)
            int updatedRows = productRepository.decreaseStockWithVersion(
                    productId, quantity, product.getVersion());
            
            if (updatedRows > 0) {
                // 4. 创建订单
                Order order = new Order();
                order.setProductId(productId);
                order.setQuantity(quantity);
                order.setStatus(1);
                orderRepository.save(order);
                return true;
            }
            
            retryTimes++;
            
            // 重试前短暂休眠
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException("线程中断", e);
            }
        }
        
        throw new RuntimeException("并发更新失败,请重试");
    }
}

// 实体类
@Entity
@Table(name = "product")
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    private Integer stock;
    
    @Version
    private Integer version;
    
    // getter和setter方法
}

// Repository 层实现
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
    
    @Modifying
    @Query("UPDATE Product p SET p.stock = p.stock - ?2, p.version = p.version + 1 " +
           "WHERE p.id = ?1 AND p.stock >= ?2 AND p.version = ?3")
    int decreaseStockWithVersion(Long productId, Integer quantity, Integer version);
}

Redis原子操作方案

实现原理

利用Redis的原子操作(如DECR、LUA脚本)来保证库存扣减的原子性。

Java代码实现

@Service
public class RedisInventoryService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private ProductRepository productRepository;
    
    @Autowired
    private OrderRepository orderRepository;
    
    private static final String STOCK_KEY_PREFIX = "product:stock:";
    private static final String LUA_SCRIPT = 
            "if redis.call('exists', KEYS[1]) == 1 then " +
            "local stock = tonumber(redis.call('get', KEYS[1])); " +
            "local num = tonumber(ARGV[1]); " +
            "if stock >= num then " +
            "return redis.call('decrby', KEYS[1], num) " +
            "else " +
            "return -1 " +
            "end " +
            "else " +
            "return -2 " +
            "end";
    
    public boolean purchaseWithRedis(Long productId, Integer quantity) {
        String stockKey = STOCK_KEY_PREFIX + productId;
        
        // 使用LUA脚本保证原子性
        Long result = (Long) redisTemplate.execute(
                new DefaultRedisScript<>(LUA_SCRIPT, Long.class),
                Collections.singletonList(stockKey),
                quantity);
        
        if (result == null) {
            throw new RuntimeException("Redis操作失败");
        }
        
        if (result == -1) {
            throw new RuntimeException("库存不足");
        }
        
        if (result == -2) {
            throw new RuntimeException("商品不存在");
        }
        
        // 异步更新数据库
        asyncUpdateDatabase(productId, quantity);
        
        return true;
    }
    
    @Async
    public void asyncUpdateDatabase(Long productId, Integer quantity) {
        try {
            // 更新数据库库存
            productRepository.decreaseStock(productId, quantity);
            
            // 创建订单
            Order order = new Order();
            order.setProductId(productId);
            order.setQuantity(quantity);
            order.setStatus(1);
            orderRepository.save(order);
        } catch (Exception e) {
            // 如果数据库更新失败,需要回滚Redis库存
            rollbackRedisStock(productId, quantity);
            throw new RuntimeException("数据库更新失败", e);
        }
    }
    
    private void rollbackRedisStock(Long productId, Integer quantity) {
        String stockKey = STOCK_KEY_PREFIX + productId;
        redisTemplate.opsForValue().increment(stockKey, quantity);
    }
    
    /**
     * 初始化Redis库存
     */
    public void initRedisStock(Long productId, Integer stock) {
        String stockKey = STOCK_KEY_PREFIX + productId;
        redisTemplate.opsForValue().set(stockKey, stock);
    }
}

分布式锁方案

实现原理

使用Redis或Zookeeper实现分布式锁,确保在分布式环境下同一时间只有一个服务实例可以操作库存。

Java代码实现

@Service
public class DistributedLockInventoryService {
    
    @Autowired
    private RedissonClient redissonClient;
    
    @Autowired
    private ProductRepository productRepository;
    
    @Autowired
    private OrderRepository orderRepository;
    
    private static final String LOCK_KEY_PREFIX = "inventory_lock:";
    
    public boolean purchaseWithDistributedLock(Long productId, Integer quantity) {
        String lockKey = LOCK_KEY_PREFIX + productId;
        RLock lock = redissonClient.getLock(lockKey);
        
        try {
            // 尝试加锁,最多等待5秒,锁过期时间为10秒
            boolean locked = lock.tryLock(5, 10, TimeUnit.SECONDS);
            
            if (!locked) {
                throw new RuntimeException("系统繁忙,请稍后重试");
            }
            
            // 查询商品库存
            Product product = productRepository.findById(productId)
                    .orElseThrow(() -> new RuntimeException("商品不存在"));
            
            // 检查库存
            if (product.getStock() < quantity) {
                throw new RuntimeException("库存不足");
            }
            
            // 扣减库存
            int updatedRows = productRepository.decreaseStock(productId, quantity);
            
            if (updatedRows == 0) {
                throw new RuntimeException("库存扣减失败");
            }
            
            // 创建订单
            Order order = new Order();
            order.setProductId(productId);
            order.setQuantity(quantity);
            order.setStatus(1);
            orderRepository.save(order);
            
            return true;
            
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("线程中断", e);
        } finally {
            // 释放锁
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

// Redisson配置类
@Configuration
public class RedissonConfig {
    
    @Value("${spring.redis.host:localhost}")
    private String redisHost;
    
    @Value("${spring.redis.port:6379}")
    private String redisPort;
    
    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer()
                .setAddress("redis://" + redisHost + ":" + redisPort)
                .setDatabase(0);
        return Redisson.create(config);
    }
}

消息队列方案

实现原理

通过消息队列进行流量削峰,将订单请求放入队列中顺序处理,避免瞬时高并发。

Java代码实现

@Service
public class MQInventoryService {
    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @Autowired
    private ProductRepository productRepository;
    
    @Autowired
    private OrderRepository orderRepository;
    
    private static final String ORDER_QUEUE = "order.queue";
    private static final String ORDER_EXCHANGE = "order.exchange";
    private static final String ORDER_ROUTING_KEY = "order.create";
    
    /**
     * 接收订单请求,发送到消息队列
     */
    public boolean submitOrder(Long productId, Integer quantity, Long userId) {
        OrderRequest orderRequest = new OrderRequest();
        orderRequest.setProductId(productId);
        orderRequest.setQuantity(quantity);
        orderRequest.setUserId(userId);
        orderRequest.setRequestId(UUID.randomUUID().toString());
        orderRequest.setCreateTime(System.currentTimeMillis());
        
        // 发送消息到队列
        rabbitTemplate.convertAndSend(ORDER_EXCHANGE, ORDER_ROUTING_KEY, orderRequest);
        
        return true;
    }
    
    /**
     * 消费订单消息,处理库存扣减
     */
    @RabbitListener(queues = ORDER_QUEUE)
    public void processOrder(OrderRequest orderRequest) {
        Long productId = orderRequest.getProductId();
        Integer quantity = orderRequest.getQuantity();
        
        try {
            // 查询商品库存
            Product product = productRepository.findById(productId)
                    .orElseThrow(() -> new RuntimeException("商品不存在"));
            
            // 检查库存
            if (product.getStock() < quantity) {
                // 库存不足,记录日志或发送通知
                handleInsufficientStock(orderRequest);
                return;
            }
            
            // 扣减库存
            int updatedRows = productRepository.decreaseStock(productId, quantity);
            
            if (updatedRows == 0) {
                handleInsufficientStock(orderRequest);
                return;
            }
            
            // 创建订单
            Order order = new Order();
            order.setProductId(productId);
            order.setQuantity(quantity);
            order.setUserId(orderRequest.getUserId());
            order.setStatus(1);
            orderRepository.save(order);
            
            // 发送订单创建成功通知
            sendOrderSuccessNotification(orderRequest);
            
        } catch (Exception e) {
            // 处理异常,可以重试或记录错误
            handleProcessError(orderRequest, e);
        }
    }
    
    private void handleInsufficientStock(OrderRequest orderRequest) {
        // 记录库存不足日志
        // 发送库存不足通知给用户
        System.err.println("库存不足,订单请求: " + orderRequest);
    }
    
    private void sendOrderSuccessNotification(OrderRequest orderRequest) {
        // 发送订单创建成功通知
        System.out.println("订单创建成功: " + orderRequest);
    }
    
    private void handleProcessError(OrderRequest orderRequest, Exception e) {
        // 处理异常,可以重试或记录错误
        System.err.println("处理订单异常: " + orderRequest + ", 错误: " + e.getMessage());
    }
}

// 订单请求DTO
@Data
public class OrderRequest {
    private String requestId;
    private Long productId;
    private Integer quantity;
    private Long userId;
    private Long createTime;
}

// RabbitMQ配置类
@Configuration
public class RabbitMQConfig {
    
    @Bean
    public Queue orderQueue() {
        return new Queue("order.queue", true);
    }
    
    @Bean
    public DirectExchange orderExchange() {
        return new DirectExchange("order.exchange");
    }
    
    @Bean
    public Binding orderBinding(Queue orderQueue, DirectExchange orderExchange) {
        return BindingBuilder.bind(orderQueue).to(orderExchange).with("order.create");
    }
}

总结与对比

方案选择建议

  1. 小型系统:推荐使用数据库乐观锁,实现简单且性能较好
  2. 中型系统:推荐使用Redis原子操作,性能优秀且实现相对简单
  3. 大型分布式系统:推荐使用分布式锁+消息队列组合方案,保证强一致性和高可用性

最佳实践

  1. 库存预热:活动开始前将库存加载到Redis中
  2. 限流降级:在网关层实现限流,防止系统过载
  3. 库存预警:设置库存阈值,及时提醒补货
  4. 数据一致性:定期同步Redis和数据库的库存数据
  5. 监控告警:实时监控库存和订单状态,及时发现问题

注意事项

  • 根据业务场景选择合适的方案
  • 考虑系统复杂度和维护成本
  • 做好异常处理和数据补偿
  • 进行充分的压力测试

库存保卫战:电商系统防超卖的5把利刃与Java实战.png


谢谢你看我的文章,既然看到这里了,如果觉得不错,随手点个赞、转发、在看三连吧,感谢感谢。那我们,下次再见。

您的一键三连,是我更新的最大动力,谢谢

山水有相逢,来日皆可期,谢谢阅读,我们再会

我手中的金箍棒,上能通天,下能探海

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。