本指南涵盖RabbitMQ Java客户端及其公共API。
如要部分如下:
- rabbitMq连接connection使用
- connection连接、channel信道的生命周期
- 交换器exchange以及队列queue的使用
- 如何消费
- 并发需要注意的事项和安全性
- 从网络故障中自动恢复
使用rabbitmq需要注意一下几点: rabbitmq5.x版本仅支持jdk1.8的版本,android则需要依赖7.x版本。rabbit4.x版本仅支持jdk1.6
版本,android则是7.x以下的版本
如要查看源码的朋友可以到gitup上下载源码: https://github.com/rabbitmq/rabbitmq-java-client/
如果需要使用,则在maven中央仓库中拉取: 这里用5.0.0版本
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.0.0</version>
</dependency>
RabbitMQ关键接口类如下:
- Channel : 代表AMQP 0-9-1协议通道,并提供大部分操作(协议方法),我们可以简单的理解它为信道或者频道
- Connection: 代表AMQP 0-9-1协议的连接
- ConnectionFactory: 连接工厂,通过她来实例化Connection连接
- Consumer:表示消息的消费者
- DefaultConsumer:消费者的默认实现
- BasicProperties:消息属性(即元数据)
- BasicProperties.Builder:消息属性BasicProperties的构建器
协议操作可通过Channel接口获得。 连接用于打开通道,注册连接生命周期事件处理程序以及关闭不再需要的连接。 连接通过ConnectionFactory实例化,这是您配置各种连接设置的方式,例如vhost或用户名。
一、rabbitMq连接connection使用
Connection和Channel之间的关系,您可以简单的理解为电视的网线和电视的节目之间的关系,Connection就是电视数据连接线,而Channel就是可以观看的各个频道卫视。当然这里的电视是可以发送消息的。
核心API类是Connection和Channel,分别代表AMQP 0-9-1连接和通道。 它们通常在使用前导入:
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Channel;
比如,下面通过连接工厂指定一些参数,创建一个连接。
ConnectionFactory factory = new ConnectionFactory();
// "guest"/"guest" by default, limited to localhost connections
factory.setUsername(userName);
factory.setPassword(password);
factory.setVirtualHost(virtualHost);
factory.setHost(hostName);
factory.setPort(portNumber);
Connection conn = factory.newConnection();
需要注意的是:所有这些参数RabbitMQ都有合理的默认值。 如果在创建连接之前属性仍未分配指定,则将使用属性的默认值。比如我们需要创建一个本地连接,假设您的电脑上已经安装了RabbitMQ,可以这样:
ConnectionFactory factory = new ConnectionFactory();
Connection conn = factory.newConnection();
ConnectionFactory默认配置如下:
Property | Default Value |
Username | "guest" |
Password | "guest" |
Virtual host | "/" |
Hostname | "localhost" |
port | 5672 for regular connections, 5671 for connections that use TLS |
当然除了上面的方式创建Connection外,我们还可以通过URI来创建,如下:
ConnectionFactory factory = new ConnectionFactory();
factory.setUri("amqp://userName:password@hostName:portNumber/virtualHost");
Connection conn = factory.newConnection();
比如:
factory.setUri("amqp://guest:guest@localhost:5672");
然后通过connection打开一个信道(频道)Channel
Channel channel = conn.createChannel();
这样,您就可以通过channel对象来发送和接受消息了。
二、connection连接、channel信道的生命周期
当连接使用完毕后,关闭连接您只需要close连接即可,如下:
channel.close();
conn.close();
需要注意的是,关闭通道channel然后在关闭连接是一种良好的操作方法,但是这个操作并不是必须的,直接关闭连接,通道的关闭会将自动完成。就像退出QQ聊天前,先关闭各个聊天窗口一样。
connection和channel的寿命
连接意味着长寿。 底层协议是为长时间运行的连接而设计和优化的。 这意味着每个操作打开一个新连接,例如 发布的消息是不必要的,并且强烈劝阻,因为它会引入大量的网络往返和开销。
通道也意味着长寿,但由于许多可恢复的协议错误将导致通道关闭,因此通道寿命可能短于其连接的寿命。 每次操作关闭和打开新通道通常是不必要的,但可能是合适的。 如有疑问,请考虑先重新使用频道。
通道级异常(例如尝试从不存在的队列中使用)将导致通道关闭。 无法再使用已关闭的通道,也不会再从服务器接收任何事件(例如消息传递)。 RabbitMQ将记录通道级异常,并将启动通道的关闭序列(见下文)。
三、交换器exchange以及队列queue的使用
客户端应用程序在使用交换器,队列,必须要在使用之前申明。继续前面的案例,以下代码申明了一个交换器以及一个队列,他然后将他们绑定在一起
//申明交换器 exchangeName:交换器名称 direct:交换器类型
channel.exchangeDeclare(exchangeName, "direct", true);
//生成一个随机队列
String queueName = channel.queueDeclare().getQueue();
//将交换器和队列绑定在一起,routerKey是路由键,后面会说
channel.queueBind(queueName, exchangeName, routingKey);
注意:这里的queue是一个自动生成的随机队列,也就意味着这个队列只有当前的客户端可以享用,不会对其他任何客户端开放,而且默认绑定生成的队列是一种非持久的、独占的(即只有当前客户端可占用)、自动清除的(即连接断开,队列自动删除)队列。
如果想要,队列可以对多个客户端共享,可以这样:
//申明交换器
channel.exchangeDeclare(exchangeName, "direct", true);
//自定义队列名称
String queueName = "testQueue";
//申明队列,即队列配置
channel.queueDeclare(queueName, true, false, false, null);
//队列绑定交换器
channel.queueBind(queueName, exchangeName, routingKey);
获取指定队列的消费者数量,以及队列中消息数量:
Queue.DeclareOk response = channel.queueDeclarePassive("queue-name");
// 队列中消息数量
response.getMessageCount();
// 队列中消费者数量
response.getConsumerCount();
如果您想申明的队列不用等待服务器任何响应,可以这样:
channel.queueDeclareNoWait(queueName, true, false, false, null);
下面是对队列一些基本操作api
1、删除队列
channel.queueDelete("queue-name")
2、只有在队列为空时,才可以删除队列
channel.queueDelete("queue-name", false, true)
3、如果队列没有任何消费者,删除队列
channel.queueDelete("queue-name", true, false)
4、删除队列中的所有消息。
channel.queuePurge("queue-name")
下面是生产者操作,将消费发送到交换器
//申明消息内容,byte数组
byte[] messageBodyBytes = "Hello, world!".getBytes();
//发布消息到交换器,需要路由键来找绑定的队列
channel.basicPublish(exchangeName, routingKey, null, messageBodyBytes);
要进行精细控制,可以使用重载变量来指定强制标志,或者使用预先设置的消息属性发送消息:
channel.basicPublish(exchangeName, routingKey, mandatory,
MessageProperties.PERSISTENT_TEXT_PLAIN,
messageBodyBytes);
您可以使用Builder类来构建自己的消息属性对象,其传递模式为2(持久性),优先级为1,内容类型为“text / plain”。 例如:
channel.basicPublish(exchangeName, routingKey,
new AMQP.BasicProperties.Builder()
.contentType("text/plain")
.deliveryMode(2)
.priority(1)
.userId("bob")
.build(),
messageBodyBytes);
比如,发送一个自定义标头的邮件:
Map<String, Object> headers = new HashMap<String, Object>();
headers.put("latitude", 51.5252949);
headers.put("longitude", -0.0905493);
channel.basicPublish(exchangeName, routingKey,
new AMQP.BasicProperties.Builder()
.headers(headers)
.build(),
messageBodyBytes);
发布一个会过期的消息:
channel.basicPublish(exchangeName, routingKey,
new AMQP.BasicProperties.Builder()
.expiration("60000")
.build(),
messageBodyBytes);
四、如何消费
接收消息的最有效方法是使用Consumer接口设置订阅。然后,消息将在到达时自动传递,而不是必须明确请求。
不同的消费者实例必须具有不同的消费者标签。强烈建议不要在连接上使用重复的消费者标签,这会导致自动连接恢复问题,并在监控消费者时导致监控数据混乱。
1、实现Consumer的最简单方法是将便捷类DefaultConsumer子类化。可以在basicConsume调用上传递此子类的对象以设置订阅:
channel.basicConsume(queueName, false, "myConsumerTag",
new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body)
throws IOException
{
String routingKey = envelope.getRoutingKey();
String contentType = properties.getContentType();
long deliveryTag = envelope.getDeliveryTag();
// 手动确认消费者已经收到消息
channel.basicAck(deliveryTag, false);
}
});
queueName: 队列名称
autoAck: 是否自动应答,自动应答代表消费者一旦接收到消息就会自动应答表示已经接受到了,队列里面会自动将消息清除。不自动应答,则需要:channel.basicAck(deliveryTag, false); 告诉消息服务器我已经收到消息了。队列就会清除掉消息
2、要显式检索消息,请使用Channel.basicGet。 返回的值是GetResponse的一个实例,可以从中提取头信息(属性)和消息体:
GetResponse response = channel.basicGet(queueName, false);
if (response == null) {
// No message retrieved.
} else {
AMQP.BasicProperties props = response.getProps();
byte[] body = response.getBody();
long deliveryTag = response.getEnvelope().getDeliveryTag();
// 手动确认消费者已经收到消息
channel.basicAck(deliveryTag, false);
...
如果发布的消息设置了“强制”标志,但无法路由,则代理会将其返回给发送客户端(通过AMQP.Basic.Return命令)。
要获得此类返回的通知,客户端可以实现ReturnListener接口并调用Channel.addReturnListener。 如果客户端尚未为特定通道配置返回侦听器,则将以静默方式删除关联的返回消息。
信道关闭是触发:
channel.addReturnListener(new ReturnListener() {
public void handleReturn(int replyCode,
String replyText,
String exchange,
String routingKey,
AMQP.BasicProperties properties,
byte[] body)
throws IOException {
...
}
});
AMQP连接和通道对象具有以下与关闭相关的方法:
addShutdownListener(ShutdownListener listener)和removeShutdownListener(ShutdownListener listener),用于管理任何侦听器,当对象转换为关闭状态时将触发这些侦听器。请注意,将ShutdownListener添加到已关闭的对象将立即触发侦听器 getCloseReason():允许调查对象关闭的原因是什么 isOpen():用于测试对象是否处于打开状态 close(int closeCode,String closeMessage):以显式通知对象关闭
连接关闭是触发:
connection.addShutdownListener(new ShutdownListener() {
public void shutdownCompleted(ShutdownSignalException cause)
{
...
}
});
可以通过显式调用getCloseReason()方法或使用ShutdownListener类的服务(ShutdownSignalException cause)方法中的cause参数来检索ShutdownSignalException,其中包含有关close原因的所有可用信息。
ShutdownSignalException类提供了分析关闭原因的方法。 通过调用isHardError()方法,我们可以获得有关连接或通道错误的信息,并且getReason()以AMQP方法的形式返回有关原因的信息 - AMQP.Channel.Close或AMQP.Connection.Close( 如果原因是库中的某些异常,则返回null,例如网络通信失败,在这种情况下,可以使用getCause()检索异常。
public void shutdownCompleted(ShutdownSignalException cause)
{
if (cause.isHardError())
{
Connection conn = (Connection)cause.getReference();
if (!cause.isInitiatedByApplication())
{
Method reason = cause.getReason();
...
}
...
} else {
Channel ch = (Channel)cause.getReference();
...
}
}
当rabbitmq服务器存在多个时,您可以将Address数组传递给newConnection()。 Address只是com.rabbitmq.client包中的一个便利类,包含主机和端口组件。 例如:
Address[] addrArr = new Address[]{ new Address(hostname1, portnumber1)
, new Address(hostname2, portnumber2)};
Connection conn = factory.newConnection(addrArr);
将尝试连接到hostname1:portnumber1,如果无法连接到hostname2:portnumber2。 返回的连接是数组中第一个成功的连接(不抛出IOException)。 这完全等同于在工厂上重复设置主机和端口,每次调用factory.newConnection(),直到其中一个成功。 从版本3.6.6开始,可以让AddressResolver的实现选择创建连接时的连接位置:
Connection conn = factory.newConnection(addressResolver);
public interface AddressResolver {
List<Address> getAddresses() throws IOException;
}
//此处未完