简介
RabbitMQ是用Erlang实现的一个高并发高可靠AMQP消息队列服务器,支持消息的持久化、事务、拥塞控制、负载均衡等特性,使得RabbitMQ拥有更加广泛的应用场
什么是AMQP?
AMQP,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制
RabbitMQ的优点
- 开源、性能优秀,稳定性保障
- 提供可靠性消息投递模式、返回模式
- 与Spring AMQP完美整合,API丰富
- 集群模式丰富,表达式配置,HA模式,镜像队列模型
- 保证数据不丢失的前提做到高可靠性、可用性
RabbitMQ的应用场景
- 异步处理
- 流量削峰
- 日志处理
- 应用解耦
RabbitMQ基础组件
- Producer:消息生产者,即发送消息的程序
- Consumer:消息消费者,即接受消息的程序
- Server:又叫broker,它提供一种传输服务,维护一条从生产者到消费者的路线,保证数据按照指定的方式传输
- Connection:连接,应用程序与broker的网络连接
- Channel:信道,是进行消息读写的通道,客户端可以建立多个channel,每个channel代表一个会话任务
- Message:消息,本质上是一段数据,由Properties和body组成,Properties可以对消息进行修饰(消息的优先级、延迟等高级特性),Body就是消息体内容
- Exchange:交换机。接受消息,交换机没有存储功能,根据路由键转发消息到绑定队列,如果没有队列绑定到交换机,那么交换机会直接丢弃消息
- Binding Key:Exchange和Queue之间的虚拟连接,用于指定当前队列与交换机Exchange的Key,只要当Routing Key与Bing Key相同的时候,交换机才会根据路由规则分发到匹配的队列Queue中
- Routing Key:路由键,生产者将消息发送到交换机Exchange时,一般会指定一个Routing Key,来指定这个消息的路由规则,Routing Key需要与Exchange Type及Binding Key联合使用才能最终生效,将消息发送到指定队列里(Routing Key的长度限制为255Bytes)
- Queue:消息队列,保存消息并将消息转发给消费者
- Virtual Host:权限控制的基本单元
RabbitMQ的几种队列模型
常见的几种队列模型如下图所示:
// 先写一个工具类,用来连接RabbitMQ
public class ConnectionUtil {
public static Connection getConnection() throws Exception {
//定义连接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置服务地址
factory.setHost("127.0.0.1");
//端口
factory.setPort(5672);
//设置账号信息,用户名、密码、vhost
factory.setVirtualHost("/");
factory.setUsername("guest");
factory.setPassword("guest");
// 通过工程获取连接
Connection connection = factory.newConnection();
return connection;
}
}
1. 简单队列
// 消息生产者
public class Producer {
private final static String QUEUE_NAME = "test_queue";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
// 从连接中创建通道
Channel channel = connection.createChannel();
// 声明(创建)队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 消息内容
String message = "Hello World !!!";
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println(" Producer Sent :" + message );
//关闭通道和连接
channel.close();
connection.close();
}
}
// 消息消费者
public class Consumer {
private final static String QUEUE_NAME = "test_queue";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.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(" Consumer Received:" + message );
}
}
}
运行消息消费者、消息生产者,我们来看控制台输出:
Producer Sent :Hello World !!!
Consumer Received:Hello World !!!
2. 工作模式
public class Producer {
private final static String QUEUE_NAME = "work_queue";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
for (int i = 0; i < 20; i++) {
// 消息内容
String message = "" + i;
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println(" Producer Sent : " + message );
// 设置一个时间间隔
Thread.sleep(i * 10);
}
channel.close();
connection.close();
}
}
// Consumer1
public class Consumer1 {
private final static String QUEUE_NAME = "work_queue";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 同一时刻服务器只会发一条消息给消费者
channel.basicQos(1);
// 定义队列的消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
// 监听队列,手动返回完成
channel.basicConsume(QUEUE_NAME, false, consumer);
// 获取消息
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" Consumer1 Received : " + message );
//休眠10毫秒
Thread.sleep(10);
// 返回确认状态
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
// Consumer2
public class Consumer2 {
private final static String QUEUE_NAME = "work_queue";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 同一时刻服务器只会发一条消息给消费者
channel.basicQos(1);
// 定义队列的消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
// 监听队列,手动返回完成状态
channel.basicConsume(QUEUE_NAME, false, consumer);
// 获取消息
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" Consumer2 Received : " + message );
// 休眠1秒
Thread.sleep(1000);
// 手动确认消息状态
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
运行Consumer1、Consumer2,再运行Producer,我们来看控制台输出:
// Producer
Producer Sent : 0
Producer Sent : 1
Producer Sent : 2
......
Producer Sent : 19
// Consumer1
Consumer1 Received : 0
Consumer1 Received : 2
Consumer1 Received : 3
Consumer1 Received : 4
Consumer1 Received : 5
Consumer1 Received : 6
Consumer1 Received : 7
Consumer1 Received : 8
Consumer1 Received : 9
Consumer1 Received : 10
Consumer1 Received : 11
Consumer1 Received : 12
Consumer1 Received : 13
Consumer1 Received : 14
Consumer1 Received : 15
Consumer1 Received : 17
Consumer1 Received : 18
Consumer1 Received : 19
// Consumer2
Consumer2 Received : 1
Consumer2 Received : 16
消息的确认模式
// true:自动确认 false:手动确认
channel.basicConsume(QUEUE_NAME, false, consumer);
// 手动确认消息状态
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
自动确认 只要消息从队列中获取,无论消费者获取到消息后是否成功消息,都认为是消息已经成功消费 手动确认 消费者从队列中获取消息后,服务器会将该消息标记为不可用状态,等待消费者的反馈,如果消费者一直没有反馈,那么该消息将一直处于不可用状态
3. 发布/订阅模式
- 1个生产者,多个消费者
- 每一个消费者都有自己的一个队列
- 生产者没有将消息直接发送到队列,而是发送到了交换机
- 每个队列都要绑定到交换机
- 生产者发送的消息,经过交换机,到达队列,实现,一个消息被多个消费者获取的目的
一个消费者队列可以有多个消费者实例,只有其中一个消费者实例会消费
// 消息生产者
public class Producer {
private final static String EXCHANGE_NAME = "test_fanout";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明exchange 交换机类型fanout
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
// 消息内容
String message = "Hello World !!!";
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());
System.out.println("Producer Sent : " + message );
channel.close();
connection.close();
}
}
// 消息消费者
public class Consumer {
private final static String QUEUE_NAME = "ps_queue";
private final static String EXCHANGE_NAME = "test_fanout";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 绑定队列到交换机
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
// 同一时刻服务器只会发一条消息给消费者
channel.basicQos(1);
// 定义队列的消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
// 监听队列,手动返回完成
channel.basicConsume(QUEUE_NAME, false, consumer);
// 获取消息
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" Consumer1 Received: " + message);
Thread.sleep(10);
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
控制台输出:
//消息生产者
Producer Sent : Hello World !!!
//只有Consumer1接受到了消息
Consumer1 Received: Hello World !!!
4. 路由模式
public class Producer {
private final static String EXCHANGE_NAME = "test_direct";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明exchange
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
// 消息内容
String message = "Hello World !!!";
// routing key是key1
channel.basicPublish(EXCHANGE_NAME, "key1", null, message.getBytes());
System.out.println(" Producer Sent : " + message);
channel.close();
connection.close();
}
public class Consumer1 {
private final static String QUEUE_NAME = "routing_queue";
private final static String EXCHANGE_NAME = "test_direct";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 绑定队列到交换机
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "key1");
// 同一时刻服务器只会发一条消息给消费者
channel.basicQos(1);
// 定义队列的消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
// 监听队列,手动返回完成
channel.basicConsume(QUEUE_NAME, false, consumer);
// 获取消息
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" Consumer1 Received: " + message);
Thread.sleep(10);
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
public class Consumer2 {
private final static String QUEUE_NAME = "routing_queue";
private final static String EXCHANGE_NAME = "test_direct";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 绑定队列到交换机
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "key2");
// 同一时刻服务器只会发一条消息给消费者
channel.basicQos(1);
// 定义队列的消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
// 监听队列,手动返回完成
channel.basicConsume(QUEUE_NAME, false, consumer);
// 获取消息
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" Consumer2 Received : " + message);
Thread.sleep(10);
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
控制台输出:
//消息生产者
Producer Sent : Hello World !!!
//只有Consumer1接受到了消息
Consumer1 Received: Hello World !!!
5. 主题模式
主题模式也称通配符模式:* 代表一个; # 代表一个或多个
// 消息生产者
public class Producer {
private final static String EXCHANGE_NAME = "test_topic";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明exchange
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
// 消息内容
String message = "Hello World!";
channel.basicPublish(EXCHANGE_NAME, "key.1", null, message.getBytes());
System.out.println(" Producer Sent : " + message );
channel.close();
connection.close();
}
}
public class Consumer1 {
private final static String QUEUE_NAME = "topic_queue";
private final static String EXCHANGE_NAME = "test_topic";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 绑定队列到交换机
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "key.1");
// 同一时刻服务器只会发一条消息给消费者
channel.basicQos(1);
// 定义队列的消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
// 监听队列,手动返回完成
channel.basicConsume(QUEUE_NAME, false, consumer);
// 获取消息
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" Consumer1 Received:" + message);
Thread.sleep(10);
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
public class Consumer2 {
private final static String QUEUE_NAME = "topic_queue";
private final static String EXCHANGE_NAME = "test_topic";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 绑定队列到交换机
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "key.*");
// 同一时刻服务器只会发一条消息给消费者
channel.basicQos(1);
// 定义队列的消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
// 监听队列,手动返回完成
channel.basicConsume(QUEUE_NAME, false, consumer);
// 获取消息
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" Consumer2 Received:" + message);
Thread.sleep(10);
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
控制台输出:
// 消息生产者
Producer Sent : Hello World !!!
// 只有Consumer1接受到了消息
Consumer1 Received: Hello World !!!