1.生产者(P-producer):生产消息,发送消息
2.消费者(C-consumer):消费自己已绑定队列中的消息
3.交换机(Exchanges):类似路由器(交换机通过路由键和队列进行绑定,分发消息到绑定的消息队列,本身并没有存储功能,所以,如果交换机没有绑定队列,则生产者生产消息一到交换机就消失了),type主要由以下五种
一、简单模型(未经过交换机,一个消息只能被消费一次)
1.简单模型:P—>Q—>C 一生一队一消
2.工作(work)消息模型: P—>Q---->多C 一生一队多消
二、订阅模型(都经过交换机,由交换机进行分发,这样一个消息才能被消费多次)
3.Fanout:广播,将消息分发给所有绑定交换机的队列 (发送到所绑定有的队列)
4.Direct(路由模型):定向,把消息分发给符合指定routing key 的队列 (指向性的发送到选中的绑定队列:生产者发送消息绑定routingKey(路由键),只转发到也绑定该routingKey的队列。如:消息绑定delete路由键能够匹配routingKey为delete的队列)
5. Topic(通配符模型):通配符,把消息分发给符合routing pattern的队列(Topic类型的与Direct,实质也是根据routingKey把消息路由到不同的队列。只不过Topic类型交换机可以让队列在绑定routingKey的时候使用通配符!如:消息绑定#.delete.# 能够匹配routingKey为delete.a,gg.delete.a.bc的队列)
注意: * 匹配一个词,# 匹配一个或多个词
4.消息队列(Queues):接收,存储,转发消息----->类似家门口的邮件邮箱(消息就像一封封信件,等着消费者去消费)
5.Channel:信道,消息读写等操作在信道中进行。客户端可以建立多个信道,每个信道代表一个会话任务。在Idea操作RabbitMQ时所有Api都是通过Channel来操作(1.获取连接,2.创建信道,3.通过信道声明队列,和消费者以及相关绑定)
生产者:发送消息到交换机
交换机:接收到消息,根据type分发到绑定的消息队列
消息队列:队列接收消息,进行存储,等待消费者(C)进行消费。
消费者:启动消费者,消费者就会去其绑定的队列进行消费。
第一,消费者与消息队列进行绑定,这样消费者才能消费该队列里的消息。
第二,消息队列与交换机进行绑定(通过路由键进行绑定,这样交换机能通过路由键确定发送到哪个绑定的队列)。
ACK(Acknowledge character):即是确认字符
RabbitMQ有一个ACK机制。当消费者获取消息后,会向RabbitMQ发送回执ACK,告知消息已经被接收,如果消费者消费消息后没有发送ACK,则消息不会被消费
当消费者开始进行消费时,消息队列中的消息会进入Unacked状态,只有接收到消费者的ACK,那么该消息才被消费,否则状态返回Ready等待消费
在代码中ACK分两种情况:
// 监听队列,第二个参数:是否自动进行消息确认。
channel.basicConsume(QUEUE_NAME, true, consumer);
// 手动进行ACK
channel.basicAck(envelope.getDeliveryTag(), false);
BabbitMQ在多个消费者消费队列中的消息时,默认的是平均分配,BabbitMQ会将消息先平均分配给多个消费者,然后让他们自己慢慢去消费
如90个消息 a:30,b:30 ,c:30
这样就会出现资源浪费,比如a的机子比较快,处理消息能力强,应该多消费一些,这样可以提升效率
// 设置每个消费者同时只能处理一条消息
channel.basicQos(1);
这样设置后就可以开启能者多劳模式了
如何避免消息丢失?
1) 消费者的ACK机制。可以防止程序异常导致消费者丢失消息。
2) 但是,如果在消费者消费之前,MQ就宕机了,消息就没了。
是可以将消息进行持久化呢?
我们创建的交换机、队列、消息默认都是储存在内存中,所以要将消息持久化,
前提是:队列、Exchange(交换机)都持久化
可以看到RabbitMQ自带默认的交换机Features都有个D标志=durable持久化
而我们的direct_exchange_test则没有,消息队列同理
如果交换机,队列没有设置durable持久化,那么服务器一旦宕机,那么交换机和队列就会消失了,因为他们没有持久化在服务器中,那么消息是存在队列中的也自然不会持久。
所以,要将消息持久化,前提是:队列、Exchange(交换机)都持久化
交换机持久化(在生产者声明交换机时进行设置,告诉RabbitMQ我的交换机要持久化储存,durable=true)
消息持久化(在生产者发送消息时进行设置,告诉RabbitMQ这个消息是持久化的)
队列持久化(在消费者声明队列时进行设置,告诉RabbitMQ这个队列是持久化的)
妈妈再也不用担心我的消息会丢失了!
简单生产者:
public class Send {
private final static String QUEUE_NAME = "simple_queue";
public static void main(String[] argv) throws Exception {
// 获取到连接
Connection connection = ConnectionUtil.getConnection();
// 从连接中创建通道,使用通道才能完成消息相关的操作
Channel channel = connection.createChannel();
// 声明(创建)队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 消息内容
String message = "Hello World!";
// 向指定的队列中发送消息
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println(" [x] Sent '" + message + "'");
//关闭通道和连接
channel.close();
connection.close();
}
简单消费者:
public class Recv {
private final static String QUEUE_NAME = "simple_queue";
public static void main(String[] argv) throws Exception {
// 获取到连接
Connection connection = ConnectionUtil.getConnection();
// 创建通道
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 定义队列的消费者
DefaultConsumer consumer = new DefaultConsumer(channel) {
// 获取消息,并且处理,这个方法类似事件监听,如果有消息的时候,会被自动调用
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
byte[] body) throws IOException {
// body 即消息体
String msg = new String(body);
System.out.println(" [x] received : " + msg + "!");
}
};
// 监听队列,第二个参数:是否自动进行消息确认。
channel.basicConsume(QUEUE_NAME, true, consumer);
}
}
工作(work)模型生产者:
public class Send {
private final static String QUEUE_NAME = "test_work_queue";
public static void main(String[] argv) throws Exception {
// 获取到连接
Connection connection = ConnectionUtil.getConnection();
// 获取通道
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 循环发布任务
for (int i = 0; i < 50; i++) {
// 消息内容
String message = "task .. " + i;
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println(" [x] Sent '" + message + "'");
Thread.sleep(i * 2);
}
// 关闭通道和连接
channel.close();
connection.close();
}
}
工作(work)模型消费者:
public class Recv {
private final static String QUEUE_NAME = "test_work_queue";
public static void main(String[] argv) throws Exception {
// 获取到连接
Connection connection = ConnectionUtil.getConnection();
// 获取通道
final Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 设置每个消费者同时只能处理一条消息
channel.basicQos(1);
// 定义队列的消费者
DefaultConsumer consumer = new DefaultConsumer(channel ) {
// 获取消息,并且处理,这个方法类似事件监听,如果有消息的时候,会被自动调用
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
byte[] body) throws IOException {
// body 即消息体
String msg = new String(body);
System.out.println(" [消费者1] received : " + msg + "!");
try {
// 模拟完成任务的耗时:1000ms
Thread.sleep(1000);
} catch (InterruptedException e) {
}
// 手动ACK
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
// 监听队列。
channel.basicConsume(QUEUE_NAME, false, consumer);
}
}
fonout类型交换机生产者:
public class Send {
private final static String EXCHANGE_NAME = "fanout_exchange_test";
public static void main(String[] argv) throws Exception {
// 获取到连接
Connection connection = ConnectionUtil.getConnection();
// 获取通道
Channel channel = connection.createChannel();
// 声明exchange,指定类型为fanout
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
// 消息内容
String message = "Hello everyone";
// 发布消息到Exchange
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());
System.out.println(" [生产者] Sent '" + message + "'");
channel.close();
connection.close();
}
}
fonout消费者:
//消费者1
public class Recv {
private final static String QUEUE_NAME = "fanout_exchange_queue_1";
private final static String EXCHANGE_NAME = "fanout_exchange_test";
public static void main(String[] argv) throws Exception {
// 获取到连接
Connection connection = ConnectionUtil.getConnection();
// 获取通道
Channel channel = connection.createChannel();
// 声明队列
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 envelope, BasicProperties properties,
byte[] body) throws IOException {
// body 即消息体
String msg = new String(body);
System.out.println(" [消费者1] received : " + msg + "!");
}
};
// 监听队列,自动返回完成
channel.basicConsume(QUEUE_NAME, true, consumer);
}
}
direct类型交换机生产者:
public class Send {
private final static String EXCHANGE_NAME = "direct_exchange_test";
public static void main(String[] argv) throws Exception {
// 获取到连接
Connection connection = ConnectionUtil.getConnection();
// 获取通道
Channel channel = connection.createChannel();
// 声明exchange,指定类型为direct
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
// 消息内容
String message = "商品删除了, id = 1001";
// 发送消息,并且指定routing key 为:insert ,代表新增商品,并声明消息持久化
channel.basicPublish(EXCHANGE_NAME, "delete", MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());
System.out.println(" [商品服务:] Sent '" + message + "'");
channel.close();
connection.close();
}
}
direct消费者:
public class Recv {
private final static String QUEUE_NAME = "direct_exchange_queue_1";
private final static String EXCHANGE_NAME = "direct_exchange_test";
public static void main(String[] argv) throws Exception {
// 获取到连接
Connection connection = ConnectionUtil.getConnection();
// 获取通道
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 绑定队列到交换机,同时指定需要订阅的routing key。假设此处需要update和delete消息
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "update");
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "delete");
// 定义队列的消费者
DefaultConsumer consumer = new DefaultConsumer(channel) {
// 获取消息,并且处理,这个方法类似事件监听,如果有消息的时候,会被自动调用
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
byte[] body) throws IOException {
// body 即消息体
String msg = new String(body);
System.out.println(" [消费者1] received : " + msg + "!");
}
};
// 监听队列,自动ACK
channel.basicConsume(QUEUE_NAME, true, consumer);
}
}
topic类型交换机生产者:
public class Send {
private final static String EXCHANGE_NAME = "topic_exchange_test";
public static void main(String[] argv) throws Exception {
// 获取到连接
Connection connection = ConnectionUtil.getConnection();
// 获取通道
Channel channel = connection.createChannel();
// 声明exchange,指定类型为topic,并声明交换机持久化
channel.exchangeDeclare(EXCHANGE_NAME, "topic",true);
// 消息内容
String message = "新增商品 : id = 1001";
// 发送消息,并且指定routing key 为:insert ,代表新增商品
channel.basicPublish(EXCHANGE_NAME, "item.insert", null, message.getBytes());
System.out.println(" [商品服务:] Sent '" + message + "'");
channel.close();
connection.close();
}
}
topic消费者:
public class Recv {
private final static String QUEUE_NAME = "topic_exchange_queue_1";
private final static String EXCHANGE_NAME = "topic_exchange_test";
public static void main(String[] argv) throws Exception {
// 获取到连接
Connection connection = ConnectionUtil.getConnection();
// 获取通道
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 绑定队列到交换机,同时指定需要订阅的routing key。需要 update、delete
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "item.update");
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "item.delete");
// 定义队列的消费者
DefaultConsumer consumer = new DefaultConsumer(channel) {
// 获取消息,并且处理,这个方法类似事件监听,如果有消息的时候,会被自动调用
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
byte[] body) throws IOException {
// body 即消息体
String msg = new String(body);
System.out.println(" [消费者1] received : " + msg + "!");
}
};
// 监听队列,自动ACK
channel.basicConsume(QUEUE_NAME, true, consumer);
}
}