RabbitMQ的基本使用
RabbitMQ 与 AMQP 遵循相同的模型架构,其架构示例图如下
1.重要概念
1.Publisher:消息生产者,就是投递消息的程序。发布者 (或称为生产者) 负责生产消息并将其投递到指定的交换器上
2.Message:消息由消息头和消息体组成,消息头用于存储与消息相关的元数据:如目标交换器的名字 (exchange_name)路由键 (RountingKey)和其他可选配置 (properties) 信息。消息体为实际需要传递的数据
3.Exchange:交换器负责接收来自生产者的消息,并将消息路由到一个或者多个队列中,如果路由不到,则返回给生产者或者直接丢弃,这取决于交换器的 mandatory 属性
- 当 mandatory 为 true 时:如果交换器无法根据自身类型和路由键找到一个符合条件的队列,则会将该消息返回给生产者
- 当 mandatory 为 false 时:如果交换器无法根据自身类型和路由键找到一个符合条件的队列,则会直接丢弃该消息
4.BindingKey:交换器与队列通过 BindingKey 建立绑定关系
5.Routingkey:基于交换器类型的规则相匹配时,消息被路由到对应的队列中,生产者将消息发给交换器的时候,一般会指定一个 RountingKey,用来指定这个消息的路由规则,当 RountingKey 与 BindingKey
6.Queue:消息队列载体,每个消息都会被投入到一个或多个队列。用于存储路由过来的消息,多个消费者可以订阅同一个消息队列,此时队列会将收到的消息将以轮询 (round-robin)的方式分发给所有消费者,即每条消息只会发送给一个消费者,不会出现一条消息被多个消费者重复消费的情况
7.Consumer:消息消费者,就是接受消息的程序。消费者订阅感兴趣的队列,并负责消费存储在队列中的消息。为了保证消息能够从队列可靠地到达消费者,RabbitMQ 提供了消息确认机制 (messageacknowledgement),并通过 autoAck 参数来进行控制
- 当 autoAck 为 true 时:此时消息发送出去 (写入TCP套接字) 后就认为消费成功,而不管消费者是否真正消费到这些消息。当 TCP 连接或 channel 因意外而关闭,或者消费者在消费过程之中意外宕机时,对应的消息就丢失。因此这种模式可以提高吞吐量,但会存在数据丢失的风险
- 当 autoAck 为 false 时:需要用户在数据处理完成后进行手动确认,只有用户手动确认完成后,RabbitMQ 才认为这条消息已经被成功处理,这可以保证数据的可靠性投递,但会降低系统的吞吐量
8.Connection:用于传递消息的 TCP 连接
9.Channel:消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务。RabbitMQ 采用类似 NIO (非阻塞式 IO ) 的设计,通过 Channel 来复用 TCP 连接,并确保每个 Channel的隔离性,就像是拥有独立的 Connection 连接。当数据流量不是很大时,采用连接复用技术可以避免创建过多的 TCP 连接而导致昂贵的性能开销
10.Virtual Host:虚拟主机,一个broker里可以开设多个vhost,用作不同用户的权限分离。RabbitMQ 通过虚拟主机来实现逻辑分组和资源隔离,一个虚拟主机就是一个小型的 RabbitMQ服务器,拥有独立的队列、交换器和绑定关系。用户可以按照不同业务场景建立不同的虚拟主机,虚拟主机之间是完全独立的,你无法将 vhost1 上的交换器与vhost2 上的队列进行绑定,这可以极大的保证业务之间的隔离性和数据安全,默认的虚拟主机名为 /
11.Broker:简单来说就是消息队列服务器实体
2.如何使用RabbitMQ发送消息
exchange接收到消息后,就根据消息的key和已经设置的binding,进行消息路由,将消息投递到一个或多个队列里
- 客户端连接到消息队列服务器,打开一个channel
- 客户端声明一个exchange,并设置相关属性
- 客户端声明一个queue,并设置相关属性
- 客户端使用routing key,在exchange和queue之间建立好绑定关系
- 客户端投递消息到exchange
代码如下:
1.导入坐标
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> <version>2.7.10</version> </dependency>
2,生产者
public class RabbitProducerFirst { public static void main(String[] args) throws IOException, TimeoutException, InterruptedException { String exchange_name = "first_exchange"; String route_key = "first_rabbitMQ"; //创建连接工厂,连接RabbitMQ ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("192.168.44.137");//端口号、用户名、密码可以使用默认的 connectionFactory.setPort(5672); connectionFactory.setUsername("root"); connectionFactory.setPassword("root"); //创建连接 Connection connection = connectionFactory.newConnection(); //创建信道 Channel channel = connection.createChannel(); //在信道中设置交换器 channel.exchangeDeclare(exchange_name, BuiltinExchangeType.DIRECT); //交换器和队列绑定放到消费者进行 for (int i = 0; i < 10; i++) { String message = "你好 rabbitMQ ,我 是 " + i; //消息进行发送 channel.basicPublish(exchange_name, route_key, false, null, message.getBytes("UTF-8")); System.out.println("rabbitMQ 发送消息 key:【" + route_key + "】,内容【" + message + "】"); Thread.sleep(100); } //关闭信道 channel.close(); //关闭连接 connection.close(); } }
3.消费者
public class RabbitConsumerFirst { public static void main(String[] args) throws IOException, TimeoutException { String exchange_name = "first_exchange"; String queue_name = "first_queue"; String binding_key = "first_rabbitMQ"; //创建连接工厂,连接RabbitMQ ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("192.168.44.137");//端口号、用户名、密码可以使用默认的 connectionFactory.setPort(5672); connectionFactory.setUsername("root"); connectionFactory.setPassword("root"); //创建连接 Connection connection = connectionFactory.newConnection(); //创建信道 Channel channel = connection.createChannel(); //在信道中设置交换器 channel.exchangeDeclare(exchange_name, BuiltinExchangeType.DIRECT); //声明队列 channel.queueDeclare(queue_name, false, false, false, null); //交换器和队列绑定 channel.queueBind(queue_name, exchange_name, binding_key); //绑定多个key //channel.queueBind(DIRECT_QUEUE, EXCHANGE_NAME, "key3"); System.out.println("等待 message....."); //声明消费者 Consumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { String message = new String(body, "utf-8"); System.out.println("rabbitMQ 接受消息 key:【" + envelope.getRoutingKey() + "】;内容:【" + message + "】"); } }; //消费者在指定的对队列上消费 // true 代表是否自动提交 // queue_name 消费数据的队列 channel.basicConsume(queue_name, true, consumer); } }
测试:先启动消费者,在启动生产者
3.常用交换器
Exchange type(交换器类型) | Default pre-declared names(预声明默认名称) |
---|---|
Direct exchange(直连交换器) | (Empty string) and amq.direct |
Fanout exchange(扇形交换器) | amq.fanout |
Topic exchange(主题交换器) | amq.topic |
Headers exchange(头信息交换器) | amq.match (and amq.headers in RabbitMQ) |
java代码中交换机的枚举类型如下
1.直连交换机(如果路由键完全匹配,消息就被投递到相应的队列):直连型交换机(direct exchange)是根据消息携带的路由键(routing key)将消息投递给对应绑定键的队列。直连交换机是一种带路由功能的交换机,一个队列会和一个交换机绑定,除此之外再绑定一个routing_key,当消息被发送的时候,需要指定一个binding_key,这个消息被送达交换机的时候,就会被这个交换机送到指定的队列里面去,同样的一个binding_key也是支持应用到多个队列中的
直连型交换机图例:
直连交换机的代码在上面给出,这里就不写了
注意:同一个交换机下消费者多个队列与消费者多个绑定key的情况
2.扇型交换机(如果交换器收到消息,将会广播到所有绑定的队列上):扇型交换机(fanout exchange)将消息路由给绑定到它身上的所有队列,而不理会绑定的路由键。如果 N 个队列绑定到某个扇型交换机上,当有消息发送给此扇型交换机时,交换机会将消息的拷贝分别发送给这所有的 N 个队列。扇型用来交换机处理消息的广播路由(broadcast routing)
因为扇型交换机投递消息的拷贝到所有绑定到它的队列,所以他的应用案例都极其相似:
- 大规模多用户在线(MMO)游戏可以使用它来处理排行榜更新等全局事件
- 体育新闻网站可以用它来近乎实时地将比分更新分发给移动客户端
- 分发系统使用它来广播各种状态和配置更新
- 在群聊的时候,它被用来分发消息给参与群聊的用户
扇型交换机图例:
3.主题交换机(可以使来自不同源头的消息能够到达同一个队列,使用topic交换器时,可以使用通配符):基于消息的 routing key 与绑定到该交换器的队列的 pattern 进行匹配,路由消息到一个或多个队列,常用于复杂的发布/订阅场景,当出现多消费者/应用的场景,消费者选择性地接收消息时,应该考虑使用 topic exchange
-
主题交换机图例
-
约束条件:
- binding key 中可以存在两种特殊字符 “” 与“#”,用于做模糊匹配,其中 “” 用于匹配一个单词,“#”用于匹配多个单词(可以是零个)
- routing key 为一个句点号 “.” 分隔的字符串(我们将被句点号 “. ” 分隔开的每一段独立的字符串称为一个单词),如“stock.usd.nyse”、“nyse.vmw”、“quick.orange.rabbit”,binding key 与 routing key 一样也是句点号 “.” 分隔的字符串
-
使用案例
- 分发有关于特定地理位置的数据,例如销售点
- 由多个工作者(workers)完成的后台任务,每个工作者负责处理某些特定的任务
- 股票价格更新(以及其他类型的金融数据更新)
- 涉及到分类或者标签的新闻更新(例如,针对特定的运动项目或者队伍)
- 云端的不同种类服务的协调
- 分布式架构 / 基于系统的软件封装,其中每个构建者仅能处理一个特定的架构或者系统
4.头交换机(headers 类型的 Exchange 不依赖于 routing key 与 binding key 的匹配规则来路由消息,而是根据发送的消息内容中的 headers 属性进行匹配):头交换机可以视为直连交换机的另一种表现形式。但直连交换机的路由键必须是一个字符串,而头属性值则没有这个约束,它们甚至可以是整数或者哈希值(字典)等。灵活性更强(但实际上我们很少用到头交换机)
- 工作流程:
- 绑定一个队列到头交换机上时,会同时绑定多个用于匹配的头(header)
- 传来的消息会携带header,以及会有一个 “x-match” 参数。当 “x-match” 设置为 “any” 时,消息头的任意一个值被匹配就可以满足条件,而当 “x-match” 设置为 “all” 的时候,就需要消息头的所有值都匹配成功
4.小结
类型名称 | 路由规则 |
---|---|
Default | 自动命名的直交换机 |
Direct | 把消息路由到BindingKey和RoutingKey完全匹配的队列中,Routing Key==Binding Key,严格匹配 |
Fanout | 发送到该交换机的消息都会路由到与该交换机绑定的所有队列上,可以用来做广播 |
Topic | topic和direct类似,也是将消息发送到RoutingKey和BindingKey相匹配的队列中,只不过可以模糊匹配 |
Headers | 根据发送的消息内容中的 headers 属性进行匹配,性能差,基本不会使用 |