RabbitMQ基础
RabbitMQ是由 Erlang 编写的一种实现了高级消息队列协议(AMQP)协议的开源消息中间件。
MQ的主要特点
用高效可靠的消息传递机制进行与平台无关的数据交流,并基于数据通信来进行分布式系统的集成。通过提供消息传递和消息队列模型,可以在分布式环境下扩展的通信。
为什么要用MQ?
- 异步
- 解耦
- 削峰
使用MQ带来的问题
- 系统的可用性降低
- 提高了系统复杂度
- 提高了代码复杂性(消息丢失、消息重复、消息一次性)
RabbitMQ基本特性
- 高可靠:保证消息的可靠性(消息丢失、消息重复、消息一次性)
- 灵活的路由:通过引入交换机(Exchange)提供了灵活的路由方式,
- 支持多客户端:为多种语言提供了客户端的操作接口实现(如:Python、Java、PHP、C#、JavaScript、Go、Spring AMQP等)
- 集群与扩展性:支持集群与扩展
- 高可用队列:通过镜像队列,使不同集群队列可以相互复制,达到数据的冗余,提供可用性
- 权限管理:可以进行资源的隔离
- 插件系统:在不修改原码情况下,可以功能增强或变更。
- 与Spring集成 Spring AMQP
RabbitMQ工作模型
- Broker:部署RabbitMQ的实体服务器。提供一种传输服务,维护一条从生产者到消费者的传输线路,保证消息数据能按照指定的方式传输
- VHost:Virtual Host,虚拟主机,一个Broker可以有多个虚拟主机,用作不同用户的权限分离。一个虚拟主机持有一组Exchange、Queue和Binding
- Exchange:消息交换机。指定消息按照什么规则路由到哪个队列Queue
- Queue:消息队列(底层以数据库-Mnesia进行实现)。消息的载体,每条消息都会被投送到一个或多个队列中
- Binding:绑定。作用是将Exchange和Queue按照某种路由规则绑定起来
- Routing Key:路由关键字。Exchange根据Routing Key进行消息传递。定义绑定时指定的关键字称为Binding Key
- Producer:消息生产者。主要将消息投递到对应的Exchange上面。一般是独立的程序
- Consumer:消息消费者。消息的接收者,一般是独立的程序
- Connection:Producer和Consumer与Broker之间的TCP长连接。
- Channel:消息通道,也称信道。在客户端的每个连接里可以建立多个channel,每个Channel代表一个会话任务。在RabbitMQ Java Client API 中,channel上定义了大量的编程接口
Exchange路由规则
1. Direct Exchange 直连交换机
定义:直连类型的交换机与一个队列绑定时,需要指定一个明确的binding key。
路由规则:发现消息到直连类型的交换机时,只有routing key和binding key完全匹配时,绑定的队列才能收到消息。
channel.basicPublish("DirectExchangeName", "binging key", null, msg.getBytes());
2. Topic Exchange 主题交换机
定义:主题类型的交换机与一个队列绑定时,可以指定按模式匹配的routing key。
匹配通配符有两个,* 代表匹配一个单词。# 代表匹配零个或者多个单词。单词与单词之间用.隔开。如:
- kiss.*:匹配 kiss 后有一个单词,如kiss.my、kiss.you
- *.kiss:匹配 kiss 前有一个单词,如my.kiss、you.kiss
- kiss.#:匹配 kiss 后有零个或者多个单词,如kiss.my、kiss.my.you
- #.kiss:匹配 kiss 前有零个或者多个单词,如my.kiss、my.you.kiss
路由规则:发送消息到主题类型的交换机时,routing key 符合 binding key 的规则时,绑定的队列才能收到消息。
// 队列1和队列3能收到消息
channel.basicPublish("TopicExchangeName", "kiss.my", null, msg.getBytes());
// 队列2和队列4能收到消息
channel.basicPublish("TopicExchangeName", "my.kiss", null, msg.getBytes());
// 只有队列3能收到消息
channel.basicPublish("TopicExchangeName", "kiss.my.you", null, msg.getBytes());
// 只有队列4能收到消息
channel.basicPublish("TopicExchangeName", "my.you.kiss", null, msg.getBytes());
3. Fanout Exchange 广播交换机
定义:广播类型的交换机与一个队列 绑定时,不需要指定binding key。
路由规则:当消息发送到广播类型的交换机时,不需要指定routing key,所有与之绑定的队列都能收到消息。
// 3个队列都会收到消息
channel.basicPublish("FanoutExchangeName", "", null, msg.getBytes());
Java RabbitMQ简易使用用例
1. 引入依赖
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.7.3</version>
</dependency>
2. 创建配置类
public class RabbitMQConfig {
private RabbitMQConfig() {
}
// 主机IP
public static final String HOST = "127.0.0.1";
// 主机port
public static final Integer PORT = 5672;
// 主机port
public static final String VHOST = "/";
// 主机port
public static final String USERNAME = "admin";
// 主机port
public static final String PASSWORD = "admin";
// 交换机名称
public static final String DIRECT_EXCHANGE = "direct_exchange";
// 队列名称
public static final String QUEUE_NAME = "direct_queue";
// Routing key
public static final String ROUTING_KEY = "kiss";
}
3. 消费端代码
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 消费者
*/
public class MessageConsumer {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
// 设置主机IP
factory.setHost(RabbitMQConfig.HOST);
// 设置端口
factory.setPort(RabbitMQConfig.PORT);
// 设置 Vhost
factory.setVirtualHost(RabbitMQConfig.VHOST);
// 设置访问用户
factory.setUsername(RabbitMQConfig.USERNAME);
factory.setPassword(RabbitMQConfig.PASSWORD);
// 建立连接
Connection connection = factory.newConnection();
// 创建Channel消息通道
Channel channel = connection.createChannel();
/**
* 声明交换机,参数String exchange, String type, boolean durable, boolean autoDelete, Map<String, Object> arguments
*
* String exchange:指定交换机名称
* String type:路由类型,direct、topic、fanout
* boolean durable:是否持久化
* boolean autoDelete:是否自动删除
* Map<String, Object> arguments:其他参数
*/
channel.exchangeDeclare(RabbitMQConfig.DIRECT_EXCHANGE, "direct", false, false, null);
/**
* 声明队列,参数String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
* String queue:指定队列名称
* boolean durable:是否持久化
* boolean exclusive:是否排他,既是否创建者私有,如果为true,会对当前队列加锁,其他通道不能访问,并且
* 在连接关闭时会自动删除,不受持久化和自动删除限制
* boolean autoDelete:是否自动删除
* Map<String, Object> arguments:其他参数
*/
channel.queueDeclare(RabbitMQConfig.QUEUE_NAME, false, false, false, null);
/**
* 绑定交换和队列,参数String queue, String exchange, String routingKey, Map<String, Object> arguments
*/
channel.queueBind(RabbitMQConfig.QUEUE_NAME, RabbitMQConfig.DIRECT_EXCHANGE, RabbitMQConfig.ROUTING_KEY, null);
// 创建消费者
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// 获取消息
String msg = new String(body, "UTF-8");
System.out.println("consumer message:" + msg);
}
};
// 开始获取消息String queue, boolean autoAck, Consumer callback
channel.basicConsume(RabbitMQConfig.QUEUE_NAME, true, consumer);
}
}
4. 生产端代码
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* 生产者
*/
public class MessageProvider {
public static void main(String[] args) {
ConnectionFactory factory = new ConnectionFactory();
// 设置主机IP
factory.setHost(RabbitMQConfig.HOST);
// 设置端口
factory.setPort(RabbitMQConfig.PORT);
// 设置 Vhost
factory.setVirtualHost(RabbitMQConfig.VHOST);
// 设置访问用户
factory.setUsername(RabbitMQConfig.USERNAME);
factory.setPassword(RabbitMQConfig.PASSWORD);
try (
// 建立连接
Connection connection = factory.newConnection();
// 创建Channel消息通道
Channel channel = connection.createChannel();
) {
String msg = "阁下可是常山赵子龙";
channel.basicPublish(RabbitMQConfig.DIRECT_EXCHANGE, RabbitMQConfig.ROUTING_KEY, null, msg.getBytes("UTF-8"));
} catch (Exception e) {
e.printStackTrace();
}
}
}