关于java:实现高并发秒杀的-7-种方式写的太好了建议收藏

38次阅读

共计 16146 个字符,预计需要花费 41 分钟才能阅读完成。

1. 引言

高并发场景在现场的日常工作中很常见,特地是在互联网公司中,这篇文章就来通过秒杀商品来模仿高并发的场景。文章开端会附上文章的所有代码、脚本和测试用例。

  • 本文环境: SpringBoot 2.5.7 + MySQL 8.0 X + MybatisPlus + Swagger2.9.2
  • 模仿工具: Jmeter
  • 模仿场景: 减库存 -> 创立订单 -> 模仿领取

2. 商品秒杀 - 超卖

在开发中,对于上面的代码,可能很相熟:在 Service 外面加上 @Transactional 事务注解和 Lock 锁。

Spring Boot 根底就不介绍了,举荐看这个收费教程:

https://github.com/javastacks/spring-boot-best-practice

管制层:Controller

@ApiOperation(value="秒杀实现形式——Lock 加锁")
@PostMapping("/start/lock")
public Result startLock(long skgId){
    try {log.info("开始秒杀形式一...");
        final long userId = (int) (new Random().nextDouble() * (99999 - 10000 + 1)) + 10000;
        Result result = secondKillService.startSecondKillByLock(skgId, userId);
        if(result != null){log.info("用户:{}--{}", userId, result.get("msg"));
        }else{log.info("用户:{}--{}", userId, "哎呦喂,人也太多了,请稍后!");
        }
    } catch (Exception e) {e.printStackTrace();
    } finally { }
    return Result.ok();}

业务层:Service

@Override
@Transactional(rollbackFor = Exception.class)
public Result startSecondKillByLock(long skgId, long userId) {lock.lock();
    try {
        // 校验库存
        SecondKill secondKill = secondKillMapper.selectById(skgId);
        Integer number = secondKill.getNumber();
        if (number > 0) {
            // 扣库存
            secondKill.setNumber(number - 1);
            secondKillMapper.updateById(secondKill);
            // 创立订单
            SuccessKilled killed = new SuccessKilled();
            killed.setSeckillId(skgId);
            killed.setUserId(userId);
            killed.setState((short) 0);
            killed.setCreateTime(new Timestamp(System.currentTimeMillis()));
            successKilledMapper.insert(killed);

            // 模仿领取
            Payment payment = new Payment();
            payment.setSeckillId(skgId);
            payment.setSeckillId(skgId);
            payment.setUserId(userId);
            payment.setMoney(40);
            payment.setState((short) 1);
            payment.setCreateTime(new Timestamp(System.currentTimeMillis()));
            paymentMapper.insert(payment);
        } else {return Result.error(SecondKillStateEnum.END);
        }
    } catch (Exception e) {throw new ScorpiosException("异样了个乖乖");
    } finally {lock.unlock();
    }
    return Result.ok(SecondKillStateEnum.SUCCESS);
}

对于下面的代码应该没啥问题吧,业务办法上加事务,在解决业务的时候加锁。

但下面这样写法是有问题的,会呈现超卖的状况,看下测试后果:模仿 1000 个并发,抢 100 商品。

这里在业务办法开始加了锁,在业务办法完结后开释了锁。但这里的事务提交却不是这样的,有可能在事务提交之前,就曾经把锁开释了,这样会导致商品超卖景象。所以加锁的机会很重要!

3. 解决商品超卖

对于下面超卖景象,次要问题呈现在事务中锁开释的机会,事务未提交之前,锁曾经开释。(事务提交是在整个办法执行完)。如何解决这个问题呢,就是把加锁步骤提前

  • 能够在 controller 层进行加锁
  • 能够应用 Aop 在业务办法执行之前进行加锁

3.1 形式一(改进版加锁)

@ApiOperation(value="秒杀实现形式——Lock 加锁")
@PostMapping("/start/lock")
public Result startLock(long skgId){
    // 在此处加锁
    lock.lock();
    try {log.info("开始秒杀形式一...");
        final long userId = (int) (new Random().nextDouble() * (99999 - 10000 + 1)) + 10000;
        Result result = secondKillService.startSecondKillByLock(skgId, userId);
        if(result != null){log.info("用户:{}--{}", userId, result.get("msg"));
        }else{log.info("用户:{}--{}", userId, "哎呦喂,人也太多了,请稍后!");
        }
    } catch (Exception e) {e.printStackTrace();
    } finally {
        // 在此处开释锁
        lock.unlock();}
    return Result.ok();}

