MQ的作用
1.应用的解耦:作为中间件,实现解耦
2.任务异步处理,提高效率
3.削峰填谷:设置消费速度
RabbitMQ相关概念
- channel:连接;每次访问建立一个连接。创建交换机、队列、设置绑定关系、发送消息,都由这个对象完成
- exchange:交换机
- queue:消息队列
- binding:exchange和queue的虚拟连接规则
RabbitMQ五种模式
-
简单模式
无交换机
-
work模式
无交换机,一个队列,多个消费端
-
发布订阅模式
有交换机,多个队列,多个绑定关系,每个队列对应一个消费端;每个队列都收到所有消息
-
routing路由模式
升级发布订阅模式:1.交换机类型改为:Direct ; 2.设置绑定消息时指定routing key 3.发送消息时指定routing key
-
topics主题模式
升级routing路由模式:1.交换机类型改为:TOPIC;2.设置绑定消息时指定的routing key 使用通配符
springboot整合RabbitMQ步骤
发布端:
-
添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
-
配置RabbitMQ路径信息
spring: rabbitmq: host: 192.168.93.5 port: 5672 virtual-host: /xzkee username: wzyee password: wzyee
-
创建交换机、队列、绑定关系
import org.springframework.amqp.core.*; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class RabbitMQConfig { //交换机名称 public static final String ITEM_TOPIC_EXCHANGE = "springboot_item_topic_exchange"; //队列名称 public static final String ITEM_QUEUE = "springboot_item_queue"; //声明交换机 @Bean("itemTopicExchange") public Exchange topicExchange(){ return ExchangeBuilder.topicExchange(ITEM_TOPIC_EXCHANGE).durable(true).build(); } //这里使用的topic模式 //声明队列 @Bean("itemQueue") public Queue itemQueue(){ return QueueBuilder.durable(ITEM_QUEUE).build(); } //绑定队列和交换机 @Bean public Binding itemQueueExchange(@Qualifier("itemQueue") Queue queue, @Qualifier("itemTopicExchange") Exchange exchange){ return BindingBuilder.bind(queue).to(exchange).with("item.#").noargs(); } }
-
发送消息
@Autowired private RabbitTemplate rabbitTemplate; @Test public void test(){ rabbitTemplate.convertAndSend(RabbitMQConfig.ITEM_TOPIC_EXCHANGE, "item.insert", "商品新 增,routing key 为item.insert"); rabbitTemplate.convertAndSend(RabbitMQConfig.ITEM_TOPIC_EXCHANGE, "item.update", "商品修 改,routing key 为item.update"); rabbitTemplate.convertAndSend(RabbitMQConfig.ITEM_TOPIC_EXCHANGE, "item.delete", "商品删 除,routing key 为item.delete"); }
监听端:1、2两步相同
3.监听
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class MyListener {
/**
* 监听某个队列的消息
* @param message 接收到的消息
*/
@RabbitListener(queues = "springboot_item_queue")
public void myListener1(String message){
System.out.println("消费者接收到的消息为:" + message);
}
}
MQ进阶使用
1.消息投递的可靠性
消息发布端到交换机的回调:1.配置文件开启 确认模式 2. 给rabbitTemplate编写回调函数
交换机到队列的回调:1.配置文件开启 退回模式 2.设置Exchange处理消息失败的模式:setMandatory 3. 给rabbitTemplate编写回调函数
spring:
rabbitmq:
publisher-confirms: true
publisher-returns: true
//在配置类里设置回调函数
@Configuration
public class RabbitConfig{
@Resource
private RabbitTemplate rabbitTemplate;
@Bean
public RabbitTemplate rabbitTemplate() {
Logger log = LoggerFactory.getLogger(RabbitTemplate.class);
// 消息确认, yml需要配置 publisher-confirms: true
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
if (ack) {
// log.debug("消息发送到exchange成功,id: {}", correlationData.getId());
} else {
log.debug("消息发送到exchange失败,原因: {}", cause);
}
});
// 设置交换机处理失败消息的模式, yml需要配置 publisher-returns: true
rabbitTemplate.setMandatory(true);
// 消息返回, yml需要配置 publisher-returns: true
rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
String correlationId = message.getMessageProperties().getCorrelationIdString();
log.debug("消息:{} 发送失败, 应答码:{} 原因:{} 交换机: {} 路由键: {}", correlationId, replyCode, replyText, exchange, routingKey);
});
return rabbitTemplate;
}
}
2.消费端确认或回退消息
默认为自动确认消息:消费端收到消息后,自动确认并将消息从队列中移除
开启手动确认:业务处理异常可以重新发送消息.
1.在消费端配置文件开启Ack
2.使用channel.basic和Ackchannel.basicNack方法确认
spring:
rabbitmq:
listener:
direct:
acknowledge-mode: manual
simple:
acknowledge-mode: manual #采取手动应答
@Component
@RabbitListener(queues="springboot_item_queue")
public class Consumer2 {
@RabbitHandler
public void process(String msg,Channel channel, Message message) throws IOException{
try {
System.out.println("get msg2 success msg = "+msg);
//确认收到消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
//消费者处理出了问题,需要告诉队列信息消费失败,第二个参数表示是否是批量消息,第三个参数表示 是否重回队列
channel.basicNack(message.getMessageProperties().getDeliveryTag(),
false, true);
System.err.println("get msg2 failed msg = "+msg);
}
}
}
3.消息限流(配合手动确认使用)
在配置文件配置
spring:
rabbitmq:
listener:
direct:
acknowledge-mode: manual
simple:
acknowledge-mode: manual #采取手动应答
prefetch: 1 #每次拉取一条消息来消费,直到确认后再拉取消息
4.消息过期时间(TTL)
对队列中的消息设置过期时间,过期自动删除
两种方式:(两种同时使用以时间短的为准)
1.设置队列属性,所有队列所有消息有相同的过期时间
2.对消息单独设置过期时间
//队列名称
public static final String ITEM_QUEUE = "springboot_item_queue";
@Bean
public Queue TTlQueue(){
return QueueBuilder.durable(TTL_QUEUE).withArgument("x-message-ttl", 10000).build(); //单位是毫秒
}
//消息处理对象,设置一些消息的参数信息
MessagePostProcessor messagePostProcessor= new MessagePostProcessor(){
@Override
public Message postProcessMessage(Message message) throws AmqpException {
//设置message的信息
message.getMessageProperties().setExpiration("5000");//消息的过期时间
return message;
}
};
rabbitTemplate.convertAndSend(RabbitMQConfig.ITEM_TOPIC_EXCHANGE, "item.delete", "商品删 除,routing key 为item.delete",messagePostProcessor);
5.死信队列
正常交换机、队列 加 另一个交换机(死信交换机)、队列(死信队列)
当原队列中的消息变成死信后,可以重新发送到另一个交换机(死信交换机),
消息变成死信的三种情况:
- 原队列消息长度达到限制
- 消费者拒收消息,并设置消息不放回原队列(参考上面讲的内容)
- 原队列消息过期
配置方式:在声明队列的时候添加参数 x-dead-letter-exchange及
x-dead-letter-routing-key
值为另一个交换机的名称和绑定关键字
public static final String NORMAL_DLX_QUEUE = "normal_dlx_queue";
public static final String NORMAL_DLX_Exchange = "normal_dlx_exchange";
//ttl
private static final int NORMAL_DLX_EXPIRATION = 10000;
//设置队列长度限制
private static final int NORMAL_DLX_LENGTH = 10;
public static final String DLX_QUEUE = "dlx_queue";
public static final String DLX_Exchange = "dlx_exchange";
//声明原队列,并绑定死信队列
@Bean
public Queue normalDlxQueue(){
return QueueBuilder.durable(NORMAL_DLX_QUEUE)
.withArgument("x-dead-letter-exchange", DLX_Exchange)
.withArgument("x-dead-letter-routing-key", "dlx.hehe")
.withArgument("x-message-ttl", NORMAL_DLX_EXPIRATION)
.withArgument("x-max-length",NORMAL_DLX_LENGTH)
.build();
}
@Bean
public TopicExchange normalDlxExchange(){
return new TopicExchange(NORMAL_DLX_Exchange);
}
@Bean
public Binding normalDlxBinding(){
return BindingBuilder.bind(normalDlxQueue()).to(normalDlxExchange()).with("test.dlx.#");
}
//声明死信队列,和死信交换机
@Bean
public Queue dlxQueue(){
return new Queue(DLX_QUEUE);
}
@Bean
public TopicExchange dlxExchange(){
return new TopicExchange(DLX_Exchange);
}
@Bean
public Binding dlxBinding(){
return BindingBuilder.bind(dlxQueue()).to(dlxExchange()).with("dlx.#");
}