MQ
优点:
- 可以实现异步通信:
(1) 同步通信:请求后,阻塞在那,一直等待响应
(2) 异步通信:请求发送后,就立即返回了。不会阻塞 - 实现系统解耦
(1)系统耦合:系统或者模块之间项目依赖 - 流量削峰
缺点:
- 系统可用性降低
- 数据一致性问题
- 系统复杂度变高
RabbitMQ
1.实现了AMQP协议
2.运行模型:
3.组成
(1) Broker运行实例: 一个实例可以有多个VHost虚拟主机
(2) virtual host (VHost)虚拟主机:一个服务可以有多个VHost,不同的Vhost之间是相互隔离的.
(3) exchange 交换机:是VHost的组成部分,生产这将消息发送到Exchange中,交换机通过绑定规则将消息分发到响应的队列
(4) queue 队列:是VHost的组成部分、
(5) Channel信道
Exchange交换机
1.direct 交换机(直连交换机)
1.创建交换机,指定绑定键
2.生产者推送消息时,需要指定对应的交换机、路由键、消息内容
3.当路由键符合绑定键规则时,消息才会推送到对应的队列中
2.Topic交换机(主题交换机)
1.创建交换机,指定绑定键
2.通配符:* 匹配一个单词; # 匹配0个或多个单词 以 . 分割单词
3.Fanout交换机(广播交换机)
1.创建交换机,不需要指定绑定键
死信队列(Dead letter)
消息过期时间
- TTL:统一设置队列里面的消息的过期时间
- 也可以设置一条消息单独的过期时间。
- 如果消息有统一的过期时间和单独的过期时间,那么以最小的过期时间为准
死信队列(dead letter)
- 死信:
(1) 过期的消息
(2) 消费者拒绝了这条消息,并且不让他重回队列
(3) 如果消息超过了定义的max length 或 max length bytes 消息也会放入到死信队列中 - 死信队列:保存过期的消息
如何创建一个死信队列?在创建队列时可以指定死信交换机,死信交换机绑定对应的死信队列
延迟队列
基于rabbitMq的插件进行实现。
- 下载插件
2.将插件放入plugins中。
3.启用插件。需要输入命令
4.插件会提供一个特殊的类型的交换机:x-delaymessage-exchange
流量控制
服务端流控:
- 内存控制
40% Conn vm_memeory_high_watermark 内存使用大小达到40%
- 磁盘控制:
disk_free_limit.relative=0.3 磁盘用量达到30%
disk_free_limit.absolute=2G 磁盘存储的达到2G则不再接受消息
消费端控制:
/**
* 默认:自动应答。消费者获取到消息后,会立即发送一个ack指令给生产者。
* 下面代码:设置为手动应答模式,如果超过两条消息没有应答,则暂停就收消息
*/
channel.basicQos(2);
channel.basicCounsume(QUEUQ_NAME,false,consumer)
安装
- RabbitMQ是基于erLang语言开发的。要安装mq首先需要有erlang的环境
- 安装好rabbitmq,通过:http://127.0.0.1:15672可以访问管理员界面,如果页面无法访问,可能是插件没开启,执行以下命令:
windows:
1. rabbitmqctl.bat start_app
2. rabbitmq-plugins enable rabbitmq_management
3. rabbitmqctl stop
Springboot集成rabbitmq
<!-- 导入依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
交换机和消息队列遵循的原则是:那边使用那边维护
应为交换机和队列是在消费者方维护的,那么启动的时候应该先启动消费者。
若先启动生产者,如果mq服务没有对应的交换机和队列,那么投递消息会失败。
生产者
1.生产消息(使用AMQPTemplate推送消息)
2.将消息丢给交换机,并携带上路由键
PropertySource("classpath:rabbitmqProvider.properties")
@Component
public class ProviderSender {
@Value(value = "${com.rabbitmq.provider.directExchange}")
private String directExchange;
@Value(value = "${com.rabbitmq.provider.directBindKey}")
private String directBindKey;
@Value(value = "${com.rabbitmq.provider.topicExchange}")
private String topicExchange;
@Value(value = "${com.rabbitmq.provider.topicBindKey}")
private String topicKey;
@Value(value = "${com.rabbitmq.provider.fanoutExchange}")
private String fanoutExchange;
@Autowired
private AmqpTemplate amqpTemplate;
public void send() {
System.out.println("================生产者开始生产消息================");
/**
* direct exchange
*/
amqpTemplate.convertAndSend(directExchange, directBindKey, "this is a direct message");
System.out.println("<========推送消息至:"+directExchange+" ====>");
/**
* topic exchange
*/
amqpTemplate.convertAndSend(topicExchange, topicKey, "this is a topic message");
System.out.println("<========推送消息至:"+topicExchange+" ====>");
/**
* fanout exchange
*/
amqpTemplate.convertAndSend(fanoutExchange, "", "this is a fanout message");
System.out.println("<========推送消息至:"+fanoutExchange+" ====>");
System.out.println("<================生产者结束生产消息================>");
}
}
消费者(消费消息)
1.维护交换机
2.维护队列
3.建立交换机和队列之间的绑定关系
/**
* 配置交换机和队列 及 之间的关系
*/
@PropertySource("classpath:rabbitMqConsumer.properties")
@Configuration
public class RabbitMQConfig {
@Value(value = "${com.rabbitmq.consumer.directExchange}")
private String directExchange;
@Value(value = "${com.rabbitmq.consumer.directQueue}")
private String directQueue;
@Value(value = "${com.rabbitmq.consumer.directQueueKey}")
private String directQueueKey;
@Value(value = "${com.rabbitmq.consumer.topicExchange}")
private String topicExchange;
@Value(value = "${com.rabbitmq.consumer.topicQueue}")
private String topicQueue;
@Value(value = "${com.rabbitmq.consumer.topicQueueKey}")
private String topicQueueKey;
@Value(value = "${com.rabbitmq.consumer.fanoutExchange}")
private String fanoutExchange;
@Value(value = "${com.rabbitmq.consumer.fanoutQueue}")
private String fanoutQueue;
@Bean("directQueue")
public Queue directQueue(){
return new Queue(directQueue);
}
@Bean
public DirectExchange directExchange() {
return new DirectExchange(directExchange);
}
@Bean("topicQueue")
public Queue topicQueue(){
return new Queue(topicQueue);
}
@Bean
public TopicExchange topicExchange() {
return new TopicExchange(topicExchange);
}
@Bean("fanoutQueue")
public Queue fanoutQueue(){
return new Queue(fanoutQueue);
}
@Bean
public FanoutExchange fanoutExchange() {
return new FanoutExchange(fanoutExchange);
}
@Bean
public Binding bindDirectExchange(@Qualifier("directQueue") Queue directQueue,DirectExchange directExchange) {
return BindingBuilder.bind(directQueue).to(directExchange).with(directQueueKey);
}
@Bean
public Binding bindTopicExchange(@Qualifier("topicQueue") Queue topicQueue,TopicExchange topicExchange) {
return BindingBuilder.bind(topicQueue).to(topicExchange).with(topicQueueKey);
}
@Bean
public Binding bindingFanoutExchange(@Qualifier("fanoutQueue") Queue fanoutQueue,FanoutExchange fanoutExchange) {
return BindingBuilder.bind(fanoutQueue).to(fanoutExchange);
}
}
@Component
@PropertySource("classpath:rabbitMqConsumer.properties")
@RabbitListener(queues = "${com.rabbitmq.consumer.directQueue}") //配置该类是rabbitMq的监听类 并配置监听的队列
public class DirectQueueConsumer {
@RabbitHandler //监听到有消息投递 则调用的处理方法 方法名称自定义
public void receiveMessage(String message){
System.out.println("<===========directQueue=========>");
System.out.println(message);
}
}
可靠性投递
- 消息丢失
(1) 生产者发送消息到Broker
(2)交换机通过绑定键将消息路由到队列失败(绑定键不匹配)
(3)消息在队列中,一致没有被消费,队列或服务出现故障。- 消息重复投递
(1)消费者:收到消息后,告诉broker消息可以删除
解决方案
服务端确认
Transcation模式
- 将channel设置成事务模式 channel.txSelect()
- channel.txCommit();//发送成功
- channel.txRollback();//发送失败
缺点:当消息推送过程中是一个阻塞的方式。没发送一次消息,都需要txSelect() txCommit() txRollback() 命令复杂过多。
工作流程:
Confirm模式
- 将channel设置为confirm模式
- broker发送成功后,会回应一个ack指令,通过:channel.waitConfirms()获取指定。缺点:每条消息都判断
- 批量确认:channel.waitForCOnfirmsOrDie 全部发送失败,该方法会抛出异常。缺点:有一条失败,则全部失败。
- 异步确认
工作流程:
路由保证
- mandatory = true+mandatory
- 使用备份交换机,交换机的属性中,可以配置备份交换机。
消费端确认模式
1.AutoAck自动应答(服务自带应答)
2.手动应答:设置channel的手动应答