1. 生产者客户端
1. 连接RabbitMQ
- 通过连接池获取连接,但首先要设置一些必要的参数,必需参数有IP地址、端口号、用户名、密码,然后就可以通过连接池创建连接对象
ConnectionFactory fac=new ConnectionFactory();//获取一个rabbitMQ连接池,并设置相关参数
fac.setHost(IP);
fac.setPassword(PASSWORD);
fac.setUsername(USER);
fac.setPort(PORT);
//从连接池中获取一个rabbitMQ连接(或者说是一个RabbitMQ客户端)
connection=fac.newConnection();
也可以选择通过URI的方式实现
ConnectionFactory fac=new ConnectionFactory();//获取一个rabbitMQ连接池,并设置相关参数
fac.setUri("amqp://username:password@ipAddress:port/virtualHost");//virtualHost是在添加RabbitMQ的user用户时设置的一个路径
//从连接池中获取一个rabbitMQ连接(或者说是一个RabbitMQ客户端)
connection=fac.newConnection();
- 然后就可以通过Connection对象创建一个Channel对象,通过Channel对象发送或者接受消息。Connection对象可以创建多个Channel对象,但Channel对象不能再多线程间共享,应该为每一个线程开辟一个Channel。
channel=connection.createChannel();//创建一个信道
2.使用交换器
- 在使用交换器和队列时,必须保证它们已经存在RabbitMQ中,所以要先声明
channel.exchangeDeclare(exchange_demo, "topic",false,false,null);//创建一个type为topic,持久化的、非自动删除的交换器
channel.queueDeclare(queue_demo, true, false, false, null);//创建一个持久化、非排他的、非自动删除的交换器
channel.queueBind(queue_demo, exchange_demo, "com.rabbitmq.*");//将交换器和队列通过路由键绑定
- exchangeDeclare方法有多个重载版本,这些重载方法都是由下面这个方法的参数缺省得到的:
Exchange.DeclareOk exchangeDeclare(String exchange,
String type,
boolean durable,
boolean autoDelete,
boolean internal,
Map<String, Object> arguments) throws IOException;
参数exchange表示交换器名称;参数type表示设置交换器的类型;durable,用于设置是否持久化消息,true表示持久化,false则相反,持久化表示可以将交换器持久化存储到本地磁盘上,在服务器重启的时候不丢失信息;autoDelete设置是否自动删除,true表示自动删除,false相反,交换器自动删除的条件是,至少有一个队列或交换器与此交换器绑定连接之后,所有交换器和队列与此交换器解除绑定;internal表示该交换器是否为内置的,true表示是,否则相反,内置交换器表示客户端不能直接发送消息到该交换器中,只能通过另一个交换器将消息路由到该交换器中;arguments表示其他的一些结构化参数
- exchangeDeclare方法还有另一个类似的方法,就是
void exchangeDeclareNoWait(String exchange,
String type,
boolean durable,
boolean autoDelete,
boolean internal,
Map<String, Object> arguments) throws IOException;
该方法表示向RabbitMQ服务器发送一个AMQP协议中声明交换器的指令之后,不需要返回执行结果,所以该方法返回值为void,而普通的exchangeDeclare方法会返回一个Exchange.DeclareOk表示需要等待服务器返回交换器是否创建成功的一个AMQP指令,exchangeDeclareNoWait方法没有返回值,所以不知道交换器是否创建成功,如果在声明之后直接使用该交换器发送数据就会发生错误,所以一般不使用该方法
- exchangeDeclare方法还有另一个类似的方法,exchangeDeclarePassive方法
Exchange.DeclareOk exchangeDeclarePassive(String name) throws IOException;
该方法主要用来检测相应的交换器是否存在,如果存在则正常返回,如果不存在则抛出异常,同时关闭Channel,并不会创建交换器。
- 删除交换器的方法:
Exchange.DeleteOk exchangeDelete(String exchange, boolean ifUnused) throws IOException;
void exchangeDeleteNoWait(String exchange, boolean ifUnused) throws IOException;
Exchange.DeleteOk exchangeDelete(String exchange) throws IOException;
其中exchange表示删除的交换器名称,ifUnused表示设置是否在交换器没有被使用的情况下删除,如果为true则表示必须在交换器没有被使用时删除,否则交换器会被直接删除。
3. 使用队列
- 声明队列:有两个重载方法
Queue.DeclareOk queueDeclare() throws IOException;
Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete,
Map<String, Object> arguments) throws IOException;
不带参数的方法会默认创建一个由RabbitMQ命名的、排他的、自动删除的、非持久化的队列;queue参数表示队列名称;durable表示是否为持久化队列,true表示设置为持久化,持久化的队列会存储到本地磁盘上,RabbitMQ重启后会加载还原队列;exclusive,表示设置是否排他,weitrue则表示设置为排他队列,排他队列表示该队列仅对首次声明该队列的连接(Connection)可见,并且在连接断开时自动删除(无论是否持久化),对于同一个Connection创建的不同Channel是可以访问同一个Connection创建的排他队列,“首次”指的是如果一个连接创建了一个排他队列,那么其他连接对象不允许创建同名队列;autoDelete表示是否自动删除,weitrue则表示是,自动删除的前提是至少有一个消费者连接到这个队列,然后所有与该队列连接的消费者都断开后,就会自动删除队列;arguments表示设置队列的一些其他参数
对于生产者和消费者都可以通过queueDeclare方法声明创建一个队列,但是对于在消费者客户端,在同一个信道(Channel)上订阅了一个队列之后,就无法声明队列;想要订阅另一个队列就必须先取消订阅,然后将信道置为“传输”模式后,才能声明队列
- 与交换器对应,也有一个
void queueDeclareNoWait(String queue, boolean durable, boolean exclusive, boolean autoDelete,
Map<String, Object> arguments) throws IOException;
- 以及
Queue.DeclareOk queueDeclarePassive(String queue) throws IOException;
- 同样的,删除队列方法有:
Queue.DeleteOk queueDelete(String queue) throws IOException;
Queue.DeleteOk queueDelete(String queue, boolean ifUnused, boolean ifEmpty) throws IOException;
//queue表示队列名,ifUnused表示该队列是否处于使用中才能删除,ifEmpty表示该队列是否要为空才能删除
void queueDeleteNoWait(String queue, boolean ifUnused, boolean ifEmpty) throws IOException;
- 清空队列的方法:
Queue.PurgeOk queuePurge(String queue) throws IOException;
//该方法用于清空队列中的消息,但不删除队列
4. 队列与交换器之间的绑定
- 队列与交换器绑定:
Queue.BindOk queueBind(String queue, String exchange, String routingKey) throws IOException;
Queue.BindOk queueBind(String queue, String exchange, String routingKey, Map<String, Object> arguments) throws IOException;
void queueBindNoWait(String queue, String exchange, String routingKey, Map<String, Object> arguments) throws IOException;
//queue表示队列名,exchange表示交换器名,routingKey表示路由键,arguments表示关于绑定的一些参数
- 队列与交换器解绑:
Queue.UnbindOk queueUnbind(String queue, String exchange, String routingKey) throws IOException;
Queue.UnbindOk queueUnbind(String queue, String exchange, String routingKey, Map<String, Object> arguments) throws IOException;
- 交换器与交换器绑定:调用该方法进行绑定后,消息会从source转发器发送到destination转发器
Exchange.BindOk exchangeBind(String destination, String source, String routingKey) throws IOException;
Exchange.BindOk exchangeBind(String destination, String source, String routingKey, Map<String, Object> arguments) throws IOException;
void exchangeBindNoWait(String destination, String source, String routingKey, Map<String, Object> arguments) throws IOException;
//destination表示目标转发器,source表示原转发器,routingKey表示路由键,arguments表示绑定的一些参数
5. 发送消息
- 如果要发送消息,可以使用Channel的basicPublish方法,该方法有多个重载版本
void basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body) throws IOException;
void basicPublish(String exchange, String routingKey, boolean mandatory, BasicProperties props, byte[] body)
throws IOException;
void basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate, BasicProperties props, byte[] body)
throws IOException;
- exchange表示交换器名;routingKey表示路由键,
- props表示消息的基本属性集,其包含14个属性成员,详细可以看com.rabbitmq.client.AMQP.BasicProperties源码,里面分别有contentType、contentEncoding、Map<String,Object> headers、Integer deliveryMode、Integer priority、correlationId、replyTo、expiration、messageId、Date timestamp、type、userId、appId、clusterId这14个属性成员;com.rabbitmq.client.MessageProperties类中提供了几个BasicProperties对象,但太简单,一般不用,通常还是会自定义消息的属性
- mandatory和immediate:当消息传递时无法发送至队列中(比如依据交换器和路由键无法找到对应的队列),是否要将该消息返回给生产者,true表示是,false表示直接丢弃消息数据;而immediate为true表示如果消息发送的目标队列上有消费者相连,那么则发送到该队列上,如果没有消费者连接在该队列上,那么则抛弃该消息数据,当与路由键匹配的所有队列都没有消费者时,就会将该消息数据返回给生产者,但在RabbitMQ3.0版本之后不再支持immediate参数
- body表示消息体,也就是真正要发送的消息数据
6. BasicProperties对象在basicPublish方法中的使用示例:首先必须通过BasicProperties对象中的builder方法创建一个设置BasicProperties对象中属性的Builder对象,然后再调用Builder对象每个属性对应的方法进行设置,然后在调用Builder对象的build方法返回一个设置好的BasicProperties对象
channel.basicPublish(exchange_demo, "com.rabbitmq.cn",
new AMQP.BasicProperties().builder()
.contentType("text/plain")//设置消息数据格式为text/plain
.contentEncoding("utf-8")//设置编码格式为utf-8
.deliveryMode(2)//设置消息的投递模式,2表示消息会持久化存储在磁盘上
.expiration("10000")//设置消息的过期时间
.headers(headers)//设置headers
.build(),
message.getBytes());
2. 消费者客户端
public class RabbitMQConsumer {
private static final String IP="192.168.10.128";
private static final String USER="root";
private static final String PASSWORD="123456";
private static final int PORT=5672;
private static final String QUEUE_NAME="queue_demo";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection=null;
Address[] address={new Address(IP,PORT)};
try {
String queue_demo="queue_demo";//声明一个队列名称
ConnectionFactory fac=new ConnectionFactory();//获取一个rabbitMQ连接池,并设置相关参数
fac.setPassword(PASSWORD);
fac.setUsername(USER);
//从连接池中获取一个rabbitMQ连接
connection=fac.newConnection(address);
final Channel channel=connection.createChannel();//创建一个频道
channel.basicQos(64);//限制从RabbitMQ推送过来的消息数量
Consumer con=new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
byte[] body) throws IOException {
System.out.println("get message:"+new String(body,"utf-8"));
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
channel.basicConsume(queue_demo, con);
if(channel!=null){
channel.close();
}
} finally {
//关闭资源
if(connection!=null){
connection.close();
}
}
}
}
1. 消息的消费模式
1. 推模式:推模式即队列会主动将队列中的消息推送到订阅队列的消费者客户端中,该模式的使用有两个相关的类,可以选择实现com.rabbitmq.client.Consumer接口,来实现自己所需要的消费逻辑,也可以选择继承com.rabbitmq.client.DefaultConsumer类重写其中的部分方法来实现消费逻辑,方法有
- void handleConsumeOk(String consumerTag):该方法一定会在Consumer接口中的其他方法调用前返回当前方法的
- void handleCancelOk(String consumerTag):当调用Channel对象中的basicCancel方法时,会调用handleConsumeOk、handleDelivery和该方法
- void handleCancel(String consumerTag) throws IOException:
- void handleShutdownSignal(String consumerTag, ShutdownSignalException sig):当Channel或者Connection对象关闭时,调用此方法
- void handleRecoverOk(String consumerTag):
- void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperties properties,byte[] body) throws IOException:主要重写或者实现该方法,当接收到RabbitMQ发送来的消息时,需要此方法接收处理消息数据
Consumer con=new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
byte[] body) throws IOException {
System.out.println("get message:"+new String(body,"utf-8"));
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
channel.basicConsume(queue_demo, con);
对于接收消息需要使用Channel中的basicConsume方法,调用该方法表示在目标队列上注册一个消费者,或者说使消费者订阅该队列,而且该方法是一个NIO,相当于开辟了一个新线程来专门接收RabbitMQ中推送过来的消息,只要Channel所在的线程没有关闭Channel或Connection资源、取消订阅队列或者断开服务器连接等情况发生,那么消费者就会持续不断的接收来自RabbitMQ推送的消息,如下所示
public class RabbitMQConsumer {
private static final String IP="192.168.10.128";
private static final String USER="root";
private static final String PASSWORD="123456";
private static final int PORT=5672;
private static final String QUEUE_NAME="queue_demo";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
Connection connection=null;
Address[] address={new Address(IP,PORT)};
try {
String queue_demo="queue_demo";//声明一个队列名称
ConnectionFactory fac=new ConnectionFactory();//获取一个rabbitMQ连接池,并设置相关参数
fac.setPassword(PASSWORD);
fac.setUsername(USER);
//从连接池中获取一个rabbitMQ连接
connection=fac.newConnection(address);
final Channel channel=connection.createChannel();//创建一个频道
channel.basicQos(3);
Consumer con=new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
byte[] body) throws IOException {
System.out.println("get message:"+new String(body,"utf-8"));
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
//for(int i=0;i<4;i++){
channel.basicConsume(queue_demo, con);
//在这二十秒期间,会不断的接收来自RabbitMQ的消息,除非断开与RabbitMQ的连接
TimeUnit.SECONDS.sleep(20);
//}
//主线程休眠结束后,关闭Channel和Connection资源,所以会终止接收消息
//如果注释掉关闭Channel和Connection资源,即使Channel所在线程执行接收,但消费者接收消息的线程仍在持续运行,直到Channel或Connection资源关闭或断开连接
if(channel!=null){
channel.close();
}
} finally {
//关闭资源
if(connection!=null){
connection.close();
}
}
}
}
有很多个重载版本,只列举几个
String basicConsume(String queue, Consumer callback) throws IOException;
String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map<String, Object> arguments, Consumer callback) throws IOException;
String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map<String, Object> arguments, DeliverCallback deliverCallback, CancelCallback cancelCallback, ConsumerShutdownSignalCallback shutdownSignalCallback) throws IOException;
- queue参数表示订阅的队列名
- autoAck表示是否自动确认收到消息,建议为false,缺省该参数的重载方法默认也是false
- consumerTag:消费者标签,用来标示区分多个消费者
- noLocal:设置为true,表示不能将同一个Connection中生产者发送的消息传递给这个Connection中的消费者,缺省该参数的重载方法默认是false
- exclusive:设置为是否排他,缺省该参数的重载方法默认是false
- arguments:设置消费者的其他参数
- callback:设置消费者的回调函数,用来处理RabbitMQ推送过来的消息
2. 拉模式:即需要客户端主动的从RabbitMQ中的队列中拉取一条消息,通过Channel中的GetResponse basicGet(String queue, boolean autoAck)方法来实现,该方法没有其他重载方法,queue就表示队列名称,autoAck表示是否自动确认,接收到的消息会被包装为GetResponse对象,该对象中可以获取消息相关的所有属性信息,比如发送该消息时配置的BasicProperties对象;
//从连接池中获取一个rabbitMQ连接
connection=fac.newConnection(address);
Channel channel=connection.createChannel();//创建一个信道
GetResponse res=channel.basicGet(queue_demo, false);//接收一条消息
System.out.println(new String(res.getBody(),"utf-8"));//解析GetResponse对象,获取消息头
channel.basicAck(res.getEnvelope().getDeliveryTag(), false);//向RabbitMQ返回确认接收
调用Channel的basicConsume方法,会将信道设置为接收模式,直到取消队列的订阅为止,在接受模式期间,RabbitMQ会不断的推送消息给消费者,但是推送消息的个数取决于调用Channel的basicQos方法设置的上限。如果只是想要获取单条消息而不是持续订阅,那么就使用拉模式,但是不能通过在循环里调用basicGet来代替basicConsume,这会严重影响RabbitMQ的性能,一般还是使用basicConsume,具有较高的吞吐量。
2. 消费端的确认与拒绝
1. 为了确保消息能够可靠的从队列发送到消费者,RabbitMQ提供了消息确认机制。消费者在订阅队列时,需要指定是否自动消息确认(autoAck参数),当autoAck为false时,RabbitMQ会等到消费者显式的回复确认信号(通过Channel调用basicAck方法)之后才会从内存(或磁盘)中移除消息(先对消息添加删除标记,然后再进行删除),如果autoAck为true,那么当RabbitMQ发出消息之后,无论消费者是否正确接收到消息,都会自动确认,然后从内存(或磁盘)中移除消息。
2. 如果autoAck为false,那么RabbitMQ中队列中的消息就被分为两部分,一部分是等待推送给消费者的消息,另一部分是消息向消费者推送了,但还未收到返回确认信息。如果RabbitMQ推送一条消息后没有收到返回的确认信号,而且该消息推送的目标消费者已经断开,那么该条消息就会重新进入队列,等待投递给新消费者。RabbitMQ无法设置未确认消息的过期时间,判断此消息是否需要重写投递给消费者的依据是消费该消息的消费者是否断开,这就使RabbitMQ允许消费者在处理消息时可以持续很长时间。
3. 在RabbitMQ的web管理平台上可以看到队列中的“Ready”状态(对应的就是队列中等待推送给消费者的消息)和“Unacked”状态(队列中推送给了消费者但还未收到确认信号的消息)的消息数。
4. 消费端拒绝消息:当RabbitMQ推送消息给消费端后,消费端可以选择拒绝消息
- void basicReject(long deliveryTag, boolean requeue):消费端调用Channel的basicReject方法,会返回RabbitMQ一个个拒绝信号,该方法中的deliveryTag是消息的一个编号,而requeue表示是否允许被拒绝的消息重新进入队列,true表示运行,false则会直接丢弃。该方法只能拒绝一条消息
- void basicNack(long deliveryTag, boolean multiple, boolean requeue):消费端调用Channel的basicNack方法,可以批量拒绝消息,multiple参数为true表示删除消息编号为deliveryTag的消息之前所有未被当前消费者确认的消息
5. 消费端申请重新发送未被确定的消息:Basic.RecoverOk basicRecover(boolean requeue),该方法有另一个重载方法,缺省了requeue这个参数,默认为true。该方法会返回一个信号给RabbitMQ,使在此之前发送到消费端中的未被确认的消息重新进入队列等待推送给消费端,requeue这个参数表示是否将这条消息重新发送给同一个客户端,true代表是,false相反。
3. 关闭连接
1. 在Connection和Channel使用完成之后,需要调用close方法关闭,但如果Connection关闭后,Channel也会自动关闭。
2. Connection和Channel的生命周期:
- open:开启状态,代表对象可以使用
- closing:正在关闭
- closed:已关闭状态,Connection和Channel最终都会成为该状态
3. 相关的关闭方法:
- void addShutdownListener(ShutdownListener listener):为Connection和Channel添加一个关闭监听器,当Connection和Channel处于closed状态时就会调用该监听器中的方法
connection.addShutdownListener(new ShutdownListener() {
@Override
public void shutdownCompleted(ShutdownSignalException cause) {
/*当触发该监听器的时候,就可以获取ShutdownSignalException对象
* ShutdownSignalException对象中包括了关闭的原因,可以通过调用该对象的
* 方法分析关闭的原因,isHardError()可以判断是Channel的原因还是Connection
* getReason方法获取关闭原因信息
*/
if(cause.isHardError()){
Connection c=(Connection) cause.getReference();
if(!cause.isInitiatedByApplication()){
Method m=cause.getReason();
}
}else{
Channel ch=(Channel) cause.getReference();
}
}
});
- void removeShutdownListener(ShutdownListener listener):移除监听器
- ShutdownSignalException getCloseReason():获取Connection和Channel关闭的原因
- boolean isOpen():判断Connection和Channel是否处于open状态
- void close(int closeCode, String closeMessage):关闭Connection和Channel的方法
- void close(int closeCode, String closeMessage, int timeout):关闭Connection和Channel的方法,
- void close(int timeout):关闭Connection和Channel的方法
- void close():关闭Connection和Channel的方法