下面这样的加锁就能够解决事务未提交之前,锁开释的问题,能够分三种状况进行压力测试:

  • 并发数 1000,商品 100
  • 并发数 1000,商品 1000
  • 并发数 2000,商品 1000

对于并发量大于商品数的状况,商品秒杀个别不会呈现少卖的请况,但对于并发数小于等于商品数的时候可能会呈现商品少卖状况,这也很好了解。

对于没有问题的状况就不贴图了,因为有很多种形式,贴图会太多

3.2 形式二(AOP 版加锁)

对于下面在管制层进行加锁的形式,可能显得不优雅,那就还有另一种形式进行在事务之前加锁,那就是 AOP。

举荐一个开源收费的 Spring Boot 最全教程:

https://github.com/javastacks/spring-boot-best-practice

自定义 AOP 注解

@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public  @interface ServiceLock {String description()  default "";
}

定义切面类

@Slf4j
@Component
@Scope
@Aspect
@Order(1) //order 越小越是最先执行,但更重要的是最先执行的最初完结
public class LockAspect {
    /**
     * 思考:为什么不必 synchronized
     * service 默认是单例的,并发下 lock 只有一个实例
     */
    private static  Lock lock = new ReentrantLock(true); // 互斥锁 参数默认 false,不偏心锁

    // Service 层切点     用于记录谬误日志
    @Pointcut("@annotation(com.scorpios.secondkill.aop.ServiceLock)")
    public void lockAspect() {}

    @Around("lockAspect()")
    public  Object around(ProceedingJoinPoint joinPoint) {lock.lock();
        Object obj = null;
        try {obj = joinPoint.proceed();
        } catch (Throwable e) {e.printStackTrace();
   throw new RuntimeException();} finally{lock.unlock();
        }
        return obj;
    }
}

在业务办法上增加 AOP 注解

@Override
@ServiceLock // 应用 Aop 进行加锁
@Transactional(rollbackFor = Exception.class)
public Result startSecondKillByAop(long skgId, long userId) {

    try {
        // 校验库存
        SecondKill secondKill = secondKillMapper.selectById(skgId);
        Integer number = secondKill.getNumber();
        if (number > 0) {
            // 扣库存
            secondKill.setNumber(number - 1);
            secondKillMapper.updateById(secondKill);
            // 创立订单
            SuccessKilled killed = new SuccessKilled();
            killed.setSeckillId(skgId);
            killed.setUserId(userId);
            killed.setState((short) 0);
            killed.setCreateTime(new Timestamp(System.currentTimeMillis()));
            successKilledMapper.insert(killed);

            // 领取
            Payment payment = new Payment();
            payment.setSeckillId(skgId);
            payment.setSeckillId(skgId);
            payment.setUserId(userId);
            payment.setMoney(40);
            payment.setState((short) 1);
            payment.setCreateTime(new Timestamp(System.currentTimeMillis()));
            paymentMapper.insert(payment);
        } else {return Result.error(SecondKillStateEnum.END);
        }
    } catch (Exception e) {throw new ScorpiosException("异样了个乖乖");
    }
    return Result.ok(SecondKillStateEnum.SUCCESS);
}

管制层:

@ApiOperation(value="秒杀实现形式二——Aop 加锁")
@PostMapping("/start/aop")
public Result startAop(long skgId){
    try {log.info("开始秒杀形式二...");
        final long userId = (int) (new Random().nextDouble() * (99999 - 10000 + 1)) + 10000;
        Result result = secondKillService.startSecondKillByAop(skgId, userId);
        if(result != null){log.info("用户:{}--{}", userId, result.get("msg"));
        }else{log.info("用户:{}--{}", userId, "哎呦喂,人也太多了,请稍后!");
        }
    } catch (Exception e) {e.printStackTrace();
    }
    return Result.ok();}

这种形式在对锁的应用上,更高阶、更好看!

3.3 形式三(乐观锁一)

除了下面在业务代码层面加锁外,还能够应用数据库自带的锁进行并发管制。

乐观锁,什么是乐观锁呢?艰深的说,在做任何事件之前,都要进行加锁确认。这种数据库级加锁操作效率较低。

应用 for update 肯定要加上事务,当事务处理完后,for update 才会将行级锁解除

如果申请数和秒杀商品数量统一,会呈现少卖

