RabbitMQ是采用erlang语言开发的,所以必须有erlang环境才可以运行
下载安装步骤
RabbitMQ 管理平台地址 http://127.0.0.1:15672
默认账号:guest/guest 用户可以自己创建新的账号
Virtual Hosts:
像mysql有数据库的概念并且可以指定用户对库和表等操作的权限。那RabbitMQ呢?RabbitMQ也有类似的权限管理。在RabbitMQ中可以虚拟消息服务器VirtualHost,每个VirtualHost相当月一个相对独立的RabbitMQ服务器,每个VirtualHost之间是相互隔离的。exchange、queue、message不能互通。
rabbitMQ的五种队列
1.点对点(简单)的队列
2.工作(公平性)队列模式
3.发布订阅模式(fanout)
4.路由模式Routing
5.通配符模式Topics
rabbitmq 关键名词
AMQP(高级消息队列协议)是一个异步消息传递所使用应用层协议规范,为面向消息中间件设计,基于此协议的客户端与消息中间件可以无视消息来源传递消息,不受客户端、消息中间件、不同的开发语言环境等条件的限制;
涉及概念解释:
Server(Broker):接收客户端连接,实现AMQP协议的消息队列和路由功能的进程;
Virtual Host:虚拟主机的概念,类似权限控制组,一个Virtual Host里可以有多个Exchange和Queue。
Exchange:交换机,接收生产者发送的消息,并根据Routing Key将消息路由到服务器中的队列Queue。
ExchangeType:交换机类型决定了路由消息行为,RabbitMQ中有三种类型Exchange,分别是fanout、direct、topic;
Message Queue:消息队列,用于存储还未被消费者消费的消息;
Message:由Header和body组成,Header是由生产者添加的各种属性的集合,包括Message是否被持久化、优先级是多少、由哪个Message Queue接收等;body是真正需要发送的数据内容;
BindingKey:绑定关键字,将一个特定的Exchange和一个特定的Queue绑定起来。
RabbitMQ交换机的作用
RabbitMQ的公平转发
目前消息转发机制是平均分配,这样就会出现俩个消费者,奇数的任务很耗时,偶数的任何工作量很小,造成的原因就是近当消息到达队列进行转发消息。并不在乎有多少任务消费者并未传递一个应答给RabbitMQ。仅仅盲目转发所有的奇数给一个消费者,偶数给另一个消费者。
为了解决这样的问题,我们可以使用basicQos方法,传递参数为prefetchCount= 1。这样告诉RabbitMQ不要在同一时间给一个消费者超过一条消息。
换句话说,只有在消费者空闲的时候会发送下一条信息。调度分发消息的方式,也就是告诉RabbitMQ每次只给消费者处理一条消息,也就是等待消费者处理完毕并自己对刚刚处理的消息进行确认之后,才发送下一条消息,防止消费者太过于忙碌,也防止它太过去清闲。
通过 设置channel.basicQos(1);
但是需要手动签收才可以
RabbitMQ的应答模式
案例:
生产者端代码不变,消费者端代码这部分就是用于开启手动应答模式的。
channel.basicConsume(QUEUE_NAME, false, defaultConsumer);
注:第二个参数值为false代表关闭RabbitMQ的自动应答机制,改为手动应答。
在处理完消息时,返回应答状态,true表示为自动应答模式。
channel.basicAck(envelope.getDeliveryTag(), false); 改为手动应答后要进入这行 告诉mq已经被消费了
发布/订阅模式Publish/Subscribe
这个可能是消息队列中最重要的队列了,其他的都是在它的基础上进行了扩展。
功能实现:一个生产者发送消息,多个消费者获取消息(同样的消息),包括一个生产者,一个交换机,多个队列,多个消费者。
思路解读(重点理解):
(1)一个生产者,多个消费者
(2)每一个消费者都有自己的一个队列
(3)生产者没有直接发消息到队列中,而是发送到交换机
(4)每个消费者的队列都绑定到交换机上
(5)消息通过交换机到达每个消费者的队列
该模式就是Fanout Exchange(扇型交换机)将消息路由给绑定到它身上的所有队列
以用户发邮件案例讲解
注意:交换机没有存储消息功能,如果消息发送到没有绑定消费队列的交换机,消息则丢失。
路由模式RoutingKey
生产者发送消息到交换机并指定一个路由key,消费者队列绑定到交换机时要制定路由key(key匹配就能接受消息,key不匹配就不能接受消息)
例如:我们可以把路由key设置为insert ,那么消费者队列key指定包含insert才可以接收消息,消费者队列key定义为update或者delete就不能接收消息。很好的控制了更新,插入和删除的操作。
采用交换机direct模式
通配符模式Topics
说明:此模式实在路由key模式的基础上,使用了通配符来管理消费者接收消息。生产者P发送消息到交换机X,type=topic,交换机根据绑定队列的routing key的值进行通配符匹配;
符号#:匹配一个或者多个词lazy.# 可以匹配lazy.irs或者lazy.irs.cor
符号*:只能匹配一个词lazy.* 可以匹配lazy.irs或者lazy.cor
RabbitMQ消息确认机制
问题产生背景:
生产者发送消息出去之后,不知道到底有没有发送到RabbitMQ服务器, 默认是不知道的。而且有的时候我们在发送消息之后,后面的逻辑出问题了,我们不想要发送之前的消息了,需要撤回该怎么做。
解决方案:
1.AMQP 事务机制
2.Confirm 模式
事务模式:
txSelect 将当前channel设置为transaction模式
txCommit 提交当前事务
txRollback 事务回滚
SpringBoot整合RabbitMQ
生产者
application.yml
spring:
rabbitmq:
####连接地址
host: 127.0.0.1
####端口号
port: 5672
####账号
username: guest
####密码
password: guest
###地址
virtual-host: /
交换机绑定队列
@Component
public class FanoutConfig {
//邮件队列
private String FANOUT_EMAIL_QUEUE = "fanout_eamil_queue";
//短信队列
private String FANOUT_SMS_QUEUE = "fanout_sms_queue";
//短信队列
private String EXCHANGE_NAME = "fanoutExchange";
// 1.定义队列邮件
@Bean
public Queue fanOutEamilQueue() {
return new Queue(FANOUT_EMAIL_QUEUE);
}
@Bean
public Queue fanOutSmsQueue() {
return new Queue(FANOUT_SMS_QUEUE);
}
// 2.定义交换机
@Bean
FanoutExchange fanoutExchange() {
return new FanoutExchange(EXCHANGE_NAME);
}
// 3.队列与交换机绑定邮件队列
@Bean
Binding bindingExchangeEamil(Queue fanOutEamilQueue, FanoutExchange fanoutExchange) {
return BindingBuilder.bind(fanOutEamilQueue).to(fanoutExchange);
}
// 4.队列与交换机绑定短信队列
@Bean
Binding bindingExchangeSms(Queue fanOutSmsQueue, FanoutExchange fanoutExchange) {
return BindingBuilder.bind(fanOutSmsQueue).to(fanoutExchange);
}
}
生产者投递消息
@Component
public class FanoutProducer {
@Autowired
private AmqpTemplate amqpTemplate;
public void send(String queueName) {
String msg = "my_fanout_msg:" + new Date();
System.out.println(msg + ":" + msg);
amqpTemplate.convertAndSend(queueName, msg);
}
}
控制层调用代码
@RestController
public class ProducerController {
@Autowired
private FanoutProducer fanoutProducer;
@RequestMapping("/sendFanout")
public String sendFanout(String queueName) {
fanoutProducer.send(queueName);
return "success";
}
}
消费者
邮件消费者
@Component
@RabbitListener(queues = "fanout_eamil_queue")
public class FanoutEamilConsumer {
@RabbitHandler
public void process(String msg) throws Exception {
System.out.println("邮件消费者获取生产者消息msg:" + msg);
}
}
短信消费者
@Component
@RabbitListener(queues = "fanout_sms_queue")
public class FanoutSmsConsumer {
@RabbitHandler
public void process(String msg) {
System.out.println("短信消费者获取生产者消息msg:" + msg);
}
}
application.yml
spring:
rabbitmq:
####连接地址
host: 127.0.0.1
####端口号
port: 5672
####账号
username: guest
####密码
password: guest
###地址
virtual-host: /
rabbitMQ幂等问题
产生原因:网络延迟传输中,消费出现异常或者是消费延迟消费,会造成MQ进行重试补偿,在重试过程中,可能会造成重复消费。
消费者如何保证消息幂等性,不被重复消费
解决办法:
①使用全局MessageID判断消费方使用同一个,解决幂等性。
②或者使用业务逻辑保证唯一(比如订单号码)
生产者:
请求头设置消息id(messageId)
@Component
public class FanoutProducer {
@Autowired
private AmqpTemplate amqpTemplate;
public void send(String queueName) {
String msg = "my_fanout_msg:" + System.currentTimeMillis();
Message message = MessageBuilder.withBody(msg.getBytes()).setContentType(MessageProperties.CONTENT_TYPE_JSON)
.setContentEncoding("utf-8").setMessageId(UUID.randomUUID() + "").build();
System.out.println(msg + ":" + msg);
amqpTemplate.convertAndSend(queueName, message);
}
}
什么是死信呢?什么样的消息会变成死信呢?
消息被拒绝(basic.reject或basic.nack)并且requeue=false.
消息TTL过期
队列达到最大长度(队列满了,无法再添加数据到mq中)
应用场景分析
在定义业务队列的时候,可以考虑指定一个死信交换机,并绑定一个死信队列,当消息变成死信时,该消息就会被发送到该死信队列上,这样就方便我们查看消息失败的原因了
如何使用死信交换机呢?
定义业务(普通)队列的时候指定参数
x-dead-letter-exchange: 用来设置死信后发送的交换机
x-dead-letter-routing-key:用来设置死信的routingKey
RabbitMQ解决分布式事务问题
RabbitMQ解决分布式事务原理: 采用最终一致性原理。需要保证以下三要素1、确认生产者一定要将数据投递到MQ服务器中(采用MQ消息确认机制)2、MQ消费者消息能够正确消费消息,采用手动ACK模式(注意重试幂等性问题)3、如何保证第一个事务先执行,采用补偿机制,在创建一个补单消费者进行监听,如果订单没有创建成功,进行补单。