应用场景
目前常见的应用软件都有消息的延迟推送的影子,应用也极为广泛,例如:
- 淘宝七天自动确认收货。在我们签收商品后,物流系统会在七天后延时发送一个消息给支付系统,通知支付系统将款打给商家,这个过程持续七天,就是使用了消息中间件的延迟推送功能。
- 12306 购票支付确认页面。我们在选好票点击确定跳转的页面中往往都会有倒计时,代表着 30 分钟内订单不确认的话将会自动取消订单。其实在下订单那一刻开始购票业务系统就会发送一个延时消息给订单系统,延时30分钟,告诉订单系统订单未完成,如果我们在30分钟内完成了订单,则可以通过逻辑代码判断来忽略掉收到的消息。
在上面两种场景中,如果我们使用下面两种传统解决方案无疑大大降低了系统的整体性能和吞吐量:
- 使用 redis 给订单设置过期时间,最后通过判断 redis 中是否还有该订单来决定订单是否已经完成。这种解决方案相较于消息的延迟推送性能较低,因为我们知道 redis 都是存储于内存中,我们遇到恶意下单或者刷单的将会给内存带来巨大压力。
- 使用传统的数据库轮询来判断数据库表中订单的状态,这无疑增加了IO次数,性能极低。
- 使用 jvm 原生的 DelayQueue ,也是大量占用内存,而且没有持久化策略,系统宕机或者重启都会丢失订单信息。
消息延时推送实现
在 RabbitMQ 3.6.x 之前我们一般采用死信队列+TTL过期时间来实现延迟队列。
在 RabbitMQ 3.6.x 开始,RabbitMQ 官方提供了延迟队列的插件,可以下载放置到 RabbitMQ 根目录下的 plugins 下
资源地址:https://www.rabbitmq.com/community-plugins.html
我这边是在windows下,下载解压放如plugin,然后cmd:
D:\Program Files\RabbitMQ Server\rabbitmq_server-3.7.9\sbin>rabbitmq-plugins enable rabbitmq_delayed_message_exchange
代码样例
/** * 延时Queue */ public static final String DELAY_QUEUE = "delay.queue"; @Bean public Queue delayQueue(){ return new Queue(DELAY_QUEUE, true); } @Bean TopicExchange delayExchange() { Map<String, Object> pros = new HashMap<String, Object>(); //设置交换机支持延迟消息推送 pros.put("x-delayed-message", "topic"); TopicExchange delayTopicExchange = new TopicExchange("delayTopicExchange",true,false,pros); delayTopicExchange.setDelayed(true); return delayTopicExchange; } @Bean Binding bindingDelayExchange(Queue delayQueue, TopicExchange delayExchange) { return BindingBuilder.bind(delayQueue).to(delayExchange).with("delay.#"); }
生产者:
public void delaySend() throws IOException { User user = new User(); user.setUserName("Sender1....."); user.setMobile("6666666"); byte[] body = Base64Utils.obj2byte(user); Message message = new Message(body,new MessageProperties()); //延时插件https://www.rabbitmq.com/community-plugins.html //然后放plugin包 //启用插件:rabbitmq-plugins enable rabbitmq_delayed_message_exchange //Lambda表达式 MessagePostProcessor messagePostProcessor = message1 -> { //设置消息持久化 message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT); message.getMessageProperties().setHeader("x-delay", 10000); //message.getMessageProperties().setDelay(20000); return message; }; System.out.println("发送演示消息>>>>>"+new Date()); rabbitTemplate.convertAndSend("delayTopicExchange","delay.message",message,messagePostProcessor); }
rabbitTemplate 通常在SpringBoot 中默认配置
测试用例:
@Test public void send6() throws Exception { topicSender.delaySend(); }
运行下:
消费端(另一工程):
/** * 延时Queue */ public static final String DELAY_QUEUE = "delay.queue";
package com.example.demo.rabbitMq.exchange.topic; import com.example.demo.utils.Base64Utils; import com.rabbitmq.client.Channel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; import java.io.IOException; import java.util.Date; @Component public class TopicReceiver6 { private Logger logger = LoggerFactory.getLogger(this.getClass()); @RabbitListener(queues = TopicRabbitConstant.DELAY_QUEUE) public void process(Message message, Channel channel) throws IOException { try { System.out.println("Receiver6 : " + new Date() + ">>>>" + Base64Utils.byteToObj(message.getBody())); } catch (Exception e) { logger.error("接收失败",e); } } }
整体测试验证
1、启动消费端
2、生产者发送消息
结果:
生产者工程Console:
消费这边工程Console:
时间间隔10s
参考:
https://www.cnblogs.com/haixiang/p/10966985.html
来源:https://www.cnblogs.com/xiaozhuanfeng/p/10969416.html