@ApiOperation(value="秒杀实现形式三——乐观锁")
@PostMapping("/start/pes/lock/one")
public Result startPesLockOne(long skgId){
    try {log.info("开始秒杀形式三...");
        final long userId = (int) (new Random().nextDouble() * (99999 - 10000 + 1)) + 10000;
        Result result = secondKillService.startSecondKillByUpdate(skgId, userId);
        if(result != null){log.info("用户:{}--{}", userId, result.get("msg"));
        }else{log.info("用户:{}--{}", userId, "哎呦喂,人也太多了,请稍后!");
        }
    } catch (Exception e) {e.printStackTrace();
    }
    return Result.ok();}

业务逻辑

@Override
@Transactional(rollbackFor = Exception.class)
public Result startSecondKillByUpdate(long skgId, long userId) {
    try {
        // 校验库存 - 乐观锁
        SecondKill secondKill = secondKillMapper.querySecondKillForUpdate(skgId);
        Integer number = secondKill.getNumber();
        if (number > 0) {
            // 扣库存
            secondKill.setNumber(number - 1);
            secondKillMapper.updateById(secondKill);
            // 创立订单
            SuccessKilled killed = new SuccessKilled();
            killed.setSeckillId(skgId);
            killed.setUserId(userId);
            killed.setState((short) 0);
            killed.setCreateTime(new Timestamp(System.currentTimeMillis()));
            successKilledMapper.insert(killed);

            // 领取
            Payment payment = new Payment();
            payment.setSeckillId(skgId);
            payment.setSeckillId(skgId);
            payment.setUserId(userId);
            payment.setMoney(40);
            payment.setState((short) 1);
            payment.setCreateTime(new Timestamp(System.currentTimeMillis()));
            paymentMapper.insert(payment);
        } else {return Result.error(SecondKillStateEnum.END);
        }
    } catch (Exception e) {throw new ScorpiosException("异样了个乖乖");
    } finally { }
    return Result.ok(SecondKillStateEnum.SUCCESS);
}

Dao 层

@Repository
public interface SecondKillMapper extends BaseMapper<SecondKill> {

    /**
     * 将此行数据进行加锁,当整个办法将事务提交后,才会解锁
     * @param skgId
     * @return
     */
    @Select(value = "SELECT * FROM seckill WHERE seckill_id=#{skgId} FOR UPDATE")
    SecondKill querySecondKillForUpdate(@Param("skgId") Long skgId);

}

下面是利用 for update 进行对查问数据加锁,加的是行锁

3.4 形式四(乐观锁二)

乐观锁的第二种形式就是利用 update 更新命令来加表锁

/**
 * UPDATE 锁表
 * @param skgId  商品 id
 * @param userId    用户 id
 * @return
 */
@Override
@Transactional(rollbackFor = Exception.class)
public Result startSecondKillByUpdateTwo(long skgId, long userId) {
    try {

        // 不校验,间接扣库存更新
        int result = secondKillMapper.updateSecondKillById(skgId);
        if (result > 0) {
            // 创立订单
            SuccessKilled killed = new SuccessKilled();
            killed.setSeckillId(skgId);
            killed.setUserId(userId);
            killed.setState((short) 0);
            killed.setCreateTime(new Timestamp(System.currentTimeMillis()));
            successKilledMapper.insert(killed);

            // 领取
            Payment payment = new Payment();
            payment.setSeckillId(skgId);
            payment.setSeckillId(skgId);
            payment.setUserId(userId);
            payment.setMoney(40);
            payment.setState((short) 1);
            payment.setCreateTime(new Timestamp(System.currentTimeMillis()));
            paymentMapper.insert(payment);
        } else {return Result.error(SecondKillStateEnum.END);
        }
    } catch (Exception e) {throw new ScorpiosException("异样了个乖乖");
    } finally { }
    return Result.ok(SecondKillStateEnum.SUCCESS);
}

Dao 层

@Repository
public interface SecondKillMapper extends BaseMapper<SecondKill> {

    /**
     * 将此行数据进行加锁,当整个办法将事务提交后,才会解锁
     * @param skgId
     * @return
     */
    @Select(value = "SELECT * FROM seckill WHERE seckill_id=#{skgId} FOR UPDATE")
    SecondKill querySecondKillForUpdate(@Param("skgId") Long skgId);

    @Update(value = "UPDATE seckill SET number=number-1 WHERE seckill_id=#{skgId} AND number > 0")
    int updateSecondKillById(@Param("skgId") long skgId);
}

