RabbitMQ(一) 初识RabbitMQ
RabbitMQ是一个开源的消息代理和队列服务器,用来通过普通协议在完全不同的应用之间共享数据,RabbitMQ是使用Erlang语言来编写的,并且RabbitMQ是基于AMQP协议的。
哪些大厂在用RabbitMQ,为什么
- 滴滴、美团、头条
- 开源、性能优秀、稳定性保障
- 提供可靠性消息投递模式(confim)、返回模式(return)
- 与SpringAMQP完美结合、API丰富
- 集群模式丰富,表达式配置,HA模式,镜像队列模型
- 保证数据不丢失的前提做到高可靠性、可用性
什么是AMQP高级消息队列协议
AMQP:Advance Message Queuing Protocol
AMQP翻译:高级消息队列协议
AMQP定义:是具有现代特征的二进制协议。是一个提供统一消息服务的应用层标准高级队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。
AMQP协议模型
AMQP核心概念
Server:又称Broker,接受客户端的连接,实现AMQP实体服务
Connection:连接,应用程序与Broker的网络连接
Channel:网络信道,几乎所有的操作都在channel中进行,Channel是进行消息读写的通道。客户端可建立多个Channel,每个Channel代表一个会话任务。
Message:消息,服务器和应用程序之间传送的数据,由Properties和Body组成。Properties可以对消息进行修饰,比如消息的优先级,延迟等高级特性;Body则就是消息体内容。
Virtual host:虚拟地址,用于进行逻辑隔离,最上层的消息路由。一个Virtual Host里面可以有若干个Exchange和Queue,统一Virtual Host里面不能有相同名称的Exchange和Queue
Exchange:交换机,接收消息,根据路由键转发消息到绑定的队列
Binding:Exchange和Queue之间的虚拟连接,binding中可以包含routing key
Routing key:一个路由规则,虚拟机可用它来确定如何路由一个特定消息
Queue:也成为Mesage Queue,消息队列,保存并将它们转发给消费者
RabbitMQ的整体架构
RabbitMQ消息是如何流转的
RabbitMQ的安装与使用
提前准备:安装Linux必要依赖
下载RabbitMQ必须安装包(简单RPM安装)
配置文件修改
服务启动:rabbitmq-server start &
服务的停止:rabbitmqctl stop_app
管理插件:rabbitmq-plugins enable rabbitmq_management
准备: yum install build-essential openssl openssl-devel unixODBC unixODBC-devel make gcc gcc-c++ kernel-devel m4 ncurses-devel tk tc xz 下载: wget www.rabbitmq.com/releases/erlang/erlang-18.3-1.el7.centos.x86_64.rpm wget http://repo.iotti.biz/CentOS/7/x86_64/socat-1.7.3.2-5.el7.lux.x86_64.rpm wget www.rabbitmq.com/releases/rabbitmq-server/v3.6.5/rabbitmq-server-3.6.5-1.noarch.rpm 配置文件: vim /usr/lib/rabbitmq/lib/rabbitmq_server-3.6.5/ebin/rabbit.app 比如修改密码、配置等等,例如:loopback_users 中的 <<"guest">>,只保留guest 服务启动和停止: 启动 rabbitmq-server start & (&表示后台启动) 停止 rabbitmqctl app_stop 默认插件列表: rabbitmq-plugins list 管理插件:rabbitmq-plugins enable rabbitmq_management 访问地址:http://localost:15672/
rabbitmqctl --help 查询具体参数
快速入门-消息生产与消费
- ConnectionFactory:获取连接工厂
- Connection:一个连接
- Channel:数据通信信道,可发送和接收消息
- Queue:具体的消息存储队列
- Producer&Consumer生产者和消费者
生产者代码
需要引入
<dependency> <groupId>com.rabbitmq</groupId> <artifactId>amqp-client</artifactId> <version>3.6.5</version> </dependency>
import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; /** * 生产者 * * @author niugang */ public class Producer { public static void main(String[] args) throws Exception { //1 创建一个ConnectionFactory, 并进行配置 ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("localhost"); connectionFactory.setPort(5672); connectionFactory.setVirtualHost("/"); //2 通过连接工厂创建连接 Connection connection = connectionFactory.newConnection(); //3 通过connection创建一个Channel Channel channel = connection.createChannel(); //4 通过Channel发送数据 for (int i = 0; i < 15; i++) { String msg = "Hello RabbitMQ!"; //1 exchange 2 routingKey //exchange 为 空字符串 为走默认的exchange (AMQP default) 根据路由key 和消息队列 名称 是否匹完全匹配进行投递、 // 从控制台 Exchanges->(AMQP default) // The default exchange is implicitly bound to every queue, with a routing key equal to the queue name. It is not possible to explicitly bind to, or unbind from the default exchange. It also cannot be deleted. //默认交换被隐式地绑定到每个队列,路由键等于队列名称。无法显式绑定到默认交换器或从默认交换器取消绑定。它也不能被删除。 channel.basicPublish("", "test001", null, msg.getBytes()); } //5 记得要关闭相关的连接 channel.close(); connection.close(); } }
消费者代码
import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import com.rabbitmq.client.QueueingConsumer; import com.rabbitmq.client.QueueingConsumer.Delivery; import lombok.extern.slf4j.Slf4j; /** * 消费者 * * @author niugang */ @Slf4j public class Consumer { public static void main(String[] args) throws Exception { //1 创建一个ConnectionFactory, 并进行配置 ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("localhost"); connectionFactory.setPort(5672); connectionFactory.setVirtualHost("/"); //2 通过连接工厂创建连接 Connection connection = connectionFactory.newConnection(); //3 通过connection创建一个Channel Channel channel = connection.createChannel(); //4 声明(创建)一个队列 String queueName = "test001"; //exclusive 如果我们声明一个排他队列(仅限于此连接),则为true。 channel.queueDeclare(queueName, true, false, false, null); //5 创建消费者 QueueingConsumer queueingConsumer = new QueueingConsumer(channel); //6 设置Channel channel.basicConsume(queueName, true, queueingConsumer); while (true) { //7 获取下一个消息,方法阻塞 Delivery delivery = queueingConsumer.nextDelivery(); String msg = new String(delivery.getBody()); log.info("消费端: {}", msg); //封装一些发送参数 // Envelope envelope = delivery.getEnvelope(); } } }
Exchange交换机
Exchange接收消息,并根据路由键转发消息所绑定的队列
交换机属性
Name:交换机名称
Type:交换器类型direct、topic、fanout、headers
Durability:是否需要持久化,true为持久化
Auto Delete:当最后一个绑定到Exchange上的队列删除后,自动删除该Exchange
Internal:当前Exchange是否用于RabbitMQ内部使用,默认为false
Arguments:扩展参数,用于扩展AMQP协议自制定化使用
Direct Exchange
所有发送到Direct Exchange的消息被转发到RouteKey中指定的Queue
direct 直连模式 routing_key必须完全匹配
注意:Direct模式可以使用RabbitMQ自带的Exchange:fefault Exchange,所以不需要将Exchange进行任何绑定(binding)操作,消息传递,RouteKey必须完全匹配才会被队列接收,否则该消息会被抛弃
生产者代码
import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; /** * @author niugang */ public class Producer4DirectExchange { public static void main(String[] args) throws Exception { //1 创建ConnectionFactory ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("localhost"); connectionFactory.setPort(5672); connectionFactory.setVirtualHost("/"); //2 创建Connection Connection connection = connectionFactory.newConnection(); //3 创建Channel Channel channel = connection.createChannel(); //4 声明 String exchangeName = "test_direct_exchange"; String routingKey = "test.direct"; //5 发送 String msg = "Hello World RabbitMQ 4 Direct Exchange Message 111 ... "; channel.basicPublish(exchangeName, routingKey , null , msg.getBytes()); } }
消费者代码
import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import com.rabbitmq.client.QueueingConsumer; import com.rabbitmq.client.QueueingConsumer.Delivery; /** * @author niugang */ public class Consumer4DirectExchange { public static void main(String[] args) throws Exception { ConnectionFactory connectionFactory = new ConnectionFactory() ; connectionFactory.setHost("localhost"); connectionFactory.setPort(5672); connectionFactory.setVirtualHost("/"); connectionFactory.setAutomaticRecoveryEnabled(true); connectionFactory.setNetworkRecoveryInterval(3000); Connection connection = connectionFactory.newConnection(); Channel channel = connection.createChannel(); //4 声明 String exchangeName = "test_direct_exchange"; String exchangeType = "direct"; String queueName = "test_direct_queue"; String routingKey = "test.direct"; //表示声明了一个交换机 channel.exchangeDeclare(exchangeName, exchangeType, true, false, false, null); //表示声明了一个队列 channel.queueDeclare(queueName, false, false, false, null); //建立一个绑定关系: channel.queueBind(queueName, exchangeName, routingKey); //durable 是否持久化消息 QueueingConsumer consumer = new QueueingConsumer(channel); //参数:队列名称、是否自动ACK、Consumer channel.basicConsume(queueName, true, consumer); //循环获取消息 while(true){ //获取消息,如果没有消息,这一步将会一直阻塞 Delivery delivery = consumer.nextDelivery(); String msg = new String(delivery.getBody()); System.out.println("收到消息:" + msg); } } }
Topic Exchange
- 所有发送到Topic Exchange 的消息被转发到所有关心RouteKey指定Topic的Queue上
- Exchange将RouteKey和某Topic进行模糊匹配,此时队列需要绑定到一个Topic
注意:可以使用通配符进行模糊匹配
符号"#"匹配一个或多个词
符号"*"匹配不多不少一个词
例如“log.#”能够匹配到“log.info.oa”
例如“log.*“能够匹配到“log.error”
生产者代码
/** * @author niugang */ public class Producer4TopicExchange { public static void main(String[] args) throws Exception { //1 创建ConnectionFactory ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("localghost"); connectionFactory.setPort(5672); connectionFactory.setVirtualHost("/"); //2 创建Connection Connection connection = connectionFactory.newConnection(); //3 创建Channel Channel channel = connection.createChannel(); //4 声明 String exchangeName = "test_topic_exchange"; String routingKey1 = "user.save"; String routingKey2 = "user.update"; String routingKey3 = "user.delete.abc"; //5 发送 String msg = "Hello World RabbitMQ 4 Topic Exchange Message ..."; channel.basicPublish(exchangeName, routingKey1 , null , msg.getBytes()); channel.basicPublish(exchangeName, routingKey2 , null , msg.getBytes()); channel.basicPublish(exchangeName, routingKey3 , null , msg.getBytes()); channel.close(); connection.close(); } }
消费者代码
/** * @author niugang */ public class Consumer4TopicExchange { public static void main(String[] args) throws Exception { ConnectionFactory connectionFactory = new ConnectionFactory() ; connectionFactory.setHost("localhost"); connectionFactory.setPort(5672); connectionFactory.setVirtualHost("/"); connectionFactory.setAutomaticRecoveryEnabled(true); connectionFactory.setNetworkRecoveryInterval(3000); Connection connection = connectionFactory.newConnection(); Channel channel = connection.createChannel(); //4 声明 String exchangeName = "test_topic_exchange"; String exchangeType = "topic"; String queueName = "test_topic_queue"; //String routingKey = "user.*"; String routingKey = "user.#"; // 1 声明交换机 channel.exchangeDeclare(exchangeName, exchangeType, true, false, false, null); // 2 声明队列 channel.queueDeclare(queueName, false, false, false, null); // 3 建立交换机和队列的绑定关系: channel.queueBind(queueName, exchangeName, routingKey); //durable 是否持久化消息 QueueingConsumer consumer = new QueueingConsumer(channel); //参数:队列名称、是否自动ACK、Consumer channel.basicConsume(queueName, true, consumer); //循环获取消息 while(true){ //获取消息,如果没有消息,这一步将会一直阻塞 Delivery delivery = consumer.nextDelivery(); String msg = new String(delivery.getBody()); System.out.println("收到消息:" + msg); } } }
Fanout Exchange
不处理路由键。只需要简单的将队列绑定到交换机上
发送到交换器的消息都会被转发到与交换机绑定的所有队列上
Fanout交换机转发消息最快
fanout类型的exchange不会和routing_key产生任何关系(不会走路由键)
生产者代码
/** * @author niugang */ public class Producer4FanoutExchange { public static void main(String[] args) throws Exception { //1 创建ConnectionFactory ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("localhost"); connectionFactory.setPort(5672); connectionFactory.setVirtualHost("/"); //2 创建Connection Connection connection = connectionFactory.newConnection(); //3 创建Channel Channel channel = connection.createChannel(); //4 声明 String exchangeName = "test_fanout_exchange"; //5 发送 for(int i = 0; i < 10; i ++) { String msg = "Hello World RabbitMQ 4 FANOUT Exchange Message ..."; channel.basicPublish(exchangeName, "", null , msg.getBytes()); } channel.close(); connection.close(); } }
消费者代码
/** * @author niugang */ public class Consumer4FanoutExchange { public static void main(String[] args) throws Exception { ConnectionFactory connectionFactory = new ConnectionFactory() ; connectionFactory.setHost("localhost"); connectionFactory.setPort(5672); connectionFactory.setVirtualHost("/"); connectionFactory.setAutomaticRecoveryEnabled(true); connectionFactory.setNetworkRecoveryInterval(3000); Connection connection = connectionFactory.newConnection(); Channel channel = connection.createChannel(); //4 声明 String exchangeName = "test_fanout_exchange"; String exchangeType = "fanout"; String queueName = "test_fanout_queue"; String routingKey = ""; //不设置路由键 channel.exchangeDeclare(exchangeName, exchangeType, true, false, false, null); channel.queueDeclare(queueName, false, false, false, null); channel.queueBind(queueName, exchangeName, routingKey); //durable 是否持久化消息 QueueingConsumer consumer = new QueueingConsumer(channel); //参数:队列名称、是否自动ACK、Consumer channel.basicConsume(queueName, true, consumer); //循环获取消息 while(true){ //获取消息,如果没有消息,这一步将会一直阻塞 Delivery delivery = consumer.nextDelivery(); String msg = new String(delivery.getBody()); System.out.println("收到消息:" + msg); } } }
Binding-绑定
- Exchange和Exchange、Queue之间的连接关系
- Binding中可以包含RoutingKey或者参数
Queue-消息队列
- 消息队列,实际存储消息数据
- Durability:是否持久化,Durable : 是,Transient :否
- Auto delete:如选yes,代码当最后一个监听被移除之后,该Queue会自动被删除
Message-消息
服务器和应用之间传送的数据
本质上就是一段数据,由Properties和Payload(Body)组成
常用属性:delivery mode 、headers
delivery mode 为2 标识持久化消息
其他属性
- content_type、content_encoding、priority
- correlation_id、reply_to、expiration、message_id
- timestamp、type、user_id、app_id、cluster_id
Virtual host(虚拟主机)
- 虚拟地址,用于进行逻辑隔离,最上层的消息路由
- 一个Vitual Host里面可以有若干个Exchange和Queue
- 同一个Virtual Host里面不能有相同名称的Exchange和Queue