RabbitMQ简介
核心概念
Broker: 消息代理,这里指安装了消息中间件的服务器,也就是RabbitMQ的服务端,生产者和消费者都需要与服务端建立长连接,当消息发送者发送消息后,将由消息代理接管,消息代理保证消息传递到指定目的地
Publisher:消息的生产者,也是一个向交换器发布消息的客户端应用程序
Message : 消息中包含 routing-key(路由键),该路由键会与绑定键匹配,用于判断该消息会被发送到哪个队列中
Exchange:交换器,接受生产者发送的消息,并将消息路由到指定队列
Queue:队列,一个消息可投入一个或多个队列中,等待消费者从中获取消息
Binding:绑定关系,交换器拿到消息后,会把消息发送给队列,那它会发送到哪个队列呢?由绑定关系决定
Consumer:消息的消费者,也是一个获取消息的客户端应用程序
Connection:无论生产者发送消息,还是消费者接收消息,应用程序都需要与消息服务器建立连接,并且一个客户端(生产者端,消费者端)只能与消息服务器建立一条连接(长连接)
Channel:信道,每条连接可以建立多个信道,用来发送和接收不同类型的消息
Virtual Host:虚拟主机,用于隔离环境,比如生产环境和开发环境,会有不同的交换机队列绑定关系组成,且两边相互隔离互不影响,虚拟主机以路径标识,比如生产环境/prod、开发环境/dev
RabbitMQ整体架构
RabbitMQ工作流程
生产者会与消息服务器建立一条长连接,并在连接里面开辟一条通道来发送数据,数据就是我们所说的消息,每一个消息都必须指定路由键,消息会由代理服务器交给交换机处理,交换机会根据消息中的路由键与交换机与队列之间的绑定关系进行匹配,决定把消息放入哪个队列中,而消费中同样会与服务器建立一个长连接,同时它会对指定队列进行监听,如果发现队列中存在消息,那消费者会通过连接中的队列获取消息
Exchange交换器类型
Exchange交换器,在RabbitMQ中占有重要角色,再解释下它的作用:
生产者产生的消息,首先会发给Broker消息代理服务器,然后代理服务器会把消息交给Exchange 交换器,并且交换器与队列之间会存在绑定关系(Binding),(一个交换器可以绑定多个队列,同时一个队列可以被多个交换器绑定),由交换器根据消息中的路由规则及绑定关系决定把消息发送到哪个队列中
RabbitMQ常用的交换器类型有: fanout 、 direct 、 topic 、 headers 四种。
1)Direct
direct类型的交换器为点对点通信模式,路由规则很简单,它会把消息路由到那些BindingKey和RoutingKey完全匹配的
队列中,如下图:
2)Fanout
广播模式,会把所有发送到该交换器的消息路由到所有与该交换器绑定的队列中,如图:
3)Topic
发布订阅模式,topic类型的交换器在direct匹配规则上进行了扩展,也是将消息路由到BindingKey和RoutingKey
相匹配的队列中,这里的匹配规则稍微不同,可以进行模糊匹配,它约定:
绑定键(BindingKey)和路由键(RoutingKey)的字符串可以是由"."
分隔成单词;
路由键(RoutingKey)BindingKey中可以存在两种特殊字符“*”
和“#”
,用于模糊匹配,其中"*"
用于匹配一个单词,"#"
用于匹配0个或多个单词。
4)headers
headers类型的交换器性能很差,不实用
RabbitMQ界面操作
概览界面
安装完RabbitMQ,访问15672端口即可进入图形化界面
其中:
- 导入导出配置,可以把当前RabbitMQ服务器的交换机、队列、绑定关系等信息同步到另一台服务器
- 端口信息如下:
创建队列
参数解释:
- Name:队列的名字
- Durability:是否持久化,有两个参数可选,Durable持久化、Transient临时队列
- Auto delete :是否自动删除。如果是,则在连接至少一个使用者之后,然后断开所有使用者的连接,队列将自行删除。
- Arguments:队列可以设置一些参数
创建交换机
参数解释:
- Name:交换机的名字
- Type:交换机类型 fanout 、 direct 、 topic 、 headers
- Durability:是否持久化,有两个参数可选,Durable持久化、Transient临时队列
- Auto delete :是否自动删除。如果是,则在连接至少一个使用者之后,然后断开所有使用者的连接,队列将自行删除。
- Internal:是不是内部的交换机,如果是,则客户端无法向交换机发送消息
- Arguments:交换机创建时可以设置一些参数
点击界面创建的交换机名称,可以进入建立绑定关系、发送消息、删除交换机页面,如下图:
创建绑定关系
交换机可以绑定队列(To queue),也可以绑定交换机(To exchange)
Routing Key:绑定关系,与消息中的路由键映射,决定把消息发送到哪个队列
发布消息
如图所示:有消息中的路由键hbger.msg
与绑定关系hbger.msg
确定了把消息发送到了hbger.msg
队列
接收消息
参数解释:
Ack Mode:回复模式
-
Nack message requeue true:获取消息,不告诉RabbitMQ服务端已经收到消息,并且把消息从新放入队列中
-
Ack message requeue false:获取消息,告诉RabbitMQ服务端已经收到消息了,并且把消息从队列中删除
-
Reject requeue true:拒绝消息,把消息重新放回队列
-
Reject requeue false:拒绝消息,把消息从队列中删除
SpringBoot整合RabbitMQ
1) 导入amqp依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!--自定义消息转化器Jackson2JsonMessageConverter所需依赖-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
RabbitMQ的自动配置类为:RabbitAutoConfiguration
,引入amqp后这个类会自动生效
通过查阅RabbitAutoConfiguration
源码,发现它向容器中注入了RabbitTemplate、AmqpAdmin这两个类
AmqpAdmin 完成对Exchange,Queue,Binding的操作
RabbitTemplate 类是发送和接收消息的工具类
2) 配置相关属性,所有的属性均是与RabbitProperties
进行绑定的,以spring.rabbitmq
开头
# 指定rebbitmq服务器主机
spring.rabbitmq.host=127.0.0.1
# 客户端的连接端口默认为 5672
spring.rabbitmq.port=5672
# 用户名
spring.rabbitmq.username=guest
# 密码
spring.rabbitmq.password=guest
# 虚拟主机
spring.rabbitmq.virtual-host=/
3)在主程序类中添加@EnableRabbit
注解,开启RabbitMQ功能
@SpringBootApplication
@EnableRabbit //开启RabbitMQ功能
public class CrudApplication {
public static void main(String[] args) {
SpringApplication.run(CrudApplication.class, args);
}
}
AmqpAdmin
AmqpAdmin主要完成对Exchange,Queue,Binding的操作
创建交换机
@Test
public void createExchange(){
/**
* DirectExchange(String name, boolean durable,
boolean autoDelete, Map<String, Object> arguments)
* name:交换机名字
* durable:是否持久化
* autoDelete:是否自动删除
* arguments:参数信息
*/
DirectExchange directExchange = new DirectExchange("hbger.exchange",true,false);
amqpAdmin.declareExchange(directExchange);
}
创建队列
@Test
public void createQueue(){
/**
* Queue(String name, boolean durable, boolean exclusive,
* boolean autoDelete, @Nullable Map<String, Object> arguments)
* name:队列名字
* durable:是否持久化
* exclusive:是否排他,排他的意思是如果有一条连接,连接了队列,那么其他连接就无法连接队列
* autoDelete:是否自动删除
* arguments:参数信息
*/
Queue queue = new Queue("hbger.queue",true,false,false);
amqpAdmin.declareQueue(queue);
}
创建绑定关系
@Test
public void createBinding(){
/**
* Binding(String destination, Binding.DestinationType destinationType,
* String exchange, String routingKey, @Nullable Map<String, Object> arguments)
* destination:目的地
* destinationType:目的地类型
* exchange:交换机
* routingKey:绑定关系
* arguments:参数信息
*/
Binding binding = new Binding("hbger.queue", Binding.DestinationType.QUEUE,
"hbger.exchange","hbger",null);
amqpAdmin.declareBinding(binding);
}
RabbitTemplate
RabbitTemplate类是发送和接收消息的工具类
发送消息
如果发送的消息是一个对象,会使用序列化机制,对象必须实现Serializable接口
@Test
public void sendMessage(){
//发送普通消息
rabbitTemplate.convertAndSend("hbger.exchange","hbger","HAHA");
//发送对象消息
Employee employee = new Employee();
employee.setId(10086);
employee.setLastName("HAHA");
rabbitTemplate.convertAndSend("hbger.exchange","hbger",employee);
}
RabbitMQ默认的消息转化器是SimpleMessageConverter
若要以Json方式存储对象,就要自定义消息转换器
@Configuration
public class RabbitMQConfig {
@Bean
public MessageConverter messageConverter() {
//在容器中导入Json的消息转换器
return new Jackson2JsonMessageConverter();
}
}
获取消息
使用@RabbitListener
监听指定队列,如果队列中存在消息,就会触发该方法获取消息
一个队列可以被多个方法监听,但一条消息只能被一个方法取出,且一次只能取出一条
方法执行完后才能去接收下一条消息
/**
* queues : 监听的队列
* 参数可以写以下类型
* Message:原生消息详细信息
* Employee:发送的消息类型
* Channel :当前传输数据的通道
*/
@RabbitListener(queues={"hbger.queue"})
public void getMessage(Message message, Employee employee, Channel channel){
//消息体信息{"id":10086,"lastName":"HAHA"}
byte[] body = message.getBody();
//消息属性信息
MessageProperties messageProperties = message.getMessageProperties();
//消息头信息
Object header = messageProperties.getHeaders();
System.out.println("接收的消息为------"+messageProperties.toString());
}
@RabbitListener
可以标注在类和方法上,@RabbitHandler
只能标注在方法上
这两个注解可以配合使用,使用重载方法监听指定队列获取不同类型的消息
@Component
@RabbitListener(queues={"hbger.queue"})
public class GetMessage {
/**
* queues : 监听的队列
* 参数可以写以下类型
* Message:原生消息详细信息
* Employee:发送的消息类型
* Channel :当前传输数据的通道
*/
@RabbitHandler
public void getMessage(Message message, Employee employee, Channel channel){
//消息体信息{"id":10086,"lastName":"HAHA"}
byte[] body = message.getBody();
//消息属性信息
MessageProperties messageProperties = message.getMessageProperties();
//消息头信息
Object header = messageProperties.getHeaders();
System.out.println("接收的消息为------"+messageProperties.toString());
}
@RabbitHandler
public void getMessage(Message message, Department department, Channel channel){
//消息体信息{"id":10086,"lastName":"HAHA"}
byte[] body = message.getBody();
//消息属性信息
MessageProperties messageProperties = message.getMessageProperties();
//消息头信息
Object header = messageProperties.getHeaders();
System.out.println("接收的消息为------"+messageProperties.toString());
}
}
消息确认机制
目的是为了保证消息的可靠抵达,防止由于网络抖动,服务器宕机等原因导致消息丢失
为了防止消息丢失这种情况发生:
1)我们可以使用事务消息,由于数据是在通道中传输的,我可以设置通道为事务模式解决这一问题,但是这种方式在性能方面的开销比较大,一般也不推荐使用
2)引入特殊时机确认回调,生产者(Publisher)发送消息,代理服务器(Broker)如果接受到了消息,就会回调confirmCallback方法,Broker使用交换机将消息投递给队列,这一过程如果失败了,则会回调returnCallback 方法;而消费端,会通过ack机制告诉代理服务器是否获取到了消息,如果正确接收,告诉服务器从队列删除消息,如果未接收,告诉服务器重新投递等操作
可以通过结合数据库事务表的方式,保证消息正确抵达
发送端确认机制
#开启发送端确认
spring.rabbitmq.publisher-confirms=true
ConfirmCallback
@Configuration
public class RabbitMQConfig {
@Autowired
RabbitTemplate rabbitTemplate;
@PostConstruct
public void initRabbitTemplate(){
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/**
* correlationData 用来表示当前消息唯一关联数据
* ack 代理服务器是否成功收到消息
* cause 未收到消息的原因
* **/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println("代理服务器成功接收消息");
}
});
}
}
ReturnCallback
#开启消息未能抵达队列的回调
spring.rabbitmq.publisher-returns=true
#只要未能抵达队列,以异步发送优先回调ReturnCallback
spring.rabbitmq.template.mandatory=true
@Configuration
public class RabbitMQConfig {
@Autowired
RabbitTemplate rabbitTemplate;
@PostConstruct
public void initRabbitTemplate(){
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
/**
* 若消息没有抵达消息队列就会触发这个回调
* @param message 投递失败的消息详细信息
* @param replyCode 回复的状态码
* @param replyText 回复的文本内容
* @param exchange 当时这个消息发给哪个交换机
* @param routingKey 当时这个消息用的路由键
**/
@Override
public void returnedMessage(Message message, int replyCode,
String replyText, String exchange, String routingKey) {
System.out.println("消息失败回调函数触发");
}
});
}
}
接收端确认机制
ack机制,可以参考图形界面,接收消息界面Ack Mode:回复模式参数
默认是自动确认模式,消费端如果正确接收,就会告诉服务器把消息从队列删除消息,
比如队列中有3个消息未被接收,如果消费端此时正在接收消息,当接收完一个消息时服务器宕机了,这时会发现队列中的消息已经全部被移除,导致消息丢失
为解决这一问题,我们可以开启手动确认模式
只要没有明确回复服务器,已经接收到消息,队列中的消息会一直存在
#手动回复是否签收消息
spring.rabbitmq.listener.simple.acknowledge-mode=manual
/**
* queues : 监听的队列
* 参数可以写以下类型
* Message:原生消息详细信息
* Employee:发送的消息类型
* Channel :当前传输数据的通道
*/
@RabbitHandler
public void getMessage(Message message, Employee employee, Channel channel){
//消息属性信息
MessageProperties messageProperties = message.getMessageProperties();
//通道内按消息顺序自增,消息在channel中标记消息用的
long deliveryTag = messageProperties.getDeliveryTag();
/**
* 确认接收到了消息
* void basicAck(long deliveryTag, boolean multiple)
* deliveryTag消息标识
* multipl是否批量接收
*/
try {
channel.basicAck(deliveryTag,false);
} catch (IOException e) {
}
/**
* 拒收消息
* void basicAck(long deliveryTag, boolean multiple, boolean requeue)
* deliveryTag:消息标识
* multipl:是否批量拒收
* requeue:是否重新放入队列,如果为false消息会从队列中删除
*/
try {
channel.basicNack(deliveryTag,false,true);
} catch (IOException e) {
}
}