3.5 形式五(乐观锁)

乐观锁,顾名思义,就是对操作后果很乐观,通过利用 version 字段来判断数据是否被批改

乐观锁,不进行库存数量的校验,间接做库存扣减

这里应用的乐观锁会呈现大量的数据更新异样(抛异样就会导致购买失败)、如果配置的抢购人数比拟少、比方 120:100(人数: 商品) 会呈现少买的状况,不举荐应用乐观锁。

@ApiOperation(value="秒杀实现形式五——乐观锁")
@PostMapping("/start/opt/lock")
public Result startOptLock(long skgId){
    try {log.info("开始秒杀形式五...");
        final long userId = (int) (new Random().nextDouble() * (99999 - 10000 + 1)) + 10000;
        // 参数增加了购买数量
        Result result = secondKillService.startSecondKillByPesLock(skgId, userId,1);
        if(result != null){log.info("用户:{}--{}", userId, result.get("msg"));
        }else{log.info("用户:{}--{}", userId, "哎呦喂,人也太多了,请稍后!");
        }
    } catch (Exception e) {e.printStackTrace();
    }
    return Result.ok();}
@Override
@Transactional(rollbackFor = Exception.class)
public Result startSecondKillByPesLock(long skgId, long userId, int number) {

    // 乐观锁,不进行库存数量的校验,间接
    try {SecondKill kill = secondKillMapper.selectById(skgId);
        // 残余的数量应该要大于等于秒杀的数量
        if(kill.getNumber() >= number) {int result = secondKillMapper.updateSecondKillByVersion(number,skgId,kill.getVersion());
            if (result > 0) {
                // 创立订单
                SuccessKilled killed = new SuccessKilled();
                killed.setSeckillId(skgId);
                killed.setUserId(userId);
                killed.setState((short) 0);
                killed.setCreateTime(new Timestamp(System.currentTimeMillis()));
                successKilledMapper.insert(killed);

                // 领取
                Payment payment = new Payment();
                payment.setSeckillId(skgId);
                payment.setSeckillId(skgId);
                payment.setUserId(userId);
                payment.setMoney(40);
                payment.setState((short) 1);
                payment.setCreateTime(new Timestamp(System.currentTimeMillis()));
                paymentMapper.insert(payment);
            } else {return Result.error(SecondKillStateEnum.END);
            }
        }
    } catch (Exception e) {throw new ScorpiosException("异样了个乖乖");
    } finally { }
    return Result.ok(SecondKillStateEnum.SUCCESS);
}
@Repository
public interface SecondKillMapper extends BaseMapper<SecondKill> {

    /**
     * 将此行数据进行加锁,当整个办法将事务提交后,才会解锁
     * @param skgId
     * @return
     */
    @Select(value = "SELECT * FROM seckill WHERE seckill_id=#{skgId} FOR UPDATE")
    SecondKill querySecondKillForUpdate(@Param("skgId") Long skgId);

    @Update(value = "UPDATE seckill SET number=number-1 WHERE seckill_id=#{skgId} AND number > 0")
    int updateSecondKillById(@Param("skgId") long skgId);

    @Update(value = "UPDATE seckill  SET number=number-#{number},version=version+1 WHERE seckill_id=#{skgId} AND version = #{version}")
    int updateSecondKillByVersion(@Param("number") int number, @Param("skgId") long skgId, @Param("version")int version);
}

乐观锁会呈现大量的数据更新异样(抛异样就会导致购买失败),会呈现少买的状况,不举荐应用乐观锁

3.6 形式六(阻塞队列)

利用阻塞队类,也能够解决高并发问题。其思维就是把接管到的申请按程序寄存到队列中,消费者线程逐个从队列里取数据进行解决,看下具体代码。

阻塞队列:这里应用动态外部类的形式来实现单例模式,在并发条件下不会呈现问题。

// 秒杀队列(固定长度为 100)
public class SecondKillQueue {

    // 队列大小
    static final int QUEUE_MAX_SIZE = 100;

    // 用于多线程间下单的队列
    static BlockingQueue<SuccessKilled> blockingQueue = new LinkedBlockingQueue<SuccessKilled>(QUEUE_MAX_SIZE);

    // 应用动态外部类,实现单例模式
    private SecondKillQueue(){};

    private static class SingletonHolder{
        // 动态初始化器,由 JVM 来保障线程平安
        private  static SecondKillQueue queue = new SecondKillQueue();}

