秒杀系统在前面已经讲解了“前端优化”
以及“防止库存超卖”
的功能,但是在效率这一块还是很慢的,那么后台的秒杀完整代码流程是如何的呢?本文来讲解下,阅读前,童鞋们可以先阅读之前写的博客:
本文目录结构:
l____引言
l____ 1.秒杀原理图
l____ 2. 后台核心代码
l________ 2.1 令牌桶生成接口
l________ 2.2 秒杀接口(核心)
l________________ 2.2.1 MQ配置
l________________ 2.2.2 生产者
l________________ 2.2.3 消费者
l________ 2.3 用户查询接口
l____ 3. 测试
下面贴上我自己整理的原理图,如下:
从原理图,可以看到秒杀的流程大致如下:
令牌桶生成接口核心代码:
@Override public BaseResponseaddSpikeToken(Long seckillId, Long tokenQuantity) { // 1.验证参数 if (seckillId == null) { return setResultError("商品库存id不能为空!"); if (tokenQuantity == null) { return setResultError("token数量不能为空!"); SeckillEntity seckillEntity = seckillMapper.findBySeckillId(seckillId); if (seckillEntity == null) { return setResultError("商品信息不存在!"); // 2.使用多线程异步生产令牌 createSeckillToken(seckillId, tokenQuantity); return setResultSuccess("令牌正在生成中....."); @Async public void createSeckillToken(Long seckillId, Long tokenQuantity) { generateToken.createListToken("seckill_", seckillId + "", tokenQuantity);
Redis令牌桶生成工具类:
①GenerateToken
public void createListToken(String keyPrefix, String redisKey, Long tokenQuantity) { ListlistToken = getListToken(keyPrefix, tokenQuantity); redisUtil.setList(redisKey, listToken); public ListgetListToken(String keyPrefix, Long tokenQuantity) { ListlistToken = new ArrayList<>(); for (int i = 0; i < tokenQuantity; i++) { String token = keyPrefix + UUID.randomUUID().toString().replace("-", ""); listToken.add(token); return listToken;
②RedisUtil:
public void setList(String key, ListlistToken) { stringRedisTemplate.opsForList().leftPushAll(key, listToken);
①application.yml配置:
rabbitmq: ####连接地址 host: 127.0.0.1 ####端口号 port: 5672 ####账号 username: guest ####密码 password: guest ### 地址 virtual-host: spike_host listener: simple: retry: ####开启消费者(程序出现异常的情况下会)进行重试 enabled: true ####最大重试次数 max-attempts: 5 ####重试间隔时间 initial-interval: 1000 ####开启手动ack acknowledge-mode: manual default-requeue-rejected: false
②RabbitMQ配置:
* description: RabbitmqConfig 配置 * create by: YangLinWei * create time: 2020/5/26 10:54 上午 @Component public class RabbitmqConfig { // 添加修改库存队列 public static final String MODIFY_INVENTORY_QUEUE = "modify_inventory_queue"; // 交换机名称 private static final String MODIFY_EXCHANGE_NAME = "modify_exchange_name"; // 1.添加交换机队列 @Bean public Queue directModifyInventoryQueue() { return new Queue(MODIFY_INVENTORY_QUEUE); // 2.定义交换机 @Bean DirectExchange directModifyExchange() { return new DirectExchange(MODIFY_EXCHANGE_NAME); // 3.修改库存队列绑定交换机 @Bean Binding bindingExchangeintegralDicQueue() { return BindingBuilder.bind(directModifyInventoryQueue()).to(directModifyExchange()).with("modifyRoutingKey");
* description: 秒杀生产者 * create by: YangLinWei * create time: 2020/5/26 10:58 上午 @Component @Slf4j public class SpikeCommodityProducer implements RabbitTemplate.ConfirmCallback { @Autowired private RabbitTemplate rabbitTemplate; @Transactional public void send(JSONObject jsonObject) { String jsonString = jsonObject.toJSONString(); System.out.println("jsonString:" + jsonString); String messAgeId = UUID.randomUUID().toString().replace("-", ""); // 封装消息 Message message = MessageBuilder.withBody(jsonString.getBytes()) .setContentType(MessageProperties.CONTENT_TYPE_JSON).setContentEncoding("utf-8").setMessageId(messAgeId) .build(); // 构建回调返回的数据(消息id) this.rabbitTemplate.setMandatory(true); this.rabbitTemplate.setConfirmCallback(this); CorrelationData correlationData = new CorrelationData(jsonString); rabbitTemplate.convertAndSend("modify_exchange_name", "modifyRoutingKey", message, correlationData); // 生产消息确认机制 生产者往服务器端发送消息的时候,采用应答机制 @Override public void confirm(CorrelationData correlationData, boolean ack, String cause) { String jsonString = correlationData.getId(); System.out.println("消息id:" + correlationData.getId()); if (ack) { log.info(">>>使用MQ消息确认机制确保消息一定要投递到MQ中成功"); return; JSONObject jsonObject = JSONObject.parseObject(jsonString); // 生产者消息投递失败的话,采用递归重试机制 send(jsonObject); log.info(">>>使用MQ消息确认机制投递到MQ中失败");
* description: 库存消费者 * create by: YangLinWei * create time: 2020/5/26 10:59 上午 @Component @Slf4j public class StockConsumer { @Autowired private SeckillMapper seckillMapper; @Autowired private OrderMapper orderMapper; @RabbitListener(queues = "modify_inventory_queue") @Transactional public void process(Message message, @Headers Mapheaders, Channel channel) throws IOException { String messageId = message.getMessageProperties().getMessageId(); String msg = new String(message.getBody(), "UTF-8"); log.info(">>>messageId:{},msg:{}", messageId, msg); JSONObject jsonObject = JSONObject.parseObject(msg); // 1.获取秒杀id Long seckillId = jsonObject.getLong("seckillId"); SeckillEntity seckillEntity = seckillMapper.findBySeckillId(seckillId); if (seckillEntity == null) { log.warn("seckillId:{},商品信息不存在!", seckillId); basicNack(message, channel); return; Long version = seckillEntity.getVersion(); int inventoryDeduction = seckillMapper.optimisticDeduction(seckillId, version); if (!toDaoResult(inventoryDeduction)) { log.info(">>>seckillId:{}修改库存失败>>>>inventoryDeduction返回为{} 秒杀失败!", seckillId, inventoryDeduction); basicNack(message, channel); return; // 2.添加秒杀订单 OrderEntity orderEntity = new OrderEntity(); String phone = jsonObject.getString("phone"); orderEntity.setUserPhone(phone); orderEntity.setSeckillId(seckillId); orderEntity.setState(1l); int insertOrder = orderMapper.insertOrder(orderEntity); if (!toDaoResult(insertOrder)) { basicNack(message, channel); return; log.info(">>>修改库存成功seckillId:{}>>>>inventoryDeduction返回为{} 秒杀成功", seckillId, inventoryDeduction); basicNack(message, channel); // 调用数据库层判断 public Boolean toDaoResult(int result) { return result > 0 ? true : false; // 消费者获取到消息之后 手动签收 通知MQ删除该消息 private void basicNack(Message message, Channel channel) throws IOException { channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
@RestController public class OrderSeckillServiceImpl extends BaseApiServiceimplements OrderSeckillService { @Autowired private OrderMapper orderMapper; @Override public BaseResponsegetOrder(String phone, Long seckillId) { if (StringUtils.isEmpty(phone)) { return setResultError("手机号码不能为空!"); if (seckillId == null) { return setResultError("商品库存id不能为空!"); OrderEntity orderEntity = orderMapper.findByOrder(phone, seckillId); if (orderEntity == null) { return setResultError("正在排队中....."); return setResultSuccess("恭喜你秒杀成功!");
①模拟用户修改商品库存,更新令牌桶,浏览器访问:http://localhost:9800/addSpikeToken?seckillId=100001&tokenQuantity=100
可以看到Redis里生成商品key id为100001,值为list,大小为100的集合:
②模拟抢购,浏览器访问:http://localhost:9800/spike?phone=13800000001&seckillId=100001
可以看到数据库库存减一:
订单并生成了一条记录:
Redis减少了一个令牌:
③模拟用户查询抢购结果,浏览器访问:
本文地址:https://www.0558.la/article/968ae3bc6c00aa487ae6.html