springBoot整合rabbitMQ --实现延时消息功能(2)

本篇这主要是利用rabbitMQ 的高级特性,通过springBoot框架来实践订单超时取消功能的实现:
框架截图如下:
在这里插入图片描述
1、rabbitMq通过yml文件配置简单属性:

server:
    port: 8021
spring:
    #给项目来个名字
    application:
        name: rabbitmq-provider
    rabbitmq:
        port: 5672
        host: 127.0.0.1
        username: guest
        password: guest
        virtual-host: /

2、SpringBoot启动类Bootstrap:

/**
 * 启动类
 * @author huoyajing
 */
@SpringBootApplication(scanBasePackages = {"com.huohuo.info"})
public class Bootstrap {

	public static void main(String[] args) {
		SpringApplication.run(Bootstrap.class, args);
	}


}

3、rabbitMQ 配置类,配置queue和exchange之间的关系:

/**
 * @description:rabbit延迟消息交换机和队列配置
 * @time: 2021/9/12 4:40 下午
 */
@Configuration
public class DelayRabbitConfig {
	/**
	 * 延迟队列 TTL 名称
	 */
	public static final String USER_ORDER_DELAY_QUEUE = "queue.user.order.delay";

	/**
	 * DLX,dead letter发送到的 exchange
	 * 延时消息就是发送到该交换机的
	 */
	public static final String USER_ORDER_DELAY_EXCHANGE = "exchange.user.order.delay";

	/**
	 * routing key 名称
	 * 具体消息发送在该 routingKey 的
	 */
	public static final String USER_ORDER_DELAY_ROUTING_KEY = "routingkey.user.order.delay";

	/**
	 * 立即消费的队列名称
	 */
	public static final String USER_ORDER_QUEUE = "queue.user.order.immediate";

	/**
	 * 立即消费的exchange
	 */
	public static final String USER_ORDER_EXCHANGE = "exchange.user.order.immediate";

	/**
	 * 立即消费 routing key 名称
	 */
	public static final String USER_ORDER_ROUTING_KEY = "routingkey.user.order.immediate";

	/**
	 * 订单实际消费-队列
	 *
	 * @return
	 */
	@Bean
	public Queue orderImmediateQueue() {
		return new Queue(USER_ORDER_QUEUE, true);
	}

	/**
	 * 订单延迟消费-队列
	 *
	 * @return
	 */
	@Bean
	public Queue orderDelayQueue() {
		Map<String, Object> map = new HashMap<>(2);
		// x-dead-letter-exchange 声明了队列里的死信转发到的DLX名称(正常exchange)
		map.put("x-dead-letter-exchange", USER_ORDER_EXCHANGE);
		// x-dead-letter-routing-key 声明了这些死信在转发时携带的 routing-key 名称(正常routing)
		map.put("x-dead-letter-routing-key", USER_ORDER_ROUTING_KEY);
		return new Queue(USER_ORDER_DELAY_QUEUE, true, false, false, map);
	}

	/**
	 * 订单消息直接消费-交换机
	 *
	 * @return
	 */
	@Bean
	public DirectExchange orderImmediateExchange() {
		return new DirectExchange(USER_ORDER_EXCHANGE,true,false);
	}

	/**
	 * 订单延迟队列-交换机
	 *
	 * @return
	 */
	@Bean
	public DirectExchange orderDelayExchange() {
		return new DirectExchange(USER_ORDER_DELAY_EXCHANGE,true,false);
	}



	/**
	 * 延迟-绑定queue和exchange
	 *
	 * @return
	 */
	@Bean
	public Binding bindDelayQueueExchange() {
		return BindingBuilder
				.bind(orderDelayQueue())
				.to(orderDelayExchange())
				.with(USER_ORDER_DELAY_ROUTING_KEY);
	}

	/**
	 * 立即消费-绑定queue和exchange
	 *
	 * @return
	 */
	@Bean
	public Binding bindImmediateQueueExchange() {
		return BindingBuilder
				.bind(orderImmediateQueue())
				.to(orderImmediateExchange())
				.with(USER_ORDER_ROUTING_KEY);
	}

4、订单超时取消-将数据交给消费者

/**
 * @description:订单超时取消-将数据交给消费者
 * @author: huoyajing
 * @time: 2021/9/12 5:42 下午
 */
@Component
public class DelaySender {


