秒杀业务场景的处理方案

秒杀的处理方案

秒杀技术实现核心思想是运用缓存减少数据库瞬间的访问压力。在秒杀时,首先会将数据库的秒杀商品同步到缓存中,用户从缓存中查询秒杀商品,抢购商品时减少缓存中的库存数量。产生的秒杀订单先写到缓存,付款成功后再写入数据库。

同步秒杀商品到redis

我们需要将正在秒杀的商品从数据库同步保存到redis中,在redis中

秒杀商品是以Hash类型保存,Hash的键是商品id,值是商品对象。

用户只能查询正在秒杀的商品 ( 开始时间 < 当前时间 < 结束时间,且库存 > 0 ) ,所以我们在redis中只保存正在秒杀的商品。由于每分钟都有商品开始秒杀,也有商品结束秒杀。所以需要定时查询数据库中正在秒杀的商品,同步到redis中。我们使用SpringTask技术,每分钟同步一次数据

      用户秒杀会修改redis中的商品库存,而此时mysql中的库存是没有修改的。等到下次同步数据的时候,redis中的库存数就又成mysql中没有修改过的库存了。为了保证数据的同步,我们在将数据库数据同步到redis之前,先将redis中的商品库存数据同步到数据库中。

定时任务同步redis和数据库可参考示例代码:

