RabbitMQ 延迟队列,消息延迟推送
作者: 海向
出处:https://www.cnblogs.com/haixiang/p/10966985.html
应用场景
目前常见的应用软件都有消息的延迟推送的影子,应用也极为广泛,例如:
- 淘宝七天自动确认收货。在我们签收商品后,物流系统会在七天后延时发送一个消息给支付系统,通知支付系统将款打给商家,这个过程持续七天,就是使用了消息中间件的延迟推送功能。
- 12306 购票支付确认页面。我们在选好票点击确定跳转的页面中往往都会有倒计时,代表着 30 分钟内订单不确认的话将会自动取消订单。其实在下订单那一刻开始购票业务系统就会发送一个延时消息给订单系统,延时30分钟,告诉订单系统订单未完成,如果我们在30分钟内完成了订单,则可以通过逻辑代码判断来忽略掉收到的消息。
在上面两种场景中,如果我们使用下面两种传统解决方案无疑大大降低了系统的整体性能和吞吐量:
- 使用 redis 给订单设置过期时间,最后通过判断 redis 中是否还有该订单来决定订单是否已经完成。这种解决方案相较于消息的延迟推送性能较低,因为我们知道 redis 都是存储于内存中,我们遇到恶意下单或者刷单的将会给内存带来巨大压力。
- 使用传统的数据库轮询来判断数据库表中订单的状态,这无疑增加了IO次数,性能极低。
- 使用 jvm 原生的 DelayQueue ,也是大量占用内存,而且没有持久化策略,系统宕机或者重启都会丢失订单信息。
消息延迟推送的实现
在 RabbitMQ 3.6.x 之前我们一般采用死信队列+TTL过期时间来实现延迟队列,我们这里不做过多介绍,可以参考之前文章来了解:TTL、死信队列
在 RabbitMQ 3.6.x 开始,RabbitMQ 官方提供了延迟队列的插件,可以下载放置到 RabbitMQ 根目录下的 plugins 下。延迟队列插件下载