	private AmqpTemplate amqpTemplate;

	@Autowired
	public void delayProducer(AmqpTemplate amqpTemplate){
		this.amqpTemplate=amqpTemplate;
	}

	public void delaySend(Order order,String dateString) {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		// convertAndSend参数说明:1、延迟交换机名 2、延迟routing-key 3、order对象 4、message消息
		// 交换机会马上把所有的信息都交给所有的消费者
		this.amqpTemplate.convertAndSend(DelayRabbitConfig.USER_ORDER_DELAY_EXCHANGE, DelayRabbitConfig.USER_ORDER_DELAY_ROUTING_KEY, order, message -> {
			message.getMessageProperties().setExpiration(dateString+"");
			System.out.println("send orderInfo:"+order.getOrderName()+";订单生成时间"+sdf.format(new Date()) + " Delay sent.");
			return message;
		});
	}
}

5、、需要处理的订单信息,当用户下订单的时候,则把订单信息放入rabbitMQ 中

/**
 * @description:订单数据,触发场景
 * @author: huoyajing
 * @time: 2021/9/12 7:52 下午
 */
@RestController
public class DelayController {

	private DelaySender delaySender;

	@Autowired
	public void setDelayProducer(DelaySender delaySender) {
		this.delaySender = delaySender;
	}

