代码链接:
https://github.com/betterGa/ChangGou

文章目录

  • 一、防止秒杀重复排队
  • 二、 并发超卖问题解决
  • 三、 订单支付
    • 1、实现根据不同类型订单识别不同操作队列
    • 2、支付回调逻辑更新
  • 四、Rabbit MQ 延时消息队列
    • 1、延时队列介绍
    • 2、 TTL、DLX 实现延时队列
  • 五、延时队列实现订单关闭回滚库存
  • 六、总结

目标:

  • 防止秒杀重复排队【一个人抢购商品,如果没有支付,不允许重复排队抢购】
  • 并发超卖【一个商品卖给多个人:一商品多订单】问题解决
  • 秒杀订单支付
  • 超时支付订单库存回滚

一、防止秒杀重复排队


    可以看到,使用 Redis 中的 incr,第一回会让值增为 1,之后每次递增1。

用户每次抢单的时候,一旦排队,我们设置一个自增值,让该值的初始值为 1,每次进入抢单的时候,对它进行递增,如果值 >1,则表明重复排队了,需要抛出异常。

修改 SeckillOrderServiceImpl 的 add 排队方法,新增递增值判断是否排队中,代码如下:

进行测试:


再进行一次抢单:

下单次数变成了 “2”:

再下单,再下单,UserQueueCount 里 username 对应的 value 还是会递增。(有必要递增吗???)

二、 并发超卖问题解决

超卖问题,这里是指多人抢购同一商品的时候,多人同时判断是否有库存,如果只剩一个,则都会判断有库存,此时会导致超卖现象产生,也就是一个商品下了多个订单的现象

  • 思路分析
         解决超卖问题,可以利用 Redis 队列实现,给每件商品创建一个独立的商品个数队列,例如:A 商品有 2 个,A 商品的 ID 为 1001,则可以创建一个队列,key=SeckillGoodsCountList_1001,往该队列中塞 2 次该商品 ID。
         每次给用户下单的时候,先从队列中取数据,如果能取到数据,则表明有库存,如果取不到,则表明没有库存,将排队信息删除,这样就可以防止超卖问题产生了。
         在我们对 Redis 进行操作的时候,很多时候,都是先将数据查询出来,在内存中修改,然后存入到 Redis,在并发场景,会出现数据错乱问题,为了控制数量准确,我们单独将商品数量整一个自增键,自增键是线程安全的,所以不担心并发场景的问题。
  • 代码实现
         每次将商品压入 Redis 缓存的时候,另外多创建一个商品的队列,库存有多少,这个队列里就有多少个元素。如此实现线程安全。

修改 SeckillGoodsPushTask,添加一个 putAllIds 方法,用于将指定商品 ID 放入到指定的数字中,因为 Redis 设置为 String 类型,所以需要返回 String[ ] 类型的参数:

/**** 将商品ID存入到数组中* @param len:长度* @param id :值* @return*/public String[] putAllIds(int num,Long id){String[] ids=new String[num];for(int i=0;i<num;i++){ids[i]=id.toString();}return ids;}

修改 SeckillGoodsPushTask 的 loadGoodsPushRedis 方法,添加队列操作,代码如下:

/*** 解决超卖问题*/// 将库存个商品id 存入 RedisredisTemplate.boundListOps("SeckillGoodsCountList_" + seckillgood.getId()).leftPushAll(putAllIds(seckillgood.getStockCount(), seckillgood.getId()));

运行结果:

可以看到,确实生成了 num 个 id。

//自增计数器
redisTemplate.boundHashOps(“SeckillGoodsCount”).increment(seckillGood.getId(),seckillGood.getStockCount());

生产了自然要消费,修改多线程下单方法,分别修改数量控制,以及售罄后用户抢单排队信息的清理,修改代码如下图:

          /****当没有库存时,需要清理排队信息*/Object goods = redisTemplate.boundListOps("SeckillGoodsCountList_" + seckillStatus.getGoodsId()).rightPop();if (goods == null) {clearUserQueue(username);return;}

