概念
TTL(Time To Live) 生存时间(消息有效期等)
DLX (Dead letter exchange)死信交换机
场景
- X秒之后查询某订单支付结果
- B服务更新数据时发出MQ,A服务消费该MQ查询B服务数据并听不到自己数据库,B服务使用主从分离,主从存在延时,A、B服务数据可能不一致
- X分钟后重试请求
实现逻辑
RabbitMQ原生不支持延迟队列,但是RabbitMQ的消息可以有生存时间(TTL),并且可以在消息生存时间结束之后可以转送到指定的DLX中,这样就可以将一个exchange作为中转(并不去消费,仅仅只是保存),消费者创建队列,来消费DLX中的消息,就可以通过TTL以及DLX来实现延时消费
关于死信
官方文档:死信相关描述
截取的相关简介
消息转换为死信
- 消息被消费者显式地调用方法reject、nack 并且不再requeue。
- 消息生存时间结束
- 消息队列中消息数量到达上限
这里最好的一点就是Exchange 并不需要特殊设置,都是普通的exchange,只需要队列做一些设置即可
看图说话
图中示例delayExchange以direct类型来举例子,deadExchange以fanout为例,其余类型也支持此流程。service1、service2 产生消息,并且发送到delayExchange中,通过delayQueue来缓冲并且完成消息生存时间过期,再转到deadExchange中,之后客户端通过deadQueue来消费消息
实现
-
创建delayExchange
-
创建deadExchange
-
创建delayQueue
需要注意的的式delayQueue的参数设置
x-dead-letter-exchange 值为接收死信的exchange
x-message-ttl 值为消息生存时间 单位ms -
创建deadQueue
-
绑定
deadQueue 绑定到 deadExchenge
delayQueue1 和 delayQueue2 绑定到 delayExchange 分别设置routingKey 为 routingKey1 和 routingKey2
验证
流程验证:
- 在delayExchange发送聊天消息分别使用routingKey1和routingKey2
- 最终deadQueue中收到两条消息
- 获取消息详情
结论: 消息可以正常流转
延迟功能验证:
生产者测试代码:
import com.test.CommonTest;
import org.junit.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
/**
* 测试延时队列消息发送
*
* @author hsf
* @since
*/
public class DelayQueueTest extends CommonTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void sendMQ() {
rabbitTemplate.convertAndSend("delayExchange", "routingKey1", "routingKey1,time = " + System.currentTimeMillis());
rabbitTemplate.convertAndSend("delayExchange", "routingKey2", "routingKey2,time = " + System.currentTimeMillis());
}
}
消费者测试代码:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.context.annotation.Configuration;
import java.nio.charset.StandardCharsets;
/**
* 延时消息消费
*
* @author hsf
* @since
**/
@Configuration
public class DelayMsgListener {
private static final Logger logger = LoggerFactory.getLogger(DelayMsgListener.class);
@RabbitListener(containerFactory = "rabbitMQListenerFactory", queues = "deadQueue")
public void handle(Message message) {
String msg = new String(message.getBody(), StandardCharsets.UTF_8);
logger.info("msg = {}, receive timestamp = {} ", msg, System.currentTimeMillis());
}
}
测试输出:
routingKey1 : 1614330056785 - 1614330055356 = 1429
routingKey2 : 1614330057809 - 1614330055682 = 2127
结论:算上网络时间以及代码运行时间,延时功能有效,此处验证效果不明显,假如想要看到明显效果可以增大队列的TTL
扩展
队列和消息同时设置TTL
rabbitMQ消息本身有TTL队列也有TTL相互关系是什么样子的
新增一个queue,routingKey3 TTL设置为10000ms(10s,这样效果明显)
消费者代码不变
生产者代码:
import com.test.CommonTest;
import org.junit.Test;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
/**
* 测试延时队列消息发送
*
* @author hsf
* @since
*/
public class DelayQueueTest extends CommonTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void sendMQ() {
MessageProperties messageProperties = new MessageProperties();
//手动设置为1s
messageProperties.setExpiration("1000");
Message message = new Message(("routingKey3,time =" + System.currentTimeMillis()).getBytes(), messageProperties);
rabbitTemplate.send("delayExchange", "routingKey3", message);
//手动设置为15s
messageProperties.setExpiration("15000");
message = new Message(("routingKey3,time =" + System.currentTimeMillis()).getBytes(), messageProperties);
rabbitTemplate.send("delayExchange", "routingKey3", message);
}
}
结果:
两个消息
1614332275367 - 1614332273759 = 1608
1614332284370 - 1614332274296 = 10074
多次测试之后,第一个趋近于1s 第二个趋近于10s
结论:
当队列和消息同时设置TTL时以短的为准
消息设置TTL队列不设置
直接使用deadQueue测试,直接发送消息到deadExchange中
消费者停止消费
生产者修改代码
import com.test.CommonTest;
import org.junit.Test;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
/**
* 测试延时队列消息发送
*
* @author hsf
* @since
*/
public class DelayQueueTest extends CommonTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void sendMQ() {
MessageProperties messageProperties = new MessageProperties();
//手动设置为20s
messageProperties.setExpiration("20000");
Message message = new Message(("消息TTL测试,time =" + System.currentTimeMillis()).getBytes(), messageProperties);
rabbitTemplate.send("deadExchange", null, message);
//手动设置为30s
messageProperties.setExpiration("30000");
message = new Message(("消息TTL测试,time =" + System.currentTimeMillis()).getBytes(), messageProperties);
rabbitTemplate.send("deadExchange", null, message);
}
}
deadQueue变化过程:
消息随着生存时间逐个过期
结束
延时队列可以通过队列TLL和消息TLL实现,根据需要选择即可,rabbitMQ原生支持
另有方法使用rabbitMQ插件来实现延时队列,这里没有装插件,装了插件再来测试、验证