rabbitMQ快速入门
rabbitMQ快速入门
一、消息队列简介
MQ:全称Message Queue,消息队列,是一种进程间通信或同一进程的不同线程间的通信方式,软件的贮列用来处理一系列的输入,通常是来自用户。消息队列提供了异步的通信协议,每个贮列中的记录包含详细说明的数据,包含发生的时间、输入设备的种类等,也就说消息发送者和接收者不需要同时与消息队列交互。消息会保存在队列中,直到接收这取回它。
- 实现
消息队列常常保存在链表结构中,拥有权限的进程可以向消息队列中写入或读取消息。目前,有很多消息队列有很多开源的实现,包括JBOSS Messaging、JORAM、Apache、ActiveMQ等。
当前使用较多的消息队列有RabbitMQ、RocketMQ、ActiveMQ、Kafka、ZeroMQ、MetaMq等,而部分数据库如redis、mysql以及phxsql也可以实现消息队列的功能。
- 特点
MQ是消费者-生产者模型的一个典型的代表,一端往消息队列中不断写入消息,而另一端则可以读取或者订阅队列中的消息。MQ和JMS类似,但不同的是JMS是SUN JAVA消息中间件服务的一个标准和API定义,而MQ则是遵循了AMQP协议的具体实现和产品。
注意:1、AMQP ,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。
2. JMS ,Java消息服务(Java Message Service)应用程序接口,是一个Java平台中关于面向消息中间件的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。 Java消息服务是一个与具体平台无关的API,绝大多数MOM提供商都对JMS提供支持。常见的消息队列,大部分都实现了JMS API,如 ActiveMQ , Redis 以及 RabbitMQ 等。
3、优点:程序解耦低耦合度、异步处理、流量削峰
解耦:在不改变原业务代码情况下,消息中间件可以自动处理消息。
异步请求:生产者和消费者进行通信时,可以同时多个消费者同时进行消息连接订阅。
流量削峰:消息队列把访问数据库的并发请求,存入rabbitmq信道中进行阻塞,数据库根据性能大小接收请求,处理完再从信道中获取请求处理,避免并发请求让数据库宕机。
- 为什么使用rabbitmq?
rabbitmq遵循AMQP协议以及JMS接口,开源、免费,实现了面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。
rabbitmq,服务器是用erlang语言编写的,支持多种客户端,如:python、java、Ruby、.NET、java、c、JMS等,支持Ajax,用于在分布式系统中存储转发消息、在易用性、扩展性、高可用性等方面表现不俗。
总结:基于AMQP协议、高并发(服务器可以接收最大任务量)、高可用(单位时间内服务器可以正常工作的时间比例)、高
性能(单位时间内服务器可以处理的任务数)、支持插件、支持多语言。
二、安装RabbitMQ
前面讲了RabbitMQ是基于Erlang语言开发的,所以安装前要先安装Erlang语言环境,Erlang语言版本要与RabbitMQ版本一一对应,其他详细见官网:
https://www.rabbitmq.com/which-erlang.html#eol-series
。
首先在githubhttps://github.com/rabbitmq/erlang-rpm/releases下载好对应版本的rpm文件,并上传到linux虚拟机上。注意:下载文件版本时看清楚centos版本。
cd /usr/local/rabbitmq/ #进行存放rpm文件目录下
yum insatll -y *****.rpm #安装文件
在官网https://rabbitmq.com/install-rpm.html#downloads下载rabbitmq-server.**.rpm上传到服务器。
cd /usr/local/rabbitmq/ #进行存放rpm文件目录下
yum insatll -y *****.rpm #安装文件
进入/etc/rabbitmq/ 创建配置文件,开启远程IP访问。
vim /etc/rabbitmq/rabbitmq.config #创建配置文件
- 配置信息如下:
[{rabbit, [{loopback_users, []}]}]. #表示所有的Ip都可以访问rabbitmq可视化插件界面。
- 开启rabbitmq服务,查看所有插件并开启可视化管理插件
systemctl start rabbitmq-server #开启服务
systemctl stop rabbitmq-server #关闭服务
systemctl status rabbitmq-server #查看服务
rabbitmq-plugins list #查看所有插件
rabbitmq-plugins enable [插件名] #开启插件
rabbitmq-plugins disable [插件名] #关闭插件
- 开放端口
firewalld-cmd --add--port=5672/tcp --permanent #开放客户端连接端口
firewalld-cmd --add--port=15672/tcp --permanent #开放可视化插件连接端口
firewalld-cmd --add--port=25672/tcp --permanent #开放集群搭建端口
firewalld-cmd reload #重启服务
- 访问可视化界面
http://192.168.147.110:15672
,默认访问用户名:guest 密码:guest,一般我们要新增用户名和密码,授权,用新增的用户名密码访问。
角色权限讲解
RabbitMQ角色分类:
none:不能访问 management plugin(管理插件)
impersonator:演员???
management:
用户可以通过AMQP做的任何事外加:
列出自己可以通过AMQP登入的virtual hosts
查看自己的virtual hosts中的queues, exchanges 和 bindings
查看和关闭自己的channels 和 connections
查看有关自己的virtual hosts的“全局”的统计信息,包含其他用户在这些virtual hosts中的活动。
policymaker
management可以做的任何事外加:
查看、创建和删除自己的virtual hosts所属的policies和parameters
monitoring
management可以做的任何事外加:
列出所有virtual hosts,包括他们不能登录的virtual hosts
查看其他用户的connections和channels
查看节点级别的数据如clustering和memory使用情况
查看真正的关于所有virtual hosts的全局的统计信息
administrator
policymaker和monitoring可以做的任何事外加:
创建和删除virtual hosts
查看、创建和删除users
查看创建和删除permissions
关闭其他用户的connections
RabbitMQ权限控制
默认virtual host:"/"
默认用户:guest
guest具有 "/" 上的全部权限,仅能有localhost访问RabbitMQ包括Plugin,建议删除或更改密码。可
通过将配置文件中 loopback_users 置空来取消其本地访问的限制: [{rabbit,[{loopback_users, []}]}]
用户仅能对其所能访问的virtual hosts中的资源进行操作。这里的资源指的是virtual hosts中的exchanges、queues等,操作包括对资源进行配置、写、读。配置权限可创建、删除资源并修改资源的行为,写权限可向资源发送消息,读权限从资源获取消息。
比如:
exchange和queue的declare与delete分别需要exchange和queue上的配置权限
exchange的bind与unbind需要exchange的读写权限
queue的bind与unbind需要queue写权限exchange的读权限
发消息(publish)需exchange的写权限
获取或清除(get、consume、purge)消息需queue的读权限
对何种资源具有配置、写、读的权限通过正则表达式来匹配,具体命令如下: set_permissions
[-p <vhostpath>] <user> <conf> <write> <read> ,其中, <conf> <write> <read> 的位
置分别用正则表达式来匹配特定的资源,如 '^(amq\.gen.*|amq\.default)$' 可以匹配server生成的和默认的exchange, '^$' 不匹配任何资源
要注意的是RabbitMQ会缓存每个connection或channel的权限验证结果、因此权限发生变化后需要重连
才能生效。
三、专业术语
生产者(pudurcer)
Producing意思不仅仅是发送消息。发送消息的程序叫做producer生产者。
消费者(consumer)
Consuming 的意思和接收类似。等待接收消息的程序叫做消费者。
ConnectionFactory、Connection、Channel
ConnectionFactory 、 Connection 、 Channel 都是RabbitMQ对外提供的API中最基本的对象。
Connection 是RabbitMQ的 socket 连接,它封装了 socket 协议相关部分逻辑。
ConnectionFactory 为Connection的制造工厂。
Channel 是我们与RabbitMQ打交道的最重要的一个接口,我们大部分的业务操作是在Channel这个接口中完成的,包括定义 Queue 、定义 Exchange 、绑定 Queue 与 Exchange 、发布消息等。
Message acknowledgment
在实际应用中,可能会发生消费者收到 Queue 中的消息,但没有处理完成就宕机(或出现其他意外)的情况,这种情况下就可能会导致消息丢失。为了避免这种情况发生,我们可以要求消费者在消费完消息后发送一个回执给RabbitMQ,RabbitMQ收到消息回执( Message acknowledgment )后才将该消息从 Queue 中移除;如果RabbitMQ没有收到回执并检测到消费者的RabbitMQ连接断开,则RabbitMQ会将该消息发送给其他消费者(如果存在多个消费者)进行处理。这里不存在timeout概念,一个消费者处理消息时间再长也不会导致该消息被发送给其他消费者,除非它的RabbitMQ连接断开。
这里会产生另外一个问题,如果我们的开发人员在处理完业务逻辑后,忘记发送回执给RabbitMQ,这将会导致严重的bug—— Queue 中堆积的消息会越来越多;消费者重启后会重复消费这些消息并重复执行业务逻辑...
Message durability
如果我们希望即使在RabbitMQ服务重启的情况下,也不会丢失消息,我们可以将Queue与Message都设置为可持久化的(durable),这样可以保证绝大部分情况下我们的RabbitMQ消息不会丢失。但依然解决不了小概率丢失事件的发生(比如RabbitMQ服务器已经接收到生产者的消息,但还没来得及持久化该消息时RabbitMQ服务器就断电了),如果我们需要对这种小概率事件也要管理起来,那么我们要用到事务。
Prefetch count (要设置手动应答)
前面我们讲到如果有多个消费者同时订阅同一个 Queue 中的消息, Queue 中的消息会被平摊给多个消费者。这时如果每个消息的处理时间不同,就有可能会导致某些消费者一直在忙,而另外一些消费者很快就处理完手头工作并一直空闲的情况。我们可以通过设置 prefetchCount 来限制 Queue 每次发送给每个消费者的消息数,比如我们设prefetchCount=1,则 Queue 每次给每个消费者发送一条消息;
消费者处理完这条消息后 Queue 会再给该消费者发送一条消息。
Exchange(交换机)
生产者将消息投递到 Queue 中,实际上这在RabbitMQ中这种事情永远都不会发生。实际的情况是,生产者将消息发送到Exchange(交换器,下图中的X),由Exchange将消息路由到一个或多个 Queue 中(或者丢弃)。
routing key (路由key)
生产者在将消息发送给Exchange的时候,一般会指定一个 routing key ,来指定这个消息的路由规则,而这个routing key 需要与 Exchange Type 及 binding key 联合使用才能最终生效。
在 Exchange Type 与 binding key 固定的情况下(在正常使用时一般这些内容都是固定配置好的),我们的生产者就可以在发送消息给Exchange时,通过指定 routing key 来决定消息流向哪里。
RabbitMQ为 routing key 设定的长度限制为255 bytes。
Binding (将队列与路由绑定) 消费者进行绑定并消费消息。
RabbitMQ中通过Binding将Exchange与Queue关联起来,这样RabbitMQ就知道如何正确地将消息路由到指定的Queue了。
在绑定(Binding)Exchange与 Queue 的同时,一般会指定一个 binding key ;消费者将消息发送给Exchange时,一般会指定一个 routing key ;当 binding key 与 routing key 相匹配时,消息将会被路由到对应的Queue中。这个将在Exchange Types章节会列举实际的例子加以说明。
在绑定多个Queue到同一个Exchange的时候,这些Binding允许使用相同的 binding key 。
binding key 并不是在所有情况下都生效,它依赖于Exchange Type,比如 fanout 类型的Exchange就会无视 binding key ,而是将消息路由到所有绑定到该Exchange的Queue。
Exchange Types
RabbitMQ常用的Exchange Type有fanout、direct、topic、headers四种,(AMQP规范里还提到两种Exchange Type),分别为System与自定义)
- fanout(广播模式)
广播模式交换机:生活中常见的微信发布与订阅就是这种模式,生产者发送消息到交换机中,交换机绑定不同消费者接收队列,多台客户端异步接收不同队列中的消息进行处理,可以保证多个客户端接收数据的完整性。
- direct
路由模式交换机:生产者发送消息到交换机,交换机、队列以及路由key进行绑定,生产者发送到不同路由key的队列中,消费者根据路由key从不同的队列中获取数据进行消费。
- topic
主题模式交换机:在路由模式的基础上,消费者可以通过通配符设置路由key同时匹配多个队列中的消息,接收的消息更宽广。
*表示匹配一个单词
#表示匹配0个或多个单词
比如:fox.rabbit.layze------>*.rabbit.*或者#.layze
- headers
headers类型的Exchange不依赖于 routing key 与 binding key 的匹配规则来路由消息,而是根据
发送的消息内容中的headers属性进行匹配。
在绑定Queue与Exchange时指定一组键值对;当消息发送到Exchange时,RabbitMQ会取到该消息的headers(也是一个键值对的形式),对比其中的键值对是否完全匹配Queue与Exchange绑定时指定的键值对;如果完全匹配则消息会路由到该Queue,否则不会路由到该Queue。
该类型的Exchange目前用的不多(不过也应该很有用武之地),因此不做重点介绍。
- RPC(远程调用过程)
rabbitmq本身是基于异步请求的,消费者发送一个消息到队列,不知道是否发送成功,也不知道消费者是否成功消费。在实际应用场景,我们可能需要同步请求的应用,需要处理完某个请求响应才能执行下一个请求。
客户端发送请求(消息)时,在消息的属性( MessageProperties ,在AMQP协议中定义了14种properties,这些属性会随着消息一起发送)中设置两个值 replyTo (一个Queue名称,用于告诉服务器处理完成后将通知我的消息发送到这个Queue中)和 correlationId (此次请求的标识号,服务器处理完成后需要将此属性返还,客户端将根据这个id了解哪条请求被成功执行了或执行失败)服务器端收到消息并处理服务器端处理完消息后,将生成一条应答消息到 replyTo 指定的Queue,同时带上correlationId 属性客户端之前已订阅 replyTo 指定的Queue,从中收到服务器的应答消息后,根据其中的correlationId 属性分析哪条请求被执行了,根据执行结果进行后续业务处理。
简单模式队列
在这部分的使用指南中,我们要用 Java 写两个程序;一个是生产者,他发送一个消息,另一个是消费者,它接收消息,并且把消息打印出来。我们将会忽略一些Java API 的细节,而是将注意力主要放在我们将要做的这件事上,这件事就是发送一个 "Hello World" 消息。
工作模式队列
工作模式队列是在简单模式队列上,解决消费者性能不足,处理不及时,造成阻塞的情况,它有两个模式:轮询、公平分发
轮询:客户端轮流执行消息,进行周期性处理,缺点容易造成性能高的客户端处理太快进行等待,造成性能浪费。
公平分发:能者多劳,保证每个客户端都在运行处理消息,解决了性能浪费。
四、java-client搭建项目
简单队列模式
- 导入依赖
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.19.0</version>
</dependency>
- 消费者(一)
public class Recv {
//定义队列名称
private final static String QUEUE_NAME = "hello";
public static void main(String[] argv) throws Exception {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//工厂设置
factory.setHost("192.168.147.110");
factory.setVirtualHost("/shop");
factory.setUsername("zwf");
factory.setPassword("123456789");
//根据工厂创建连接
Connection connection = factory.newConnection();
//根据连接创建信道
Channel channel = connection.createChannel();
//绑定队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [x] Received '" + message + "'");
};
/**
* 消费队列
* 1.队列名称
* 2.自动确认
* 3.收到的消息的实体类
*/
channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> {
});
}
- 消费者(二)
private final static String QUEUE_NAME = "work_fair";
public static void main(String[] argv) throws Exception {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//工厂设置
factory.setHost("192.168.10.100");
factory.setVirtualHost("/shop");
factory.setUsername("shop");
factory.setPassword("shop");
//根据工厂创建连接
Connection connection = factory.newConnection();
//根据连接创建信道
Channel channel = connection.createChannel();
//绑定队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//限制每次只发送一条,消费者处理完在发送下一条
int prefetchCount = 1;
channel.basicQos(prefetchCount);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [x] Received '" + message + "'");
/**
* 手动确认
* 1. 消息实体里的唯一标识
* 2. 是否多条确认
*/
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
};
/**
* 消费队列
* 1.队列名称
* 2.自动确认
* 3.收到的消息的实体类
*/
channel.basicConsume(QUEUE_NAME, false, deliverCallback, consumerTag -> {
});
}
- 生产者
private final static String QUEUE_NAME = "hello";
public static void main(String[] argv) throws Exception {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//工厂设置
factory.setHost("192.168.147.110");
factory.setVirtualHost("/shop");
factory.setUsername("zwf");
factory.setPassword("123456789");
//根据连接工厂创建连接
try (Connection connection = factory.newConnection();
//根据连接创建信道
Channel channel = connection.createChannel()) {
/**
* 声明队列
* 第一个参数queue:队列名称
* 第二个参数durable:是否持久化
* 第三个参数Exclusive:排他队列,如果一个队列被声明为排他队列,该队列仅对首次声明它的连接可 见,并在连接断开时自动删除。
* 这里需要注意三点:
* 1. 排他队列是基于连接可见的,同一连接的不同通道是可以同时访问同一个连接创建的排他队列的。
* 2. "首次",如果一个连接已经声明了一个排他队列,其他连接是不允许建立同名的排他队列的,这个与普通队列不同。
* 3. 即使该队列是持久化的,一旦连接关闭或者客户端退出,该排他队列都会被自动删除的。
* 这种队列适用于只限于一个客户端发送读取消息的应用场景。
* 第四个参数Auto-delete:自动删除,如果该队列没有任何订阅的消费者的话,该队列会被自动删除。
* 这种队列适用于临时队列。
*/
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
String message = "Hello World!";
//发送消息
channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
System.out.println(" [x] Sent '" + message + "'");
}
}
工作队列模式 (轮询、公平分发模式)
- 消费者(一) 公平分发
private final static String QUEUE_NAME = "work_fair";
public static void main(String[] argv) throws Exception {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//工厂设置
factory.setHost("192.168.10.100");
factory.setVirtualHost("/shop");
factory.setUsername("shop");
factory.setPassword("shop");
//根据工厂创建连接
Connection connection = factory.newConnection();
//根据连接创建信道
Channel channel = connection.createChannel();
//启动公平分发
// channel.BasicQs(1);
//绑定队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//限制每次只发送一条,消费者处理完在发送下一条
int prefetchCount = 1;
channel.basicQos(prefetchCount);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [x] Received '" + message + "'");
/**
* 手动确认
* 1. 消息实体里的唯一标识
* 2. 是否多条确认
*/
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
};
/**
* 消费队列
* 1.队列名称
* 2.自动确认
* 3.收到的消息的实体类
*/
channel.basicConsume(QUEUE_NAME, false, deliverCallback, consumerTag -> {
});
}
- 消费者(二) 公平分发
private final static String QUEUE_NAME = "work_fair";
public static void main(String[] argv) throws Exception {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//工厂设置
factory.setHost("192.168.10.100");
factory.setVirtualHost("/shop");
factory.setUsername("shop");
factory.setPassword("shop");
//根据工厂创建连接
Connection connection = factory.newConnection();
//根据连接创建信道
Channel channel = connection.createChannel();
//绑定队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//限制每次只发送一条,消费者处理完在发送下一条
int prefetchCount = 1;
channel.basicQos(prefetchCount);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [x] Received '" + message + "'");
/**
* 手动确认
* 1. 消息实体里的唯一标识
* 2. 是否多条确认
*/
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
};
/**
* 消费队列
* 1.队列名称
* 2.自动确认
* 3.收到的消息的实体类
*/
channel.basicConsume(QUEUE_NAME, false, deliverCallback, consumerTag -> {
});
- 生产者
private final static String QUEUE_NAME = "work_fair";
public static void main(String[] argv) throws Exception {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//工厂设置
factory.setHost("192.168.10.100");
factory.setVirtualHost("/shop");
factory.setUsername("shop");
factory.setPassword("shop");
//根据连接工厂创建连接
try (Connection connection = factory.newConnection();
//根据连接创建信道
Channel channel = connection.createChannel()) {
/**
* 声明队列
* 第一个参数queue:队列名称
* 第二个参数durable:是否持久化
* 第三个参数Exclusive:排他队列,如果一个队列被声明为排他队列,该队列仅对首次声明它的连接可见,并在连接断开时自动删除。
* 这里需要注意三点:
* 1. 排他队列是基于连接可见的,同一连接的不同通道是可以同时访问同一个连接创建的排他队列的。
* 2. "首次",如果一个连接已经声明了一个排他队列,其他连接是不允许建立同名的排他队列的,这个与普通队列不同。
* 3. 即使该队列是持久化的,一旦连接关闭或者客户端退出,该排他队列都会被自动删除的。
* 这种队列适用于只限于一个客户端发送读取消息的应用场景。
* 第四个参数Auto-delete:自动删除,如果该队列没有任何订阅的消费者的话,该队列会被自动删除。
* 这种队列适用于临时队列。
*/
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
for (int i = 0; i < 20; i++) {
String message = "Hello World!" + i;
//发送消息
channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
System.out.println(" [x] Sent '" + message + "'");
}
}
}
广播模式(使用交换机)
- 生产者
//定义交换机名称
private final static String EXCHANGE_NAME = "exchange_fanout";
public static void main(String[] argv) throws Exception {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//工厂设置
factory.setHost("192.168.10.100");
factory.setVirtualHost("/shop");
factory.setUsername("shop");
factory.setPassword("shop");
//根据连接工厂创建连接
try (Connection connection = factory.newConnection();
//根据连接创建信道
Channel channel = connection.createChannel()) {
/**
* 绑定交换机
* 1. 交换机名称
* 2. 交换机类型
*/
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
String message = "Hello World!";
//发送消息
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes(StandardCharsets.UTF_8));
System.out.println(" [x] Sent '" + message + "'");
}
}
- 消费者(一)
//定义交换机名称
private final static String EXCHANGE_NAME = "exchange_fanout";
public static void main(String[] argv) throws Exception {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//工厂设置
factory.setHost("192.168.10.100");
factory.setVirtualHost("/shop");
factory.setUsername("shop");
factory.setPassword("shop");
//根据工厂创建连接
Connection connection = factory.newConnection();
//根据连接创建信道
Channel channel = connection.createChannel();
//根据信道获取排他队列
String queueName = channel.queueDeclare().getQueue();
//将交换机和队列绑定
channel.queueBind(queueName, EXCHANGE_NAME, "");
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [x] Received '" + message + "'");
};
/**
* 消费队列
* 1.队列名称
* 2.自动确认
* 3.收到的消息的实体类
*/
channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
});
}
- 消费者(二)
private final static String EXCHANGE_NAME = "exchange_fanout";
public static void main(String[] argv) throws Exception {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//工厂设置
factory.setHost("192.168.10.100");
factory.setVirtualHost("/shop");
factory.setUsername("shop");
factory.setPassword("shop");
//根据工厂创建连接
Connection connection = factory.newConnection();
//根据连接创建信道
Channel channel = connection.createChannel();
//根据信道获取排他队列
String queueName = channel.queueDeclare().getQueue();
//将交换机和队列绑定
channel.queueBind(queueName, EXCHANGE_NAME, "");
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [x] Received '" + message + "'");
};
/**
* 消费队列
* 1.队列名称
* 2.自动确认
* 3.收到的消息的实体类
*/
channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
});
}
路由模式
- 生产者
//定义交换机名称
private final static String EXCHANGE_NAME = "exchange_direct";
public static void main(String[] argv) throws Exception {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//工厂设置
factory.setHost("192.168.10.100");
factory.setVirtualHost("/shop");
factory.setUsername("shop");
factory.setPassword("shop");
//根据连接工厂创建连接
try (Connection connection = factory.newConnection();
//根据连接创建信道
Channel channel = connection.createChannel()) {
/**
* 绑定交换机
* 1. 交换机名称
* 2. 交换机类型
*/
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
String message = "Hello World!";
//发送消息
channel.basicPublish(EXCHANGE_NAME, "info", null, message.getBytes(StandardCharsets.UTF_8));
System.out.println(" [x] Sent '" + message + "'");
}
}
- 消费者
//定义交换机名称
private final static String EXCHANGE_NAME = "exchange_direct";
public static void main(String[] argv) throws Exception {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//工厂设置
factory.setHost("192.168.10.100");
factory.setVirtualHost("/shop");
factory.setUsername("shop");
factory.setPassword("shop");
//根据工厂创建连接
Connection connection = factory.newConnection();
//根据连接创建信道
Channel channel = connection.createChannel();
//根据信道获取排他队列
String queueName = channel.queueDeclare().getQueue();
/**
* 将交换机和队列绑定
* 1. 队列名称
* 2. 交换机名称
* 3. 路由键
*/
channel.queueBind(queueName, EXCHANGE_NAME, "error");
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [x] Received '" + message + "'");
};
/**
* 消费队列
* 1.队列名称
* 2.自动确认
* 3.收到的消息的实体类
*/
channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
});
}
主题模式
- 生产者
//定义交换机名称
private final static String EXCHANGE_NAME = "exchange_topic";
public static void main(String[] argv) throws Exception {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//工厂设置
factory.setHost("192.168.10.100");
factory.setVirtualHost("/shop");
factory.setUsername("shop");
factory.setPassword("shop");
//根据连接工厂创建连接
try (Connection connection = factory.newConnection();
//根据连接创建信道
Channel channel = connection.createChannel()) {
/**
* 绑定交换机
* 1. 交换机名称
* 2. 交换机类型
*/
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
String orangeKey = "quick.orange.rabbit";
String rabbitKey = "lazy.pink.rabbit";
String lazyKey = "lazy.orange.male.rabbit";
String message1 = "Hello World!1";
String message2 = "Hello World!2";
String message3 = "Hello World!3";
//发送消息
channel.basicPublish(EXCHANGE_NAME, orangeKey, null, message1.getBytes(StandardCharsets.UTF_8));
System.out.println(" [x] Sent '" + message1 + "'");
channel.basicPublish(EXCHANGE_NAME, rabbitKey, null, message2.getBytes(StandardCharsets.UTF_8));
System.out.println(" [x] Sent '" + message2 + "'");
channel.basicPublish(EXCHANGE_NAME, lazyKey, null, message3.getBytes(StandardCharsets.UTF_8));
System.out.println(" [x] Sent '" + message3 + "'");
}
}
- 消费者
private final static String EXCHANGE_NAME = "exchange_topic";
public static void main(String[] argv) throws Exception {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//工厂设置
factory.setHost("192.168.10.100");
factory.setVirtualHost("/shop");
factory.setUsername("shop");
factory.setPassword("shop");
//根据工厂创建连接
Connection connection = factory.newConnection();
//根据连接创建信道
Channel channel = connection.createChannel();
//根据信道获取排他队列
String queueName = channel.queueDeclare().getQueue();
/**
* 将交换机和队列绑定
* 1. 队列名称
* 2. 交换机名称
* 3. 路由键
*/
channel.queueBind(queueName, EXCHANGE_NAME, "*.orange.*");
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [x] Received '" + message + "'");
};
/**
* 消费队列
* 1.队列名称
* 2.自动确认
* 3.收到的消息的实体类
*/
channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
});
}
RPC(远程控制过程)模式
- 客户端
public class RPCClient implements AutoCloseable {
private Connection connection;
private Channel channel;
// 队列名称
private String requestQueueName = "rpc_queue";
// 初始化连接
public RPCClient() throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.10.100");
factory.setPort(5672);
factory.setUsername("shop");
factory.setPassword("shop");
factory.setVirtualHost("/shop");
connection = factory.newConnection();
channel = connection.createChannel();
}
public static void main(String[] args) {
try (RPCClient fibonacciRpc = new RPCClient()) {
for (int i = 0; i < 10; i++) {
String i_str = Integer.toString(i);
System.out.println(" [x] Requesting fib(" + i_str + ")");
// 请求服务端
String response = fibonacciRpc.call(i_str);
System.out.println(" [.] Got '" + response + "'");
}
} catch (IOException | TimeoutException | InterruptedException e) {
e.printStackTrace();
}
}
// 请求服务端
public String call(String message) throws IOException, InterruptedException {
// correlationId请求标识ID
final String corrId = UUID.randomUUID().toString();
// 获取队列名称
String replyQueueName = channel.queueDeclare().getQueue();
// 设置replyTo队列和correlationId请求标识
AMQP.BasicProperties props = new AMQP.BasicProperties
.Builder()
.correlationId(corrId)
.replyTo(replyQueueName)
.build();
// 发送消息至队列
channel.basicPublish("", requestQueueName, props, message.getBytes("UTF-8"));
// 设置线程等待,每次只接收一个响应结果
final BlockingQueue<String> response = new ArrayBlockingQueue<>(1);
// 接受服务器返回结果
String ctag = channel.basicConsume(replyQueueName, true, (consumerTag, delivery) -> {
if (delivery.getProperties().getCorrelationId().equals(corrId)) {
// 将给定的元素在给定的时间内设置到线程队列中,如果设置成功返回true, 否则返回false
response.offer(new String(delivery.getBody(), "UTF-8"));
}
}, consumerTag -> {
});
// 从线程队列中获取值,如果线程队列中没有值,线程会一直阻塞,直到线程队列中有值,并且取得该值
String result = response.take();
// 从消息队列中丢弃该值
channel.basicCancel(ctag);
return result;
}
// 关闭连接
public void close() throws IOException {
connection.close();
}
}
- 服务端
public class RPCServer {
// 队列名称
private static final String RPC_QUEUE_NAME = "rpc_queue";
/**
* 计算斐波那契数列
*
* @param n
* @return
*/
private static int fib(int n) {
if (n == 0) return 0;
if (n == 1) return 1;
return fib(n - 1) + fib(n - 2);
}
public static void main(String[] args) {
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.10.100");
factory.setPort(5672);
factory.setUsername("shop");
factory.setPassword("shop");
factory.setVirtualHost("/shop");
try {
// 通过工厂创建连接
final Connection connection = factory.newConnection();
// 获取通道
final Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(RPC_QUEUE_NAME, false, false, false, null);
channel.queuePurge(RPC_QUEUE_NAME);
/*
限制RabbitMQ只发不超过1条的消息给同一个消费者。
当消息处理完毕后,有了反馈,才会进行第二次发送。
*/
int prefetchCount = 1;
channel.basicQos(prefetchCount);
System.out.println(" [x] Awaiting RPC requests");
Object monitor = new Object();
// 获取消息
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
// 获取replyTo队列和correlationId请求标识
AMQP.BasicProperties replyProps = new AMQP.BasicProperties
.Builder()
.correlationId(delivery.getProperties().getCorrelationId())
.build();
String response = "";
try {
// 接收客户端消息
String message = new String(delivery.getBody(), "UTF-8");
int n = Integer.parseInt(message);
System.out.println(" [.] fib(" + message + ")");
// 服务端根据业务需求处理
response += fib(n);
} catch (RuntimeException e) {
System.out.println(" [.] " + e.toString());
} finally {
// 将处理结果发送至replyTo队列同时携带correlationId属性
channel.basicPublish("", delivery.getProperties().getReplyTo(), replyProps,
response.getBytes("UTF-8"));
// 手动回执消息
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
// RabbitMq consumer worker thread notifies the RPC server owner thread
// RabbitMq消费者工作线程通知RPC服务器其他所有线程运行
synchronized (monitor) {
monitor.notify();
}
}
};
// 监听队列
/*
autoAck = true代表自动确认消息
autoAck = false代表手动确认消息
*/
boolean autoAck = false;
channel.basicConsume(RPC_QUEUE_NAME, autoAck, deliverCallback, consumerTag -> {
});
// Wait and be prepared to consume the message from RPC client.
// 线程等待并准备接收来自RPC客户端的消息
while (true) {
synchronized (monitor) {
try {
monitor.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
rabbitmq事务机制
rabbitmq事务不是强事务,不像关系型数据强事务,关系型数据库事务,只要一步操作出现异常,前面所有的操作步骤全部要回退。而rabbitmq则是前面成功操作的不走不会回退,很少使用。
private final static String QUEUE_NAME = "tx";
public static void main(String[] argv) throws Exception {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//工厂设置
factory.setHost("192.168.10.100");
factory.setVirtualHost("/shop");
factory.setUsername("shop");
factory.setPassword("shop");
//根据连接工厂创建连接
Connection connection = factory.newConnection();
//根据连接创建信道
Channel channel = connection.createChannel();
try {
/**
* 声明队列
* 第一个参数queue:队列名称
* 第二个参数durable:是否持久化
* 第三个参数Exclusive:排他队列,如果一个队列被声明为排他队列,该队列仅对首次声明它的连接可见,并在连接断开时自动删除。
* 这里需要注意三点:
* 1. 排他队列是基于连接可见的,同一连接的不同通道是可以同时访问同一个连接创建的排他队列的。
* 2. "首次",如果一个连接已经声明了一个排他队列,其他连接是不允许建立同名的排他队列的,这个与普通队列不同。
* 3. 即使该队列是持久化的,一旦连接关闭或者客户端退出,该排他队列都会被自动删除的。
* 这种队列适用于只限于一个客户端发送读取消息的应用场景。
* 第四个参数Auto-delete:自动删除,如果该队列没有任何订阅的消费者的话,该队列会被自动删除。
* 这种队列适用于临时队列。
*/
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//开启事务
channel.txSelect();
String message = "Hello World!";
//发送消息
channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
System.out.println(" [x] Sent '" + message + "'");
Integer.parseInt("aaa");
//提交事务
channel.txCommit();
} catch (Exception e) {
e.printStackTrace();
//回滚事务
channel.txRollback();
} finally {
if (channel != null) {
channel.close();
}
if (connection != null) {
connection.close();
}
}
}
rabbitmq确认机制
生产者将信道设置成confirm模式,一旦信道进入confirm模式,所有在该信道上面发布的消息都会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后,broker就会发送一个确认给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么确认消息会将消息写入磁盘之后发出,broker回传给生产者的确认消息中deliver-tag域包含了确认消息的序列号,此外broker也可以设置basic.ack的multiple域,表示到这个序列号之前的所有消息都已经得到了处理。
confirm模式最大的好处在于他是异步的,一旦发布一条消息,生产者应用程序就可以在等信道返回确认的同时继续发送下一条消息,当消息最终得到确认之后,生产者应用便可以通过回调方法来处理该确认消息,如果RabbitMQ因为自身内部错误导致消息丢失,就会发送一条nack消息,生产者应用程序同样可以在回调方法中处理该nack消息。
在channel 被设置成 confirm 模式之后,所有被 publish 的后续消息都将被 confirm(即 ack) 或者被nack一次。但是没有对消息被 confirm 的快慢做任何保证,并且同一条消息不会既被 confirm又被nack。
注意:两种事物控制形式不能同时开启!
- 测试代码(同步确认机制)
private final static String QUEUE_NAME = "confirm_sync";
public static void main(String[] argv) throws Exception {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//工厂设置
factory.setHost("192.168.10.100");
factory.setVirtualHost("/shop");
factory.setUsername("shop");
factory.setPassword("shop");
//根据连接工厂创建连接
Connection connection = factory.newConnection();
//根据连接创建信道
Channel channel = connection.createChannel();
try {
/**
* 声明队列
* 第一个参数queue:队列名称
* 第二个参数durable:是否持久化
* 第三个参数Exclusive:排他队列,如果一个队列被声明为排他队列,该队列仅对首次声明它的连接可见,并在连接断开时自动删除。
* 这里需要注意三点:
* 1. 排他队列是基于连接可见的,同一连接的不同通道是可以同时访问同一个连接创建的排他队列的。
* 2. "首次",如果一个连接已经声明了一个排他队列,其他连接是不允许建立同名的排他队列的,这个与普通队列不同。
* 3. 即使该队列是持久化的,一旦连接关闭或者客户端退出,该排他队列都会被自动删除的。
* 这种队列适用于只限于一个客户端发送读取消息的应用场景。
* 第四个参数Auto-delete:自动删除,如果该队列没有任何订阅的消费者的话,该队列会被自动删除。
* 这种队列适用于临时队列。
*/
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//开启确认模式
channel.confirmSelect();
String message = "Hello World!";
//发送消息
channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
System.out.println(" [x] Sent '" + message + "'");
//发送单条确认
if (channel.waitForConfirms()) {
System.out.println("消息发送成功");
} else {
System.out.println("消息发送失败");
}
//批量确认
channel.waitForConfirmsOrDie();
System.out.println("消息发送成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (channel != null) {
channel.close();
}
if (connection != null) {
connection.close();
}
}
}
- 测试代码(异步确认机制)
// 队列名称
public static final String QUEUE_NAME = "confirm_async";
public static void main(String[] args) {
// 定义连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setPort(5672);
factory.setHost("192.168.10.100");
factory.setUsername("shop");
factory.setPassword("shop");
factory.setVirtualHost("/shop");
Connection connection = null;
Channel channel = null;
try {
// 维护信息发送回执deliveryTag
final SortedSet<Long> confirmSet = Collections.synchronizedSortedSet(new TreeSet<Long>());
// 创建连接
connection = factory.newConnection();
// 获取通道
channel = connection.createChannel();
// 开启confirm确认模式
channel.confirmSelect();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 添加channel 监听
channel.addConfirmListener(new ConfirmListener() {
// 已确认
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
// multiple=true已确认多条 false已确认单条
if (multiple) {
System.out.println("handleAck--success-->multiple" + deliveryTag);
// 清除前 deliveryTag 项标识id
confirmSet.headSet(deliveryTag + 1L).clear();
} else {
System.out.println("handleAck--success-->single" + deliveryTag);
confirmSet.remove(deliveryTag);
}
}
// 未确认
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
// multiple=true未确认多条 false未确认单条
if (multiple) {
System.out.println("handleNack--failed-->multiple-->" + deliveryTag);
// 清除前 deliveryTag 项标识id
confirmSet.headSet(deliveryTag + 1L).clear();
} else {
System.out.println("handleNack--failed-->single" + deliveryTag);
confirmSet.remove(deliveryTag);
}
}
});
// 循环发送消息演示消息确认
while (true) {
// 创建消息
String message = "Hello World!";
// 获取unconfirm的消息序号deliveryTag
Long seqNo = channel.getNextPublishSeqNo();
channel.basicPublish("", QUEUE_NAME, null, message.getBytes("utf-8"));
// 将消息序号deliveryTag添加至SortedSet
confirmSet.add(seqNo);
}
} catch (IOException | TimeoutException e) {
e.printStackTrace();
} finally {
try {
// 关闭通道
if (null != channel && channel.isOpen())
channel.close();
// 关闭连接
if (null != connection && connection.isOpen())
connection.close();
} catch (TimeoutException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
五、springboot-AMQP
- 导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
- 核心配置文件(yml文件,客户端和服务端只需要修改端口和servlet站点路径)
spring:
rabbitmq:
host: 192.168.147.110
port: 5672
username: zwf
password: 123456789
virtual-host: /shop
server:
port: 9998
servlet:
context-path: /client
- 生产者配置类
@Configuration
public class rabbitmqConfig {
//声明队列
@Bean
public Queue topicsQueue(){
//默认开启持久化 关闭排他队列 关闭自动删除
return new Queue("Q_Topics");
}
//声明交换机
@Bean
public Exchange topicsExchange(){
return new ExchangeBuilder("exchange_topics",BuiltinExchangeType.TOPIC.getType()).build();
}
@Bean
//绑定交换机与队列
public Binding queueBindExchange(){
//把队列与交换机 路由key绑定
return new Binding(topicsQueue().getName(), Binding.DestinationType.QUEUE,topicsExchange().getName(),"fox.rabbit.elephone.animal",null);
}
}
- 生产者业务层
@Component
public class AMQPSend {
@Autowired
private RabbitMessagingTemplate template;
public void sender(){
//发送消息
String sender="hello I am rabbitmq";
//向交换机中发送消息 形成消息队列
template.convertAndSend("exchange_topics","fox.rabbit.elephone.animal",sender);
}
}
- 生产者测试类(启动服务)
@SpringBootTest
class RabbitmqServerApplicationTests {
@Autowired
private AMQPSend amqpSend;
@Test
void contextLoads() {
//发送消息
amqpSend.sender();
}
}
- 消费者业务层
/**
* @author Mr Zeng
* @version 1.0
* @date 2023-10-07 8:57
*/
//监听rabbitmq 消费队列中的消息
@Component
//监听队列
@RabbitListener(queues = "Q_Topics")
public class ConsumerRecevice {
@RabbitHandler
public void recvMsg(String msg){
//打印接收队列消息
System.out.println(msg);
}
}
本文来自博客园,作者:戴莫先生Study平台,转载请注明原文链接:https://www.cnblogs.com/smallzengstudy/p/17746720.html