应用场景,用户选择一个时间段定时发送信息到相应平台或者设备
首先使用普通的死信交换机通过设置ttl只能是延后一定时间发送的无法完成定时
public static final String DELAY_QUEUEA_NAME = "delay.queue";
public static final String DEAD_LETTER_EXCHANGE = "dead.letter.exchange";
// 声明死信Exchange
@Bean("deadLetterExchange")
public DirectExchange deadLetterExchange(){
return new DirectExchange(DEAD_LETTER_EXCHANGE);
}
// 声明延时队列A 延时10s
// 并绑定到对应的死信交换机
@Bean("delayQueueA")
public Queue delayQueueA(){
Map<String, Object> args = new HashMap<>(2);
// x-dead-letter-exchange 这里声明当前队列绑定的死信交换机
args.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE);
// x-dead-letter-routing-key 这里声明当前队列的死信路由key
args.put("x-dead-letter-routing-key", DEAD_LETTER_QUEUEA_ROUTING_KEY);
// x-message-ttl 声明队列的TTL 延时10s
args.put("x-message-ttl", 6000);
return QueueBuilder.durable(DELAY_QUEUEA_NAME).withArguments(args).build();
}
设置消息的Expiration时可以完成单个消息的定时但是会有一个问题,如果这个队列的前面的消息没有过期而后面消息先过期是无法进入到死型交换机的。
所以要使用一个插件https://github.com/rabbitmq/rabbitmq-delayed-message-exchange
下载对应的版本的ez后缀文件,放到RabbitMq下的plugins中
在sbin下cmd控制台下输入
开启插件
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
关闭插件
rabbitmq-plugins disable rabbitmq_delayed_message_exchange
查看插件
rabbitmq-plugins list
这里注意如果版本不一致会报错开启插件不成功,后续连接RabbitMq的时候也会报错,找不到x-delayed-message
插件安装成功后,正式开发(spring-rabbit+yml配置):
常量定义 MQConstant:
public class MQConstant {
//死信队列
public static final String DEAD_LETTER_NAME = "dead.letter.queue";
//死信交换机(应该叫做延时交换机了)
public static final String DEAD_LETTER_EXCHANGE = "dead.letter.exchange";
//死信交换机路由key
public static final String DEAD_LETTER_QUEUE_ROUTING_KEY = "delay.queue.routing.key";
}
声明定义死信交换机和死信队列 DelayMQConfig:
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DelayMQConfig {
// 声明死信交换机
@Bean("deadLetterExchange")
public CustomExchange deadLetterExchange(){
Map<String, Object> args = new HashMap<>();
args.put("x-delayed-type", "direct");
return new CustomExchange(MQConstant.DEAD_LETTER_EXCHANGE,"x-delayed-message",true,false,args);
}
//声明死信队列
@Bean("deadLetterQueue")
public Queue deadLetterQueue(){
return QueueBuilder.durable(MQConstant.DEAD_LETTER_NAME).build();
}
// 声明死信队列绑定关系
@Bean
public Binding deadLetterBinding(@Qualifier("deadLetterQueue") Queue queue,
@Qualifier("deadLetterExchange") CustomExchange exchange){
return BindingBuilder.bind(queue).to(exchange).with(MQConstant.DEAD_LETTER_QUEUE_ROUTING_KEY).noargs();
}
}
结合业务应用我将消费者和生产者放入到了ServiceImpl中了,然后BizException和baseMapper都是我使用的框架自定义的类,但是也不影响理解。
这里的Plan就是一个分发计划其中distributeStatus就是分发状态(0-未分发,1-已分发)这里只修改了分发状态后续接入第三方接口实现分发。
@Override
@Transactional
public Long add(PlanCreateDTO planCreateDTO) {
Plan plan = BeanUtil.toBean(planCreateDTO, Plan.class);
if (planCreateDTO.getDistributeType()==0){
plan.setDistributeStatus(1);
//todo 后续调用第三方接口实现分发
this.baseMapper.insert(plan);
}else{
plan.setDistributeStatus(0);
Duration duration = Duration.between(LocalDateTime.now(),plan.getDistributeTime());
long difference = duration.toMillis();
//现在时间和发布时间的差值为负数
if (difference<=0){
throw new BizException("定时时间已过期");
}
int delayTime = (int) difference;
//由于MessageProperties的delay属性是毫秒且是Integer类型,用long型转换会出现负值,大概最长期限是24.8551天
if (delayTime<=0){
throw new BizException("超出定时发布的最长期限");
}
this.baseMapper.insert(plan);
//生产者,将plan序列化的json字符串发送给死信交换机,并给消息设置延时时间
rabbitTemplate.convertAndSend(MQConstant.DEAD_LETTER_EXCHANGE, MQConstant.DEAD_LETTER_QUEUE_ROUTING_KEY, JSON.toJSONString(plan), message -> {
message.getMessageProperties().setDelay(delayTime);
// message.getMessageProperties().setExpiration(String.valueOf(duration.toMillis()));//设置过期时间是无效的他不能应用于这个自定义交换机
return message;
} );
}
return plan.getId();
}
//消费者,监听死信队列
@RabbitListener(queues = MQConstant.DEAD_LETTER_NAME)
public void receive(Message message, Channel channel) throws IOException {
String msg = new String(message.getBody());
Plan plan = JSON.parseObject(msg,Plan.class);
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
plan.setDistributeStatus(1);
//todo 后续调用第三方接口实现分发
this.baseMapper.updateById(plan);
}
发送定时发布的请求如果Rabbit的Exchange出现这样即代表成功。