1、Publish/Subscribe模式
每个消费者都有属于自己的队列,生产者将消息发送给交换器,然后交换器负责将消息分发给对应的队列。
常见的交换器分发方式有
fanout:会把所有发送到该交换器的消息路由到所有与该交换器绑定的队列中。 direct:将消息路由到RoutingKey和BindingKey完全匹配的队列中。 topic:将消息路由到RoutingKey和BindingKey相匹配的队列中。 # 匹配多个单词,可以是零个 * 匹配单个单词 对于没有匹配上的参数,会丢弃或者返回给生产者(需要设置mandatory) headers:headers 类型的交换器不依赖于路由键的匹配规则来路由消息,而是根据发送的消息内容中的 headers 属性进行匹配。在绑定队列和交换器时制定一组键值对 当发送消息到交换器时, RabbitM 会获取到该消息的 headers (也是一个键值对的形式) ,对比其中的键值对是否完全 匹配队列和交换器绑定时指定的键值对,如果完全匹配则消息会路由到该队列,否则不会路由 到该队列 headers 类型的交换器性能会很差,而且也不实用,基本上不会看到它的存在。
fanout方式:
定义一个生产者和两个消费者。
import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import ink.lmsy.mq_01.simplequeue.ConnectionUtils; import java.io.IOException; import java.util.concurrent.TimeoutException; public class PublishSubscribeProducer { private static final String EXCHANGE_NAME = "exchange_1"; public static void main(String[] args) throws IOException, TimeoutException { Connection connection = ConnectionUtils.getConnection(); Channel channel = connection.createChannel(); // 声明一个交换器 channel.exchangeDeclare(EXCHANGE_NAME, "fanout", false, false, false, null); String message = "message"; System.out.println("send message"); channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes()); channel.close(); connection.close(); } }
生产者中指定了消费者的名称为exchange_1,然后声明了一个交换器,接着向该交换器发送消息
方法详解:
DeclareOk exchangeDeclare(String exchange, String type) throws IOException; DeclareOk exchangeDeclare(String exchange, String type, boolean durable) throws IOException; DeclareOk exchangeDeclare(String exchange, String type, boolean durable, boolean autoDelete, Map<String, Object> arguments) throws IOException; DeclareOk exchangeDeclare(String exchange, String type, boolean durable, boolean autoDelete, boolean internal, Map<String, Object> arguments) throws IOException;
参数详解 exchange:表示交换器的名称 type:表示交换器的类型,常见的类型由fanout、direct、topic,header一般不用 durable:true表示持久化,false表示非持久化。持久化可以将交换器存盘。 autoDelete:true表示自动删除。自动删除的前提:至少有一个队列或交换器和当前交换器绑定,然后所有的 的队列或交换器与该交换器解绑,才会删除。 internal:true表示为内置的交换器,即多个交换器连接起来,该交换器和其他交换器连接,不和队列连接。 arguments:一些其他参数
方法详解:
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:路由键,交换器会根据该路由键将消息发送到对应的队列中 mandatory:如果设置为true,则如果根据找不到相应的队列,该消息会返回给生产者。false该消息会被丢弃 immediate:设置为true,则交换器将消息发送到队列上,如果交换器发现该队列上没有消费者,那么会将该消 息重新返回给生产者。
该消费者定义了一个属于自己的队列queue_1,然后声明了交换器和队列。接着将交换器和队列绑定在一起
import com.rabbitmq.client.*; import ink.lmsy.mq_01.simplequeue.ConnectionUtils; import java.io.IOException; import java.util.concurrent.TimeoutException; public class PublishSubscribeConsumer1 { private static final String EXCHANGE_NAME = "exchange_1"; private static final String QUEUE_NAME = "queue_1"; public static void main(String[] args) throws IOException, TimeoutException { Connection connection = ConnectionUtils.getConnection(); Channel channel = connection.createChannel(); channel.exchangeDeclare(EXCHANGE_NAME, "fanout", false, false, false, null); channel.queueDeclare(QUEUE_NAME, false, false, false, null); channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ""); DefaultConsumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(java.lang.String consumerTag, Envelope envelop, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println("C1 receive message is : " + new java.lang.String(body)); } }; channel.basicConsume(QUEUE_NAME, true, consumer); } }
方法详解:
com.rabbitmq.client.AMQP.Queue.BindOk queueBind(String queue, String exchange, String routingKey) throws IOException; com.rabbitmq.client.AMQP.Queue.BindOk queueBind(String queue, String exchage, String routingKey, Map<String, Object> arguments) throws IOException;
参数详解: queue:队列和交换器绑定过程中,队列的名称 exchange:队列和交换器绑定过程中,交换器的名称 routingKey:绑定交换器和队列的路由键。生产者可以通过给消息加上路由键来对消息进行路由,指定消息发送 到哪一个队列上。 argumeng: 一些其他参数
import com.rabbitmq.client.*; import ink.lmsy.mq_01.simplequeue.ConnectionUtils; import java.io.IOException; import java.util.concurrent.TimeoutException; public class PublishSubscribeConsumer2 { private static final String EXCHANGE_NAME = "exchange_1"; private static final String QUEUE_NAME = "queue_2"; public static void main(String[] args) throws IOException, TimeoutException { Connection connection = ConnectionUtils.getConnection(); Channel channel = connection.createChannel(); channel.exchangeDeclare(EXCHANGE_NAME, "fanout", false, false, false, null); channel.queueDeclare(QUEUE_NAME, false, false, false, null); channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ""); DefaultConsumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelop, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println("C2 receive message is : " + new String(body)); } }; channel.basicConsume(QUEUE_NAME, true, consumer); } }
2、消费消息
上述方法详解中,还剩下一个basicConsume方法没有讲解,接下来讲解一下消息是怎么被消费的。
消费模式分为两种
推(Push)模式:使用Basic.Consume
拉(Pull)模式:使用Basic.Get
推模式
在推模式中,可以使用持续订阅的方式来消费消息。接收消息一般会通过实现Consumer接口或继承DefaultConsumer类来实现。
方法详解:
String basicConsume(String queue, Consumer consumer) throws IOException; String basicConsume(String queue, boolean autoAck, Consumer consumer) throws IOException; String basicConsume(String queue, boolean autoAck, Map<String, Object> arguments, Consumer consumer) throws IOException; String basicConsume(String queue, boolean autoAck, String consumerTag, Consumer consumer) throws IOException; String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map<String, Object> arguments, Consumer consumer) throws IOException;
参数详解: queue:表示消费者从哪个队列消费消息 auoAck:设置是否自动确认,即消息收到后消费者是否需要自动确认。设置为false表示不自动确认。 consumerTag:消费者标签,用于区分多个消费者 noLocal:设置为true,表示同一个connection中的生产者不可以将消息放给该connection下的消费者 exclusive:是否设置为排他 consumer:是一个回调函数,表示得到消息后该执行什么操作
拉模式:
通过channel.basicGet方法可以单条获取消息。
GetResponse basicGet(String queue, boolean aotuAck) throws IOException
注意不要将该方法放在循环下,循环的获取消息这样会降低性能。