/*** 每分钟查询一次数据库,更新redis中的秒杀商品数据* 条件为startTime < 当前时间 < endTime,库存大于0*/@Scheduled(cron = "0 * * * * *")public void refreshRedis() {// 将redis中秒杀商品的库存数据同步到mysqlList seckillGoodsListOld = redisTemplate.boundHashOps("seckillGoods").values();for (SeckillGoods seckillGoods : seckillGoodsListOld) {// 在数据库中查询秒杀商品SeckillGoods sqlSeckillGoods = seckillGoodsMapper.selectById(seckillGoods.getId());// 修改秒杀商品的库存sqlSeckillGoods.setStockCount(seckillGoods.getStockCount());seckillGoodsMapper.updateById(sqlSeckillGoods);}// 1.查询数据库中正在秒杀的商品QueryWrapper queryWrapper = new QueryWrapper();Date date = new Date();String now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);queryWrapper.le("startTime", now) // 当前时间晚于开始时间.ge("endTime", now) // 当前时间早于开始时间.gt("stockCount", 0); // 库存大于0List seckillGoodsList = seckillGoodsMapper.selectList(queryWrapper);// 2.删除之前的秒杀商品redisTemplate.delete("seckillGoods");// 3.保存现在正在秒杀的商品for (SeckillGoods seckillGoods : seckillGoodsList) {redisTemplate.boundHashOps("seckillGoods").put(seckillGoods.getGoodsId(), seckillGoods);}}

分页查询秒杀商品列表(返回有分页的格式)

将redis中存储的秒杀商品数据构造分页结构返回给前端可参考如下代码:

@Overridepublic Page findPageByRedis(int page, int size) {// 1. 查询所有秒杀商品List seckillGoodsList = redisTemplate.boundHashOps("seckillGoods").values();// 2. 获取当前页商品列表// 开始截取索引int start = (page - 1) * size;// 结束截取索引int end = start + size > seckillGoodsList.size() ? seckillGoodsList.size():start + size;// 获取当前页结果集List seckillGoods = seckillGoodsList.subList(start, end);// 3. 构造页面对象Page seckillGoodsPage = new Page();seckillGoodsPage.setCurrent(page) // 当前页.setSize(size) // 每页条数.setTotal(seckillGoodsList.size()) // 总条数.setRecords(seckillGoods); //结果集return seckillGoodsPage;}

根据id查询秒杀商品

 @Overridepublic SeckillGoods findSeckillGoodsByRedis(Long goodsId) {return (SeckillGoods) redisTemplate.boundHashOps("seckillGoods").get(goodsId);}

生成秒杀订单

为了让用户购买速度更快,秒杀商品时不会将商品添加到购物车,而是直接生成订单。并且由于访问量较大,为了避免数据库压力过大,我们会先将订单数据保存在redis当中,等用户支付完成后,再将redis中的订单数据保存到数据库中。

在用户成功秒杀下单后,商品库存减少,如果用户长时间不支付,则该商品始终被用户占据,其他用户也无法购买。我们需要给订单设置过期时间,过期后删除订单,回退商品库存。

创建订单简单示例代码

@Overridepublic Orders createOrder(Orders orders) {// 1.生成订单对象orders.setId(IdWorker.getIdStr()); // 手动生产订单idorders.setStatus(1); // 订单状态未付款orders.setCreateTime(new Date()); // 订单创建时间orders.setExpire(new Date(new Date().getTime()+1000*60*5));// 计算商品价格CartGoods cartGoods = orders.getCartGoods().get(0);Integer num = cartGoods.getNum();BigDecimal price = cartGoods.getPrice();BigDecimal sum = price.multiply(BigDecimal.valueOf(num));orders.setPayment(sum);// 2.减少秒杀商品库存// 查询秒杀商品SeckillGoods seckillGoods = findSeckillGoodsByRedis(cartGoods.getGoodId());// 查询库存,库存不足抛出异常Integer stockCount = seckillGoods.getStockCount();if (stockCount <= 0){throw new BusException(CodeEnum.NO_STOCK_ERROR);}// 减少库存seckillGoods.setStockCount(seckillGoods.getStockCount() - cartGoods.getNum());redisTemplate.boundHashOps("seckillGoods").put(seckillGoods.getGoodsId(),seckillGoods);// 3.保存订单数据redisTemplate.setKeySerializer(new StringRedisSerializer());// 设置订单一分钟过期redisTemplate.opsForValue().set(orders.getId(),orders,1, TimeUnit.MINUTES);/*** 给订单创建副本,副本的过期时间长于原订单* redis过期后触发过期事件时,redis数据已经过期,此时只能拿到key,拿不到value。* 而过期事件需要回退商品库存,必须拿到value即订单详情,才能拿到商品数据,进行回退操作* 我们保存一个订单副本,过期时间长于原订单,此时就可以通过副本拿到原订单数据*/redisTemplate.opsForValue().set(orders.getId()+"_copy",orders,2,TimeUnit.MINUTES);return orders;}

编写redis监听器,监听过期未支付订单 (RedisKeyExpirationListener.java和RedisListenerConfig.java)

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;/*** redis监听器*/
@Configuration
public class RedisListenerConfig {// 配置redis监听器,监听redis过期时间@Beanpublic RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory){RedisMessageListenerContainer container = new RedisMessageListenerContainer();container.setConnectionFactory(connectionFactory);return container;}
}

 

订单过期后,关闭交易,回退商品库存

/*** redis监听类继承KeyExpirationEventMessageListener*/
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {@Autowiredprivate RedisTemplate redisTemplate;@Autowiredprivate SeckillService seckillService;public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {super(listenerContainer);}/*** 订单过期后,关闭交易,回退商品库存* @param message* @param pattern*/@Overridepublic void onMessage(Message message, byte[] pattern) {// 获取订单idString orderId = message.toString();// 拿到复制订单信息Orders orders = (Orders) redisTemplate.opsForValue().get(orderId + "_copy");Long goodId = orders.getCartGoods().get(0).getGoodId();//产品idInteger num = orders.getCartGoods().get(0).getNum();//产品数据// 查询秒杀商品SeckillGoods seckillGoods = seckillService.findSeckillGoodsByRedis(goodId);// 回退库存seckillGoods.setStockCount(seckillGoods.getStockCount()+num);redisTemplate.boundHashOps("seckillGoods").put(goodId,seckillGoods);// 删除复制订单数据redisTemplate.delete(orderId+"_copy");}
}

支付秒杀订单

/*** 支付秒杀订单* @param id 订单id* @return*/@GetMapping("/pay")public BaseResult pay(String id){// 支付秒杀订单// 1.查询订单,设置相应数据Orders orders = (Orders) redisTemplate.opsForValue().get(orderId);if (orders == null){throw new BusException(CodeEnum.ORDER_EXPIRED_ERROR);}orders.setStatus(2);orders.setPaymentTime(new Date());orders.setPaymentType(2); // 支付宝支付// 2.从redis删除订单redisTemplate.delete(orderId);redisTemplate.delete(orderId+"_copy");// 将订单存入数据库orderService.add(orders);return BaseResult.ok();}


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部