RabbitMQ
前言:
RabbitMQ是一个消息代理 : 负责接受
并转发
消息。
放在生活的角度来看,RabbitMQ就好比快递站点,快递员将快递放到站点,并确信快递员最终会把快递交给收件人。
在这个例子里面,RabbitMQ好比快递站点,快递员。
消费者是收件人。
AMQP协议中的核心思想是生产者和消费者隔离,生产者不直接把消息发给队列消费。
生产者通常不知道是否一个消息会被发送到队列,或者说生产者不知道消费何时会被队列消费,怎么消费。就好比快递员把快递放到站点,他不知道你何时过来取。生产者只是将消息发送到一个交换机,先由exchange接受,然后exchange按照特定的策略转发到Queue进行存储,同理,消费者也是如此,exchange就类似一个交换机,转发各个消息分发到相应的队列中。
RabbitMQ的使用:
关于RabbitMQ的使用,官方推荐了七种工作模式,在早期版本,只有五种,后面添加了RPC的工作模式,然后又添加了一种。但是新的东西,总不是最核心的。我们从第一种工作模式开始讲起。。
主要介绍前6种,主要有:
简单模式:一个生产者,一个消费者,生产者将消息发送到队列,消费者从队列中获取消息
work模式:一个生产者,多个消费者,每个消费者获取到的消息唯一
消息订阅模式:一个生产者发送的消息会被多个消费者获取
路由模式:发送消息到交换机并且指定路由key,消费者将队列绑定到交换机时需要指定路由key
topic模式:将路由key和某个模式的进行匹配,此时队列绑定到一个模式上,“#”匹配一个词或多个词,*只匹配一个词
简单模式:
简单模式,只有生产者和消费者的概念:
生产者如下:
/**
* 获取MQ的链接
* 1.创建链接
* 2.创建通道
* 3.
*/
public class Sender {
private final static String QUEUE_NAME = "simple_queue";
public static void main(String[] args) throws IOException {
//1.创建连接
Connection connection = ConnectionUtil.getConnection();
//2.创建通道
Channel channel = connection.createChannel();
//声明队列,需要输入如下参数:
/**
* 队列名,声明是哪个队列
* 是否持久化,是否在消费后还存在
* 是否排外 即只允许该channel访问该队列 一般等于true的话,用于一个队列只能有一个消费者来消费的场景
* 是否自动删除 消费完消息后自动删除
* 其他属性 这里暂时为null,在其他模式会使用
*
*/
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//消息内容,参数说明如下:
/**
* 交换机,第一种工作模式,不需要交换机的概念
* 队列名,告诉它是哪个队列的消息
* 其他属性 路由,第一种工作模式不需要使用
* 消息body 通过getBytes方式获取
*/
String message = "Rabbit你好~";
channel.basicPublish("", QUEUE_NAME,null,message.getBytes());
System.out.println("[x]Sent '"+message + "'");
//最后关闭通关和连接,资源很珍贵,使用完毕请关闭
channel.close();
connection.close();
}
}
消费者如下:
批注:生产者是创建链接和创建通道,在消费者里需要获取链接和获取通道
public class Receiver {
private final static String QUEUE_NAME = "simple_queue";
public static void main(String[] args) throws IOException, InterruptedException {
//1.获取连接
Connection connection = ConnectionUtil.getConnection();
//2.获取通道
Channel channel = connection.createChannel();
//参数不再重复说明
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
QueueingConsumer consumer = new QueueingConsumer(channel);
channel.basicConsume(QUEUE_NAME, true, consumer);
while(true){
//该方法会阻塞
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println("[x] Received '"+message+"'");
}
}
}
工作模式:
工作模式的概念是,一个生产者,多个消费者,每个消费者获取到的消息唯一,好比发报纸,发报纸的是一个人,获取报纸的是很多人,每个人获取的报纸内容一致
同样只需要利用到生产者和消费的概念
//生产者
public class Sender {
private final static String QUEUE_NAME = "queue_work";
public static void main(String[] args) throws IOException, InterruptedException {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
for(int i = 0; i < 100; i++){
String message = "冬马小三" + i;
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println("[x] Sent '"+message + "'");
Thread.sleep(i*10);
}
channel.close();
connection.close();
}
}
设置两个消费者
//消费者1
public class Receiver1 {
private final static String QUEUE_NAME = "queue_work";
public static void main(String[] args) throws IOException, InterruptedException {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false,false, false,null);
//同一时刻服务器只会发送一条消息给消费者
channel.basicQos(1);
QueueingConsumer consumer = new QueueingConsumer(channel);
//关于手工确认 待之后有时间研究下
channel.basicConsume(QUEUE_NAME, false, consumer);
while(true){
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println("[x] Received1 '"+message+"'");
Thread.sleep(10);//让消费者1睡眠10ms
//返回确认状态
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
public class Receiver2 {
private final static String QUEUE_NAME = "queue_work";
public static void main(String[] args) throws IOException, InterruptedException {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false,false, false,null);
//同一时刻服务器只会发送一条消息给消费者
channel.basicQos(1);
QueueingConsumer consumer = new QueueingConsumer(channel);
channel.basicConsume(QUEUE_NAME, false, consumer);
while(true){
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println("[x] Received2 '"+message+"'");
Thread.sleep(1000);//让消费者2睡眠1000ms
//返回确认状态
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
测试结果:
1、 消费者1和消费者2获取到的消息内容是不同的,同一个消息只能被一个消费者获取。
2、 如果把睡眠解除,或者让消费者1,2睡眠一样长的事件,消费者1和消费者2获取到的消息的数量是相同的,一个是奇数一个是偶数。
如果把以下代码注释,那么消费者1,2最终会拿到一样多的消息,只不过消费者1需要执行很久。。也就是说,如果加了这行代码,限定服务器每次只给一个消息给消费者消费,不会造成堵塞,会造成执行快的消费者消费更多的消息。。。‘’能者多劳“
//同一时刻服务器只会发送一条消息给消费者
channel.basicQos(1);
消息确认:
消息成功从队列拿出,前面提到快递员根本不知道你什么时候过来站点拿快递,也就是说生产者不知道消费者何时归来消费。消费成功没有。
有两种模式可以提供消息确认:
模式1:自动确认
只要消息从队列中获取,无论消费者获取到消息后是否成功消息,都认为是消息已经成功消费。
模式2:手动确认
消费者从队列中获取消息后,服务器会将该消息标记为不可用状态,等待消费者的反馈,如果消费者一直没有反馈,那么该消息将一直处于不可用状态。
手动确认代码如下:
订阅模式
1.和工作模式不同的是:一个消息 被多个消费者消费
2.生产者不把消息直接发送到队列,而是发送到交换机
3.每个队列都需要和交换机进行绑定
4.生产者发送消息,经过交换机,到达队列,实现一个消息被多个消费者获取的目的
向交换机发送消息:
public class Send {
private final static String EXCHANGE_NAME = "test_exchange_fanout";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明exchange,交换机,类型必须是:fanout
//参数一是交换机名称
//参数二是交换机类型,必须fanout
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
// 消息内容
String message = "商品已经被更新,id=1001";
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());
System.out.println(" 后台系统: '" + message + "'");
channel.close();
connection.close();
}
}
批注:消息发送到没有队列绑定的交换机时,消息将丢失
因为,交换机没有存储消息的能力,消息只能存在在队列中。
消费者1:
public class Recv {
private final static String QUEUE_NAME = "test_queue_ps_1";
private final static String EXCHANGE_NAME = "test_exchange_fanout";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 绑定队列到交换机
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
// 同一时刻服务器只会发一条消息给消费者
channel.basicQos(1);
// 定义队列的消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
// 监听队列,手动返回完成
channel.basicConsume(QUEUE_NAME, false, consumer);
// 获取消息
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" 前台系统: '" + message + "'");
Thread.sleep(10);
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
消费者2:
public class Recv2 {
private final static String QUEUE_NAME = "test_queue_ps_2";
private final static String EXCHANGE_NAME = "test_exchange_fanout";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 绑定队列到交换机
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
// 同一时刻服务器只会发一条消息给消费者
channel.basicQos(1);
// 定义队列的消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
// 监听队列,手动返回完成
channel.basicConsume(QUEUE_NAME, false, consumer);
// 获取消息
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" 搜索系统: '" + message + "'");
Thread.sleep(10);
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
测试结果:
同一个消息被多个消费者获取
常见面试题:
使用订阅模式能否实现商品数据的同步?
答案:可以的。
后台系统就是消息的生产者。
前台系统和搜索系统是消息的消费者。
后台系统将消息发送到交换机中,前台系统和搜索系统都创建自己的队列,然后将队列绑定到交换机,即可实现。
剩余部分下次更新!!!!!
敬爱于明天