业务场景:订单系统中的订单在规定时间内未支付,就需要修改订单的状态,在开会系统中的,在提前时间内需要对参会的人员发送消息通知等等,需要在一个时间之前或者之后时间点进行操作的数据
原理就是将rabbitmq中的ttl特性与死信队列结合起来实现延迟队列的
TTL:对队列的生存时间进行设置 对一条消息进行TTL设置好 队列的ttl会在规定时间内,消息死亡 消息中的ttl则会受到队列中的前面消息影响
下面代码主要是对队列的ttl进行实践:下面的代码就是对队列配置进行定义的
@SpringBootConfiguration
public class TtlDelayConfig {
public static final String TTL_DELAY_EXCHANGE = "ttlDelayExchange";
public static final String DEAD_LETTER_EXCHANGE = "ttlDeadExchange";
public static final String TTL_ROUTY_KEY = "ttlRoutyKeyA";
public static final String TTL_ROUTY_KEY_B = "ttlRoutyKeyB";
public static final String TTL_DELAY_QUEUE = "ttlDelayQueu";
public static final String TTL_DELAY_QUEUEB = "ttlDelayQue2";
public static final String DEAD_LETTER_QUEUE = "deadLetterQueue";
public static final String DEAD_LETTER_QUEUEB = "deadLetterQueueB";
public static final String ROUTY_KEY_QUEUE_EXCHANGE = "routyKeyA";
public static final String ROUTY_KEY_QUEUE_EXCHANGEB = "routyKeyB";
/**
* 因为在同一个模块中命名了多个队列以及交换机,存在很多bean的名称相同的情况
* 所以对于bean的名称不是很规范
* 流程就是往延迟对列中发送消息,同时对队列中死信路由属性,以及交换机,ttl属性设置好对应的时间
* 只要队列中的消息在规定时间内没有被消费掉,那么就会变成死信,只需监听死信队列中对队列中的消息进行消费
*
*/
/**
* 声明一个延迟队列
* @return
*/
@Bean("deadLetterQueueB05")
public DirectExchange directExchange(){
return new DirectExchange(TTL_DELAY_EXCHANGE);
}
/**
* 声明一个死信交换机
* @return
*/
@Bean("deadLetterQueueB03")
public DirectExchange deadLetterExchange(){
return new DirectExchange(DEAD_LETTER_EXCHANGE);
}
/**
* 定义好一个超时的队列
* @return
*/
@Bean("deadLetterQueueB01")
public Queue delayQueueA(){
Map<String, Object> map = new HashMap<>();
//设置好死信的交换机
map.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE);
//设置好死信与队列的路由键
map.put("x-dead-letter-routing-key", TTL_ROUTY_KEY);
//设置队列的ttl时间-设置好10秒
map.put("x-message-ttl", 1000);
return QueueBuilder.durable(TTL_DELAY_QUEUE).withArguments(map).build();
}
/**
* 定义好一个超时时间为6秒的队列
* @return
*/
@Bean("deadLetterQueue09")
public Queue delayQueueB(){
Map<String, Object> map = new HashMap<>();
//设置好死信的交换机
map.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE);
//设置好死信与队列的路由键
map.put("x-dead-letter-routing-key", TTL_ROUTY_KEY_B);
//设置队列的ttl时间-设置好10秒
map.put("x-message-ttl", 6000);
return QueueBuilder.durable(TTL_DELAY_QUEUEB).withArguments(map).build();
}
/**
* 声明一个队列用来处理超时10秒的死信
* @return
*/
@Bean("dQueue09")
public Queue deadLetterQueueA(){
return new Queue(DEAD_LETTER_QUEUE);
}
/**
* 声明一个队列用来处理超时6秒的死信
* @return
*/
@Bean("eue09")
public Queue deadLetterQueueB(){
return new Queue(DEAD_LETTER_QUEUEB);
}
/**
* 声明延迟队列与交换机的绑定
* @return
*/
@Bean("deadL09")
public Binding delayBindA(){
return BindingBuilder.bind(delayQueueA()).to(directExchange()).with(ROUTY_KEY_QUEUE_EXCHANGE);
}
/**
* 声明延迟队列与交换机的绑定
* @return
*/
@Bean("de9")
public Binding delayBindB(){
return BindingBuilder.bind(delayQueueB()).to(directExchange()).with(ROUTY_KEY_QUEUE_EXCHANGEB);
}
/**
* 声明死信队列与交换机的绑定
* @return
*/
@Bean
public Binding deadBindA(){
return BindingBuilder.bind(deadLetterQueueA()).to(deadLetterExchange()).with(TTL_ROUTY_KEY);
}
/**
* 声明死信队列与交换机的绑定
* @return
*/
@Bean
public Binding deadBindB(){
return BindingBuilder.bind(deadLetterQueueB()).to(deadLetterExchange()).with(TTL_ROUTY_KEY_B);
}
}
下面的代码需要编写一个发送消息的方法来发送消息:
@Component
public class SendMsg {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendMsg(Object obj, Integer type){
switch (type){
case 10:
rabbitTemplate.convertAndSend(TTL_DELAY_EXCHANGE, ROUTY_KEY_QUEUE_EXCHANGE, obj );
break;
case 6:
rabbitTemplate.convertAndSend(TTL_DELAY_EXCHANGE, ROUTY_KEY_QUEUE_EXCHANGEB, obj);
break;
}
}
在controller编写代码进行访问:
@Autowired
private SendMsg sendMsg;
@RequestMapping(value = "sendDeadMessge", method = RequestMethod.GET)
public String sendDeadMessage(){
rabbitTemplate.convertAndSend("businessExcahnge", "", "deadletter");
return "dead";
}
@RequestMapping(value = "ttl", method = RequestMethod.GET)
public String send(String msg, Integer type){
log.info("当前时间为:{}," , LocalDateTime.now());
sendMsg.sendMsg("hello ttl", type);
return "ttl";
}
下面就是利用消息中的ttl属性来对实现延迟队列的代码
延迟队列中的配置类代码如下:
@SpringBootConfiguration
public class DelayConfig {
//设置延迟的交换机
public static final String DELAY_EXCAHNGE = "delay_exchange";
//设置死信交换机
public static final String DEAD_EXCHANGE = "dead_exchange";
//设置好延迟队列
public static final String DELAY_QUEUE = "delay_business_queue";
//设置好死信队列
public static final String DEAD_QUEUE = "deadLetterQueueC";
//设置好延迟队列的路由键
public static final String DELAY_ROUTY_KEY = "delayMessage";
//设置好死信队列的路由键
public static final String DEAD_ROUTY_KEY = "deadMessage";
/**
* 由于是在一个项目配置了多个queue导致bean的名称存在相同
* 所以队列或者交换机的bean的命名不是很规范
* 流程:与队列中的流程有很大的区别:
* 它是通过对消息中的ttl属性进行了时间的限制,如果消息没被消费就会丢弃到
* 队列设置好的死信交换机中去,最大的缺点就是:会受当前消息消费情况的影响,不消费,后面设置的消息也就永远不会
* 被消费掉,可以用代码测试得出结论
*/
/**
* 声明一个延迟交换机
* @return
*/
@Bean("delayExchange1")
public DirectExchange directExchange(){
return new DirectExchange(DELAY_EXCAHNGE);
}
/**
* 声明死信交换机
* @return
*/
@Bean("dead_Exchange2")
public DirectExchange directDeadExchange(){
return new DirectExchange(DEAD_EXCHANGE);
}
/**
* 声明一个延迟队列并且设置好死信属性
* @return
*/
@Bean("delayQueue1")
public Queue delayQueue(){
Map<String, Object> map = new HashMap<>();
//添加死信交换机
map.put("x-dead-letter-exchange", "dead_exchange");
//声明死信交换机的路由键
map.put("x-dead-letter-routing-key", "dead_letter_message");
return QueueBuilder.durable(DELAY_QUEUE).withArguments(map).build();
}
/**
* 声明一个死信队列
* @return
*/
@Bean("deadLetterQueueC1")
public Queue deadLetterQueueC(){
return new Queue(DEAD_QUEUE);
}
/**
* 绑定一个延迟队列与交换机
* @return
*/
@Bean
public Binding delayBinding(){
return BindingBuilder.bind(delayQueue()).to(directExchange()).with(DELAY_ROUTY_KEY);
}
/**
* 绑定一个死信队列与死信交换机
* @return
*/
@Bean
public Binding deadLetterBinding(){
return BindingBuilder.bind(deadLetterQueueC()).to(directDeadExchange()).with("dead_letter_message");
}
下面需要编写一个消息发送类,需要对消息的时间属性进行设置:
/**
* 给消息设置好ttl时间设置
* @param obj
* @param type
*/
public void delayMsg(String obj, Integer type){
//给消息设定好超时限制
MessageProperties messageProperties = new MessageProperties();
messageProperties.setExpiration(type.toString()); // 设置过期时间,单位:毫秒
byte[] msgBytes = obj.getBytes();
Message message = new Message(msgBytes, messageProperties);
rabbitTemplate.convertAndSend(DELAY_EXCAHNGE, DELAY_ROUTY_KEY, message);
}
通过controller层来实现代码访问:
@RequestMapping(value = "feature", method = RequestMethod.GET)
public String sendOneMessage(String msg, Integer type){
log.info("当前时间为:{}," , LocalDateTime.now());
sendMsg.delayMsg("hello everyone", type);
return "one ttl";
}
rabbitmq中的插件实现延迟队列,可以很好解决前面两点,不受队列中的前一条消息影响
具体上代码: 配置类:
//队列名称
public static final String DELAYED_QUEUE_QUEUE_NAME = "delay.queue.demo.delay.queue";
//延迟交换机的名称
public static final String DELAYED_EXCHANGE_NAME = "delay.queue.demo.delay.exchange";
//延迟队列与交换机的路由键
public static final String DELAY_ROUTYING_KEY = "delay.queue.demo.delay.routykey";
/**
* 定义一个延迟队列的名称
* @return
*/
@Bean
public Queue immediateQueue(){
return new Queue(DELAYED_QUEUE_QUEUE_NAME);
}
/**
* 交换机
* @return
*/
@Bean
public CustomExchange customExchange(){
Map<String, Object> args = new HashMap<>();
args.put("x-delayed-type", "direct");
return new CustomExchange(DELAYED_EXCHANGE_NAME, "x-delayed-message", true, false, args);
}
/**
* 将队列与交换机通过路由键进行绑定
* @return
*/
@Bean
public Binding bindingNotify(){
return BindingBuilder.bind(immediateQueue()).to(customExchange()).with(DELAY_ROUTYING_KEY).noargs();
}
发送消息的类:
/**
* 给消息设置好ttl时间设置
* @param obj
* @param type
*/
public void delayMsg(String obj, Integer type){
//给消息设定好超时限制
// MessageProperties messageProperties = new MessageProperties();
// messageProperties.setExpiration(type.toString()); // 设置过期时间,单位:毫秒
// byte[] msgBytes = obj.getBytes();
// Message message = new Message(msgBytes, messageProperties);
rabbitTemplate.convertAndSend(DELAYED_EXCHANGE_NAME, DELAY_ROUTYING_KEY, obj, message -> {
// 注意这里时间可以使long,而且是设置header
message.getMessageProperties().setHeader("x-delay",type);
return message;
});```
消息监听类:
@RabbitListener(queues = "delay.queue.demo.delay.queue")
public void receiceB(Message message, Channel channel) throws IOException {
String msg = new String(message.getBody());
log.info("当前接收的信息插件86218376218831279数据为:{},时间为{}", msg, LocalDateTime.now());
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}