一、简介
RabbitMQ是一个开源的AMQP实现,服务器端用Erlang语言编写,支持多种客户端。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。
二、RabbitMQ核心概念
Producer(生产者):就是投递消息的一方。生产者负责生产消息,然后发布到RabbitMQ中。其中消息一般分为两个部分:消息体和附加消息。
消息体(payload):在实际应用中,消息体一般是一个带有业务逻辑结构的数据,比如一个JSON字符串。然后可以进一步对这个消息体进行序列化操作。
附加信息:用来表述这条消息,比如目标交换器的名称、路由键和一些自定义属性等等。
Broker:消息中间件的服务节点。对于RabbitMQ来说,一个RabbitMQ Broker可以简单地看作一个RabbitMQ服务节点,或者RabbitMQ服务实例。也可以将一个RabbitMQ Broker看作一台RabbitMQ服务器。
Virtual Host(虚拟主机):表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。一个Broker中可以有多个Virtual Host;就好比一个mysql-server中可以有多个database。每个vhost本质上就是一个mini版的RabbitMQ服务器,拥有自己的队列、交换器、绑定和权限机制。vhost是AMQP概念的基础,必须在连接时指定,RabbitMQ默认的vhost是 /。
Channel:频道或信道,是建立在Connection连接之上的一种轻量级的连接。大部分的操作是在Channel这个接口中完成的,包括定义队列的声明queueDeclare、交换机的声明exchangeDeclare、队列的绑定queueBind、发布消息basicPublish、消费消息basicConsume等。如果把Connection比作一条光线电缆的话,那么Channel信道就是光线电缆中的其中一束光纤。一个Connection上可以创建任意数量的Channel。
RoutingKey(路由键):生产者将消息发给交换器的时候,一般会指定一个RoutingKey,用来指定这个消息的路由规则。RoutingKey需要与交换器类型和绑定键(BindingKey)联合使用,在交换器类型和绑定键(BindingKey)固定的情况下,生产者可以在发送消息给交换器时,通过指定RoutingKey来决定消息流向哪里。
Exchange(交换器):生产者将消息发送到Exchange(交换器,通常也可以用大写的“X”来表示),由交换器将消息路由到一个或者多个队列中,如果路由不到,或返回给生产者,或直接丢弃。
RabbitMQ常见的交换器有fanout、direct、topic、headers这四种:
fanout:扇形交换机,它会把所有发送到该交换器的消息路由到所有与该交换器绑定的队列中。
direct:直连交换机,它会把消息路由到那些BindingKey和RoutingKey完全匹配的队列中。
topic:主题交换机,与direct类似,但它可以通过通配符进行模糊匹配(其中*代表匹配一个单词,#表示一串匹配的字段)。
headers:头交换机,不依赖于路由键的匹配规则来路由消息,而是根据发送的消息内容的headers属性进行匹配。headers类型的交换器性能很差,一般不使用。
Queue(队列):是RabbitMQ的内部对象用于存储消息。
Binding(绑定):RabbitMQ中通过绑定将交换器与队列关联起来,在绑定的时候一般会指定一个绑定键(BindingKey),这样RabbitMQ就知道正确地将消息路由到队列了。
Cosumer(消费者):就是接收消息的一方。消费者连接到RabbitMQ服务器,并订阅到队列上。当消费者消费一条消息时,只有消费消息的消息体(payload)。在消息路由的过程中,消息的标签会丢弃,存入到队列中的消息只有消息体,消费者也只会消费到消息体,也就是不知道消息的生产者是谁,当然消费者也不需要知道。
三、RabbitMQ运作流程
生产者发送消息的过程:
-
生产者连接到RabbitMQ Broker,建立一个连接(Connection),开启一个信道(Channel)。
-
生产者声明一个交换器,并设置相关属性,比如交换机类型、是否持久化等。
-
生产者声明一个队列并设置相关属性,比如是否排他、是否持久化、是否自动删除等。
-
生产者通过路由键将交换器和队列绑定起来。
-
生产者发送消息至RabbitMQ Broker,其中包含路由键、交换器等信息。
-
相应的交换器根据接收到的路由键查找相匹配的队列。
-
如果找到,则将从生产者发送过来的消息存入相应的队列中。
-
如果没有找到,则根据生产者配置的属性选择丢弃还是回退给生产者。
-
关闭信道、关闭连接。
代码实例:
public static void main(String[] args) {
// 1、创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 2、设置连接属性
factory.setHost("192.168.100.000");
factory.setUsername("***");
factory.setPassword("***");
Connection connection = null;
Channel channel = null;
try {
// 3、从连接工厂获取连接
connection = factory.newConnection("生产者");
// 4、从链接中创建通道
channel = connection.createChannel();
// 定义fanout类型的交换器
channel.exchangeDeclare("ps_test", "fanout");
// 消息内容
String message = "Hello Publish";
// 发送消息到ps_test交换器上
channel.basicPublish("ps_test", "", null, message.getBytes());
System.out.println("消息 " + message + " 已发送!");
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
} finally {
// 7、关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
// 8、关闭连接
if (connection != null && connection.isOpen()) {
try {
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
消费者接收消息的过程:
-
生产者连接到RabbitMQ Broker,建立一个连接(Connection),开启一个信道(Channel)
-
消费者向RabbitMQ Broker请求消费相应队列中的消息,可能会设置相应的回调函数,以及做一些准备工作。
-
等待RabbitMQ Broker回应并投递相应队列中的消息,消费者接收消息。
-
消费者确认(ack)接收到的消息。
-
RabbitMQ从队列中删除相应已经被确认的消息。
-
关闭信道、关闭连接。
代码实例:
public static void main(String[] args) {
// 1、创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 2、设置连接属性
factory.setHost("192.168.100.000");
factory.setUsername("***");
factory.setPassword("***");
Connection connection = null;
Channel channel = null;
try {
// 3、从连接工厂获取连接
connection = factory.newConnection("消费者");
// 4、从链接中创建通道
channel = connection.createChannel();
/**
* 5、声明(创建)队列
* 如果队列不存在,才会创建
* RabbitMQ 不允许声明两个队列名相同,属性不同的队列,否则会报错
*
* queueDeclare参数说明:
* @param queue 队列名称
* @param durable 队列是否持久化
* @param exclusive 是否排他,即是否为私有的,如果为true,会对当前队列加锁,其它通道不能访问,
* 并且在连接关闭时会自动删除,不受持久化和自动删除的属性控制。
* 一般在队列和交换器绑定时使用
* @param autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除
* @param arguments 队列参数,设置队列的有效期、消息最大长度、队列中所有消息的生命周期等等
*/
channel.queueDeclare("queue1", false, false, false, null);
// 6、定义收到消息后的回调
DeliverCallback callback = new DeliverCallback() {
public void handle(String consumerTag, Delivery message) throws IOException {
System.out.println("收到消息:" + new String(message.getBody(), "UTF-8"));
}
};
// 7、监听队列
channel.basicConsume("queue1", true, callback, new CancelCallback() {
public void handle(String consumerTag) throws IOException {
}
});
System.out.println("开始接收消息");
System.in.read();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
} finally {
// 8、关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
// 9、关闭连接
if (connection != null && connection.isOpen()) {
try {
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
欢迎关注本人公众号,一起讨论学习