其中 clearUserQueue 方法:

 /*** 清理用户排队抢单信息*/public void clearUserQueue(String username) {// 排队标识redisTemplate.boundHashOps("UserQueueCount").delete(username);// 排队信息清理redisTemplate.boundHashOps("UserQueueStatus").delete(username);}
}

(但是现在有个问题:

// 库存递减
seckillGoods.setStockCount(seckillGoods.getStockCount() - 1);

递减的时候可能出现并发问题,导致库存不精准。
     而且,当商品库存 stockCount 为 1 时,执行 seckillGoods.getStockCount() - 1,库存值变为 0,然后就会更新到 MySQL 数据库中, 并且从 namespace 为 SeckillGoods_[time] 的缓存记录中删除,也就是说,该商品不再参与秒杀了 ,这是有问题的,因为调用 createOrder() 方法,会查询缓存中 namespace 为 SeckillGoods_[time] 的记录,这时是查不到的,会提示“已售罄!”,也就是说,剩下一个库存的时候 ,用户永远买不到。修改逻辑为:

三、 订单支付

之前生成订单后,支付微服务会向 MQ 中添加订单信息,然后订单微服务进行监听;现在生成秒杀订单后,需要与之前的普通订单加以区分,让秒杀订单与普通订单服务的队列名称不同,即可。

    可以看到,普通订单用的队列名为 queue.order。

在微信支付结果通知的 API 中:

     可以看到,“商家数据包” 是原样返回的。
    
    
     创建二维码时,通过 “附加数据” 指定队列名。在 统一下单 API 中可以看到:

可以把队列名封装在 attach 中:

 普通订单:
exchange:"exchange.order",
routingkey:"queue.order",秒杀订单:
exchange:"exchange.seckillorder"
routingkey:"queue.seckillorder"

将 exchange 和 routingkey 放在 json 中,作为 attach 请求参数。

1、实现根据不同类型订单识别不同操作队列

在 WeiXinPayServiceImpl 中:

    // 获取自定义数据String exchange = parameterMap.get("exchange");String routingkey = parameterMap.get("routingkey");Map<String,String> attachMap=new HashMap<>();// 如果是秒杀订单,需要传 username,后续作为秒杀订单表查询依据。String username=parameterMap.get("username");if(!StringUtils.isEmpty(username)){attachMap.put("username",username);}attachMap.put("exchange",exchange);attachMap.put("routingkey",routingkey);String attach = JSON.toJSONString(attachMap);paramMap.put("attach",attach);

对应的,在微信支付结果通知中,需要取出这个 attach:

 String attach = resultMap.get("attach");Map<String,String> attachMap=JSON.parseObject(attach,Map.class);rabbitTemplate.convertAndSend(attachMap.get("exchange"),attachMap.get("routingkey"),JSON.toJSONString(resultMap));

在支付工程的 application.yml 中,添加 mq 的配置:

在 pay 工程用 @Configuration 修饰的 MQConfig 中创建秒杀队列:

/***** 秒杀队列创建* @return*/// 创建队列@Beanpublic Queue orderSeckillQueue() {return new Queue(environment.getProperty("mq.pay.queue.seckillorder"));}// 创建交换机@Beanpublic Exchange orderSeckillExchange() {// 持久化,不自动删除return new DirectExchange(environment.getProperty("mq.pay.exchange.seckillorder"), true, false);}// 绑定@Beanpublic Binding seckillBinding(Queue orderSeckillQueue, Exchange orderSeckillExchange) {return BindingBuilder.bind(orderSeckillQueue).to(orderSeckillExchange).with(environment.getProperty("mq.pay.routing.seckillkey")).noargs();}

(一般都是在 MQ 中创建队列的,这里方便起见,在程序中创建。这样,一启动 pay 工程,当调用到 pay 工程里的方法时,就会运行这个配置类,创建秒杀队列。)

(而且注意到,使用 @Bean 注册 bean 时,方法参数名必须和 bean 的方法名保持一致,比如除了秒杀队列以外,之前还有普通队列的创建,普通队列创建的 public Queue orderQueue() 方法,和 秒杀队列的 public Queue orderSeckillQueue() 方法,返回类型都是 Queue,那么,seckillBinding 方法里传的参数,如果参数名写成 queue,它会先按照类型进行装配,普通队列和秒杀队列方法的返回类型都是 Queue,是要报错的。)

进行测试,启动 eureka、pay,需要自定义 exchange 和 queue 名:

生成支付二维码后,扫描并付款,看 RabbitMQ 中的队列:

这时启动 seckill 工程,会对秒杀队列进行监听:

运行结果:

     至此,秒杀入队和监听队列的逻辑都没有问题。

2、支付回调逻辑更新

如果回调订单支付结果,支付成功的话,需要修改订单状态、清除用户排队信息;如果失败的话,需要修改订单状态、回滚库存。

先在 SeckillOrderService 接口里提供修改订单状态、清除用户排队的方法:

  void updatePayStatus(String transactionId, String username, String endtime);

实现:

@Overridepublic void updatePayStatus(String transactionId, String username, String endtime){/** 修改订单状态信息 */// 获取订单SeckillOrder seckillOrder = (SeckillOrder) redisTemplate.boundHashOps("SeckillOrder").get(username);if (seckillOrder != null) {// 设置状态为已支付seckillOrder.setStatus("1");seckillOrder.setTransactionId(transactionId);// 支付完成时间,格式为yyyyMMddHHmmssSimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmmss");try {Date payTime = simpleDateFormat.parse(endtime);seckillOrder.setPayTime(payTime);// 更新到数据库seckillOrderMapper.insert(seckillOrder);// 删除 Redis 中的订单记录redisTemplate.boundHashOps("SeckillOrder").delete(username);// 删除 Redis 排队信息redisTemplate.boundHashOps("UserQueueCount").delete(username);redisTemplate.boundHashOps("UserQueueStatus").delete(username);} catch (ParseException e) {e.printStackTrace();}}}

在 SeckillOrderService 接口里提供 修改订单状态、回滚库存 的方法:

void deleteOrder(String username);

实现:

  /*** 删除订单** @param username*/@Overridepublic void deleteOrder(String username) {// 删除订单redisTemplate.boundHashOps("SeckillOrder").delete(username);// 查询用户排队信息SeckillStatus userQueueStatus = (SeckillStatus) redisTemplate.boundHashOps("UserQueueStatus").get(username);// 删除排队信息redisTemplate.boundHashOps("UserQueueCount").delete(username);redisTemplate.boundHashOps("UserQueueStatus").delete(username);// 回滚库存// Redis 递增String namespace = "SeckillGoods_" + userQueueStatus.getTime();SeckillGoods seckillGoods = (SeckillGoods) redisTemplate.boundHashOps(namespace).get(userQueueStatus.getGoodsId());// 如果商品为空,到数据库查询if (seckillGoods == null) {seckillGoods = seckillGoodsMapper.selectByPrimaryKey(userQueueStatus.getGoodsId());// 更新数据库内存seckillGoods.setStockCount(seckillGoods.getStockCount() + 1);seckillGoodsMapper.updateByPrimaryKey(seckillGoods);} else {seckillGoods.setStockCount(seckillGoods.getStockCount() + 1);redisTemplate.boundHashOps(namespace).put(seckillGoods.getId(), seckillGoods);redisTemplate.boundListOps("SeckillGoodsCountList" + seckillGoods.getId()).leftPush(seckillGoods.getId());}}

数据库中表的属性主要是:

有了这两个逻辑,接下来,完善监听队列的逻辑:

 @RabbitHandlerpublic void getMessage(String message) throws Exception {/*  System.out.println("Message:"+message);*/// 将支付信息转成 MapMap<String, String> resultMap = JSON.parseObject(message, Map.class);String return_code = resultMap.get("return_code");// 获取订单号String outtradeno = resultMap.get("out_trade_no");// 获取事务idString transactionid = resultMap.get("transaction_id");// 获取支付完成时间String timeEnd = resultMap.get("time_end");// 获取自定义数据String attach = resultMap.get("attach");Map<String, String> attachMap = JSON.parseObject(attach, Map.class);String username = attachMap.get("username");if ("SUCCESS".equals(return_code)) {String result_code = resultMap.get("result_code");if ("SUCCESS".equals(result_code)) {// 秒杀订单支付成功后,需要修改订单状态,清理用户排队信息seckillOrderService.updatePayStatu(transactionid, username, timeEnd);} else {// 支付失败,需要删除订单,回滚库存seckillOrderService.deleteOrder(username);}}}
}

测试:

     此时有一个秒杀订单。

下单:

查询订单信息:

    
把 orderId 作为 outtradeno,可以知道将支付信息发送到哪个队列生成支付二维码:

     其中,username 是用户名,可以根据用户名查询用户排队信息;outtradeno 是商户订单号,对于同一个商家而言,订单号是唯一的,也是下单必需的。
    
付款后,可以看到 MySQL 数据库中生成了订单记录:

而且队列信息被清除了,只剩下 时间段内秒杀商品队列,和防止超卖问题生成的商品数的商品队列:

可以看到,监听到支付信息后,根据支付信息判断,用户支付成功了,则修改订单信息,并将订单入库,删除用户排队信息。

四、Rabbit MQ 延时消息队列

1、延时队列介绍

延时队列 ,即 放置在该队列里面的消息是不需要立即消费的,而是等待一段时间之后取出消费。
     那么,为什么需要延迟消费呢?我们来看以下的场景:
     网上商城下订单后, 30 分钟后没有完成支付,取消订单(如:淘宝、去哪儿网) ;
     系统创建了预约之后,需要在预约时间到达前一小时,提醒被预约的双方参会;
     系统中的业务失败之后,需要重试。
     这些场景都非常常见,我们可以思考,比如第二个需求,系统创建了预约之后,需要在预约时间到达前一小时提醒被预约的双方参会。那么一天之中肯定是会有很多个预约的,时间也是不一定的,假设现在有 1点、2点、3点 三个预约,如何让系统知道在当前时间等于 0点、1点、2点 给用户发送信息呢,是不是需要一个轮询,一直去查看所有的预约,比对当前的系统时间和预约提前一小时的时间是否相等呢?这样做非常浪费资源,而且轮询的时间间隔不好控制。如果我们使用延时消息队列呢,我们在创建时把需要通知的预约放入消息中间件中,并且设置该消息的过期时间,等过期时间到达时再取出消费即可。

  • RabbitMQ 实现延时队列一般而言有两种形式:
    • 第一种方式:利用两个特性: Time To Live(TTL)、Dead Letter Exchanges(DLX)[A 队列过期- --> 转发给 B 队列]
    • 第二种方式:利用 RabbitMQ 中的插件 x-delay-message

2、 TTL、DLX 实现延时队列

  • TTL:Rabbit MQ 可以针对队列设置 x-expires(则队列中所有的消息都有相同的过期时间) 或者针对 Message 设置 x-message-ttl (对消息进行单独设置,每条消息 TTL 可以不同),来控制消息的生存时间,如果超时(两者同时设置以最先到期的时间为准),则消息变为 dead letter(死信)。

  • Dead Letter Exchanges(DLX):Rabbit MQ 的 Queue 可以配置 x-dead-letter-exchange 和 x-dead-letter-routing-key(可选)两个参数,如果队列内出现了 dead letter,则按照这两个参数 重新路由 转发到指定的队列。

    • x-dead-letter-exchange:出现 dead letter 之后将 dead letter 重新发送到指定 exchange。
    • x-dead-letter-routing-key:出现 dead letter 之后将 dead letter 重新按照指定的 routing-key 发送

  • DLX 延时队列实现
    (1)创建工程
    创建 springboot_rabbitmq_delay 工程,并引入相关依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>changgou-parent</artifactId><groupId>com.changgou</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>springboot_rabbitmq_delay</artifactId><dependencies><!--starter-web--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--加入ampq--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency><!--测试--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency></dependencies>
</project>

application.yml 配置:

spring:application:name: springboot-demorabbitmq:host: 127.0.0.1port: 5672password: guestusername: guest

(2) 队列创建
     创建 2 个队列,用于接收消息的叫延时队列 queue.message.delay,用于转发消息的队列叫 queue.message ,同时创建一个交换机,代码如下:

@Configuration
public class QueueConfig {// 信息发送队列public static final String QUEUE_MESSAGE = "queue.message";// 交换机public static final String DLX_EXCHANGE = "dix.exchange";// 延迟队列public static final String QUEUE_MESSAGE_DELAY = "queue.message.delay";@Beanpublic Queue messageQueue() {return new Queue(QUEUE_MESSAGE, true);}@Beanpublic Queue delayMessage() {return QueueBuilder.durable(QUEUE_MESSAGE_DELAY)// 消息超时进入死信队列,绑定死信交换机.withArgument("x-dead-letter-exchange",DLX_EXCHANGE)// 绑定交换机.withArgument("x-dead-letter-routing-key",QUEUE_MESSAGE).build();}@Beanpublic DirectExchange directExchange() {return new DirectExchange(DLX_EXCHANGE);}// 交换机与队列绑定@Beanpublic Binding basicBinding(Queue messageQueue, DirectExchange directExchange) {return BindingBuilder.bind(messageQueue).to(directExchange).with(QUEUE_MESSAGE);}
}

(3) 消息监听
创建 MessageListener 用于监听消息:

@Component
@RabbitListener(queues = QueueConfig.QUEUE_MESSAGE)
public class MessageListener {/*** 监听队列* @param msg*/@RabbitHandlerpublic void msg(@Payload Object msg){SimpleDateFormat dateFormat=new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss");System.out.println("当前时间"+dateFormat.format(new Date()));System.out.println("收到信息:"+msg);}
}

(4)创建启动类

@SpringBootApplication
@EnableRabbit
public class SpringRabbitMQApplication {public static void main(String[] args) {SpringApplication.run(SpringRabbitMQApplication.class,args);}
}

(5)测试

@SpringBootTest
@RunWith(SpringRunner.class)
public class RabbitMQTest {@Autowiredprivate RabbitTemplate rabbitTemplate;/**** 发送消息*/@Testpublic void sendMessage() throws InterruptedException, IOException {SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");System.out.println("发送当前时间:"+dateFormat.format(new Date()));Map<String,String> message = new HashMap<>();message.put("name","szitheima");rabbitTemplate.convertAndSend(QueueConfig.QUEUE_MESSAGE_DELAY, message, new MessagePostProcessor() {@Overridepublic Message postProcessMessage(Message message) throws AmqpException {message.getMessageProperties().setExpiration("10000");return message;}});System.in.read();}
}

其中 message.getMessageProperties().setExpiration("10000"),设置消息超时时间,超时后,会将消息转入到另外一个队列。

运行结果:

![在这里插入图片描述](https://img-blog.csdnimg.cn/20210501164938727.png?

五、延时队列实现订单关闭回滚库存

秒杀流程回顾:

如上图,步骤分析如下:
(1)用户抢单,经过秒杀系统实现抢单,下单后会将向 MQ 发送一个延时队列消息,包含抢单信息,延时半小时后才能监听到
(2)秒杀系统同时启用延时消息监听,一旦监听到订单抢单信息,判断 Redis 缓存中是否存在订单信息,如果存在,则回滚
(3)秒杀系统还启动支付回调信息监听,如果支付完成,则将订单同步到 MySQL,如果没完成,清理排队信息,回滚库存
(4)每次秒杀下单后调用支付系统,创建二维码,如果用户支付成功了,微信系统会将支付信息发送给支付系统指定的回调地址,支付系统收到信息后,将信息发送给 MQ ,第 3 个步骤就可以监听到消息了。

延时队列实现订单关闭回滚库存,需要创建一个过期队列 Queue1、接收消息的队列 Queue2、中转交换机。
在 seckill 工程的 mq 包中新建 QueueConfi 配置类:

@Configuration
public class QueueConfig {// queue1@Beanpublic Queue delaySeckillQueue() {return QueueBuilder.durable("delaySeckillQueue")// 当前队列消息一旦过期,进入到死信队列交换机.withArgument("x-dead-letter-exchange","seckillExchange")// 将死信队列的消息路由到指定队列.withArgument("x-dead-letter-routing-key","seckillQueue").build();}// queue2@Beanpublic Queue seckillQueue() {return new Queue("seckillQueue");}// 交换机@Beanpublic Exchange seckillExchange() {return new DirectExchange("seckillExchange");}// 队列绑定交换机public Binding seckillQueueBindingExchange(Queue seckillQueue,Exchange seckillExchange){return BindingBuilder.bind(seckillQueue).to(seckillExchange).with("seckillQueue").noargs();}
}

在多线程下单的 createOrder 方法中添加发送信息的逻辑:

SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd HH:mm:ss");
System.out.println("下单信息发送时间:" + simpleDateFormat.format(new Date()));// 发送消息给 queue1
rabbitTemplate.convertAndSend("delaySeckillQueue",(Object) JSON.toJSONString(seckillStatus),new MessagePostProcessor() {@Overridepublic Message postProcessMessage(Message message) throws AmqpException {message.getMessageProperties().setExpiration("10000");return message;}});

提供 DelaySeckillMessageListener 监听类:

/*** 延时订单监听* 监听 queue2*/
@Component
@RabbitListener(queues = "seckillQueue")
public class DelaySeckillMessageListener {@AutowiredWeiXinPayFeign weiXinPayFeign;@AutowiredSeckillOrderService seckillOrderService;@Autowiredprivate RedisTemplate redisTemplate;@RabbitHandlerpublic void getMessage(String message){SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd HH:mm:ss");System.out.println("回滚时间:" + simpleDateFormat.format(new Date()));// 获取队列中信息SeckillStatus seckillStatus = JSON.parseObject(message, SeckillStatus.class);Object userQueueStatus = redisTemplate.boundHashOps("UserQueueStatus").get(seckillStatus.getUsername());// 如果没有排队信息,说明订单已经处理// 否则说明尚未完成支付if (userQueueStatus!=null) {// 关闭微信支付订单Result result=weiXinPayFeign.cancelOrder(seckillStatus.getOrderId().toString());Map<String,String> resultMap = (Map<String, String>) result.getData();if(resultMap!=null&&"SUCCESS".equals(resultMap.get("return_code"))&&"SUCCESS".equals(resultMap.get("result_code"))){// 删除订单seckillOrderService.deleteOrder(seckillStatus.getUsername());}}}
}

删除订单的 deleteOrder 方法中,包括 删除排队信息、回滚信息逻辑。
    
监听 Queue2 的逻辑:
(1)通过 SeckillStatus 的 username 检查 Redis 中是否有订单信息
(2)如果有订单信息,需要先关闭微信支付订单,防止中途用户支付。再调用删除订单方法,删除秒杀订单在 MQ 中的排队信息,并回滚库存
(3)如果关闭订单时,用户已支付,修改订单状态即可
(4)如果关闭订单时,发生了别的错误,记录日志,人工处理

六、总结

(1)利用 Redis 的单线程性质,解决并发中可能出现的问题。
    
(2)防止秒杀重复排队:
    用户每次抢单的时候,在 Redis 中排队,namespace 为 userQueuecount,给 username 对应的 value 递增1,如果 value>1,说明重复排队了。
    
(3)超卖是指 一个商品下了多个订单的现象。使用 Redis 的 List 类型,例如:A 商品有 2 个,A 商品的 ID 为 1001,则可以创建一个队列,key=SeckillGoodsCountList_1001,往该队列中塞 2 次该商品 ID。每次给用户下单的时候,先从队列中取数据,如果能取到数据,则表明有库存;如果取不到,则表明没有库存,将 UserQueueCount、UserQueueStatus 排队信息删除。
    
(4)普通订单和秒杀订单是要区分开来的。
    普通订单的交换机名为 exchange.order;秒杀订单的交换机名为 exchange.seckillorder。
    普通订单用的队列名为 queue.order;秒杀订单的队列名为 queue.seckillorder。
    在微信支付统一下单(生成支付二维码)API 的参数中,有个"attach",是附加数据,可以用作自定义参数。
    在微信支付结果通知 API 的响应参数中,也有个 “attach”,会把商家数据原样返回。
    通过这个 attach,在生成支付二维码时,指定 exchange、routingkey 和 username ,然后在微信支付结果通知中取出 attach。这样就能在微信支付结果通知时,向 exchange、routingkey 对应的队列发送支付结果。
    SeckillMessageListener 类监听秒杀订单对应的队列。
    如果结果是支付成功的话,调用 updatePayStatu 方法,根据 key 为 username ,获取到 namespace 为 SeckillOrder 的记录里,对应的 value,即订单对象 SeckillOrder,然后修改订单状态为"1" 已支付、设置 transactionid 、支付完成时间(支付结果里的 完成时间"end_time"),并把订单对象同步到数据库,然后清除用户排队信息 UserQueueCount、UserQueueStatus;
    如果支付失败的话,调用 deleteOrder 方法,需要把 SeckillOrder 里 username 对应的记录删除,然后查询 UserQueueStatus 里 username 对应的记录,从而获得订单id、商品id,到 Redis 中查询 SeckillGoods_[time] 中的商品 id 对应的秒杀商品对象 seckillGoods,如果查询结果为空,说明此时商品不参与秒杀活动了,需要到数据库中进行查询,然后把商品库存数+1。如果查询到了秒杀商品,需要把 Redis 中SeckillGoods_[time]中商品的库存数 +1,并且对应的 SeckillGoodsCountList_[goodsid] 也要加一个记录,表示秒杀商品数量回滚。
    
(5)在多线程抢单的 createOrder 方法里,向 delaySeckillQueue 发送消息,消息内容为 seckillStatus 对象,过期时间为 10秒,10 秒后,这个队列会过期,把消息转发给 seckillQueue,使用 DelaySeckillMessageListener 方法监听 seckillQueue,查询 namespace 为 UserQueueStatus 中,username 对应的记录,如果为空,说明没有排队信息,订单已被处理;否则说明订单尚未完成支付,需要先调用微信支付 closeOrder 的 API,关闭微信支付订单,如果关闭成功的话,再删除订单,调用上面的 deleteOrder 方法。

微服务商城系统(十六)秒杀核心相关推荐

  1. 微服务商城系统(六)商品搜索 SpringBoot 整合 Elasticsearch

    文章目录 一.Elasticsearch 和 IK 分词器的安装 二.Kibana 使用 三.数据导入 Elasticsearch 1.SpringData Elasticsearch 介绍 2.搜索 ...

  2. springcloud 整合 gateway_GitHub上最火的SpringCloud微服务商城系统项目,附全套教程

    项目介绍 mall-swarm是一套微服务商城系统,采用了 Spring Cloud Greenwich.Spring Boot 2.MyBatis.Docker.Elasticsearch等核心技术 ...

  3. mall-swarm是一套微服务商城系统

    介绍: mall-swarm是一套微服务商城系统,采用了 Spring Cloud Hoxton & Alibaba.Spring Boot 2.3.Oauth2.MyBatis.Elasti ...

  4. mall-swarm微服务商城系统

    mall-swarm是一套微服务商城系统,采用了 Spring Cloud 2021 & Alibaba.Spring Boot 2.7.Oauth2.MyBatis.Docker.Elast ...

  5. 微服务商城系统(十四)微信支付

    文章目录 一.支付微服务 1.微信支付 API 2.HttpClient 工具类 3.支付微服务搭建 二.微信支付二维码生成 三.检测支付状态 四.内网穿透 五.支付结果通知 1.支付结果回调通知 2 ...

  6. 微服务商城系统(十) Spring Security Oauth2 + JWT 用户认证

    文章目录 一.用户认证分析 1.认证 与 授权 2.单点登录 3.第三方账号登录 4.第三方认证 5.认证技术方案 6.Security Oauth 2.0 入门 7. 资源服务授权 (1)资源服务授 ...

  7. 微服务商城系统(十三)订单、支付流程分析

    文章目录 一.订单 1.登录页面配置 2.用户收件地址查询 3. 下单 (1)表结构介绍 (2)下单实现 (3)库存变更 (4)增加积分 二. 支付流程分析 1. 二维码创建 2.微信扫码支付简介 ( ...

  8. 微服务商城系统(一)框架搭建、商品微服务搭建

    文章目录 一.预备 1.微服务 2.缓存 3.通用Mapper 和 PageHelper 4.持久化 5.电商模式 二.系统设计 三.框架搭建 1.环境准备 2.项目结构介绍 3.公共工程搭建 (1) ...

  9. 微服务商城系统 实战记录 用户、商家、后台管理员注册与登录功能实现

    代码见 https://github.com/betterGa/ChangGou 文章目录 一.用户注册 1.使用 ajax (POST 方法) 2.使用 thymeleaf 3.解决跨域问题 二.用 ...

最新文章

  1. 20行Python代码给微信头像戴帽子
  2. 利用BIND 9基于电信网通智能DNS 搭建
  3. 64bit win7+VS2013+opencv2.4.9配置
  4. hadoop学习01 网址收集
  5. 一维卷积filter_面试题:CNN的卷积核是单层的还是多层的?
  6. PHP正则贪婪/懒惰匹配模式
  7. 如何找到SAP ABAP odata服务实现的具体backend 系统
  8. [原] XAF How to implement a custom attribute to customize the Application Model
  9. C 为什么非要引入那几种类型转换?
  10. 集训01-03 (c++实现)
  11. java super用法_Java基础面试题汇总
  12. 【IDEA】idea Gradle 里面java类显示为灰色
  13. python document_python-docx 常用方法
  14. soap xml_SOAP XML消息–使用Liquid XML Studio进行了解和创建
  15. 无法卸载 Mac 上的磁盘时该怎么办?
  16. matlab 陈学松,基于强化学习的空调系统运行优化OPTIMIZATIONOF-同济大学.PDF
  17. 正则表达式的例题分析
  18. 服务器删除的excel文件备份在哪里,excel自动备份文件在哪!如何找回EXCEL表格已删除的文件...
  19. 电脑蓝色,我的电脑蓝色是怎么回事
  20. 访问网页出现503服务器,503错误,手把手教你网页出现503错误怎么解决

热门文章

  1. 硬盘缓存(Cache)与NCQ技术
  2. C++ 校园足球联赛 题解
  3. 播放器播放视频画面均变暗(但网页视频正常)的解决方案
  4. 开源软件保护策略——专利权不可或缺
  5. 二进制好看的深浅色系颜色大全
  6. arp miss攻击_ARP配置教程(一)
  7. # 超酷的7个JavaScript学习网站
  8. 711便利店算法java_G711算法学习
  9. c语言中常用的输入输出函数有哪些,C语言中常用的输入和输出函数
  10. 如何成为一名“优秀”的商业分析师