参考文献:https://juejin.cn/post/7049160818834800677
参考文献:RabbitMQ常见面试题总结 | Java学习&面试指南-程序员大彬 (topjavaer.cn)
RabbitMQ是一个由erlang开发的消息队列。消息队列用于应用间的异步协作。
Message:由消息头和消息体组成,消息体是不透明的,消息头是由一系列的可选属性组成,属性包括routing-key、priority、delivery-mode(是否持久化存储)等。消息的本质就是应用之间传递的数据。
Publisher:消息的生产者。
Consumer:消息的消费者。
Connection:生产者/消费者和Broker之间的TCP连接。
Channel:信道,是在Connection内部建立的逻辑连接多个channel共享一个Connection。
由于创建和销毁Connection的开销大,所以在Connection内部建立Channel。
不同的Channel之间互相隔离。
Channel是双向的数据通道。
RabbitMQ几乎所有操作均是通过Channel完成,包括发送消息、创建队列、交换机等、消费消息。先创建Connection、然后创建Channel,再使用Channel执行操作。
Exchange:交换机,是Message到达Broker的第一站,根据发放规则,匹配查询表中的routing-key,将消息分发到 Queue中。常用的类型有:direct (point-to-point),topic (publish-subscribe) ,andfanout (multicast)等交换机。
Binding:绑定,是Exchange和Queue之间的虚拟链接,Binding中可以包含routing-key,Binding的信息被保存到Exchange中,这样Exchange就能知道将Message对应到哪一个Queue中。
Queue:队列,存储Message,队列特点先进先出。一个消息可以分发到一个或者多个队列。
Virtual Host:每一个vhost本质都是一个mini版的RabbitMQ服务器,拥有自己的队列、交换机、绑定和权限机制。vhost之间是完全隔离的。在同一个 Virtual Host 中,Exchange 与 Queue 不能重名;但是在不同 Virtual Host 中,Exchange 与 Queue 可以同名。
三个主要原因:解耦、异步、削峰。
解耦:比如在用户下单中,订单系统需要通知库存系统进行库存减少,如果库存系统在此时访问失败,那么下单操作就会失败,订单系统和库存系统耦合。如果使用消息队列,用户下单操作中,订单系统会直接返回成功,再把订单消息持久化,等待库存系统可以访问时,就能够正常的减少库存。
异步:将消息写入队列中,非必要业务逻辑就以异步方式运行。不会影响主要业务流程。
削峰:如一个订单系统最多能同时处理一万条订单消息,如果在高峰时期有几万条下单消息,此时远远超过该系统的峰值,服务器就有可能宕机。只能限制订单超过一万后不允许用户下单。但是我们可以用消息队列来做缓冲,便能够取消该限制,将一秒内的订单消息分批分时间段来进行处理,可能此时的用户在完成下单操作后的十几秒后才能够收到下单成功的信息。但是也比不能下单体验感好。
简而言之,消费端根据数据库能够处理的并发量,从消息队列中慢慢拉取消息进行处理,在生产中,这个短暂的高峰期积累是允许的。
1.系统可用性降低。引入消息队列后,如果消息队列挂了,那么会影响整个业务系统的可用性。
2.系统复杂性增高。加入消息队列后,要多考虑很多问题,如数据一致性问题、消息如何保证不被重复消费、消息如何保证传输的可靠性等。
Exchange分发消息时根据类型的不同分发策略也不同。目前Exchange有四种类型:
direct、fanout、topic、headers。
Exchange规则
5.1 direct交换机
direct交换机会将消息路由到binding key 和 routing key完全匹配的队列中。它是完全匹配、单播的模式。
如图:如图所示,直接交换器通过路由key和绑定key是否完全匹配,如果完全匹配,则一定会到指定的队列中,如果路由key和绑定key不匹配,则进入不了对应的队列中。消费者只有消费符合规则的消息。
5.2 fanout交换机
所有发到 fanout 类型交换机的消息都会路由到所有与该交换机绑定的队列上去。fanout 类型转发消息是最快的。
如图fanout交换机下面有3个绑定到该交换机队列,那么消息发送后,交换机会把消息发送到每个队列中。如果消费者不在线,那么不在线期间发送过来的消息则收不到,上线后也收不到,只有在线状态才能收到消息,相当于广播,播放一声,如果在线就知道,不在线也就不知道了。
5.3 topic交换机
topic交换机使用routing key和binding key进行模糊匹配,匹配成功则将消息发送到相应的队列。 routing key和binding key都是句点号“. ”分隔的字符串,binding key中可以存在两种特殊字符“*”与 “#”,用于做模糊匹配,其中“*”用于匹配一个单词,“#”用于匹配多个单词。
5.4 headers交换机
headers类型的交换器不依赖于路由键的匹配规则来路由信息,而是根据发送的消息内容中的headers属性进行匹配。
在绑定队列和交换器时指定一组键值对,当发送的消息到交换器时,RabbitMQ会获取到该消息的headers,对比其中的键值对是否完全匹配队列和交换器绑定时指定的键值对,如果匹配,消息就会路由到该队列。headers类型的交换器性能很差,基本上不推荐使用。
消息丢失场景:生产者生产消息到rabbitMQ Server消息丢失、rabbitMQ Server存储的消息丢失、RabbitMQ Server到消费者消息丢失。
对应这三种场景可以从三方面来进行解决:生产者确认机制、数据持久化、消费者手动确认消息。
慢慢补充........
参考:https://blog.csdn.net/qq_25919879/article/details/113055350
2.1依赖
com.rabbitmq
amqp-client
5.8.0
commons-io
commons-io
2.6
package com.yang.one;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @BelongsProject: my-rabbitmq
* @BelongsPackage: com.yang.one
* @Author: yzh
* @CreateTime: 2023-11-29
*/
//生产者 生产消息
public class Producer {
//1.定义一个队列名称
public static final String QUEUE_NAME="hello";
//发送消息
public static void main(String[] args) throws IOException, TimeoutException {
//创建一个连接工厂
ConnectionFactory factory = new ConnectionFactory();
//工厂根据ip连接rabbitmq
factory.setHost("127.0.0.1");
//用户名
factory.setUsername("root");
//密码
factory.setPassword("root");
//创建连接
Connection connection = factory.newConnection();
//获取信道
Channel channel = connection.createChannel();
/**
* 生成一个队列
* 1.队列名称
* 2.队列里面的消息是否持久化(磁盘) 默认情况下存储在内存中
* 3.该队列是否只供一个消费者进行消费,是否可以消息共享,true可以多个消费者消费, false只能一个消费者消费
* 4.是否自动删除 最后一个消费者断开连接之后,该队列是否自动删除 true自动删除 false不自动删除
* 5.其他参数
*/
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//发消息
/**
* 发送一个消息
* 1.发送到那个交换机
* 2.路由的key值是那一个 本次是队列名称
* 3.其他参数信息
* 4.发送消息的消息体
*/
String message ="hello world";
channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
System.out.println("消息发送完毕");
}
}
package com.yang.one;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @BelongsProject: my-rabbitmq
* @BelongsPackage: com.yang.one
* @Author: yzh
* @CreateTime: 2023-11-29
*/
//消费者
public class Consumer {
//1.定义一个队列名称
public static final String QUEUE_NAME = "hello";
//接收消息
public static void main(String[] args) throws IOException, TimeoutException {
//创建一个连接工厂
ConnectionFactory factory = new ConnectionFactory();
//工厂根据ip连接rabbitmq
factory.setHost("127.0.0.1");
//用户名
factory.setUsername("root");
//密码
factory.setPassword("root");
//创建连接
Connection connection = factory.newConnection();
//获取信道
Channel channel = connection.createChannel();
//声明
DeliverCallback deliverCallback = (consumerTag, message) -> {
System.out.println(new String(message.getBody()));
};
CancelCallback cancelCallback = consumerTag -> {
System.out.println("消息消费被中断");
};
/**
* 消费者消费消息
* 1.消费哪个队列
* 2.消费成功之后是否要自动应答,true代表自动应答,false代表手动应答
* 3.消费者未成功消费的回调
* 4.消费者取消消费的回调
*/
channel.basicConsume(QUEUE_NAME, true, deliverCallback, cancelCallback);
}
}
工作队列(又称任务队列)的主要思想是避免立即执行资源密集型任务,而不得不等待它完成。相反我们安排任务在之后执行。我们把任务封装为消息并将其发送到队列。在后台运行的工作进程将弹出任务并最终执行作业。当有多个工作线程时,这些工作线程将一起处理这些任务。
在该 demo 中一个task线程发消息,三个worker工作线程处理消息
package com.yang.one.utils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @BelongsProject: my-rabbitmq
* @BelongsPackage: com.yang.one.utils
* @Author: yzh
* @CreateTime: 2023-11-29
*/
public class RabbitMqUtils {
public static Channel getChannel() throws IOException, TimeoutException {
//创建一个连接工厂
ConnectionFactory factory = new ConnectionFactory();
//工厂根据ip连接rabbitmq
factory.setHost("127.0.0.1");
//用户名
factory.setUsername("root");
//密码
factory.setPassword("root");
//创建连接
Connection connection = factory.newConnection();
//获取信道
Channel channel = connection.createChannel();
return channel;
}
}
package com.yang.one.two;
import com.rabbitmq.client.Channel;
import com.yang.one.utils.RabbitMqUtils;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;
/**
* @BelongsProject: my-rabbitmq
* @BelongsPackage: com.yang.one.two
* @Author: yzh
* @CreateTime: 2023-11-29
*/
//生产者
public class Task {
//1.定义一个队列名称
public static final String QUEUE_NAME="work1";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
/**
* 生成一个队列
* 1.队列名称
* 2.队列里面的消息是否持久化(磁盘) 默认情况下存储在内存中
* 3.该队列是否只供一个消费者进行消费,是否可以消息共享,true可以多个消费者消费, false只能一个消费者消费
* 4.是否自动删除 最后一个消费者断开连接之后,该队列是否自动删除 true自动删除 false不自动删除
* 5.其他参数
*/
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
Scanner sc = new Scanner(System.in);
while (sc.hasNext()){
String message = sc.next();
channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
System.out.println("消息发送完成:"+message);
}
}
}
package com.yang.one.two;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.yang.one.utils.RabbitMqUtils;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @BelongsProject: my-rabbitmq
* @BelongsPackage: com.yang.one.two
* @Author: yzh
* @CreateTime: 2023-11-29
*/
public class Worker implements Runnable{
private String message;
public Worker(String message) {
this.message = message;
}
//1.定义一个队列名称
public static final String QUEUE_NAME="work1";
//消息消费的方法
public static void consumeMessage(String threadName) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
DeliverCallback deliverCallback = (consumerTag, message) -> {
System.out.println(threadName + ",接收到消息:" + new String(message.getBody()));
};
CancelCallback cancelCallback = consumerTag -> {
System.out.println(threadName + ",消息消费被中断");
};
System.out.println(threadName + "启动,等待消费......");
channel.basicConsume(QUEUE_NAME, true, deliverCallback, cancelCallback);
}
@Override
public void run() {
try {
consumeMessage(Thread.currentThread().getName());
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
for (int i = 1; i <=3 ; i++) {
Thread thread = new Thread(new Worker("消息" + i), "工作线程" + i);
thread.start();
}
}
}
慢慢补充........