    /**
     * 单例队列
     * @return
     */
    public static SecondKillQueue getSkillQueue(){return SingletonHolder.queue;}

    /**
     * 生产入队
     * @param kill
     * @throws InterruptedException
     * add(e) 队列未满时,返回 true;队列满则抛出 IllegalStateException(“Queue full”)异样——AbstractQueue
     * put(e) 队列未满时,直接插入没有返回值;队列满时会阻塞期待,始终等到队列未满时再插入。* offer(e) 队列未满时,返回 true;队列满时返回 false。非阻塞立刻返回。* offer(e, time, unit) 设定期待的工夫,如果在指定工夫内还不能往队列中插入数据则返回 false,插入胜利返回 true。*/
    public  Boolean  produce(SuccessKilled kill) {return blockingQueue.offer(kill);
    }
    /**
     * 生产出队
     * poll() 获取并移除队首元素,在指定的工夫内去轮询队列看有没有首元素有则返回,否者超时后返回 null
     * take() 与带超时工夫的 poll 相似不同在于 take 时候如果以后队列空了它会始终期待其余线程调用 notEmpty.signal()才会被唤醒
     */
    public  SuccessKilled consume() throws InterruptedException {return blockingQueue.take();
    }

    /**
     * 获取队列大小
     * @return
     */
    public int size() {return blockingQueue.size();
    }
}

生产秒杀队列:实现 ApplicationRunner 接口

// 生产秒杀队列
@Slf4j
@Component
public class TaskRunner implements ApplicationRunner{

    @Autowired
    private SecondKillService seckillService;

    @Override
    public void run(ApplicationArguments var){new Thread(() -> {log.info("队列启动胜利");
            while(true){
                try {
                    // 过程内队列
                    SuccessKilled kill = SecondKillQueue.getSkillQueue().consume();
                    if(kill != null){Result result = seckillService.startSecondKillByAop(kill.getSeckillId(), kill.getUserId());
                        if(result != null && result.equals(Result.ok(SecondKillStateEnum.SUCCESS))){log.info("TaskRunner,result:{}",result);
                            log.info("TaskRunner 从音讯队列取出用户,用户:{}{}",kill.getUserId(),"秒杀胜利");
                        }
                    }
                } catch (InterruptedException e) {e.printStackTrace();
                }
            }
        }).start();}
}
@ApiOperation(value="秒杀实现形式六——音讯队列")
@PostMapping("/start/queue")
public Result startQueue(long skgId){
    try {log.info("开始秒杀形式六...");
        final long userId = (int) (new Random().nextDouble() * (99999 - 10000 + 1)) + 10000;
        SuccessKilled kill = new SuccessKilled();
        kill.setSeckillId(skgId);
        kill.setUserId(userId);
        Boolean flag = SecondKillQueue.getSkillQueue().produce(kill);
        // 尽管进入了队列,然而不肯定能秒杀胜利 进队出队有工夫间隙
        if(flag){log.info("用户:{}{}",kill.getUserId(),"秒杀胜利");
        }else{log.info("用户:{}{}",userId,"秒杀失败");
        }
    } catch (Exception e) {e.printStackTrace();
    }
    return Result.ok();}

留神:在业务层和 AOP 办法中,不能抛出任何异样,throw new RuntimeException()这些抛异样代码要正文掉。因为一旦程序抛出异样就会进行,导致生产秒杀队列过程终止!

应用阻塞队列来实现秒杀,有几点要留神:

  • 生产秒杀队列中调用业务办法加锁与不加锁状况一样,也就是 seckillService.startSecondKillByAop()seckillService.startSecondKillByLock() 办法后果一样,这也很好了解
  • 当队列长度与商品数量统一时,会呈现少卖的景象,能够调大数值
  • 上面是队列长度 1000,商品数量 1000,并发数 2000 状况下呈现的少卖

3.7. 形式七(Disruptor 队列)

Disruptor 是个高性能队列,研发的初衷是解决内存队列的提早问题,在性能测试中发现居然与 I / O 操作处于同样的数量级,基于 Disruptor 开发的零碎单线程能撑持每秒 600 万订单。

