秒杀学习笔记

单机秒杀

情况一

最简单的查询更新操作,不涉及各种锁,会出现超卖情况。

流程

首先清空订单表

新建线程池

运行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) {
//来自码云码友<马丁的早晨>的建议 使用AOP + 锁实现
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();

//数量大于0,更新数据
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进行加锁,跟单机加锁一样,会由于锁和事物执行顺序问题,会出现超卖的情况(建议锁上移)。


秒杀学习笔记
http://example.com/2022/07/26/秒杀并发学习/
作者
JH
发布于
2022年7月26日
许可协议