RabbitMQ消息队列总结
RabbitMQ消息队列
为什么用到消息队列:
当操作呈一条链路的时候,如果一个地方宕机了,那就整个都无法使用。
消息队列可以作为进程间或者同一进程不同线程之间的通信,一个异步通信协议
同时也有储存功能,等待用户取回它
优点:
应用解耦:接入MQ之前,代码中需要用到消息的都需要添加相应代码;接入MQ之后,之前的代码无需修改,只需要把代码加上MQ就好,这样修改代码会方便很多
异步处理:异步发送消息,不像一条链路一样一条路走到黑,而是使用了多线程或者多进程的方式去解决它
流量削峰:如果一下子请求量过大,会导致服务宕机,这时候可以走消息队列,第一次取出一部分,第二次取出一部分,以此类推
缺点:
复杂度增大
为什么使用RabbitMQ:
处理高并发,速度快,也不会存在消息错误的问题,完全开源的一个软件
RabbitMQ安装:
默认端口:15672
默认用户名与密码:guest
默认集群端口:25672
默认amqp端口:5672
添加用户:可以使用Web控制台的添加用户操作,添加对应的用户及权限
权限: Admin | Monitoring | Policymaker | Management | Impersonator | None
概念:
生产者:发送消息的一方就是生产者
队列:传输管道,大小与磁盘有关,其本质就是一个大的缓冲区
消费者:等待接收消息的一方
Java操作队列:
简单队列操作流程:连接工厂 --- 连接 --- 信道
Closable接口:
当实现了这个接口,所用的资源就会在使用后自动关闭,Connection和Channel都是实现了Closable接口
创建连接:
在ConnectFactory可以使用setHost等方法配置连接工厂,最后使用 newConnection方法 就可以创建Connection实例
信道配置:
queueDeclare方法用于绑定队列:
参数:
队列名称
持久化
排他队列(基于第一个创建它的连接可见,其他连接看不到,并且不能创建同名的排他队列,就算设置了持久化当关闭后都会自动删除)
自动删除
其他参数
发送方:channel配置basicPublish,可以配置交换机名称、队列名称、自动确认(确认收到消息)以及发送的二进制流信息;
接收方:使用deliveryCallback方法获取RabbitMQ中的消息,消息传入此方法的message中,可以通过getBody获取消息
操作工作队列:basicQos && basicAck
解决发送方与接收方信息速度差的问题:如果发送方快而接收方慢,那就会把内存撑爆
采用一个生产者多个消费者的策略,进行消息获取
轮询模式:
当一个信道名称下有多个接收者的时候,会以轮询的方式依次发送给对应的消费者,不需要特殊配置
优点:
可以减轻RabbitMQ服务器的重担
缺点:
如果两个接收方也有速度差,那总时间时长会根据消费慢的那个去计算,而快的那个会一直处于空闲状态
处理:快的就多消费一点(能者多劳)
公平模式:
能者多劳,当一个队列名称下有多个接收者,且接收者之间有速度差的时候,就是一边都处理完了,另一边还在处理的情况下可以开启公平模式
设置队列的basicQos来设置公平模式的的参数:
设置每个接收者能处理的最大信息量,如果达到该最大信息量而且还没有读取完,则把消息发给下一个接收者
就是你没处理完,那我这个就不给你先了,等你处理完我才会把消息给你
还需要开启信道的自动确认:
basicAck:第一个参数表示回调消息的唯一标识,第二个参数表示是否确认多条信息,并且把信道原本的自动确认basicConsume关掉
操作订阅队列;BuiltinExchangeType.FANOUT
每个接收方都可以接收到相同的全部信息,这就是订阅队列
采用一个生产者多个信道发到多个消费者的策略,进行消息获取,RabbitMQ会给我们自动生成排他队列,用于绑定消费者
有四种模式: direct、topic、headers和fanout ==> 做fanout(粉丝订阅)
不再使用队列的名字进行绑定,而是使用交换机名字进行绑定:
发送方:使用exchangeDeclare替换queueDeclare,把消息发送到交换机处
接收方:在basicPublish里面声明交换机名称,替换掉原来的队列声明,然后把队列名称从信道里面提取出来,消费者还是需要在basicConsume绑定信道名称
即先要获取队列,然后绑定队列,最后消费消息
操作路由队列:BuiltinExchangeType.DIRECT
有些消息是所有消费者都能消费,但有些消息是特定消费者才能消费
同样是采用交换机的策略,但是是使用direct模式,而不是以上的fanout模式、
绑定的routeKey是设置在交换机上面的,因为平时要先操作接收方,所以routeKey放在接收方与信道进行绑定并传入RabbitMQ
如果是使用了RabbitTemplate,那么开启的首先是发送方,所以routeKey会放在发送方与信道进行绑定并传入RabbitMQ
和以上一样的,发送方需要修改交换机发送策略为DIRECT,然后在basicPublish中的第二个参数添加routeKey
而接收方需要在queueBind里面的最后一个参数添加routeKey,要注意设置交换机名称
操作主题队列:BuiltinExchangeType.TOPIC
路由队列的缺点:随着使用时间时长的增加,路由数量也随之增加,到最后难以管理
主题队列就是把路由队列的routeKey换成了带通配符的routeKey,其中 * 表示匹配一个单词, # 表示匹配0个或多个单词(用 . 分割)
一个队列匹配到多个适合的routeKey,也只发一次给对应队列
如果无法匹配,消息会被丢弃
操作RPC队列:
远程过程调用的服务器和客户端同时为消费者和生产者:
客户端发送消息至消息队列,携带自己监听回调的队列ID和唯一标识,然后传入服务器,同时客户端监听回调的队列ID。
此时服务器获取到消息并处理完成,发送至客户端申请的队列ID,放入消息队列中。
客户端根据唯一标识获取自己所需要的消息,如果不是自己的就不获取。
使用AMQP.BasicProperties类来构建属性,获取之后可以放在发送方basicPublish的prop属性值里(即第三个参数)
RabbitMQ的事务:
RabbitMQ的事务很弱,和Redis一样
三个方法: channel.txSelect 开启事务
channel.txCommit 提交事务
channel.txRollback 回滚事务
用于生产者判断自己的消息是否发送到RabbitMQ队列里面,防止我们没发成功却没提示
信道的确认模式:
同步确认:(不推荐)
单条确认:每发出一条消息,就会调用waitForConfirm方法等待服务器确认
启用确认模式:channel.confirmSelect方法
等待确认消息:channel.waitForConfirm方法,返回布尔值,可以用于判断、输出
批量确认:每发出一批消息,调用waitForConfirmOrDie方法等待服务器确认
启用确认模式:channel.confirmSelect方法
等待批量确认消息:channel.waitForConfirmOrDie方法,返回void,如果有一条不成功,就抛出异常
异步确认:(推荐)
原理:
维护一个SortedSet,每一条信息都会对应一个唯一的UUID,每发送一条消息就在SortedSet里面加入他的UUID。当它多条确认后,会返回一个UUID给我们,就是说我们在这个ID号之前的消息都已经确认了,已经发送到位了,
这时,可以拿到返回的ID,在SortedSet里面把在这个ID前的所有元素都删除掉
实践:
为信道开启异步确认功能:channelconfirmSelect
维护一个SortedSet
为信道创建一个监听器,传入并重写ConfirmListener,里面每个方法第一个参数代表着唯一的序列号,第二个参数表示是否多条确认成功,T就是多条确认,F就是单条确认
在每一次basicPublish之前获取下一次发送的UUID,在发送后把UUID加入到SortedSet中
- public class Send {
- private final static String QUEUE_NAME = "async";
- public static void main(String[] argv) throws Exception {
- final SortedSet<Long> set = Collections.synchronizedSortedSet(new TreeSet<Long>());
- ConnectionFactory factory = new ConnectionFactory();
- factory.setHost("localhost");
- try (Connection connection = factory.newConnection();
- Channel channel = connection.createChannel()) {
- channel.confirmSelect();
- channel.queueDeclare(QUEUE_NAME, false, false, false, null);
- //监听器
- channel.addConfirmListener(new ConfirmListener() {
- //确认方法 l为唯一序列号 b为是否确认多条:true为已经确认多条,false为已经确认单条
- @Override
- public void handleAck(long l, boolean b) throws IOException {
- if (b) {
- System.out.println("Ack Multiple" + l);
- set.headSet(l+1L).clear();
- }else {
- System.out.println("NAck Multiple" + l);
- set.remove(l);
- }
- }
- //未确认方法
- @Override
- public void handleNack(long l, boolean b) throws IOException {
- if (b) {
- System.out.println(" No Ack Multiple" + l);
- set.headSet(l+1L).clear();
- }else {
- System.out.println(" No NAck Multiple" + l);
- set.remove(l);
- }
- }
- });
- while (true) {
- String message = "Hello World!";
- Long id = channel.getNextPublishSeqNo();
- channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
- set.add(id);
- }
- }
- }
- }
SpringAMQP:
通过RabbitTemplate去发送和接收消息
五大步:
POM引入依赖
yml配置rabbitMQ的环境信息
主启动类无需修改
编写配置类,用于配置JavaBean:
1.交换机(设置交换机名)
2.队列(设置队列名)
3.绑定(用于绑定交换机和队列名),使用BindBuilder来构建,还可以在使用with方法绑定信道与routeKey(Topic队列的工作)
编写发送类:
使用RabbitTemplate的convertAndSend来发送消息,第一个传入交换机名,第二个传入自己的routeKey,第三个传入自己要发送的消息
由于在上面给信道绑定了匹配方式,所以我们发送的消息会携带着routeKey走入自己所属的信道
接收方此时使用@RabbitListener监听队列,里面填入queues属性,用于表明自己监听的是哪一个信道,然后在监听方法上使用@RabbitHandler方法,表示这是一个监听方法,方法中传入参数类似于MVC的数据绑定,会自动传入参数
例如我现在发送出一个String,那么我在接收方就要接受一个String,可以对这个String进行一系列操作