	@GetMapping("/sendDelay")
	public Object sendDelay() {
		Order order1 = new Order();
		order1.setOrderId("001");
		order1.setOrderStatus(1);
		order1.setOrderName("huohuo订单1");
		//订单1睡10秒
		delaySender.delaySend(order1,"10000");

		//让服务一直挂起,不然,接收消息时,服务已经停了
		while (true) {
			try {
				TimeUnit.SECONDS.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

}

6、消费消息,符合条件的处理逻辑,不符合条件的订单直接忽略

/**
 * @description:订单超时取消-消费者
 * @author: huoyajing
 * @time: 2021/9/12 5:53 下午
 */
@Component
public class DelayReceiver {
	@RabbitListener(queues = DelayRabbitConfig.USER_ORDER_QUEUE)
	@RabbitHandler
	public void orderDelayQueue(Order order) {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		System.out.println("receive orderInfo:"+order.getOrderName()+";收到延时消息时间:" + sdf.format(new Date()) + " Delay sent.");
		if (null != order && null != order.getOrderStatus()) {
			//0待支付;1已完成;2已取消
			if (0 == order.getOrderStatus()) {
				//实现对应的取消功能(核心逻辑处理)
				System.out.println("该订单未支付,取消订单:" + order);
			} else if (1 == order.getOrderStatus()) {
				System.out.println("该订单已完成支付:" + order);
			} else if (2 == order.getOrderStatus()) {
				System.out.println("该订单已取消:" + order);
			}
		}
	}
}

启动bootstrap,项目启动好之后,利用swagger或者postman即可以调用接口,触发订单接口,然后则实现了生产消费的流程,结果如下:

send orderInfo:huohuo订单1;订单生成时间2021-09-19 17:00:33 Delay sent.
receive orderInfo:huohuo订单1;收到延时消息时间:2021-09-19 17:00:43 Delay sent.
该订单已完成支付:Order[id=001,name=huohuo订单1,state=1]

订单生成的时间为:17点00分33秒,代码设置了10秒延后,也就是实际项目中设置的半小时或者十分钟取消订单的时间,延后消息为17点00分43秒,由于订单1是已完成的,所以直接忽略,若是状态为0,待支付,则走取消订单,释放库存的逻辑。

当多个订单过来,设置不同休眠时间,会怎样呢?

/**
 * @description:订单数据,触发场景
 * @author: huoyajing
 * @time: 2021/9/12 7:52 下午
 */
@RestController
public class DelayController {

	private DelaySender delaySender;

	@Autowired
	public void setDelayProducer(DelaySender delaySender) {
		this.delaySender = delaySender;
	}

	@GetMapping("/sendDelay")
	public Object sendDelay() {
		Order order1 = new Order();
		order1.setOrderId("001");
		order1.setOrderStatus(1);
		order1.setOrderName("huohuo订单1");

		Order order2 = new Order();
		order2.setOrderId("002");
		order2.setOrderStatus(0);
		order2.setOrderName("huohuo订单2");

		Order order3 = new Order();
		order3.setOrderId("003");
		order3.setOrderStatus(0);
		order3.setOrderName("huohuo订单3");
		//订单1睡10秒
		delaySender.delaySend(order1,"10000");
		//订单2睡4秒
		delaySender.delaySend(order2,"4000");
		//订单3睡3秒
		delaySender.delaySend(order3,"3000");


		//让服务一直挂起,不然,接收消息时,服务已经停了
		while (true) {
			try {
				TimeUnit.SECONDS.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

	}

}

send orderInfo:huohuo订单1;订单生成时间2021-09-19 17:04:40 Delay sent.
send orderInfo:huohuo订单2;订单生成时间2021-09-19 17:04:40 Delay sent.
send orderInfo:huohuo订单3;订单生成时间2021-09-19 17:04:40 Delay sent.
receive orderInfo:huohuo订单1;收到延时消息时间:2021-09-19 17:04:50 Delay sent.
该订单已完成支付:Order[id=001,name=huohuo订单1,state=1]
receive orderInfo:huohuo订单2;收到延时消息时间:2021-09-19 17:04:50 Delay sent.
该订单未支付,取消订单:Order[id=002,name=huohuo订单2,state=0]
receive orderInfo:huohuo订单3;收到延时消息时间:2021-09-19 17:04:50 Delay sent.
该订单未支付,取消订单:Order[id=003,name=huohuo订单3,state=0]

如上测试,我们设置订单2,3休眠时间分别为4秒和3秒,感觉应该是先执行订单3,再执行订单2,最后执行订单1,但是测试结果出来我们发现一个很神奇的过程,就是订单2,3不管休眠多少,最终是根据订单1执行后的顺序执行的。也就是订单2,3被阻塞了。
再试一下:

        //订单1睡10秒
		delaySender.delaySend(order1,"2000");
		//订单2睡4秒
		delaySender.delaySend(order2,"10000");
		//订单3睡3秒
		delaySender.delaySend(order3,"3000");

如上结果执行完订单1,执行订单2,订单3等候订单2

send orderInfo:huohuo订单1;订单生成时间2021-09-19 17:14:36 Delay sent.
send orderInfo:huohuo订单2;订单生成时间2021-09-19 17:14:36 Delay sent.
send orderInfo:huohuo订单3;订单生成时间2021-09-19 17:14:36 Delay sent.
receive orderInfo:huohuo订单1;收到延时消息时间:2021-09-19 17:14:38 Delay sent.
该订单已完成支付:Order[id=001,name=huohuo订单1,state=1]
receive orderInfo:huohuo订单2;收到延时消息时间:2021-09-19 17:14:46 Delay sent.
该订单未支付,取消订单:Order[id=002,name=huohuo订单2,state=0]
receive orderInfo:huohuo订单3;收到延时消息时间:2021-09-19 17:14:46 Delay sent.
该订单未支付,取消订单:Order[id=003,name=huohuo订单3,state=0]

为何会出现这种情况,其实很简单,Queue的性质导致的,纵然设置的时间短,但是出不了队列也没办法,就好比买东西似的,谁去的早谁先买,而不是谁花的钱多谁就有优先权。
在这里插入图片描述
PS:写代码还是得认真,不能觉得理所当然,本来很简单的一个demo,结果由于自己的粗心大意,然后找了半天问题,却硬是不晓得哪里有问题,也么有报错,就是消费者无反应,就是不消费。其实就是代码写错了,对于静态变量的编写以及引用,由于只有部分字段不一样,如果不仔细,很容易就引用错了,就比如我应该引入queue,但是却引用了routing,自己却硬没发现,也是醉了,哈哈。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值