一、概述:
1、大多数服务中可通过消息服务中间件来提高系统异步通讯、扩展解耦能力
2、消息服务中有两个概念
- 消息代理
- 目的地
当消息发送者发送消息以后,将由消息代理接管,消息代理保证消息传递到指定目的地。
3、消息队列两种形式目的地
- 队列(queue):点对点消息通讯(point-to-point)
- 主题(topic):发布(publish)/订阅(subscribe)消息通讯
4、点对点模式:
- 消息发送者发送消息,消息代理将消息放入一个队列中,消息接收者在这个队列中获取消息内容,消息读取后移出队列
- 消息只有唯一的发送者 可有多个接收者 (当有多个接收者时,只会交给一个接收者来进行接收消息。其他接收者无法获取到消息
##### 5、发布订阅模式:
- 发送者发送消息到主题,多个接收者监听者主题,那么就会在消息到达时同时接收到消息。(多个接收者均能收到消息)
##### 6、JMS (Java Message service) java消息服务
- 基于java 消息代理规范。ActiveMq、HornetMq是JMS实现
7、AMQP(Advanced Message Queuing Protocol)
- 高级消息队列协议,也是消息代理的规范,兼容JMS
- RabbitMQ是AMQP实现
8、spring支持
- spring-jms 提供了对JMS 的支持
- spring-rabbit 提供了对与AMQP 的支持
- 需要connectionFactory 的实现来连接消息代理
- 提供JMSTemplate、RabbitTemplate 来发送消息
- @JmsListener (JMS)、@RabbitLisener (AMQP) 注解监听消息代理发布的消息
- @EnableJMS、@EnableRabbit开启支持
9、Spring boot 自动配置
- JmsAutoConfiguration
- RabbitAutoConfiguration
10、市面MQ产品
- ActiveMQ、RabbitMQ、RocketMQ、Kafka
二、RabbitMQ 概念
1、核心概念
-
Message : 消息,有消息头和消息体构成,消息体是不透明的,消息头有一系列可选属性组成,属性包括routing-key(路由键)、priority(相对于其他消息的优先权) 、delivery-mode(指出该消息可能需要持久性存储)
-
Publisher : 消息的生产者,也是一个向交换器发布消息的的客户端程序
-
Exchange : 交换器,用来接收生产者所产生的消息并将这些消息路由给服务器中的队列。
- Exchange 有四种类型:不同类型的Exchange转发消息的策略有所区别
- direct(默认)
- fanout
- topic
- header
- Exchange 有四种类型:不同类型的Exchange转发消息的策略有所区别
-
Queue :消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接这个队列将其取走。
-
Binding :绑定,用于队列和交换机之间的关联,一个绑定就是基于路由键将交换器连接起来的路由规则,所以可以将交换机理解成一个由绑定构成的路由表,Exchange和Queue的绑定可以是多对多的关系
-
Connection :网络连接,比如一个TCP连接
-
Channel :信道,多路复用连接中的一条独立的双向数据流通道,信道是建立在真实的TCP连接内的虚拟连接,AMQP命令都是通过信道发送出去的,不管是发布消息,订阅队列还是接收消息,这些东西都是通过信道完成,因为对于操作系统来说建立和销毁TCP都是非常昂贵的开销,所以引用信道的概念,以服用一条TCP连接。
-
Virtual Host : 虚拟主机,表示一批交换机、消息队列和相关对象。虚拟主机是共享身份认证和加密环境的独立服务器域。每个vhost本质上就是一个mini版的RabbitMQ服务器,拥有自己的队列、交换器、绑定和权限机制。vhost是AMQP概念的基础,必须在连接时指定,RabbitMQ默认的vhost 是 /。
-
Broker :表示消息队列服务器实体。
三、springBoot 整合RabbitMQ
1、引入amqp场景
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<version>3.1.2</version>
</dependency>
2、RabbitAutoConfiguration自动配置
-
rabbitTemplate
-
amqpAdmin
-
RabbitConnectionFactoryCreator
-
rabbitMessagingTemplate
3、配置属性
@ConfigurationProperties(prefix = "spring.rabbitmq")
spring.rabbitmq.host=localhost # 主机地址
spring.rabbitmq.port=5672 # 端口号
spring.rabbitmq.virtual-host=/ # 虚拟主机端口
4、添加注解
@SpringBootApplication
@EnableRabbit // 开启rabbitMQ 功能
public class RabbitMqDemoApplication {
public static void main(String[] args) {
SpringApplication.run(RabbitMqDemoApplication.class, args);
}
}
5、RabbitMQ 应用
- 创建交换机
DirectExchange directExchange = new DirectExchange("hello-java-exchange",true,false);
amqpAdmin.declareExchange(directExchange);
/**
* name 交换机名称
* durable 是否持久化
* autoDelete 是否自动删除
* arguments 自定义参数
*/
public DirectExchange(String name, boolean durable, boolean autoDelete, Map<String, Object> arguments) {
super(name, durable, autoDelete, arguments);
}
- 交换机创建
@Resource
AmqpAdmin amqpAdmin;
@Test
void createExchange() {
DirectExchange directExchange = new DirectExchange("hello-java-exchange",true,false);
amqpAdmin.declareExchange(directExchange);
log.info("exchange {} 创建成功","hello-java-exchange");
}
- 队列创建
/**
* name 交换机名称
* durable 是否持久化
* autoDelete 是否自动删除
* exclusive 如果我们正在声明一个独占队列(该队列将仅由解密器的连接使用),则为true
* arguments 自定义参数
*/
public Queue(String name, boolean durable, boolean exclusive, boolean autoDelete,
@Nullable Map<String,Object> arguments)
@Test
void createQueue() {
amqpAdmin.declareQueue(new Queue("hello-java-queue",true,false,true));
log.info("queue {} 创建成功", "hello-java-queue");
}
- 创建绑定关系
/**
* lazyQueue 延迟队列
* destination 目的地
* destinationType 目的地类型
* exchange 交换机
* routingKey 路由key
* arguments 参数
*/
public Binding(@Nullable Queue lazyQueue, @Nullable String destination, DestinationType destinationType,
String exchange, @Nullable String routingKey, @Nullable Map<String, Object> arguments)
@Test
void createBinding() {
amqpAdmin.declareBinding(new Binding("hello-java-queue", Binding.DestinationType.QUEUE,"hello-java-exchange","hello.java",null));
log.info("binding {} 创建成功", "hello-java-binding");
}
- 发送消息
@Resource
RabbitTemplate rabbitTemplate;
@Test
void sendMessage(){
rabbitTemplate.convertAndSend("hello-java-exchange","hello.java","hello world");
log.info("消息发送完成:{}","hello world");
}
-
接收消息
1、方法一:
@Component
public class RabbitMqService {
@RabbitListener(queues = {"hello-java-queue"})
public void receiveMessage(Message message){
System.out.println(message);
}
}
2、方法二:
@Component
@RabbitListener(queues = {"hello-java-queue"})
public class RabbitMqService {
// 参数可以自己变更
@RabbitHandler
public void receiveMessage(Object message){
System.out.println(message);
}
}
- 配置收发消息序列化
@Configuration
public class RabbitConfig {
@Bean
public MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
}
四、RabbitMQ消息确认机制 - 可靠可达
1、消息可靠机制
为保证消息不丢失,可靠抵达,可以使用事务消息,性能下降250倍,为此引入消息可靠机制。
2、生产者端确认
配置:
# NONE值是禁用发布确认模式,是默认值
# CORRELATED值是发布消息成功到交换器后会触发回调方法
# SIMPLE值经测试有两种效果,其一效果和CORRELATED值一样会触发回调方法,
# 其二在发布消息成功后使用rabbitTemplate调用waitForConfirms或waitForConfirmsOrDie方法等待broker节点返回发送结果,
# 根据返回结果来判定下一步的逻辑,要注意的点是waitForConfirmsOrDie方法如果返回false则会关闭channel,则接下来无法发送消息到broker;
spring.rabbitmq.publisher-confirm-type=correlated
# 只要抵达队列,以异步发送优先回调我们的returnConfirm
spring.rabbitmq.template.mandatory=true
# 开启发送端确认
spring.rabbitmq.publisher-returns=true
spring.main.allow-circular-references = true
-
publisher:
- confirmCallBack确认模式
- 在创建connectionFactory的时候设置pulisherConfirms(true)选项,开启confirmcallBack.
- 消息只要被broker接收到就会执行confirmcallback,如果是cluser模式,需要所有broker接收到才会调用confirmcallback。
- 被broker接收到只能表示message已经到达服务器,并不能保证消息一定会传递到目标queue里,所以需要用到returnCallback.
@PostConstruct //RabbitConfig对象创建完成以后,执行这个方法 public void initRabbitTemplate(){ rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() { /** * 只要消息抵达 Broker 就 ack = true * @param correlationData 当前消息的唯一关联数据(这个是消息的唯一id) * @param ack 是都成功收到 * @param cause 失败原因 */ @Override public void confirm(CorrelationData correlationData, boolean ack, String cause) { System.out.println("confirm....correlationData【"+correlationData+"】==>ack【"+ack+"】==>"+cause); } }); }
- returnCallBack 到达了交换机 但是未投递到queue退回模式
@PostConstruct //RabbitConfig对象创建完成以后,执行这个方法 public void initRabbitTemplate() { //设置消息抵达队列的确认回调 rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() { /** * message 投递失败消息详情 * replyCode 回复的状态码 * replyText 回复的状态文本 * exchange 当时发送给哪个交换机 * routingKey 当时使用的路由键 */ @Override public void returnedMessage(@Nonnull ReturnedMessage returned) { System.out.println("returnMessage:" + returned.getMessage()+ returned.getReplyText()); } }); }
- confirmCallBack确认模式
3、消费端确认
- consumer ack机制
#手动提交ack
spring.rabbitmq.listener.simple.acknowledge-mode=manual
手动确认模式,如果没有明确告诉MQ收到消息,没有ack,消息就一直是unacked状态,即使服务器宕机了。消息不会丢失。重新变为ready,重新启动服务器后,消息会再次发送。
// channel内按顺序自增的
long deliveryTag = message.getMessageProperties().getDeliveryTag();
// 签收货物 deliveryTag(第几条消息) multiple= false 非批量模式
channel.basicAck(deliveryTag,false);
拒绝签收消息
// deliveryTag(第几条消息) multiple= false 非批量模式 requeue = true 拒收货物,会重新进行发送
// requeue = false 拒收货物 不进入队列 不会进行重新发送。
channel.basicNack(deliveryTag,false,true);