一、mq的作用,为什么要用mq
MQ是一种非常常见的上下游“逻辑解耦+物理解耦”的消息通信服务
MQ的不足是:
1)系统更复杂,多了一个MQ组件
2)消息传递路径更长,延时会增加
3)消息可靠性和重复性互为矛盾,消息不丢不重难以同时保证
4)上游无法知道下游的执行结果,这一点是很致命的
二、如何安装mq
使用docker安装:docker run -d --name rabbitmq -p 5671:5671 -p 5672:5672 -p 4369:4369 -p 25672:25672 -p 15671:15671 -p 15672:15672 rabbitmq:management
解释:
rabbitmq:management:安装可视化界面
-d 安装最新版本rabbitmq
--name 容器名字
-p 容器映射到主机的端口号
端口号解释:4369=erlang发现(rabbitmq是erlang写的)、25672=集群端口号、(5672、5671)=AMQP端口、15672=web管理后台端口
三、rabbitmq的可视化界面操作

概念:
1、message:消息是由消息体和消息头组成的,消息体是不透明的,消息头是由一些列参数属性组成的,这些属性包括:routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性储存)
2、publisher:消息生产者,是一个向交换器发送消息的客户端应用程序
3、exchange:交换器,用来接收生产者发送的消息,并将这些消息路由给服务器中的队列。交换器四种类型:direct、fanout、topic、header;direct是点对点,fanout是和交换机绑定的队列都可以收到消息,topic是通过routing-key(路由键,可以使用通配符,#)进行消息投递,
4、queue:队列,存储消息的容器
基本操作:
1、添加交换机
2、添加绑定:

3、添加队列:

4、添加用户

四、spring boot与rabbitmq的整合
1、添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2、添加配置
#主机地址
spring.rabbitmq.host=ip
#虚拟主机
spring.rabbitmq.virtual-host=/
#端口号
spring.rabbitmq.port=5672
3、开启自动注入,添加@EnableRabbit 注解
4、添加序列化配置
@Configuration
public class RabbitConfig {
@Bean
public Jackson2JsonMessageConverter createMessageConverter() {
return new Jackson2JsonMessageConverter();
}
}
五、交换机、队列、绑定的代码实现
添加注入的bean
@Autowired
private AmqpAdmin amqpAdmin;
@Autowired
private RabbitTemplate rabbitTemplate;
1、创建交换机
@Test
public void createExchange() {
/**
* String name, 交换机的名字
* boolean durable,是否持久化
* boolean autoDelete,是否自动删除
* Map<String, Object> arguments,需要添加的参数
*/
DirectExchange directExchange = new DirectExchange("hello-java-exchange", true, false);
amqpAdmin.declareExchange(directExchange);
}
2、创建队列
@Test
public void createQueue(){
/**
* String name,队列名
* boolean durable,是否持久化
* boolean exclusive,如果为true,声明这个队列只能给当前连接使用,其他连接不能使用
* boolean autoDelete, 是否自动删除
* @Nullable Map<String, Object> arguments:参数
*/
Queue queue = new Queue("hello-java-queue", true, false, false);
amqpAdmin.declareQueue(queue);
}
3、创建绑定
@Test
public void createBinding() {
/**
* String destination,目的地,是队列或者交换机的名字
* Binding.DestinationType destinationType,目的地类型,分为队列或者交换机
* String exchange,交换机
* String routingKey,路由键
* @Nullable Map<String, Object> arguments:参数
*/
Binding binding = new Binding("hello-java-queue", Binding.DestinationType.QUEUE, "hello-java-exchange", "hello.java", null);
amqpAdmin.declareBinding(binding);
}
六、接收发送消息代码实现
1、发送消息
@Test
public void sendMessage() {
/**
* String exchange,
* String routingKey,
* Object message
* 如果使用对象发送消息必须实现序列化接口,Serializable
* 如果我们没有配置rabbit的序列化属性:
* 如果配置了属性
* if (this.messageConverter != null) {
* template.setMessageConverter(this.messageConverter);
* }
* 如果没有配置初始化一个默认
* protected void initDefaultStrategies() {
* setMessageConverter(new SimpleMessageConverter());
* }
* 对象序列化之后:rO0ABXNyACBjb20uZXhhbXBsZS5kZW1vLnRlc3QuVGVzdFVzZXJWb6zWLkCl6yQHAgACSQADYWdlTAAEbmFtZXQAEkxqYXZhL2xhbmcvU3RyaW5nO3hwAAAAA3QABGphdmE=
*
* 让发送的对象数据转成一个json
* ObjectProvider标识这个属性从容器中获取
* 将Jackson2JsonMessageConverter注入容器中
*/
for (int i = 0; i < 10; i++) {
TestUserVo vo = new TestUserVo();
vo.setAge(i);
vo.setName("java");
rabbitTemplate.convertAndSend("hello-java-exchange", "hello.java", vo);
}
}
2、接收消息
接收消息可以使用@RabbitListener、@RabbitHandler这两个注解,listener可以标注在类和方法上,handler只能标注在方法上且存在listener下面,可以处理同一个队列下的不同消息实体。
@RabbitListener(queues = {"hello-java-queue"})
public void test(Object message, TestUserVo vo, Channel channel){
System.out.println("<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>"+vo.toString());
}
@Component
@RabbitListener(queues = {"hello-java-queue"})
public class RabbitMqService {
/**
* 可以接收的参数
* @param message 原生消息体和头
* @param vo 发送的消息的实体类型
* @param channel 当前传输的通道
*
* queue可以多人监听,一个人处理,然后删除消息
* 只有当一个消息处理完,才会发送下一个消息
*/
@RabbitHandler
public void test1(Message message, TestUserVo vo, Channel channel){
System.out.println("<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>"+vo.toString());
}
@RabbitHandler
public void test2(Message message, Student student, Channel channel){
System.out.println("<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>"+student.toString());
}
}
七、消息一致性
通过三步保证消息一致性:
1、消息发送的确认模式和退回模式
当发送者将消息发送到消息服务器,就会回调confirmCallback。当消息未正确到达队列就会回调returnConfirm
开启步骤:添加配置
# 开启发送确认
spring.rabbitmq.publisher-confirm-type=correlated
# 开启发送端消息抵达队列确认
spring.rabbitmq.publisher-returns=true
# 只要抵达队列,以异步发送优先回调我们这个returnConfirm
spring.rabbitmq.template.mandatory=true
设置确认回调
@PostConstruct
public void initRabbitTemplate() {
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/**
*只要消息到达broker就会回调
* @param correlationData 当前消息的唯一关联数据或者称为消息的唯一id
* @param ack 消息是否成功收到
* @param cause 失败的原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
}
});
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
/**
* 只有消息没有达到queue才会回调
* @param message 投递失败的消息信息
* @param replyCode 回复的状态码
* @param replyText 回复的文本内容
* @param exchange 当前消息的交换机
* @param routingKey 当前消息的路由键
*/
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
}
});
}
2、消息消费的ask机制
消费端确认消息被成功消费,此时broker才可以删除消息
消费者获取到消息,成功处理,可以回复ack给broker
basic.ack 用于确认消息,broker将移除消息
basic.nack 用于否定确认,可以指定broker是否丢弃消息,可以批量
basic.reject 否定确认,不支持批量
默认是自动确认,只要接收到消息,客户端会自动确认,服务端会移除这个消息
出现的问题:出现消息丢失
修改为手动确认:只要我们没有告诉mq确认收到消息,消息就一致是unacked状态,即使消费端宕机,消息也不会丢失,会重新变为ready,下一个消费者会重新收到这个消息。
开启步骤:
1、添加手动确认消息配置:
# 开启手动回复
spring.rabbitmq.listener.simple.acknowledge-mode=manual
2、消息收到后手动确认
@RabbitListener(queues = {"hello-java-queue"})
public void test1(Message message, TestUserVo vo, Channel channel) throws IOException {
System.out.println("<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>"+vo.toString());
// int a =10/ 0;
channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
// channel.basicNack(message.getMessageProperties().getDeliveryTag(),true, true);
}
开启手动确认后,可以防止消息丢失,只有确认的消息才会被删除,没有确认的消息是unacked状态。

本文介绍RabbitMQ的基础概念,如消息、生产者、交换器等,并详细讲解如何使用Spring Boot整合RabbitMQ,包括安装配置、消息收发、保证消息一致性的方法。
1621

被折叠的 条评论
为什么被折叠?