首先我们创建交换机和消息队列,application.properties 中配置与上一篇文章相同。
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class MQConfig {
public static final String LAZY_EXCHANGE = "Ex.LazyExchange";
public static final String LAZY_QUEUE = "MQ.LazyQueue";
public static final String LAZY_KEY = "lazy.#";
@Bean
public TopicExchange lazyExchange(){
//Map<String, Object> pros = new HashMap<>();
//设置交换机支持延迟消息推送
//pros.put("x-delayed-message", "topic");
TopicExchange exchange = new TopicExchange(LAZY_EXCHANGE, true, false, pros);
exchange.setDelayed(true);
return exchange;
}
@Bean
public Queue lazyQueue(){
return new Queue(LAZY_QUEUE, true);
}
@Bean
public Binding lazyBinding(){
return BindingBuilder.bind(lazyQueue()).to(lazyExchange()).with(LAZY_KEY);
}
}
我们在 Exchange 的声明中可以设置exchange.setDelayed(true)来开启延迟队列,也可以设置为以下内容传入交换机声明的方法中,因为第一种方式的底层就是通过这种方式来实现的。
//Map<String, Object> pros = new HashMap<>();
//设置交换机支持延迟消息推送
//pros.put("x-delayed-message", "topic");
TopicExchange exchange = new TopicExchange(LAZY_EXCHANGE, true, false, pros);
发送消息时我们需要指定延迟推送的时间,我们这里在发送消息的方法中传入参数 new MessagePostProcessor() 是为了获得 Message对象,因为需要借助 Message对象的api 来设置延迟时间。
import com.anqi.mq.config.MQConfig;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class MQSender {
@Autowired
private RabbitTemplate rabbitTemplate;
//confirmCallback returnCallback 代码省略,请参照上一篇
public void sendLazy(Object message){
rabbitTemplate.setMandatory(true);
rabbitTemplate.setConfirmCallback(confirmCallback);
rabbitTemplate.setReturnCallback(returnCallback);
//id + 时间戳 全局唯一
CorrelationData correlationData = new CorrelationData("12345678909"+new Date());
//发送消息时指定 header 延迟时间
rabbitTemplate.convertAndSend(MQConfig.LAZY_EXCHANGE, "lazy.boot", message,
new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
//设置消息持久化
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
//message.getMessageProperties().setHeader("x-delay", "6000");
message.getMessageProperties().setDelay(6000);
return message;
}
}, correlationData);
}
}
我们可以观察 setDelay(Integer i)底层代码,也是在 header 中设置 x-delay。等同于我们手动设置 header
message.getMessageProperties().setHeader("x-delay", "6000");
/**
* Set the x-delay header.
* @param delay the delay.
* @since 1.6
*/
public void setDelay(Integer delay) {
if (delay == null || delay < 0) {
this.headers.remove(X_DELAY);
}
else {
this.headers.put(X_DELAY, delay);
}
}
消费端进行消费
import com.rabbitmq.client.Channel;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.Map;
@Component
public class MQReceiver {
@RabbitListener(queues = "MQ.LazyQueue")
@RabbitHandler
public void onLazyMessage(Message msg, Channel channel) throws IOException{
long deliveryTag = msg.getMessageProperties().getDeliveryTag();
channel.basicAck(deliveryTag, true);
System.out.println("lazy receive " + new String(msg.getBody()));
}
测试结果
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@SpringBootTest
@RunWith(SpringRunner.class)
public class MQSenderTest {
@Autowired
private MQSender mqSender;
@Test
public void sendLazy() throws Exception {
String msg = "hello spring boot";
mqSender.sendLazy(msg + ":");
}
}
果然在 6 秒后收到了消息 lazy receive hello spring boot:
转载请注明出处,谢谢。https://www.cnblogs.com/haixiang/p/10966985.html
RabbitMQ 延迟队列,消息延迟推送的更多相关文章
- RabbitMq(7)消息延时推送
应用场景 目前常见的应用软件都有消息的延迟推送的影子,应用也极为广泛,例如: 淘宝七天自动确认收货.在我们签收商品后,物流系统会在七天后延时发送一个消息给支付系统,通知支付系统将款打给商家,这个过程持 ...
- #研发中间件介绍#异步消息可靠推送Notify
郑昀 基于朱传志的设计文档 最后更新于2014/11/11 关键词:异步消息.订阅者集群.可伸缩.Push模式.Pull模式 本文档适用人员:研发 电商系统为什么需要 NotifyServer? ...
- ASP.NET Core2基于RabbitMQ对Web前端实现推送功能
在我们很多的Web应用中会遇到需要从后端将指定的数据或消息实时推送到前端,通常的做法是前端写个脚本定时到后端获取,或者借助WebSocket技术实现前后端实时通讯.因定时刷新的方法弊端很多(已不再采用 ...
- NET Core2基于RabbitMQ对Web前端实现推送功能
NET Core2基于RabbitMQ对Web前端实现推送功能 https://www.cnblogs.com/Andre/p/10012329.html 在我们很多的Web应用中会遇到需要从后端将指 ...
- 基于HTTP协议之WEB消息实时推送技术原理及实现
很早就想写一些关于网页消息实时推送技术方面的文章,但是由于最近实在忙,没有时间去写文章.本文主要讲解基于 HTTP1.1 协议的 WEB 推送的技术原理及实现.本人曾经在工作的时候也有做过一些用到网页 ...
- PHP版微信公共平台消息主动推送,突破订阅号一天只能发送一条信息限制
2013年10月06日最新整理. PHP版微信公共平台消息主动推送,突破订阅号一天只能发送一条信息限制 微信公共平台消息主动推送接口一直是腾讯的私用接口,相信很多朋友都非常想要用到这个功能. 通过学习 ...
- Swift - 本地消息的推送通知(附样例)
使用UILocalNotification可以很方便的实现消息的推送功能.我们可以设置这个消息的推送时间,推送内容等. 当推送时间一到,不管用户在桌面还是其他应用中,屏幕上方会都显示出推送消息. 1, ...
- .NET之微信消息模板推送
最近在项目中使用到了微信消息模板推送的功能,也就是将对应的消息推送到对应的用户微信上去,前提是你必须要有一个微信公众号并且是付费了的才会有这个功能,还有就是要推送的用户必须是的关注了你的微信公众号的. ...
- dwr3实现消息精确推送详细步骤
最近项目中需要用到推送消息,找了很久终于找到一篇不错的文章,方便以后查看就转载了,也分享给大家,希望能帮到有需要的人. 第一.在项目中引入dwr.jar,然后在web.xml中进行配置,配置如下: & ...
- NET SignaiR 实现消息的推送,并使用Push.js实现通知
一.使用背景 1. SignalR是什么? ASP.NET SignalR 是为 ASP.NET 开发人员提供的一个库,可以简化开发人员将实时 Web 功能添加到应用程序的过程.实时 Web 功能是指 ...
随机推荐
- Python学习笔记: 闭包
闭包的基本定义 在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数.这个被引用的自由变 ...
- poj2195 bfs+最小权匹配
题意:给个矩阵,矩阵里有一些人和房子(人数和房子数相等),一个人只进一个房子(可以路过房子而不进),每走一步花费1美金,求所有人都进入房子的最小花费,这是典型的二分图带权匹配问题. 这题就是建图有点麻 ...
- Android开发——Android 6.0权限管理机制详解
.Android 6.0运行时主动请求权限 3.1 检测和申请权限 下面的例子介绍上面列出的读写SD卡的使用例子,可以使用以下的方式解决: public boolean isGrantExterna ...
- C++基础——1.变量和基本类型(基于c++11)
C++11类型 基本类型 字面值常量(literal) 比如:一个形如42的值,即为常量 变量 初始值 初始化不是赋值,初始化是创建变量的时候给一个初始值:而赋值是擦除当前值,用新值代替. 列表初始化 ...
- foreach遍历数组的表格
<?php /** * * @authors Your Name (you@example.org) * @date 2017-03-17 19:06:19 * @version $Id$ */ ...
- css 透明度使用
设置元素整体透明度: div{ opacity: 0.5; } 设置背景色透明度 div{ background: rgba(0,0,0,0.5); }
- day01_02.php的开发环境准备
PHP开发环境的准备 此套课程推荐xampp,也就是Apache+Mysql+PHP 但是我自己的机器装的是wamp环境,稍微有一些不一样,但是不影响使用
- 紫书第三章训练1 E - DNA Consensus String
DNA (Deoxyribonucleic Acid) is the molecule which contains the genetic instructions. It consists of ...
- c3p0-config.xml模板详解
c3p0-config.xml模板详解 <c3p0-config> <default-config> <!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数.De ...
- 【bzoj1163/bzoj1339】[Baltic2008]Mafia 网络流最小割
题目描述 匪徒准备从一个车站转移毒品到另一个车站,警方准备进行布控. 对于每个车站进行布控都需要一定的代价,现在警方希望使用最小的代价控制一些车站,使得去掉这些车站后,匪徒无法从原定的初始点到达目标点 ...