// 事件生成工厂(用来初始化预调配事件对象)public class SecondKillEventFactory implements EventFactory<SecondKillEvent> {

    @Override
    public SecondKillEvent newInstance() {return new SecondKillEvent();
    }
}
// 事件对象(秒杀事件)public class SecondKillEvent implements Serializable {
    private static final long serialVersionUID = 1L;
    private long seckillId;
    private long userId;

 // set/get 办法略

}
// 应用 translator 形式生产者
public class SecondKillEventProducer {private final static EventTranslatorVararg<SecondKillEvent> translator = (seckillEvent, seq, objs) -> {seckillEvent.setSeckillId((Long) objs[0]);
        seckillEvent.setUserId((Long) objs[1]);
    };

    private final RingBuffer<SecondKillEvent> ringBuffer;

    public SecondKillEventProducer(RingBuffer<SecondKillEvent> ringBuffer){this.ringBuffer = ringBuffer;}

    public void secondKill(long seckillId, long userId){this.ringBuffer.publishEvent(translator, seckillId, userId);
    }
}
// 消费者(秒杀处理器)
@Slf4j
public class SecondKillEventConsumer implements EventHandler<SecondKillEvent> {private SecondKillService secondKillService = (SecondKillService) SpringUtil.getBean("secondKillService");

    @Override
    public void onEvent(SecondKillEvent seckillEvent, long seq, boolean bool) {Result result = secondKillService.startSecondKillByAop(seckillEvent.getSeckillId(), seckillEvent.getUserId());
        if(result.equals(Result.ok(SecondKillStateEnum.SUCCESS))){log.info("用户:{}{}",seckillEvent.getUserId(),"秒杀胜利");
        }
    }
}
public class DisruptorUtil {

    static Disruptor<SecondKillEvent> disruptor;

    static{SecondKillEventFactory factory = new SecondKillEventFactory();
        int ringBufferSize = 1024;
        ThreadFactory threadFactory = runnable -> new Thread(runnable);
        disruptor = new Disruptor<>(factory, ringBufferSize, threadFactory);
        disruptor.handleEventsWith(new SecondKillEventConsumer());
        disruptor.start();}

    public static void producer(SecondKillEvent kill){RingBuffer<SecondKillEvent> ringBuffer = disruptor.getRingBuffer();
        SecondKillEventProducer producer = new SecondKillEventProducer(ringBuffer);
        producer.secondKill(kill.getSeckillId(),kill.getUserId());
    }
}
@ApiOperation(value="秒杀实现形式七——Disruptor 队列")
@PostMapping("/start/disruptor")
public Result startDisruptor(long skgId){
    try {log.info("开始秒杀形式七...");
        final long userId = (int) (new Random().nextDouble() * (99999 - 10000 + 1)) + 10000;
        SecondKillEvent kill = new SecondKillEvent();
        kill.setSeckillId(skgId);
        kill.setUserId(userId);
        DisruptorUtil.producer(kill);
    } catch (Exception e) {e.printStackTrace();
    }
    return Result.ok();}

通过测试,发现应用 Disruptor 队列队列,与自定义队列有着同样的问题,也会呈现超卖的状况,但效率有所提高。

4. 小结

对于下面七种实现并发的形式,做一下总结:

  • 一、二形式是在代码中利用锁和事务的形式解决了并发问题,次要解决的是锁要加载事务之前
  • 三、四、五形式次要是数据库的锁来解决并发问题,形式三是利用 for upate 对表加行锁,形式四是利用 update 来对表加锁,形式五是通过减少 version 字段来管制数据库的更新操作,形式五的成果最差
  • 六、七形式是通过队列来解决并发问题,这里须要特地留神的是,在代码中不能通过 throw 抛异样,否则生产线程会终止,而且因为进队和出队存在工夫间隙,会导致商品少卖

下面所有的状况都通过代码测试,测试分一下三种状况:

  • 并发数 1000,商品数 100
  • 并发数 1000,商品数 1000
  • 并发数 2000,商品数 1000

思考:分布式状况下如何解决并发问题呢?下次持续试验。

版权申明:本文为 CSDN 博主「止步前行」的原创文章,遵循 CC 4.0 BY-SA 版权协定,转载请附上原文出处链接及本申明。原文链接:https://blog.csdn.net/zxd1435513775/article/details/122643285

近期热文举荐:

1.1,000+ 道 Java 面试题及答案整顿(2022 最新版)

2. 劲爆!Java 协程要来了。。。

3.Spring Boot 2.x 教程,太全了!

4. 别再写满屏的爆爆爆炸类了,试试装璜器模式,这才是优雅的形式!!

5.《Java 开发手册(嵩山版)》最新公布,速速下载!

感觉不错,别忘了顺手点赞 + 转发哦!

正文完
 0