概述
說到消息通信,可能我們首先會想到的是郵箱,QQ,微信,短信等等這些通信方式,這些通信方式都有發送者,接收者,還有一箇中間存儲離線消息的容器。但是這些通信方式和我們要講的 RabbitMQ 的通信模型是不一樣的,比如和郵件的通信方式相比,郵件服務器基於 POP3/SMTP 協議,通信雙方需要明確指定,並且發送的郵件內容有固定的結構。
RabbitMQ 服務器基於 AMQP(全稱:Advanced Message Queuing Protocol 高級消息隊列協議) 協議,這個協議是不需要明確指定發送方和接收方的,而且發送的消息也沒有固定的結構,甚至可以直接存儲二進制數據,並且和郵件服務器一樣,也能存儲離線消息,最關鍵的是 RabbitMQ 既能夠以一對一的方式進行路由,還能夠以一對多的方式進行廣播
simple(無交換機)
點對點,一對一,一個生產者,一個消費者,直接和隊列交互
work(無交換機)
也是點對點,,一個生產者,兩個(多個)消費者,直接和隊列交互
多個消費者,默認是輪詢消費
弊端:引起處理快的消費者處於等待狀態,資源浪費。處理慢的消費者消息堆積
解決:限流+手工回覆模式
模擬兩個消費者處理消息速度不一樣:設置其中的一個消費者一次只接收處理一條消息,處理完才繼續接收,手動回覆消息確認
生產者(發送一萬條消息)
public class Send {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
public static final String QUEUE_NAME = "WORK_QUEUE";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.239.129");
connectionFactory.setPort(5672);
connectionFactory.setUsername("mocar");
connectionFactory.setPassword("mocar");
connectionFactory.setVirtualHost("/mocar");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
//隊列聲明,隊列不存在則創建,否則不做處理
//exclusive:獨有的,是否只允許當前連接使用此隊列
channel.queueDeclare(QUEUE_NAME,true,false,false,null);
for (int i = 1; i <= 10000; i++) {
String msg="第" + i+ "條隊列消息";
channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
}
System.out.println("發送完成");
}
}
消費者1
public class Receiving {
public static final String QUEUE_NAME = "WORK_QUEUE";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.239.129");
connectionFactory.setPort(5672);
connectionFactory.setUsername("mocar");
connectionFactory.setPassword("mocar");
connectionFactory.setVirtualHost("/mocar");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
//創建消費者
DefaultConsumer consumer = new DefaultConsumer(channel) {
//等待隊列有消息之後,自動回調
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//super.handleDelivery(consumerTag, envelope, properties, body);
//消費消息
String msg = new String(body,"utf-8");
System.out.println("消費者1處理消息:\t" + msg);
}
};
//讓消費者監聽隊列
//消費後ACK確認消息成功消費
channel.basicConsume(QUEUE_NAME,true,consumer);
System.out.println("成功消費");
}
}
消費者2,設置限流
ps:
//設置一次只接收處理一條消息,別一次把所有消息都給到這裏
//處理完纔會繼續處理下一條消息
channel.basicQos(1);
//手工回覆確認消息處理完畢
channel.basicAck(envelope.getDeliveryTag(),false);
//設置不自動ack確認
channel.basicConsume(QUEUE_NAME,false,consumer);
代碼如下
public class Receiving2 {
public static final String QUEUE_NAME = "WORK_QUEUE";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.239.129");
connectionFactory.setPort(5672);
connectionFactory.setUsername("mocar");
connectionFactory.setPassword("mocar");
connectionFactory.setVirtualHost("/mocar");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
//設置一次只接收處理一條消息,別一次把所有消息都給到這裏
//處理完纔會繼續處理下一條消息
channel.basicQos(1);
//創建消費者
DefaultConsumer consumer = new DefaultConsumer(channel) {
//等待隊列有消息之後,自動回調
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//super.handleDelivery(consumerTag, envelope, properties, body);
//消費消息
String msg = new String(body,"utf-8");
System.out.println("消費者2處理消息:\t" + msg);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
channel.basicAck(envelope.getDeliveryTag(),false);//手工回覆確認消息處理完畢
}
};
//讓消費者監聽隊列
//消費後ACK確認消息成功消費
channel.basicConsume(QUEUE_NAME,false,consumer);
System.out.println("成功消費");
}
}
消費者1處理了幾乎全部的消息
消費者2,一萬條消息中處理了10條左右,即限流成功
Publish/Subscribe(交換機,type=fanout)
圖片解析:
- 生產者聲明交換機。
- 消費者聲明隊列,並和交換機綁定。
簡單的發佈訂閱模式和上面的點對點方式有區別
區別描述:
- 發佈訂閱模式中,生產者只和交換機打交道,聲明交換機,然後將消息發送給交換機就完事了。
- 而消費者這邊,交換機需要先和隊列綁定,然後消費者從隊列中取出消息。
- 所以消費者中要先聲明交換機和隊列,然後交換機和隊列綁定,消費者才能消費消息
ps:第一次消息丟失
發佈訂閱模式,如果生產者先啓動,生產者第一次發送消息到交換機中,因爲交換機還沒有和任何隊列綁定關係,所以交換機不會將消息轉到對應的隊列中存儲,導致了第一次發送的消息丟失。
解決方案:可以在rabbitmq管理界面事先建立好交換機和隊列,然後進行綁定。
進入管理頁面:
創建交換機
創建兩個隊列
交換機和隊列綁定
點擊創建好的交換機
交換機分別綁定隊列1、2
完成綁定,這樣即使是第一次啓動,生產者發送的消息也不會丟失了
生產者(聲明交換機)
public class Send {
public static final String EXCHANGE_NAME = "FANOUT_EXCHANGE";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.239.129");
connectionFactory.setPort(5672);
connectionFactory.setUsername("mocar");
connectionFactory.setPassword("mocar");
connectionFactory.setVirtualHost("/mocar");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
//交換機聲明,交換機不存在則創建,否則不做處理
channel.exchangeDeclare(EXCHANGE_NAME,"fanout",true);
for (int i = 1; i <= 10; i++) {
String msg="第" + i+ "條隊列消息";
channel.basicPublish(EXCHANGE_NAME,"",null,msg.getBytes());
}
System.out.println("發送完成");
}
}
消費者1(聲明隊列並綁定交換機)
public class Receiving {
public static final String QUEUE_NAME = "FANOUT_EXCHANGE_QUEUE1";
public static final String EXCHANGE_NAME = "FANOUT_EXCHANGE";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.239.129");
connectionFactory.setPort(5672);
connectionFactory.setUsername("mocar");
connectionFactory.setPassword("mocar");
connectionFactory.setVirtualHost("/mocar");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
//聲明隊列
channel.queueDeclare(QUEUE_NAME,true,false,false,null);
//交換機和隊列綁定
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
//創建消費者
DefaultConsumer consumer = new DefaultConsumer(channel) {
//等待隊列有消息之後,自動回調
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//super.handleDelivery(consumerTag, envelope, properties, body);
//消費消息
String msg = new String(body,"utf-8");
System.out.println("消費者1處理消息:\t" + msg);
}
};
//讓消費者監聽隊列
//消費後ACK確認消息成功消費
channel.basicConsume(QUEUE_NAME,true,consumer);
System.out.println("成功消費");
}
}
消費者2(聲明隊列並綁定交換機)
public class Receiving2 {
public static final String QUEUE_NAME = "FANOUT_EXCHANGE_QUEUE2";
public static final String EXCHANGE_NAME = "FANOUT_EXCHANGE";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.239.129");
connectionFactory.setPort(5672);
connectionFactory.setUsername("mocar");
connectionFactory.setPassword("mocar");
connectionFactory.setVirtualHost("/mocar");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
//聲明隊列
channel.queueDeclare(QUEUE_NAME,true,false,false,null);
//交換機和隊列綁定
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
//設置一次只接收處理一條消息,別一次把所有消息都給到這裏
//處理完纔會繼續處理下一條消息
channel.basicQos(1);
//創建消費者
DefaultConsumer consumer = new DefaultConsumer(channel) {
//等待隊列有消息之後,自動回調
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//super.handleDelivery(consumerTag, envelope, properties, body);
//消費消息
String msg = new String(body,"utf-8");
System.out.println("消費者2處理消息:\t" + msg);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
channel.basicAck(envelope.getDeliveryTag(),false);//手工回覆確認消息處理完畢
}
};
//讓消費者監聽隊列
//消費後ACK確認消息成功消費
channel.basicConsume(QUEUE_NAME,false,consumer);
System.out.println("成功消費");
}
}
結果:生產者發送了10條消息,消費者1、2都拿到了10條消息
即:生產者發送多少消息,消費者1、2就拿到消息
Routing(交換機,type=direct)
即:生產者發佈消息到交換機,消費者按照不同的需求,訂閱不同類型的消息。
交換機按照隊列訂閱的消息類型轉發不同的消息到隊列中
和上面的發佈訂閱一樣,只是需要設置routingKey
- 生產者在發佈消息的時候設置好消息的routingKey類型
- 消費者在和交換機綁定的時候,設置需要訂閱的消息routingKey類型
生產者(發送三種不同類型RoutingKey的消息)
public class Send {
public static final String EXCHANGE_NAME = "DIRECT_EXCHANGE";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.239.129");
connectionFactory.setPort(5672);
connectionFactory.setUsername("mocar");
connectionFactory.setPassword("mocar");
connectionFactory.setVirtualHost("/mocar");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
//交換機聲明,交換機不存在則創建,否則不做處理
channel.exchangeDeclare(EXCHANGE_NAME,"direct",true);
channel.basicPublish(EXCHANGE_NAME,"nba",null,"渣渣怎麼可能進得去NBA".getBytes());
channel.basicPublish(EXCHANGE_NAME,"cba",null,"渣渣怎麼可能進得去CBA".getBytes());
channel.basicPublish(EXCHANGE_NAME,"ncba",null,"渣渣怎麼可能進得去NCBA".getBytes());
System.out.println("發送完成");
}
}
消費者1(設置訂閱的routingKey類型消息)
public class Receiving {
public static final String QUEUE_NAME = "DIRECT_EXCHANGE_QUEUE1";
public static final String EXCHANGE_NAME = "DIRECT_EXCHANGE";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.239.129");
connectionFactory.setPort(5672);
connectionFactory.setUsername("mocar");
connectionFactory.setPassword("mocar");
connectionFactory.setVirtualHost("/mocar");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
//聲明隊列
channel.queueDeclare(QUEUE_NAME,true,false,false,null);
//交換機和隊列綁定
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"nba");
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"cba");
//創建消費者
DefaultConsumer consumer = new DefaultConsumer(channel) {
//等待隊列有消息之後,自動回調
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//super.handleDelivery(consumerTag, envelope, properties, body);
//消費消息
String msg = new String(body,"utf-8");
System.out.println("消費者1處理消息:\t" + msg);
}
};
//讓消費者監聽隊列
//消費後ACK確認消息成功消費
channel.basicConsume(QUEUE_NAME,true,consumer);
System.out.println("成功消費");
}
}
消費者2(設置訂閱的routingKey類型消息)
public class Receiving2 {
public static final String QUEUE_NAME = "DIRECT_EXCHANGE_QUEUE2";
public static final String EXCHANGE_NAME = "DIRECT_EXCHANGE";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.239.129");
connectionFactory.setPort(5672);
connectionFactory.setUsername("mocar");
connectionFactory.setPassword("mocar");
connectionFactory.setVirtualHost("/mocar");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
//聲明隊列
channel.queueDeclare(QUEUE_NAME,true,false,false,null);
//交換機和隊列綁定
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"nba");
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"ncba");
//設置一次只接收處理一條消息,別一次把所有消息都給到這裏
//處理完纔會繼續處理下一條消息
channel.basicQos(1);
//創建消費者
DefaultConsumer consumer = new DefaultConsumer(channel) {
//等待隊列有消息之後,自動回調
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//super.handleDelivery(consumerTag, envelope, properties, body);
//消費消息
String msg = new String(body,"utf-8");
System.out.println("消費者2處理消息:\t" + msg);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
channel.basicAck(envelope.getDeliveryTag(),false);//手工回覆確認消息處理完畢
}
};
//讓消費者監聽隊列
//消費後ACK確認消息成功消費
channel.basicConsume(QUEUE_NAME,false,consumer);
System.out.println("成功消費");
}
}
結果:
Topics(交換機,type=topic)
即:按通配符的方式發佈和訂閱消息