在RabbitMQ入门中我们提到RabbitMQ几种不同的通讯方式,
pom
<!-- com.rabbitmq/amqp-client -->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.9.0</version>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
RabbitMQClient
public class RabbitMQClient {
public static Connection getConnection(){
ConnectionFactory factory = new ConnectionFactory();
//指定IP和端口号
factory.setHost("192.168.0.109");
factory.setPort(5672);
//用户名和密码
factory.setUsername("test");
factory.setPassword("test");
//指定vHost
factory.setVirtualHost("/test");
Connection connection = null;
try {
connection = factory.newConnection();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
return connection;
}
}
在操作之前先提一下 ACK的消息确认机制是消费者从RabbitMQ收到消息并处理完成后,反馈给RabbitMQ,RabbitMQ收到反馈后才将此消息从队列中删除。
1、"Hello World!"
它是一个生产者、一个消费者
Publisher
public class Publisher {
@Test
public void publish() throws IOException, TimeoutException {
Connection connection = RabbitMQClient.getConnection();
//创建Channel
Channel channel = connection.createChannel();
String msg = "Hello-World";
//发布消息到exchange,同时指定路由规则
/**
* 参数1 : 指定exchange ""表示使用默认的exchange
* 参数2 : 指定路由规则,使用具体的队列名称
* 参数3 : 指定传递的消息所携带的properties
* 参数4 : 指定发布的具体消息 byte[]类型
* PS : exchange不会把消息持久化到本地,Queue才会持久化消息
*/
channel.basicPublish("","HelloWorld",null,msg.getBytes());
System.out.println("生产者发布消息成功!");
//释放资源
channel.close();
connection.close();
}
}
Consumer
public class Consumer {
@Test
public void consume() throws IOException, TimeoutException {
Connection connection = RabbitMQClient.getConnection();
Channel channel = connection.createChannel();
//声明队列 HelloWorld
/**
* 参数1 : 指定队列名称
* 参数2 : 当前队列是否需要持久化
* 参数3 : 是否排外(connection.close()-当前队列会被自动删除,当前队列只能被一个消费者消费)
* 参数4 : 如果队列没有消费者在消费,则自动删除
* 参数5 : 指定当前队列的其他信息
*/
channel.queueDeclare("HelloWorld",true,false,false,null);
//开启监听Queue
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("接收到消息:" + new String(body,"UTF-8"));
}
};
/**
* 参数1:指定消费哪个队列
* 参数2:指定是否自动ACK(true,接收到消息后,会立即告诉RabbitMQ)
* 参数3:指定消费回调
*/
channel.basicConsume("HelloWorld",true,defaultConsumer);
System.in.read();
//释放资源
channel.close();
connection.close();
}
}
分别启动消费者、生产者
2、Work Queues
一个生产者有两个消费者
Publisher
public class Publisher {
@Test
public void publish() throws IOException, TimeoutException {
//获取Connection
Connection connection = RabbitMQClient.getConnection();
//创建Channel
Channel channel = connection.createChannel();
//发布消息到exchange,同时指定路由的规则
for (int i = 0; i < 10; i++) {
String msg = "Work Queues" + i;
channel.basicPublish("","WorkQueues",null,msg.getBytes());
}
System.out.println("生产者发布消息成功!");
//释放资源
channel.close();
connection.close();
}
}
对于两个消费者的代码和上面的一样,只需要把消费队列的名称改为 WorkQueues
依次启动消费者、生产者
根据结果可以看出,两位消费者平均消费了。 但是考虑到实际情况中消费能力不一样,调整代码再次测试。
将自动ACK改为手动ACK,并且模拟消费能力不一致,在每次消费时让消费者休眠不同时间,Consumer2中的代码一致,只需要修改休眠时间为200
public class Consumer1 {
@Test
public void consume() throws IOException, TimeoutException {
Connection connection = RabbitMQClient.getConnection();
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare("WorkQueues",true,false,false,null);
//指定当前消费者,一次消费多少条消息
channel.basicQos(1);
//开启监听Queue
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//为了体现消费能力不同,让两个消费者消费一次休眠不同的时间,这里2号休眠时间是1号的2倍
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1号消费者接收到消息:" + new String(body,"UTF-8"));
//手动ack
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
/**
* 参数1:指定消费哪个队列
* 参数2:指定是否自动ACK(true,接收到消息后,会立即告诉RabbitMQ)
* 参数3:指定消费回调
*/
channel.basicConsume("WorkQueues",false,defaultConsumer);
System.in.read();
//释放资源
channel.close();
connection.close();
}
}
依次启动消费者、生产者
3、Publish / Subscribe
一个消息发送给多个消费者,这种模式一般被称为“发布/订阅”模式。
很明显这里需要自己创建一个Exchange
Publisher
public class Publisher {
@Test
public void publish() throws IOException, TimeoutException {
Connection connection = RabbitMQClient.getConnection();
//创建Channel
Channel channel = connection.createChannel();
//创建Exchange——绑定某一个队列
/**
* 参数1:exchange的名称
* 参数2:指定exchange类型
*/
channel.exchangeDeclare("pubsub-exchange", BuiltinExchangeType.FANOUT);
channel.queueBind("pubsub-queue1","pubsub-exchange","");
channel.queueBind("pubsub-queue2","pubsub-exchange","");
String msg = "Hello-World";
//发布消息到exchange,同时指定路由规则
/**
/**
* 参数1 : 指定exchange ""表示使用默认的exchange
* 参数2 : 指定路由规则,使用具体的队列名称
* 参数3 : 指定传递的消息所携带的properties
* 参数4 : 指定发布的具体消息 byte[]类型
* PS : exchange不会把消息持久化到本地,Queue才会持久化消息
*/
for (int i = 0; i < 10; i++) {
String msg = "pubsub" + i;
channel.basicPublish("pubsub-exchange","Work",null,msg.getBytes());
}
System.out.println("生产者发布消息成功!");
//释放资源
channel.close();
connection.close();
}
}
对于交换机的类型,在RabbitMQ入门篇中也有详细描述
而消费者中的代码基本不变,只需要把队列名称对应修改即可
依次启动消费者,生产者 两个消费者都消费十条
4、Routing
它是一种完全按照routing key(路由关键字)进行投递的:当消息中的routing key和队列中的binding key完全匹配时,才进行会将消息投递到该队列中
Publisher
public class Publisher {
@Test
public void publish() throws IOException, TimeoutException {
//获取Connection
Connection connection = RabbitMQClient.getConnection();
//创建Channel
Channel channel = connection.createChannel();
//创建exchange routing-queue-info routing-queue-error
channel.exchangeDeclare("routing-exchange", BuiltinExchangeType.DIRECT);
//绑定queue
channel.queueBind("routing-queue-error","routing-exchange","ERROR");
channel.queueBind("routing-queue-info","routing-exchange","INFO");
//发布消息到exchange,同时指定路由的规则
channel.basicPublish("routing-exchange","ERROR",null,"ERROR".getBytes());
channel.basicPublish("routing-exchange","INFO",null,"INFO1".getBytes());
channel.basicPublish("routing-exchange","INFO",null,"INFO2".getBytes());
channel.basicPublish("routing-exchange","INFO",null,"INFO3".getBytes());
System.out.println("生产者发布消息成功!");
//释放资源
channel.close();
connection.close();
}
}
Comsumer
public class Consumer1 {
@Test
public void consume() throws IOException, TimeoutException {
Connection connection = RabbitMQClient.getConnection();
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare("routing-queue-error",true,false,false,null);
//指定当前消费者,一次消费多少条消息
channel.basicQos(1);
//开启监听Queue
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者ERROR接收到消息:" + new String(body,"UTF-8"));
//手动ack
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
/**
* 参数1:指定消费哪个队列
* 参数2:指定是否自动ACK(true,接收到消息后,会立即告诉RabbitMQ)
* 参数3:指定消费回调
*/
channel.basicConsume("routing-queue-error",false,defaultConsumer);
System.in.read();
//释放资源
channel.close();
connection.close();
}
}
消费者INFO就不贴了,把对应的队列名称修改即可
如此不同的消费者只会接收属于自己的消息
5、Topics
topic exchange和direct exchange类似,都是通过routing key和binding key进行匹配,不同的是topic exchange可以为routing key设置多重标准。
direct路由器类似于sql语句中的精确查询;topic 路由器有点类似于sql语句中的模糊查询。
Direct:完全根据key进行投递的,例如,绑定时设置了routing key为”abc”,那么客户端提交的消息,只有设置了key为”abc”的才会投递到队列。
Topic:对key进行模式匹配后进行投递,符号”#”匹配一个或多个词,符号”*”匹配正好一个词。例如”abc.#”匹配”abc.def.ghi”,”abc.*”只匹配”abc.def”。
Publisher
public class Publisher {
@Test
public void publish() throws IOException, TimeoutException {
//获取Connection
Connection connection = RabbitMQClient.getConnection();
//创建Channel
Channel channel = connection.createChannel();
//创建exchange绑定队列 topic-queue1 topic-queue2
channel.exchangeDeclare("topic-exchange", BuiltinExchangeType.TOPIC);
channel.queueBind("topic-queue1","topic-exchange","*.red.*");
channel.queueBind("topic-queue2","topic-exchange","fast.#");
channel.queueBind("topic-queue2","topic-exchange","*.*.rabbit");
//发布消息到exchange
channel.basicPublish("topic-exchange","fast.red.dog",null,"很快红色猴子".getBytes());
channel.basicPublish("topic-exchange","slow.black.cat",null,"黑慢猫".getBytes());
channel.basicPublish("topic-exchange","fast.white.rabbit",null,"快白兔".getBytes());
System.out.println("生产者发布消息成功!");
//释放资源
channel.close();
connection.close();
}
}
Consumer1
public class Consumer1 {
@Test
public void consume() throws IOException, TimeoutException {
Connection connection = RabbitMQClient.getConnection();
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare("topic-queue1",true,false,false,null);
//指定当前消费者,一次消费多少条消息
channel.basicQos(1);
//开启监听Queue
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("1号消费者喜欢红色动物:" + new String(body,"UTF-8"));
//手动ack
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
/**
* 参数1:指定消费哪个队列
* 参数2:指定是否自动ACK(true,接收到消息后,会立即告诉RabbitMQ)
* 参数3:指定消费回调
*/
channel.basicConsume("topic-queue1",false,defaultConsumer);
System.in.read();
//释放资源
channel.close();
connection.close();
}
}
Consumer2使用topic-queue2