基于异步线程和延迟队列实现的定时抢购(粗糙版)
需求描述:
抢购开启后,指定时间内,结束抢购,并处理抢购结果。
- 获取开启抢购信息
- 倒计时
- 计时结束后,读取snap:product:XXX的剩余商品数量,不为0,则将剩余商品数量放置到商品余额中。
- snap:product:XXX设置为0(代表抢购结束),后续完善根据winners:product:XXX进行抢购订单的生成
设计实现:
思路: 利用延迟队列实现倒计时,利用异步线程每隔1S询问延迟队列状态。
@Configuration
public class DelayQueueConfig {
@Bean
public DelayQueue<DelaySnapMessage> delayQueue(){
return new DelayQueue<DelaySnapMessage>();
}
}
@Service
public class DelayService {
private static final Logger LOGGER = LoggerFactory.getLogger(DelayService.class);
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private ProductMapper productMapper;
@Autowired
private DelayQueue<DelaySnapMessage> delayQueue;
public void put(String snapProductKey){
delayQueue.put(new DelaySnapMessage(snapProductKey,30, TimeUnit.SECONDS));
}
public void put(String snapProductKey, long snapTime){
delayQueue.put(new DelaySnapMessage(snapProductKey, snapTime, TimeUnit.SECONDS));
LOGGER.info("抢购开始时间:{}", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-mm-dd HH:mm:ss")));
}
@Async //异步执行,在Application类也需要添加该注解
@PostConstruct //该方法在注入为bean之前会先执行一次。
protected void handleDelay(){
new Thread(new Runnable() {
@Override
public void run() {
LOGGER.info("抢购延迟处理线程开始执行时间:{}", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-mm-dd HH:mm:ss")));
int i = 0;
while (i < 12){
DelaySnapMessage message = delayQueue.poll();
if(message != null){
String snapProductKey = message.getSnapProductKey();
LOGGER.info("抢购结束时间:{}", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-mm-dd HH:mm:ss")));
int productNum = (Integer) redisTemplate.opsForValue().get(message.getSnapProductKey());
if(productNum > 0){
LOGGER.info("抢购商品 {},剩余数量:{}",snapProductKey,productNum);
}
redisTemplate.opsForValue().set(snapProductKey,0);
//将剩余的库存返回到MySQL中
Long productId = GetRedisKey.getProductIdBySnapKey(snapProductKey);
Product product = productMapper.selectByPrimaryKey(productId);
product.setInventory(new Long(product.getInventory() + productNum));
productMapper.updateByPrimaryKey(product);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
LOGGER.info("抢购延迟处理线程结束执行时间:{}", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-mm-dd HH:mm:ss")));
}
}).start();
}
}
总结
优点:
直接调用JDK,不适用额外中间件,依赖简单。
缺点:
使用异步线程进行数据库更新,存在数据库同步隐患。(设计缺陷)
延迟队列,需要持续询问(poll() )效率很低。
目前设计实现是每1S询问一次,存在抢购结束时间1S的误差。