Java实现秒杀系统,怎么解决并发和库存不被击穿

前言

总结Java实现秒杀系统设计方案

方案如下

为了实现秒杀系统,需要解决高并发和防止库存被击穿两个主要问题。以下是一些Java实现秒杀系统的建议:

  1. 使用Redis等内存数据库作为库存和订单数据的存储,减少数据库访问压力。
  2. 使用消息队列,将订单信息异步入库,降低后端压力。可以使用RabbitMQ或Kafka等。
  3. 对订单业务逻辑使用分布式锁,如Redis的SETNX命令,防止同一个商品被重复下单导致超卖。
  4. 使用缓存(Redis)预热热点商品库存数量,减少数据库访问。
  5. 对下单接口进行限流,比如使用Guava的RateLimiter,控制请求流量在系统承受能力范围内。
  6. 前端使用CDN缓存静态资源,减少后端压力。
  7. 对下单接口进行防刷处理,如谷歌 reCAPTCHA 验证码,限制机器人恶意刷单。
  8. 按用户分批下单,每批次只允许限定数量的请求,然后休眠一小段时间再处理下一批,以平滑请求流量。
  9. 采用无状态服务设计,使用Nginx等做反向代理和负载均衡。
  10. 垂直拆分下单服务,按商品 ID 来路由分发请求到不同的服务器。

秒杀系统解决高并发问题,具体代码实现

  1. 使用Redis的incr命令作为原子自增操作,确保商品库存数量准确:
    //商品库存数量原子自减
    Long stock = redisTemplate.opsForValue().increment("product_" + productId, -1);
  2. 在Service层使用synchronized或者ReentrantLock进行加锁,防止并发导致的超卖:
    //Service层方法加synchronized锁
    public synchronized void orderProduct(int productId){
    //下单逻辑 
    }
    //或使用ReentrantLock加锁
    Lock lock = new ReentrantLock(); 
    lock.lock();
    try{
    //下单逻辑
    }finally{
    lock.unlock()
    }
  3. 使用Redis的SETNX实现分布式锁,防止分布式环境下的并发:
    //占有分布式锁
    if(redisTemplate.opsForValue().setIfAbsent("lock", "1")){
    //下单逻辑
    //释放分布式锁
    redisTemplate.delete("lock");
    }
  4. 使用Guava的RateLimiter限流,平滑突发流量:
    RateLimiter rateLimiter = RateLimiter.create(100); //限流大小100
    rateLimiter.acquire(); //请求限流
    //下单逻辑

    5.异步处理下单请求:

    //接收下单请求,扔到RabbitMQ队列
    orderService.asyncOrder(order);
    //消费端异步处理下单逻辑
    @RabbitListener(queues="order_queue") 
    public void consumeOrder(Order order){
    //下单逻辑..
    }

    解决库存被击穿问题,特别是当库存有一个,然后两个人同时抢到锁,代码实现

    库存被击穿是秒杀场景下常见的问题。当商品库存仅剩 1 个时,如果不做特殊处理,可能出现 2 个线程同时检查库存均大于0的情况,然后继续执行减库存,这就会造成超卖。
    解决库存被击穿问题的Java代码实现思路如下:

  5. 加锁 + 二次检查
    // 加锁
    synchronized(productId) {
    // 获取库存
    int stock = getStockFromRedis(productId);
    // 库存不足,直接返回
    if(stock <= 0) {
    return; 
    }
    // 二次检查库存
    if(stock > 1) { 
    // 减库存
    reduceStock(productId);
    // 下单
    } else {
    // 库存不足提示
    }
    }

    上面代码先对商品加锁,再获取库存,如果库存不足则直接返回。如果还有库存,则再进行二次检查,这个时候即使有并发也不可能被击穿。

  6. 使用库存预减
    直接在取库存时就使用 decr 命令把库存减 1,如果减 1 成功则下单,如果减 1 失败则说明库存不足。

    // 库存预减
    boolean stockOk = redis.decr(productId) > 0; 
    if(stockOk) {
    // 下单
    } else {
    // 库存不足
    }

    这种方式利用了 Redis 的 decr 原子性,也可以避免击穿。
    综上,要防止击穿主要还是要正确使用加锁和原子操作。

Java实现秒杀系统,怎么解决并发和库存不被击穿

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

滚动到顶部