方法1
采用ttl+dlx
原理:创建一个有统一过期时间的的队列使消息到时过期,或者给消息本身设置过期时间,消息过期之后会进入死信队列,等待死信队列收到过期消息之后做相应的处理
具体参考:死信队列
本次介绍设置统一过期的队列
创建队列与交换机
/**
* 设置统一过期时间
* @return
*/
@Bean
public Queue delayQueue(){
//maxPriority(15):设置该队列中的消息的优先级最大值.发布消息的时候,可以指定消息的优先级,
// 优先级高的先被消费.如果没有设置该参数,那么该队列不支持消息优先级功能.也就是说,就算发布消息的时候传入了优先级的值,也不会起什么作用.
return QueueBuilder
.durable("delay-queue")
.ttl(20000)
.maxPriority(15)
//绑定死信交换机
.deadLetterExchange("dlx-exchange1")
//设置路由键
.deadLetterRoutingKey("error")
.build();
}
/**
* 死信队列
* @return
*/
@Bean
public Queue dlxQueue1(){
return QueueBuilder.durable("dlx-queue1").build();
}
/**
* 死信交换机
* @return
*/
@Bean
public DirectExchange dlxExchange1(){
return ExchangeBuilder.directExchange("dlx-exchange1").build();
}
/**
* 延迟队列与交换机绑定
* @return
*/
@Bean
public Binding delayBind(){
return BindingBuilder.bind(delayQueue()).to(delayExchange()).with("delay");
}
/**
* 死信队列与死信交换机绑定
* @return
*/
@Bean
public Binding dlx1Bind(){
return BindingBuilder.bind(dlxQueue1()).to(dlxExchange1()).with("error");
}
@Bean
public RabbitAdmin delayRabbitAdmin(ConnectionFactory connectionFactory){
RabbitAdmin rabbitAdmin=new RabbitAdmin(connectionFactory);
rabbitAdmin.declareQueue(delayQueue());
rabbitAdmin.declareQueue(dlxQueue1());
rabbitAdmin.declareExchange(delayExchange());
rabbitAdmin.declareExchange(dlxExchange1());
return rabbitAdmin;
}
生产者发送消息
private static SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Resource(name = "rabbitTemplate")
private RabbitTemplate rabbitTemplate;
/**
* 组装消息
* @param msg
* @return
*/
private static Map<String, Object> createMsg1(Object msg,Integer delayMills) {
String msgId = UUID.randomUUID().toString().replace("-", "").substring(0, 32);
Map<String,Object> message= Maps.newHashMap();
message.put("sendTime",sdf.format(new Date()));
message.put("msg", msg);
message.put("delay",delayMills/1000+"s");
message.put("msgId",msgId);
return message;
}
@ApiOperation("ttl+dlx")
@GetMapping("ttl")
public String ttl(@RequestParam String msg){
Map<String, Object> map = createMsg1(msg,20000);
rabbitTemplate.convertAndSend("delay-exchange", "delay",map, message -> {
MessageProperties messageProperties = message.getMessageProperties();
//设置消息优先级
messageProperties.setPriority(Integer.MAX_VALUE);
return message;
});
return "ok";
}
消费者接收消息
如何保证消息按时过期,主要和两个参数prefetch(一次抓取消息的数量,可以减少网络传输时间,默认250)、concurrency(消费者(线程)数量,默认为1,concurrency在整个项目中是有限制的,加起来的数量不超过2047(默认),修改数量可参考增加消费者数量)有关
具体代码如下:
/**
* concurrency默认为100,一个RabbitListener对应的消费者数量
* @param message 消息
* @param c 通道
* @param msg 消息内容
* @throws IOException
*/
//使用queuesToDeclare属性,如果不存在则会创建队列,注:此处声明的队列要和生产者属性保持一致
@RabbitListener(queuesToDeclare = @Queue(value = "dlx-queue1",declare = "false"),concurrency ="100")
public void delay(Message message,Channel c,Map msg) throws IOException, ParseException {
String name = Thread.currentThread().getName();
MessageProperties properties = message.getMessageProperties();
long sendTime = sdf.parse(msg.get("sendTime").toString()).getTime();
long now = System.currentTimeMillis();
String s=(now-sendTime)/1000+"s";
String delay = msg.get("delay").toString();
log.info("当前处理线程名:{},要求延迟:{},实际延迟:{}",name,delay,s);
//手动回执,不批量签收,回执后才能处理下一批消息
long tag = properties.getDeliveryTag();
//模拟业务处理时长-比如查询订单有没有被处理,没有处理的话可以设置超时
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
c.basicAck(tag,false);
}
结果:
方法2
使用延时队列插件,具体安装参考延迟队列安装
注:使用延迟队列插件时需要关闭消息确认模式中的回退模式,不然会误报,因为消息会在延迟时间过后才会到达指定队列
创建延迟交换机以及队列
public static final String DELAY_EXCHANGE = "message-exchange";
public static final String DELAY_ROUTING_KEY="delay1";
/**
* 定义延迟交换机
* @return
*/
@Bean
public CustomExchange delayMessageExchange(){
Map<String, Object> arguments= Maps.newHashMap();
//指定交换机类型
arguments.put("x-delayed-type", "direct");
return new CustomExchange(DELAY_EXCHANGE,"x-delayed-message",true,false,arguments);
}
/**
* 延迟队列
* @return
*/
@Bean
public Queue delayMessageQueue(){
return QueueBuilder.durable("delay-message-queue").build();
}
/**
* 队列与延迟交换机绑定
* @return
*/
@Bean Binding delayMessageBind(){
return BindingBuilder.bind(delayMessageQueue()).to(delayMessageExchange()).with(DELAY_ROUTING_KEY).noargs();
}
@Bean
public RabbitAdmin delayRabbitAdmin(ConnectionFactory connectionFactory){
RabbitAdmin rabbitAdmin=new RabbitAdmin(connectionFactory);
rabbitAdmin.declareExchange(delayMessageExchange());
rabbitAdmin.declareBinding(delayMessageBind());
return rabbitAdmin;
}
生产者发送消息
private static SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Resource(name = "rabbitTemplate1")
private RabbitTemplate rabbitTemplate;
/**
* 组装消息
* @param msg
* @return
*/
private static Map<String, Object> createMsg1(Object msg,Integer delayMills) {
String msgId = UUID.randomUUID().toString().replace("-", "").substring(0, 32);
Map<String,Object> message= Maps.newHashMap();
message.put("sendTime",sdf.format(new Date()));
message.put("msg", msg);
message.put("delay",delayMills/1000+"s");
message.put("msgId",msgId);
return message;
}
@ApiOperation("使用延迟队列插件")
@GetMapping("delay")
public String delay(@RequestParam String msg,@RequestParam Integer delayMills){
Map<String, Object> map = createMsg1(msg,delayMills);
rabbitTemplate1.convertAndSend(DelayConfiguration.DELAY_EXCHANGE, DelayConfiguration.DELAY_ROUTING_KEY,map, message->{
MessageProperties properties = message.getMessageProperties();
//设置消息多少ms之后到达队列
properties.setDelay(delayMills);
return message;
});
return "ok";
}
消费者接收消息
如何保证消息按时过期,主要和两个参数prefetch(一次抓取消息的数量,可以减少网络传输时间,默认250)、concurrency(消费者(线程)数量,默认为1,concurrency在整个项目中是有限制的,加起来的数量不超过2047(默认),修改数量可参考增加消费者数量)有关
代码实现
@RabbitListener(queuesToDeclare = @Queue(value = "delay-message-queue",declare = "false"),concurrency ="100")
public void receiveD(Map msg, Channel channel,Message message) throws IOException, ParseException {
String name = Thread.currentThread().getName();
MessageProperties properties = message.getMessageProperties();
long sendTime = sdf.parse(msg.get("sendTime").toString()).getTime();
long now = System.currentTimeMillis();
String s=(now-sendTime)/1000+"s";
String delay = msg.get("delay").toString();
log.info("当前处理线程名:{},要求延迟:{},实际延迟:{}",name,delay,s);
//手动回执,不批量签收,回执后才能处理下一批消息
long tag = properties.getDeliveryTag();
//模拟业务处理时长-比如查询订单有没有被处理,没有处理的话可以设置超时
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
channel.basicAck(tag, false);
}
结果: