rabbitMQ整合springboot
rabbitMQ
RabbitMQ 基础架构如下图:
- Broker:接收和分发消息的应用,RabbitMQ Server就是 Message Broker
- Virtual host:出于多租户和安全因素设计的,把 AMQP 的基本组件划分到一个虚拟的分组中,类似于网 络中的 namespace 概念。当多个不同的用户使用同一个 RabbitMQ server 提供的服务时,可以划分出多 个vhost,每个用户在自己的 vhost 创建 exchange/queue 等
- Connection:publisher/consumer 和 broker 之间的 TCP 连接
- Channel:如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCP Connection 的开销将是巨大的,效率也较低。Channel 是在 connection 内部建立的逻辑连接,如果应用程序支持多线 程,通常每个thread创建单独的 channel 进行通讯,AMQP method 包含了channel id 帮助客户端和 message broker 识别 channel,所以 channel 之间是完全隔离的。Channel 作为轻量级的 Connection 极大减少了操作系统建立 TCP connection 的开销
订阅模型
Topics 通配符模式:
-
P:生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给X(交换机)
-
C:消费者,消息的接收者,会一直等待消息到来
-
Exchange:交换机(X)。一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、 递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。Exchange有常见以下3种类型:
- ➢ Fanout:广播,将消息交给所有绑定到交换机的队列
- ➢ Direct:定向,把消息交给符合指定routing key 的队列
- ➢ Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列 .
Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与 Exchange 绑定,或者没有符合 路由规则的队列,那么消息会丢失!
-
Queue:消息队列,接收消息、缓存消息
项目结构
新建两个模块
导入maven依赖
consumer下
<!--RabbitMQ 启动依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
producer下
<!--2. rabbitmq-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
配置yml文件
consumer
# 配置RabbitMQ的基本信息 ip 端口 username password.
spring:
rabbitmq:
host: 192.168.xx.xx #主机ip
port: 5672 #端口
username: guest
password: guest
virtual-host: /
#开启ack
listener:
simple:
acknowledge-mode: manual #采取手动应答
# 拒绝消息是否重回队列
default-requeue-rejected: true
prefetch: 2 #perfetch = 1,表示消费端每次从mq拉去一条消息来消费,直到手动确认消费完毕后,才会继续拉去下一条消息。
#concurrency: 1 # 指定最小的消费者数量
#max-concurrency: 1 #指定最大的消费者数量
retry:
enabled: true # 是否支持重试
producer
# 配置RabbitMQ的基本信息 ip 端口 username password..
spring:
rabbitmq:
host: 192.168.125.128 # ip
port: 5672
username: guest
password: guest
virtual-host: /
# publisher-confirms: true
# publisher-confirm-ty
publisher-confirm-type: correlated
#确认消息已发送到交换机(Exchange) 可以把publisher-confirms: true 替换为 publisher-confirm-type: correlated
#确认消息已发送到队列(Queue)
publisher-returns: true
实现
producer:
只需要配置一个config类,配置.
1.交换机
//1.交换机
@Bean("bootExchange")
public Exchange bootExchange(){
//通配符模式的交换机
return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
}
2.Queue 队列
//2.Queue 队列
@Bean("bootQueue")
public Queue bootQueue(){
return QueueBuilder.durable(QUEUE_NAME).build();
}
3.队列和交互机绑定关系 Binding
@Bean
public Binding bindQueueExchange(@Qualifier("bootQueue") Queue queue, @Qualifier("bootExchange") Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("#.*").noargs();
}
在Test调用方法就行
//1.注入RabbitTemplate
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testSend(){
/*参数:
1. exchange:交换机名称。简单模式下交换机会使用默认的 ""
2. routingKey:路由名称
3. body:发送消息数据
*/
rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME,"boot.haha","boot mq hello~~~");
}
完整代码:里面有死信队列,和ack等高级特性
@Configuration
public class RabbitMQConfig {
public static final String EXCHANGE_NAME = "boot_topic_exchange";
public static final String DLX_EXCHANGE_NAME = "dlx_boot_topic_exchange";
public static final String QUEUE_NAME = "boot_queue";
public static final String DLX_QUEUE_NAME = "dlx_boot_queue";
//1.交换机
@Bean("bootExchange")
public Exchange bootExchange(){
return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
}
//1.1 死信交换机
@Bean("dlxBootExchange")
public Exchange bootDLXExchange(){
return ExchangeBuilder.topicExchange(DLX_EXCHANGE_NAME).durable(true).build();
}
//2.Queue 队列,绑定死信交换机,"dlx.test" 对应死信交换机的"dlx.#"
@Bean("bootQueue")
public Queue bootQueue(){
//ttl 设置队列中所以消息过期时间,ms .maxLength
return QueueBuilder.durable(QUEUE_NAME).ttl(100000).deadLetterExchange(DLX_EXCHANGE_NAME).deadLetterRoutingKey("dlx.test").build();
}
//2.1Queue 死信队列
@Bean("dlxBootQueue")
public Queue bootQueue1(){
//ttl 设置队列中所以消息过期时间,ms
return QueueBuilder.durable(DLX_QUEUE_NAME).build();
}
//3. 队列和交互机绑定关系 Binding
/*
1. 知道哪个队列
2. 知道哪个交换机
3. routing key
*/
@Bean
public Binding bindQueueExchange(@Qualifier("bootQueue") Queue queue, @Qualifier("bootExchange") Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("#.*").noargs();
}
/**
* @param queue:
* @param exchange:
* @return Binding
* @description 死信交换机绑定死信队列
*/
@Bean
public Binding bindQueueDeadLetterExchange(@Qualifier("dlxBootQueue") Queue queue, @Qualifier("dlxBootExchange") Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("dlx.#").noargs();
}
/**
* @param connectionFactory:
* @return RabbitTemplate
* @description RabbitMQ 为我们提供了两种方式用来控制消息的投递可靠性模式。
* confirm 确认模式
* return 退回模式
* rabbitmq 整个消息投递的路径为:
* producer--->rabbitmq broker--->exchange--->queue--->consumer
* 消息从 producer 到 exchange 则会返回一个 confirmCallback 。
* 消息从 exchange-->queue 投递失败则会返回一个 returnCallback 。
* 我们将利用这两个 callback 控制消息的可靠性投递
*/
@Bean
public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate();
rabbitTemplate.setConnectionFactory(connectionFactory);
//设置消息投递失败的策略,有两种策略:自动删除或返回到客户端。
//我们既然要做可靠性,当然是设置为返回到客户端(true是返回客户端,false是自动删除)
rabbitTemplate.setMandatory(true);
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println();
System.out.println("相关数据:" + correlationData);
if (ack) {
System.out.println("投递成功,确认情况:" + ack);
} else {
System.out.println("投递失败,确认情况:" + ack);
System.out.println("原因:" + cause);
}
}
});
/*rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
System.out.println();
System.out.println("ReturnCallback: " + "消息:" + message);
System.out.println("ReturnCallback: " + "回应码:" + replyCode);
System.out.println("ReturnCallback: " + "回应信息:" + replyText);
System.out.println("ReturnCallback: " + "交换机:" + exchange);
System.out.println("ReturnCallback: " + "路由键:" + routingKey);
System.out.println();
}
});*/
rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback(){
@Override
public void returnedMessage(ReturnedMessage returnedMessage) {
System.out.println("ReturnCallback: " + "交换机:" +returnedMessage.getExchange());
System.out.println("ReturnCallback: " + "消息:" + returnedMessage.getMessage());
System.out.println("ReturnCallback: " + "回应码:" +returnedMessage.getReplyCode());
System.out.println("ReturnCallback: " + "回应信息:" +returnedMessage.getReplyText());
System.out.println("ReturnCallback: " + "路由键:" +returnedMessage.getRoutingKey());
}
});
return rabbitTemplate;
}
}
consumer
只需要在一个bean内,方法上加上注解 @RabbitListener(queues = “boot_queue”)
以下三个方法都行
package com.itheima.consumerspringboot.listener;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class RabbimtMQListener {
// @RabbitListener(queues = "boot_queue")
public void ListenerQueue(Message message){
//System.out.println(message);
System.out.println(new String(message.getBody()));
}
/**
* ack确认
* @param message 队列中的消息;
* @param channel 当前的消息队列;
* @param tag 取出来当前消息在队列中的的索引,
* 用这个@Header(AmqpHeaders.DELIVERY_TAG)注解可以拿到;
* @throws IOException
*/
// @RabbitListener(queues = "boot_queue")
public void myAckListener(String message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException, InterruptedException {
Thread.sleep(1000);
System.out.println(message);
try {
/**
* 无异常就确认消息
* basicAck(long deliveryTag, boolean multiple)
* deliveryTag:取出来当前消息在队列中的的索引;
* multiple:为true的话就是批量确认,如果当前deliveryTag为5,那么就会确认
* deliveryTag为5及其以下的消息;一般设置为false
*/
int i = 1/0;
System.out.println("消息确认");
channel.basicAck(tag, false);
}catch (Exception e){
/**
* 有异常就绝收消息
* basicNack(long deliveryTag, boolean multiple, boolean requeue)
* requeue:true为将消息重返当前消息队列,还可以重新发送给消费者;
* false:将消息丢弃
*/
System.out.println("消息异常,退回");
channel.basicNack(tag,false,true);
}
}
@RabbitListener(queues = "boot_queue")
public void receive_email(Object msg, Message message, Channel channel) throws InterruptedException, IOException {
System.out.println("QUEUE_INFORM_EMAIL msg"+msg);
long tag = message.getMessageProperties().getDeliveryTag();
Thread.sleep(1000);
System.out.println(message);
try {
/**
* 无异常就确认消息
* basicAck(long deliveryTag, boolean multiple)
* deliveryTag:取出来当前消息在队列中的的索引;
* multiple:为true的话就是批量确认,如果当前deliveryTag为5,那么就会确认
* deliveryTag为5及其以下的消息;一般设置为false
*/
int i = 1/0;
System.out.println("消息确认");
channel.basicAck(tag, true);
}catch (Exception e){
/**
* 有异常就绝收消息
* basicNack(long deliveryTag, boolean multiple, boolean requeue)
* requeue:true为将消息重返当前消息队列,还可以重新发送给消费者;
* false:将消息丢弃
*/
System.out.println("消息异常,退回");
channel.basicNack(tag,false,false);
}
}
}
nnel.basicAck(tag, true);
}catch (Exception e){
/**
* 有异常就绝收消息
* basicNack(long deliveryTag, boolean multiple, boolean requeue)
* requeue:true为将消息重返当前消息队列,还可以重新发送给消费者;
* false:将消息丢弃
*/
System.out.println("消息异常,退回");
channel.basicNack(tag,false,false);
}
}
}