Direct模式:点对点,直接发送和监听
1.配置队列Queue、交换机Exchange和路由键
由于点对点模式没有交换机,所以此处仅仅定义队列即可,不需要配置交换机和路由键
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MQConfig {
public static final String QUEUE = "queue";
/**
* Direct模式 交换机Exchange
* */
@Bean
public Queue queue() {
return new Queue(QUEUE, true);
}
}
2.发送消息
直接往队列中发送消息,convertAndSend是个重载方法,第一个参数可以是队列类型比如点对点直接发送,也可以是交换机类型比如fanout模式和topic模式
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class MQSender {
@Autowired
RabbitTemplate rabbitTemplate ;
// direct模式
public void sendDirect(String str) {
// 往队列MQConfig.QUEUE发送消息str
rabbitTemplate.convertAndSend(MQConfig.QUEUE, str);
}
}
3.监听接收消息(注解@RabbitListener
)
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
@Service
public class MQReceiver {
// 监听队列MQConfig.QUEUE,打印接收的消息
@RabbitListener(queues=MQConfig.QUEUE)
public void receive(String message) {
System.out.println("receive message:"+message);
}
}
Topic模式:按照路由键与发送消息时的匹配关系进行转发
1.配置队列Queue、交换机Exchange和路由键
此处配置了两个队列和一个topic模式的交换机
@Configuration
public class MQConfig {
public static final String TOPIC_QUEUE1 = "topic.queue1";
public static final String TOPIC_QUEUE2 = "topic.queue2";
public static final String TOPIC_EXCHANGE = "topicExchage";
/**
* Topic模式 此次配置了两个队列和一个交换机Exchange,两个队列以不同的绑定键绑定到了此交换机上
* */
// 队列1
@Bean
public Queue topicQueue1() {
return new Queue(TOPIC_QUEUE1, true);
}
// 队列2
@Bean
public Queue topicQueue2() {
return new Queue(TOPIC_QUEUE2, true);
}
// topic交换机
@Bean
public TopicExchange topicExchage(){
return new TopicExchange(TOPIC_EXCHANGE);
}
// 绑定1,把队列1与topic交换机绑定,绑定键是topic.key1
@Bean
public Binding topicBinding1() {
return BindingBuilder.bind(topicQueue1()).to(topicExchage()).with("topic.key1");
}
// 绑定2,把队列2与topic交换机绑定,绑定键是topic.#
@Bean
public Binding topicBinding2() {
return BindingBuilder.bind(topicQueue2()).to(topicExchage()).with("topic.#");
}
}
2.发送消息
topic和fanout模式中往exchange交换机中发送消息,不是对列
@Service
public class MQSender {
@Autowired
RabbitTemplate rabbitTemplate ;
// topic模式
public void sendTopic(String str) {
log.info("send topic message:"+str);
rabbitTemplate.convertAndSend(MQConfig.TOPIC_EXCHANGE, "topic.key1", str+"1");
rabbitTemplate.convertAndSend(MQConfig.TOPIC_EXCHANGE, "topic.key2", str+"2");
// 可以知道,队列2可以接收到两个消息,但是队列1只能接收到消息1
}
}
3.监听接收消息
监听的是队列
@Service
public class MQReceiver {
// 监听队列TOPIC_QUEUE1,打印接收的消息
@RabbitListener(queues=MQConfig.TOPIC_QUEUE1)
public void receiveTopic1(String message) {
log.info(" topic queue1 message:"+message);
}
// 监听队列TOPIC_QUEUE2,打印接收的消息
@RabbitListener(queues=MQConfig.TOPIC_QUEUE2)
public void receiveTopic2(String message) {
log.info(" topic queue2 message:"+message);
}
}
Fanout模式:广播发送
1.配置队列Queue、交换机Exchange和路由键
此处仍然使用上述的两个对列queue1和queue2
@Configuration
public class MQConfig {
public static final String FANOUT_EXCHANGE = "fanoutxchage";
public static final String TOPIC_QUEUE1 = "topic.queue1";
public static final String TOPIC_QUEUE2 = "topic.queue2";
/**
* Fanout模式 交换机Exchange
* */
// fanout交换机
@Bean
public FanoutExchange fanoutExchage(){
return new FanoutExchange(FANOUT_EXCHANGE);
}
// TOPIC_QUEUE1队列绑定到fanout交换机
@Bean
public Binding FanoutBinding1() {
return BindingBuilder.bind(topicQueue1()).to(fanoutExchage());
}
// TOPIC_QUEUE2队列绑定到fanout交换机
@Bean
public Binding FanoutBinding2() {
return BindingBuilder.bind(topicQueue2()).to(fanoutExchage());
}
}
2.发送消息
@Service
public class MQSender {
@Autowired
RabbitTemplate rabbitTemplate ;
// Fanout模式
public void sendFanout(String str) {
log.info("send fanout message:"+str);
rabbitTemplate.convertAndSend(MQConfig.FANOUT_EXCHANGE, "", str);
}
}
3.监听接收消息
@Service
public class MQReceiver {
// 监听队列MQConfig.TOPIC_QUEUE1,打印接收的消息
@RabbitListener(queues=MQConfig.TOPIC_QUEUE1)
public void receiveTopic1(String message) {
log.info(" topic queue1 message:"+message);
}
// 监听队列MQConfig.TOPIC_QUEUE2,打印接收的消息
@RabbitListener(queues=MQConfig.TOPIC_QUEUE2)
public void receiveTopic2(String message) {
log.info(" topic queue2 message:"+message);
}
}
两个队列都会收到消息
死信队列
1.死信队列介绍
死信队列主要是替代了传统处理流程的“定时器”处理逻辑,采用RabbitMQ的死信队列/延迟队列进行处理。
死信队列/延迟队列,顾名思义,指的是可以延迟一定的时间再处理相应的业务逻辑,而这也可以看作是死信队列的作用,即死信队列/延迟队列可以实现特定的消息、业务数据等待一定的时间TTL后再被消费者监听消费处理。比如购物车订单超时未付款删除,就可以在客户加购商品时设置一个超时时间,到了超时时间后就会进入死信队列,根据订单状态进行判断下一步处理方法,无需定时器实时轮询。
在没有死信消息出现的时候,其就是普通的队列,当出现死信时,会转发到指定的交换机中。
在以下几种情况会称为死信。
- 消息 TTL 过期时。
- 被
Nack/Reject
并且requeue = false
时。 - 队列达到最大长度(超过
Max length
时)。
死信队列配置
- 可以为每一个需要使用死信业务的队列配置一个死信交换机
- 每个队列都可以配置专属自己的死信队列,相关消息的进入死信队列需要经过死信交换机来进程归纳处理
- 死信交换机也只是一个普通的交换机,只是它是用来专门处理死信的交换机
- 创建队列时可以给这个队列附带一个死信的交换机,在这个队列里因各自情况出现问题的作废的消息会被重新发到附带的交换机,然后让这个交换机重新路由这条消息。
2.配置队列Queue、交换机Exchange和路由键
@Configuration
public class MQConfig {
// 死信交换机(在定义产生死信消息的队列时使用此参数),固定写法,不可改变
// DEAD_LETTER_EXCHANGE: 当此普通队列中存在死信时,把死信消息转发到的死信交换机
private static final String DEAD_LETTER_EXCHANGE = "x-dead-letter-exchange";
// 死信交换机绑定(在定义产生死信消息的队列时使用此参数),固定写法,不可改变
// DEAD_LETTER_EXCHANGE:死信消息被转发到死信交换机后,将按照此路由键转发到死信队列)
private static final String DEAD_LETTER_ROUTING_KEY = "x-dead-letter-routing-key";
// 普通队列(由于此队列可能产生死信消息,所以使用DEAD_LETTER_EXCHANGE和DEAD_LETTER_EXCHANGE参数
@Bean
public Queue normalQueue() {
Map<String, Object> args = new HashMap<String, Object>(2);
args.put(DEAD_LETTER_EXCHANGE, "DEAD_LETTER_EXCHANGE");
args.put(DEAD_LETTER_ROUTING_KEY, "REDIRECT_KEY");
// 当产生死信消息时,把死信消息转发到死信交换机DEAD_LETTER_EXCHANGE中,死信交换机再通过路由REDIRECT_KEY转发到死信队列
return new Queue("NORMAL_QUEUE", true, false, false, args);
}
// 死信队列
@Bean
public Queue deadLetterQueue() {
return new Queue("DEAD_LETTER_QUEUE", true);
}
// 普通交换机
@Bean
public Exchange normalExchange() {
//return ExchangeBuilder.directExchange("NORMAL_EXCHANGE").durable(true).build();
return new DirectExchange("NORMAL_EXCHANGE");
}
// 死信交换机
@Bean
public TopicExchange deadLetterExchange() {
return new TopicExchange("DEAD_LETTER_EXCHANGE");
}
/**
* 创建绑定的构造方法
* Binding(String destination, Binding.DestinationType destinationType, String exchange, String routingKey, @Nullable Map<String, Object> arguments)
* destination:目的地,就是队列的名字
* destinationType:目的地类型
* exchange:交换机名字
* routingKey:路由键
*arguments:自定义属性
*/
// 普通队列与普通交换机绑定
@Bean
public Binding deadLetterBinding() {
return new Binding(
"NORMAL_QUEUE",
Binding.DestinationType.QUEUE,
"NORMAL_EXCHANGE",
"NORMAL_KEY", null);
}
// 死信队列与死信交换机绑定
@Bean
public Binding redirectBinding() {
return new Binding(
"DEAD_LETTER_QUEUE",
Binding.DestinationType.QUEUE,
"DEAD_LETTER_EXCHANGE",
"REDIRECT_KEY", null);
}
}
3.发送消息
@Service
public class MQSender {
@Autowired
RabbitTemplate rabbitTemplate ;
public void sendOrderMessage(final OrderInfo orderInfo){
String msg = RedisService.beanToString(orderInfo);
try {
if (orderInfo!=null){
// 因为普通交换机是NORMAL_EXCHANGE,且普通交换机和正常队列的绑定键是NORMAL_KEY
// 所以此处发送的消息会被路由到普通队列
rabbitTemplate.convertAndSend("NORMAL_EXCHANGE","NORMAL_KEY",msg, new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
MessageProperties mp=message.getMessageProperties();
//TODO:动态设置TTL(为了测试方便,暂且设置10s)
mp.setExpiration(String.valueOf(1000*20));
System.out.println("in the sendOrderMessage hs");
return message;
}
});
}
}catch (Exception e){
log.error("秒杀成功后生成抢购订单-发送信息入死信队列,等待着一定时间失效超时未支付的订单-发生异常,消息为:{}",orderInfo.getId(),e.fillInStackTrace());
}
}
}
4.监听接收消息
@Service
public class MQReceiver {
@RabbitListener(queues = "DEAD_LETTER_QUEUE")
public void consumeExpireOrder(String message){
OrderInfo orderInfo = RedisService.stringToBean(message, OrderInfo.class);
System.out.println("in the 死信监听中");
try {
log.info("用户秒杀成功后超时未支付-监听者-接收消息:{}",orderInfo);
if (orderInfo!=null && orderInfo.getStatus().intValue()==0){
orderService.updateStatus(orderInfo.getId());
long goodsId = orderInfo.getGoodsId();
//修改库存
long stock = redisService.incr(GoodsKey.getMiaoshaGoodsStock, ""+goodsId);
}
}catch (Exception e){
log.error("用户秒杀成功后超时未支付-监听者-发生异常:",e.fillInStackTrace());
}
}
}