RabbitMQ 提供了 6 种工作模式:
- 简单模式
- work queues
- Publish/Subscribe 发布与订阅模式
- Routing 路由模式
- Topics 主题模式
- RPC 远程调用模式(远程调用,不太算MQ;暂不作介绍)。
实例代码引用的依赖:
<dependencies>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>4.0.2</version>
</dependency>
</dependencies>
1 simple - 简单队列
①模型:
P:消息的生产者
红色的:队列
C:消费者
3个对象: 生产者 队列 RabbitMQ 消费者
②获取MQ连接
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class ConnectionUtils {
/*
* 获取 MQ 的连接
* */
public static Connection getConnection() throws Exception {
//1. 定义一个连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 获取服务地址
factory.setHost("127.0.0.1");
//3. AMQP 5672
factory.setPort(5672);
//4. vhost
factory.setVirtualHost("/vhost_lyd");
//5. 用户名
factory.setUsername("user_lyd");
//6. 密码
factory.setPassword("1234");
return factory.newConnection();
}
}
③生产者生产消息
import com.janet.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
public class Send {
private static final String QUEUE_NAME = "test_simple_queue";
public static void main(String[] args) throws Exception {
//1. 获取一个连接
Connection connection = ConnectionUtils.getConnection();
//2. 从连接中获取一个通道
Channel channel = connection.createChannel();
//3. 声明并创建一个队列,如果队列已存在,则使用这个队列
//第一个参数:队列名称ID
//第二个参数:是否持久化,false对应不持久化数据,MQ停掉数据就会丢失
//第三个参数:是否队列私有化,false则代表所有消费者都可以访问,true代表只有第一次拥有它的消费者才能一直使用,其他消费者不让访问
//第四个:是否自动删除,false代表连接停掉后不自动删除掉这个队列
//其他额外的参数, null
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
String msg = "hello simple";
//第一个参数:exchange 交换机,暂时用不到,在后面进行发布订阅时才会用到
//第二个参数:队列名称
//第三个参数:额外的设置属性
//最后一个参数:要传递的消息字节数组
channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
System.out.println("-----send msg:"+msg);
channel.close();
connection.close();
}
}
运行之后:
④消费者接收消息
import com.janet.util.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* 消费者获取消息
*/
public class Recv {
private static final String QUEUE_NAME = "test_simple_queue";
public static void main(String[] args) throws Exception {
//1. 获取连接
Connection connection = ConnectionUtils.getConnection();
//2. 创建通道
Channel channel = connection.createChannel();
//3. 队列声明
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
DefaultConsumer consumer = new DefaultConsumer(channel) {
//获取到达的消息
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "utf-8");
System.out.println("api recv: " + msg);
}
};
//4. 监听队列
//创建一个消息消费者
//第一个参数:队列名
//第二个参数代表是否自动确认收到消息,false代表手动编程来确认消息,这是MQ的推荐做法
//第三个参数要传入DefaultConsumer的实现类
channel.basicConsume(QUEUE_NAME,true,consumer);
}
}
⑤简单队列的不足:
耦合性高,生产者一一对应消费者(如果想有多个消费者消费队列中消息,这时候就不行了),如果消费者的队列名变更,这时候消费者得同时变更。
2 work queues - 工作队列
为什么会出现工作队列:
Simple队列 是一一对应的,而且我们实际开发,生产者发送消息是毫不费力的,而消费者一般是要跟跟业务相结合的,消费者接收到消息之后就需要处理,可能需要花费时间,这时候队列就挤压了很多消息。
模型:
1)轮询分发
生产者:
import com.janet.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
public class Send {
/*
* |-----C1
* P ----Queue------|
* |-----C2
* */
private static final String QUEUE_NAME = "test_work_queue";
public static void main(String[] args) throws Exception {
//1. 获取一个连接
Connection connection = ConnectionUtils.getConnection();
//2. 从连接中获取一个通道
Channel channel = connection.createChannel();
//3. 声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
for(int i = 0; i <40; i++){
String msg = "hello"+i;
System.out.println("[MQ] send:"+msg);
channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
Thread.sleep(i*20);
}
channel.close();
connection.close();
}
}
消费者1
import com.janet.util.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Recv1 {
private static final String QUEUE_NAME = "test_work_queue";
public static void main(String[] args) throws Exception {
//1. 获取连接
Connection connection = ConnectionUtils.getConnection();
//2. 创建通道
Channel channel = connection.createChannel();
//3. 队列声明
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//4. 定义一个消费者
Consumer consumer = new DefaultConsumer(channel) {
// 消息到达,触发这个消息
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "utf-8");
System.out.println("[1]: " + msg);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("[1] done");
}
}
};
//4. 监听队列
boolean autoAck = true;
channel.basicConsume(QUEUE_NAME,autoAck,consumer);
}
}
消费者2:
import com.janet.util.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Recv2 {
private static final String QUEUE_NAME = "test_work_queue";
public static void main(String[] args) throws Exception {
//1. 获取连接
Connection connection = ConnectionUtils.getConnection();
//2. 创建通道
final Channel channel = connection.createChannel();
//3. 队列声明
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//4. 定义一个消费者
Consumer consumer = new DefaultConsumer(channel) {
// 消息到达,触发这个消息
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "utf-8");
System.out.println("[2]: " + msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("[2] done");
}
}
};
//4. 监听队列
boolean autoAck = true;
channel.basicConsume(QUEUE_NAME,autoAck,consumer);
}
}
现象:
消费者1 和消费者 2 处理消息是一样快的。
消费者1:偶数
消费者2:奇数
这种方式叫做轮询分发(round-robin),结果就是不管谁忙谁清闲,都不会多给一个消息,任务消息总是你一个我一个。
2)公平分发 fair dipatch
生产者:
public class Send {
/*
* |-----C1
* P ----Queue------|
* |-----C2
* */
private static final String QUEUE_NAME = "test_work_queue";
public static void main(String[] args) throws Exception {
//1. 获取一个连接
Connection connection = ConnectionUtils.getConnection();
//2. 从连接中获取一个通道
Channel channel = connection.createChannel();
//3. 声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//每个消费者发送确认消息之前,消息队列不发送下一个消息到消费者,一次只能处理一个消息。
//如果不写basicQos(1),则自动MQ会将所有请求平均发送给所有消费者
//限制发送给同一个消费者 不得超过一条信息
channel.basicQos(1);
for(int i = 0; i <40; i++){
String msg = "hello"+i;
System.out.println("[MQ] send:"+msg);
channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
Thread.sleep(i*20);
}
channel.close();
connection.close();
}
}
消费者1:
public class Recv1 {
private static final String QUEUE_NAME = "test_work_queue";
public static void main(String[] args) throws Exception {
//1. 获取连接
Connection connection = ConnectionUtils.getConnection();
//2. 创建通道
final Channel channel = connection.createChannel();
//3. 队列声明
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
channel.basicQos(1); //保证每次只分发一个
//4. 定义一个消费者
Consumer consumer = new DefaultConsumer(channel) {
// 消息到达,触发这个消息
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "utf-8");
System.out.println("[1]: " + msg);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("[1] done");
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
//4. 监听队列
boolean autoAck = false; //自动应答 false
channel.basicConsume(QUEUE_NAME,autoAck,consumer);
}
}
消费者2
public class Recv2 {
private static final String QUEUE_NAME = "test_work_queue";
public static void main(String[] args) throws Exception {
//1. 获取连接
Connection connection = ConnectionUtils.getConnection();
//2. 创建通道
final Channel channel = connection.createChannel();
//3. 队列声明
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
channel.basicQos(1); //保证每次只分发一个
//4. 定义一个消费者
Consumer consumer = new DefaultConsumer(channel) {
// 消息到达,触发这个消息
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "utf-8");
System.out.println("[2]: " + msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("[2] done");
//手动回执
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
//4. 监听队列
boolean autoAck = false;
channel.basicConsume(QUEUE_NAME,autoAck,consumer);
}
}
生产者每次只发一个消息,消费者手动的确认消息,消费者消费完成之后,给生产者发消息,生产者再发下一个消息。
现象:消费者 2 比消费者 1 多 ,能者多劳。
3. 订阅模式 publish/subscribe
①模型
解读:
- 一个生产者,多个消费者
- 每一个消费者都有自己的队列
- 生产者没有直接把消息发送到队列 而是发送到了交换机 转发器(exchange)
- 每个队列都要绑定到交换机上,
- 生产者发送的消息 经过交换机 达到队列 就能实现一个消息被多个消费者消费
注册 -> 邮件 -> 短信 -
②生产者
public class Send {
private static final String EXCHANGE_NAME = "test_exchange_fanout";
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//声明交换机
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");//分发
//发送消息
String msg = "hello_ps";
channel.basicPublish(EXCHANGE_NAME,"",null,msg.getBytes());
System.out.println("Send: "+msg);
channel.close();;
connection.close();
}
}
消息去哪了?丢失了!! 因为交换机没有存储的能力,在RabbitMQ里面只有队列有存储能力。因为这时候还没有队列绑定到这个交换机,所以数据丢失了。
③消费者1
public class Recv {
private static final String QUEUE_NAME = "test_queue_fanout_email";
private static final String EXCHANGE_NAME = "test_exchange_fanout";
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtils.getConnection();
final Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//绑定队列到交换机转发器
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
channel.basicQos(1); //保证每次只分发一个
//定义一个消费者
Consumer consumer = new DefaultConsumer(channel) {
// 消息到达,触发这个消息
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "utf-8");
System.out.println("[1]: " + msg);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("[1] done");
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
//4. 监听队列
boolean autoAck = false; //自动应答 false
channel.basicConsume(QUEUE_NAME,autoAck,consumer);
}
}
④消费者2:
public class Recv2 {
private static final String QUEUE_NAME = "test_queue_fanout_sms";
private static final String EXCHANGE_NAME = "test_exchange_fanout";
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtils.getConnection();
final Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//绑定队列到交换机转发器
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
channel.basicQos(1); //保证每次只分发一个
//定义一个消费者
Consumer consumer = new DefaultConsumer(channel) {
// 消息到达,触发这个消息
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "utf-8");
System.out.println("[2]: " + msg);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("[2] done");
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
//4. 监听队列
boolean autoAck = false; //自动应答 false
channel.basicConsume(QUEUE_NAME,autoAck,consumer);
}
}
绑定了队列:
4. 路由模式
5. 主题模式 Topic
Topic exchange 将路由和某种模式匹配