1、是什么?
1.1、RabbitMQ简介:
RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件)。RabbitMQ服务器是用Erlang语言编写的,而群集和故障转移是构建在开放电信平台框架上的。所有主要的编程语言均有与代理接口通讯的客户端库。支持多系统、多种语言
- RabbitMQ 作用:异步,解耦,缓冲,消息分发。
- RabbitMQ 主要分为3个部分,生产者,交换机和队列,消费者。
- 需要注意消息持久化,目的为了防止 RabbitMQ 宕机;考虑 ACK 机制,目的为了如果消费者对消息的处理失败了,那么后续要如何处理。
1.2、什么是AMQP?
AMQP,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。Erlang中的实现有 RabbitMQ等。
2、RabbitMQ 安装
- 进入官网,下载相对应系统以及版本,需要先下载对应的Erlang支持包且安装,其次安装对应的rabbitMQ.
- 输入对应网址,则对应登录页,输入用户名密码guest,guest。
- 进入对应的管理页面,说明RabbitMQ 安装成功
3、RabbitMQ使用
3.1、核心概念
3.1.1、Message :
消息,消息是不具名的,它由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括routing-key(路由键)、priority(相对于其他消息的优先权)、 delivery-mode(指出 该消息可能需要持久性存储)等。
3.1.2、Publisher:
消息的生产者,也是一个向交换器发布消息的客户端应用程序
3.1.3、Exchange:
交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列。Exchange有4种类型: direct(默认), fanout, topic, 和headers,不同类型的Exchange转发消息的策略有所区别
3.1.4、Queue:
消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。
3.1.5、Binding:
绑定,用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连
接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。Exchange 和Queue的绑定可以是多对多的关系。
3.1.6、Connection:
网络连接,比如一个TCP连接。
3.1.7、Channel:
信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的TCP连接内的虚拟连接, AMQP 命令都是通过信道发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。因为对于操作系统来说建立和销毁 TCP 都是非常昂贵的开销,所以引入了信道的概念,以复用一条 TCP 连接。
3.1.8、Consumer:
消息的消费者,表示一个从消息队列中取得消息的客户端应用程序。
3.1.9、Virtual Host:
虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个 vhost 本质上就是一个 mini 版的 RabbitMQ 服务器,拥有自己的队列、交换器、绑定和权限机制。 vhost 是 AMQP 概念的基础,必须在连接时指定,RabbitMQ 默认的 vhost 是 / 。
3.2:代码实操:
- 需要导入的依赖:
<dependencies>
<!-- https://mvnrepository.com/artifact/com.rabbitmq/amqp-client -->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.7.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.26</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.26</version>
</dependency>
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>4.0.2</version>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.1.7.RELEASE</version>
</dependency>
</dependencies>
- 通用工具类:
/**
* 获取MQ连接
* @return
* @throws IOException
* @throws TimeoutException
*/
public static Connection getConnection() throws IOException, TimeoutException {
//定义一个工厂连接
ConnectionFactory factory = new ConnectionFactory();
//设置服务地址
factory.setHost("localhost");
//设置AMQP 5672
factory.setPort(5672);
//设置vHost
factory.setVirtualHost("/test");
//设置用户名
factory.setUsername("test");
//设置密码
factory.setPassword("test");
// 通过工程获取连接
Connection connection = factory.newConnection();
return connection;
}
3.2.1、简单模式:
-
一个生产者,一个消费者
-
生产者:
/**
* 生产者发送消息到队列
*/
public class send {
private final static String QUEUE_NAME = "q_test_01";
public static void main(String[] args) throws IOException, TimeoutException {
//获取连接以及mq通道
Connection connection = ConnectionUtils.getConnection();
//从连接中创建通道
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//消息内容
String message = "JAVA IS LAJI";
channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
System.out.println("[x] Send '" + message + " '");
//关闭通道和连接
channel.close();;
connection.close();
}
- 消费者:
/**
* 消费者从队列中获取消息
*/
public class Recv {
private final static String QUEUE_NAME = "q_test_01";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//获取连接
Connection connection = ConnectionUtils.getConnection();
//创建通道
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//super.handleDelivery(consumerTag, envelope, properties, body);
String msg = new String(body, "utf-8");
System.out.println(" [x] Received '" + msg + "'");
}
};
//监听队列
channel.basicConsume(QUEUE_NAME,true,consumer);
}
//旧的API
public static void oldAPI() throws IOException, TimeoutException, InterruptedException {
//获取连接
Connection connection = ConnectionUtils.getConnection();
//创建通道
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//定义队列消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
//监听队列
channel.basicConsume(QUEUE_NAME,true,consumer);
//获取信息
while(true){
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" [x] Received '" + message + "'");
}
}
3.2.2、work模式:
- 自动模式
消费者从消息队列获取消息后,服务端就认为该消息已经成功消费。
- 手动模式
消费者从消息队列获取消息后,服务端并没有标记为成功消费
消费者成功消费后需要将状态返回到服务端
-
一个生产者,多个消费者,每个消费者获取到的消息唯一。
-
生产者:
模拟向多个队列发送多条信息
/**
* 向队列中发送100条消息。
*/
public class Send {
private final static String QUEUE_NAME = "test_queue_work";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//获取通道和连接
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
for (int i = 1 ; i <= 100 ; i++){
//消息内容
String message = "" + i;
channel.basicPublish("" ,QUEUE_NAME, null,message.getBytes());
System.out.println(" [x] Sent '" + message + "'");
Thread.sleep(i * 10);
}
channel.close();
connection.close();
}
- 消费者1:
注意:利用thread.sleep()模消息延迟接收
//同一时间服务器只会发一条消息给消费者
channel.basicQos(1);
//返回确认状态,注释掉表示使用自动模式
channel.basicAck(envelope.getDeliveryTag(),false);
/**
* 消费者1
*/
public class Recv {
private final static String QUEUE_NAME = "test_queue_work";
public static void main(String[] args) throws IOException, TimeoutException {
//获取mq连接和通道
Connection connection = ConnectionUtils.getConnection();
final Channel channel = connection.createChannel();
//建立队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//同一时间服务器只会发一条消息给消费者
channel.basicQos(1);
//定义队列消费者
final DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//super.handleDelivery(consumerTag, envelope, properties, body);
String message = new String(body,"utf-8");
System.out.println(" [y] Received '" + message + "'");
try {
//休眠
Thread.sleep(10);
//返回确认状态,注释掉表示使用自动模式
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//返回确认状态,注释掉表示使用自动模式
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
// 监听队列,false表示手动返回完成状态,true表示自动
channel.basicConsume(QUEUE_NAME,false,consumer);
}
- 消费者2
public class Recv2 {
private final static String QUEUE_NAME = "test_queue_work";
public static void main(String[] args) throws IOException, TimeoutException {
//获取mq连接和通道
Connection connection = ConnectionUtils.getConnection();
final Channel channel = connection.createChannel();
//建立队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//同一时间服务器只会发一条消息给消费者
channel.basicQos(1);
//定义队列消费者
final DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//super.handleDelivery(consumerTag, envelope, properties, body);
String message = new String(body,"utf-8");
System.out.println(" [y] Received '" + message + "'");
try {
//休眠
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//返回确认状态,注释掉表示使用自动模式
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
// 监听队列,false表示手动返回完成状态,true表示自动
channel.basicConsume(QUEUE_NAME,false,consumer);
}
结论:若是自动提交两边返回的数量是一样的,且总论是单数和双数分开(消费者为2个的时候,反正就是轮流),但是改为手动提交则是"多劳多得",睡眠时间少的会获取消息比较多
3.2.3、订阅模式:
-
一个生产者发送的消息会被多个消费者获取。
-
生产者:可以将消息发送到队列或者是交换机。
-
消费者:只能从队列中获取消息。
-
如果消息发送到没有队列绑定的交换机上,那么消息将丢失。(交换机不具备持久化功能)
-
每个发到 fanout 类型交换器的消息都会分到所有绑定的队列上去。 fanout 交换器不处理路由键,只是简单的将队列绑定到交换器上,每个发送到交换器的消息都会被转发到与该交换器绑定的所有队列上。很像子网广播,每台子网内的主机都获得了一份复制的消息。 fanout 类型转发消息是最快的 。
-
生产者:
public class Send {
public final static String EXCHANGE_NAME = "test_exchange_fanout";
public static void main(String[] args) throws IOException, TimeoutException {
//获取连接以及通道
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//声明exchange
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
//消息内容
String message = "Hello World";
channel.basicPublish(EXCHANGE_NAME,"",null,message.getBytes());
System.out.println(" [X] Send '" + message + " '");
channel.close();
connection.close();
}
- 消费者:
注意:需要把队列绑定到交换机中
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//绑定队列到交换机
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
//同一时刻服务器只会发送一条消息给消费者
/**
* 看作是搜索系统
*/
public class Recv {
private final static String QUEUE_NAME = "test_queue_work1";
private final static String EXCHANGE_NAME = "test_exchange_fanout";
public static void main(String[] args) throws IOException, TimeoutException {
//获取连接以及通道
Connection connection = ConnectionUtils.getConnection();
final Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//绑定队列到交换机
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
//同一时刻服务器只会发送一条消息给消费者
channel.basicQos(1);
//定义队列消费者
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//super.handleDelivery(consumerTag, envelope, properties, body);
String msg = new String(body);
System.out.println(" [Recv] Received '" + msg + "'");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
//监听队列,手动返回完成
channel.basicConsume(QUEUE_NAME,false,consumer);
}
- 消费者2:
/**
* 看作是前台系统
*/
public class Recv2 {
private final static String QUEUE_NAME = "test_queue_work2";
private final static String EXCHANGE_NAME = "test_exchange_fanout";
public static void main(String[] args) throws IOException, TimeoutException {
//获取连接以及通道
Connection connection = ConnectionUtils.getConnection();
final Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//绑定队列到交换机
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
//同一时刻服务器只会发送一条消息给消费者
channel.basicQos(1);
//定义队列消费者
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//super.handleDelivery(consumerTag, envelope, properties, body);
String msg = new String(body);
System.out.println(" [Recv] Received '" + msg + "'");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
//监听队列,手动返回完成
channel.basicConsume(QUEUE_NAME,false,consumer);
}
3.2.4、路由模式
-
发送消息到交换机并且要指定路由key
-
消费者将队列绑定到交换机时需要指定路由key
-
是一种完全匹配,只有匹配到的消费者才能消费消息
-
消息中的路由键( routing key)如果和 Binding 中的 binding key 一致, 交换器就将消息发到对应的队列中。路由键与队列名完全匹配,如果一个队列绑定到交换机要求路由键为“ dog”,则只转发 routing key 标记为“ dog”的消息,不会转发“ dog.puppy”,也不会转发“ dog.guard”等等。它是完全匹配、单播的模式。
- 生产者:
注意:发布消息给交换机的时候指定路由 "insert"
channel.basicPublish(EXCHANGE_NAME,"insert",null,message.getBytes());
**
* 路由模式
*/
public class Send {
private final static String EXCHANGE_NAME = "test_exchange_direct";
public static void main(String[] args) throws IOException, TimeoutException {
//获取连接以及通道
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//创建交换机
channel.exchangeDeclare(EXCHANGE_NAME,"direct");
//消息内容
String message = "增加商品";
channel.basicPublish(EXCHANGE_NAME,"insert",null,message.getBytes());
System.out.println("Send : " + message);
channel.close();
connection.close();
}
}
- 消费者:
注意:队列绑定交换机的时候指定路由
//绑定队列到交换机
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"update");
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"delete");
/**
* 假设为前台系统
*/
public class Recv {
private final static String QUEUE_NAME = "test_queue_direct_1";
private final static String EXCHANGE_NAME = "test_exchange_direct";
public static void main(String[] args) throws IOException, TimeoutException {
//获取通道以及连接
Connection connection = ConnectionUtils.getConnection();
final Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//绑定队列到交换机
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"update");
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"delete");
//定义同一时刻只会发送一条消息给消费者
channel.basicQos(1);
//定义消费者
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// super.handleDelivery(consumerTag, envelope, properties, body);
//接收的消息
String message = new String(body);
//打印
System.out.println("Recv : " + message);
//睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
//监听队列
channel.basicConsume(QUEUE_NAME,false,consumer);
}
}
- 消费者2
/**
* 假设为前台系统
*/
public class Recv2 {
private final static String QUEUE_NAME = "test_queue_direct_2";
private final static String EXCHANGE_NAME = "test_exchange_direct";
public static void main(String[] args) throws IOException, TimeoutException {
//获取通道以及连接
Connection connection = ConnectionUtils.getConnection();
final Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//绑定队列到交换机
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"update");
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"delete");
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"insert");
//定义同一时刻只会发送一条消息给消费者
channel.basicQos(1);
//定义消费者
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// super.handleDelivery(consumerTag, envelope, properties, body);
//接收的消息
String message = new String(body);
//打印
System.out.println("Recv2 : " + message);
//睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
//监听队列
channel.basicConsume(QUEUE_NAME,false,consumer);
}
}
3.2.5、通配符模式
- topic交换器通过模式匹配分配消息的路由键属性,将路由键和某个模式进行匹配,此时队列需要绑定到一个模式上。它将路由键和绑定键的字符串切分成单词,这些单词之间用点隔开。它同样也会识别两个通配符:符号“#”和符号“*”。#匹配0个或多个单词,*匹配一个单词。如下图所示:
- 生产者:
注意:
channel.basicPublish(EXCHANGE_NAME, "goods.delete", null, message.getBytes());
public class Send {
private final static String EXCHANGE_NAME = "test_exchange_topic";
public static void main(String[] args) throws IOException, TimeoutException {
//获取连接以及通道
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//创建交换机
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
//消息内容
String message = "TEST TOPIC!!!";
channel.basicPublish(EXCHANGE_NAME, "goods.delete", null, message.getBytes());
System.out.println("Send : " + message);
channel.close();
connection.close();
}
}
- 消费者:
注意:
//绑定队列到交换机
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"goods.add");
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"goods.insert");
/**
* 假设为前台系统
*/
public class Recv {
private final static String QUEUE_NAME = "test_queue_topic_1";
private final static String EXCHANGE_NAME = "test_exchange_topic";
public static void main(String[] args) throws IOException, TimeoutException {
//获取通道以及连接
Connection connection = ConnectionUtils.getConnection();
final Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//绑定队列到交换机
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"goods.add");
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"goods.insert");
//定义同一时刻只会发送一条消息给消费者
channel.basicQos(1);
//定义消费者
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// super.handleDelivery(consumerTag, envelope, properties, body);
//接收的消息
String message = new String(body);
//打印
System.out.println("Recv : " + message);
//睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
//监听队列
channel.basicConsume(QUEUE_NAME,false,consumer);
}
- 消费者2:
注意:
//绑定队列到交换机
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"goods.#");
/**
* 假设为前台系统
*/
public class Recv2 {
private final static String QUEUE_NAME = "test_queue_topic_2";
private final static String EXCHANGE_NAME = "test_exchange_topic";
public static void main(String[] args) throws IOException, TimeoutException {
//获取通道以及连接
Connection connection = ConnectionUtils.getConnection();
final Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//绑定队列到交换机
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"goods.#");
//定义同一时刻只会发送一条消息给消费者
channel.basicQos(1);
//定义消费者
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// super.handleDelivery(consumerTag, envelope, properties, body);
//接收的消息
String message = new String(body);
//打印
System.out.println("Recv : " + message);
//睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
//监听队列
channel.basicConsume(QUEUE_NAME,false,consumer);
}
}
结果:消费者1不能收到消息,消费者2可以收到消息