单机秒杀 情况一 最简单的查询更新操作,不涉及各种锁,会出现超卖情况。
流程 首先清空订单表
新建线程池
运行200个线程模拟并发请求,
每个线程的步骤:
查询商品剩余数量
如果大于0,商品数量减1,
创建订单,新建订单记录。
秒杀成功添加记录到订单表,商品数量减1,
会出现超卖现象
情况二 事务中加锁
面对高并发是锁的实现要使用aop 实现,锁不能加在方法中,因为事务一般是方法结束后提交,而锁在finally 方法中提交,从而会出现锁已经解锁而事务还没来得及提交,下个锁获得到的数据就不对。
会出现事务没有提交之前,锁已经释放,导致出现超卖1现象。
情况三 使用自定义切面AOP,实现锁上移,事务提交后释放锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Override @Servicelock @Transactional(rollbackFor = Exception.class) public Result startSeckilAopLock (long seckillId, long userId) { String nativeSql = "SELECT number FROM seckill WHERE seckill_id=?" ; Object object = dynamicQuery.nativeQueryObject(nativeSql, new Object []{seckillId}); Long number = ((Number) object).longValue(); if (number>0 ){ nativeSql = "UPDATE seckill SET number=number-1 WHERE seckill_id=?" ; dynamicQuery.nativeExecuteUpdate(nativeSql, new Object []{seckillId}); SuccessKilled killed = new SuccessKilled (); killed.setSeckillId(seckillId); killed.setUserId(userId); killed.setState(Short.parseShort(number+"" )); killed.setCreateTime(new Timestamp (System.currentTimeMillis())); dynamicQuery.save(killed); }else { return Result.error(SeckillStatEnum.END); } return Result.ok(SeckillStatEnum.SUCCESS); }
情况四 使用了select…for update的方式,这样就通过数据库实现了悲观锁,会对查询的数据记录加锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 String nativeSql = "SELECT number FROM seckill WHERE seckill_id=? FOR UPDATE" ; Object object = dynamicQuery.nativeQueryObject(nativeSql, new Object []{seckillId});Long number = ((Number) object).longValue();if (number>0 ){nativeSql = "UPDATE seckill SET number=number-1 WHERE seckill_id=?" ; dynamicQuery.nativeExecuteUpdate(nativeSql, new Object []{seckillId}); SuccessKilled killed = new SuccessKilled (); killed.setSeckillId(seckillId); killed.setUserId(userId); killed.setState((short )0 ); killed.setCreateTime(new Timestamp (System.currentTimeMillis())); dynamicQuery.save(killed); return Result.ok(SeckillStatEnum.SUCCESS); }
情况五 基于数据库悲观锁实现,更新加锁并判断剩余数量。
情况六 基于数据库乐观锁实现,先查询商品版本号,然后根据版本号更新,判断更新数量。少量用户抢购的时候会出现 少买 的情况。
情况七 进程内队列 LinkedBlockingQueue 实现,同步消费
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @Component public class TaskRunner implements ApplicationRunner { private final static Logger LOGGER = LoggerFactory.getLogger(TaskRunner.class); @Autowired private ISeckillService seckillService; @Override public void run (ApplicationArguments var ) { new Thread (() -> { LOGGER.info("提醒队列启动成功" ); while (true ){ try { SuccessKilled kill = SeckillQueue.getSkillQueue().consume(); if (kill!=null ){ Result result = seckillService.startSeckilAopLock(kill.getSeckillId(), kill.getUserId()); if (result.equals(Result.ok(SeckillStatEnum.SUCCESS))){ LOGGER.info("用户:{}{}" ,kill.getUserId(),"秒杀成功" ); } } } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } }
情况八 基于高性能队列 Disruptor实现,同步消费,
分布式秒杀 1 Redis 分布式锁 使用redis进行加锁,跟单机加锁一样,会由于锁和事物执行顺序问题,会出现超卖的情况(建议锁上移)。