前两篇文章我们简单介绍了消息中间件以及RabbitMQ的一些概念,本节我们主要介绍RabbitMQ的特性以及使用。 首先说一下,我使用的RabbitMQ的版本以及安装。
RabbitMQ的连接 安装好RabbitMQ后如何使用RabbitMQ呢,首先如何连接RabbitMQ
public static Connection getRabbitmqConnection() throws Exception{ ConnectionFactory factory = new ConnectionFactory(); factory.setHost(IP); factory.setPort(5672); factory.setUsername(USERNAME); factory.setPassword(PASSWORD); factory.setVirtualHost("my_vhost"); Connection connection = factory.newConnection(); return connection; }
tips:Connection 可以用来创建多个Channel,但是Channel实例不能线程间共享,应用程序为每一个线程开辟一个Channel。但是Channel的操作可以并发运行,网络上的通信帧交错,会影响 发送方确认机制运行,所以多线程间共享Channel实例是非线程安全的。
direct交换器 direct交换器的结构图: 在这里插入图片描述 将消息路由到BindingKey和RoutingKey完全相同的队列中。
direct模式生产者 public class DirectProvider { public static final String EXCHANGE_NAME = "direct_exchange"; public static void main(String[] args) throws Exception{ Connection connection = Utils.getRabbitmqConnection(); Channel channel = connection.createChannel(); channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT); String[] routingKeys = {"direct1","direct2","direct3"}; for (int i=0;i<3;i++){ String routingKey = routingKeys[i]; String msg = "hello rabbitmq "+i; channel.basicPublish(EXCHANGE_NAME,routingKey,null,msg.getBytes()); log.info("send message,routingKey:"+routingKey+",message:"+msg); } channel.close(); connection.close(); } }
代码分析 Connection connection = Utils.getRabbitmqConnection(); Channel channel = connection.createChannel(); channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
(1)首先创建一个RabbitMQ的连接 (2)然后创建一个通道Channel (3)第三行创建一个交换器(非持久化。非自动删除、绑定类型为direct) exchangeDeclare 有多个重载方法,这些重载方法中都是由下面这个方法中缺省某些参数构成。
public DeclareOk exchangeDeclare(String exchange, String type, boolean durable, boolean autoDelete, boolean internal, Map<String, Object> arguments) throws IOException {
返回DeclareOK,用来标识成功声明一个交换器。
参数说明: exchange:交换器的名称 type:交换器类型,常见的如:direct、topic、fanout等 durable:是否持久化。true表示持久化,反之非持久化。持久化可以将交换器存盘,服务器重启时不会丢失数据。 autoDelete:是否自动删除。true为自动删除,前提是至少有一个队列或者交换器与这个交换器绑定,之后解绑所有与之绑定的队列和交换器。 internal:是否内置,true是内置交换器,客户端程序无法直接发送消息到这个交换器,只能通过交换器路由到交换器。(默认为false) argument:其他结构化参数。
public void basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body) throws IOException
public void basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate,BasicProperties props, byte[] body) throws IOException
channel.basicPublish(EXCHANGE_NAME,routingKey,null,msg.getBytes());
发送消息,可以使用Channel的basicPublish方法。为了更好地控制发送,可以使用mandatory这个参数,或者发一些特定属性的信息。
参数详细说明: exchange:交换器名称 RoutingKey:路由键 props:消息的基本属性,其中包括14个属性成员,contentType、contentEncoding、headers等 byte[] body:消息体
mandatory参数详解 mandatory参数作用是当消息传递过程中没有达到就将消息返回给生产者,当其为true时,交换器无法根据自身类型和路由键找到符合条件的队列,那么RabbitMQ就会调用Basic.Return命令将消息返回给生产者,当其为false时,消息直接丢弃。
public class DirectProvider { public static final String EXCHANGE_NAME = "direct_exchange"; public static void main(String[] args) throws Exception{ Connection connection = Utils.getRabbitmqConnection(); Channel channel = connection.createChannel(); channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT); String[] routingKeys = {"direct1","direct2","direct3"}; for (int i=0;i<3;i++){ String routingKey = routingKeys[i]; String msg = "hello rabbitmq "+i; //channel.basicPublish(EXCHANGE_NAME,routingKey,null,msg.getBytes()); channel.basicPublish(EXCHANGE_NAME,"",true, MessageProperties.PERSISTENT_TEXT_PLAIN, msg.getBytes()); channel.addReturnListener(new ReturnListener() { @Override public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties basicProperties, byte[] bytes) throws IOException { String message = new String(bytes); System.out.println("返回的结果:"+message); } }); log.info("send message,routingKey:"+routingKey+",message:"+msg); } channel.close(); connection.close(); }
如上代码所示:
channel.basicPublish(EXCHANGE_NAME,"",true, MessageProperties.PERSISTENT_TEXT_PLAIN, msg.getBytes()); channel.addReturnListener(new ReturnListener() { @Override public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties basicProperties, byte[] bytes) throws IOException { String message = new String(bytes); System.out.println("返回的结果:"+message); } });
上述代码生产者没有成功将消息路由到队列,此时RabbitMQ通过Basic.Retrurn 返回消息,生产者客户端ReturnListener监听到事件。
immediate参数详解 当immediate参数为true时,如果交换器将消息路由到队列发现队列上没有消费者,那么这条消息就不会存入队列。当与路由键匹配的所有队列没有消费者时,会通过Basic.Return 返回生产者。
总结:mandatory参数告诉服务器至少将消息路由到一个队列,否则返回生产者。immediate参数告诉服务器,如果消息关联的队列有消费者,立即投递;如果没有就将消息返回生产者,不用将消息存入队列等待消费者。(RabbitMQ 3.0后去掉了immediate参数,会影响性能)
direct交换器消费者
public class DirectConsumer { public static void main(String[] args) throws Exception{ Connection connection = Utils.getRabbitmqConnection(); Channel channel = connection.createChannel(); channel.exchangeDeclare(DirectProvider.EXCHANGE_NAME, BuiltinExchangeType.DIRECT); String queueName = "directExchangeQueue"; String bindingkey = "direct1"; // 队列名 // durable 设置是否持久化。为true则设置队列为持久化。持久化的队列会存盘 // exclusive 设置是否排他。为true则设置队列为排他的,如果一个队列被声明为排他队列,该队列仅对首次声明它的连接可见,并在连接断开时自动删除 // autoDelete 设置是否自动删除。为true则设置队列为自动删除 // arguments 设置队列的其他一些参数 channel.queueDeclare(queueName, false, false, false ,null); channel.queueBind(queueName, DirectProvider.EXCHANGE_NAME, bindingkey); 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"); log.info("get message, routingKey: {}, message: {}", envelope.getRoutingKey(), message); } }; channel.basicConsume(queueName , true, consumer); } }
代码分析 public DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments) throws IOException { public DeclareOk queueDeclare()
不带任何参数的queueDeclare方法创建一个队列(有RabbitMQ命名、排他的、自动删除的、非持久化的)
参数详细说明: queue:队列名称 durable:是否持久化,true为持久化,队列持久化会存盘,在服务器重启时保证不丢失信息。 exclusive:是否排他,true为排他,如果队列声明为排他,该队列仅第一次声明的连接可见,连接断开时自动删除(排他队列基于Connection可见,同一个连接的不同Channel是可以访问同一个连接创建的排他队列) autoDelete:是否自动删除。true为自动删除,前提是至少有一个队列或者交换器与这个交换器绑定,之后解绑所有与之绑定的队列和交换器。 argument:设置队列的参数,比如:x-message-ttl、x-expires等
public com.rabbitmq.client.AMQP.Queue.BindOk queueBind(String queue, String exchange, String routingKey, Map<String, Object> arguments) throws IOException {
将队列和交换器绑定。
参数详细说明: queue:队列名称 exchange:交换器的名称 routingKey:用来绑定队列和交换器的路由键 argument:定义绑定的一些参数
//完全参数 public String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map<String, Object> arguments, Consumer callback) throws IOException {
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"); log.info("get message, routingKey: {}, message: {}", envelope.getRoutingKey(), message); } };
channel.basicConsume(queueName , true, consumer); //public String basicConsume(String queue, boolean autoAck, Consumer callback)
参数详细说明: queue:队列名称 autoAck:设置是否自动确认,建议false consumerTag:消费者标签,用于区分多个消费者 noLocal:设置为true表示不能将同一个Connection中生产者发送的消息传给这个Connection中的消费者 exclusive:设置是否排他 arguments:消费者的其他参数 callback:设置消费者回调函数。用来处理RabbitMQ推送的消息
消费端的消息确认 为了保证消息从队列可靠到达消费者,RabbitMQ同时也提供了消息去人机制。消费者在订阅队列时,可以指定autoAck参数,当autoAck为false时,RabbitMQ会等待消费者显式回复确认信号后才从内存中移除消息。autoAck为true时,RabbitMQ会自动把发送出去的消息设置为去人,然后从内存删除,而不去管消费者是否真正消费了消息。
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"); try { // 当我们正常消息,手动ack后,消息就会从mq中删除 // multiple为false表示一条一条确认 channel.basicAck(envelope.getDeliveryTag(), false); //public void basicAck(long deliveryTag, boolean multiple) } catch (Exception e) { // 发生异常,发送nack,根据requeue参数来决定是将消息丢弃开始还是再重新放回队列 channel.basicNack(envelope.getDeliveryTag(), false, false); } log.info("get message, routingKey: {}, message: {}", envelope.getRoutingKey() ,message); } };
channel.basicConsume(queueName, false, consumer); //public String basicConsume(String queue, boolean autoAck, Consumer callback)
basicNack方法可以用来拒绝消息。
public void basicNack(long deliveryTag, boolean multiple, boolean requeue)
deliveryTag:可以看做消息的编号 multiple:如果设置为false,则表示拒绝编号为deliveryTag的这条消息 requeue:如果为true表示RabbitMQ将这条消息重新存入队列,发给下一个消费者,如果为false,则将这条消息立即从队列中移除,不会发给新的消费者