RabbitMQ
應用場景
1.RabbitMQ介紹
關於RabbitMQ的介紹詳情,中文文檔地址:http://rabbitmq.mr-ping.com/
特點:異步處理、應用解耦、流量削鋒、日誌處理
2.RabbitMQ安裝
詳情參考:https://blog.csdn.net/qq_41950069/article/details/81346675
添加用戶:
爲新建的用戶添加host:
3.Java中使用RabbitMQ
a.簡單隊列
耦合性高,生產者和消費者一一對應,隊列名變更的時候需要同時變更
代碼
<dependency> <groupId>com.rabbitmq</groupId> <artifactId>amqp-client</artifactId> <version>4.0.3</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.10</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.5</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> |
public class ConnectionUtil { public static Connection getConnection() throws IOException, TimeoutException { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("127.0.0.1"); factory.setPort(5672); factory.setVirtualHost("/vitual_h"); factory.setUsername("wen"); factory.setPassword("wen"); factory.newConnection(); return factory.newConnection(); } } |
public class Sender { private static final String QUEUE_NAME = "test_simple_queue";
public static void main(String[] args) throws IOException, TimeoutException { Connection connection = ConnectionUtil.getConnection(); //從連接中獲取通道 Channel channel = connection.createChannel(); //創建隊列聲明 channel.queueDeclare(QUEUE_NAME, false, false, false, null); String msg = "hello Simple"; channel.basicPublish("", QUEUE_NAME, null, msg.getBytes()); System.out.println("--send msg : " + msg); channel.close(); connection.close(); } } |
public class Receiver { private static final String QUEUE_NAME = "test_simple_queue";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException { Connection connection = ConnectionUtil.getConnection(); Channel channel = connection.createChannel(); // methodOld(channel); channel.queueDeclare(QUEUE_NAME, false, false, false, null); DefaultConsumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { String string = new String(body, StandardCharsets.UTF_8); System.out.println("new api receiver:" + string);
} }; channel.basicConsume(QUEUE_NAME,true,consumer); }
private static void methodOld(Channel channel) throws IOException, InterruptedException { //定義隊列的消費者 QueueingConsumer consumer = new QueueingConsumer(channel); channel.basicConsume(QUEUE_NAME, true, consumer); while (true) { QueueingConsumer.Delivery delivery = consumer.nextDelivery(); String msg = new String(delivery.getBody()); System.out.println("[receive msg]" + msg); } } } |
b.工作隊列WorkQueue
代碼
public class Sender { private static final String QUEUE_NAME = "test_work_queue";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException { Connection connection = ConnectionUtil.getConnection(); Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
for (int i=0;i<50;i++){ String msg="hello "+i; channel.basicPublish("",QUEUE_NAME,null,msg.getBytes()); System.out.println("發送消息成功 :"+msg); Thread.sleep(1*20); } channel.close(); connection.close(); } } |
public class Receiver1 { private static final String QUEUE_NAME = "test_work_queue";
public static void main(String[] args) throws IOException, TimeoutException { 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, AMQP.BasicProperties properties, byte[] body) throws IOException { String msg = new String(body, StandardCharsets.UTF_8); System.out.println("[1] receive msg :"+msg);
try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }finally { System.out.println("[1] done "); } } }; boolean autoAck=true; channel.basicConsume(QUEUE_NAME,autoAck,consumer);
} } |
Receiver2的代碼與Receiver1的代碼一致 |
現象:
消費者1和消費者2處理的消息數量是一樣的,中間件採用輪詢分發(round-robin)
公平分發fair dipatch
代碼
public class Sender { private static final String QUEUE_NAME = "test_work_queue";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException { Connection connection = ConnectionUtil.getConnection(); Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null); //限制中間件每次發送一個消息,且消息消費者沒有確認消息消費的情況下,channel不在發送消息,一次只發送一個消息 int prefetchCount = 1; channel.basicQos(prefetchCount); for (int i = 0; i < 50; i++) { String msg = "hello " + i; channel.basicPublish("", QUEUE_NAME, null, msg.getBytes()); System.out.println("發送消息成功 :" + msg); Thread.sleep(1 * 20); } channel.close(); connection.close(); } } |
public class Receiver1 { private static final String QUEUE_NAME = "test_work_queue";
public static void main(String[] args) throws IOException, TimeoutException { Connection connection = ConnectionUtil.getConnection(); //匿名內部類訪問外部類的局部變量需要聲明爲final,否則,變量出棧後就被清除 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, AMQP.BasicProperties properties, byte[] body) throws IOException { String msg = new String(body, StandardCharsets.UTF_8); System.out.println("[1] receive msg :"+msg);
try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }finally { System.out.println("[1] done "); channel.basicAck(envelope.getDeliveryTag(),false); } } }; //關閉自動應答 boolean autoAck=false; channel.basicConsume(QUEUE_NAME,autoAck,consumer); } } |
public class Receiver2 { private static final String QUEUE_NAME = "test_work_queue";
public static void main(String[] args) throws IOException, TimeoutException { 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, AMQP.BasicProperties properties, byte[] body) throws IOException { String msg = new String(body, StandardCharsets.UTF_8); System.out.println("[2] receive msg :" + msg);
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println("[2] done "); channel.basicAck(envelope.getDeliveryTag(),false);
} } }; //關閉自動應答 boolean autoAck=false; channel.basicConsume(QUEUE_NAME, autoAck, consumer); } } |
消息應答與消息持久化
boolean autoAck=false;
channel.basicConsume(QUEUE_NAME, autoAck, consumer);
- boolean autoAck=true;(自動確認模式)一旦RabbitMQ將消息分發給消費者,就會從內存中刪除,這種情況下,發生正在執行的消費者進程被關閉,消息將消失。
- boolean autoAck=false;(手動模式)RabbitMQ將消息發送給消費者後不會馬上刪除內存中的消息,只有收到消費者消費消息的確認請求後纔會從內存中刪除消息。
c.訂閱模式
模型
Exchange:交換機沒有存儲能力,在RabbitMQ中只有隊列有存儲能力
消息消費的特點:消費者只能消費消費者啓動時間後生產者發送的消息,對於消費者啓動之前的消息,消費者無法消費
public class Sender { private static final String EXCHANGE_NAME = "test_exchange_fanout";
public static void main(String[] args) throws IOException, TimeoutException { Connection connection = ConnectionUtil.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(); } } |
public class Receiver1 { 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 IOException, TimeoutException { Connection connection = ConnectionUtil.getConnection(); final Channel channel = connection.createChannel(); //隊列聲明 channel.queueDeclare(QUEUE_NAME,false,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 { String msg = new String(body, StandardCharsets.UTF_8); System.out.println("[topic 1] receive msg :"+msg);
try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }finally { System.out.println("[topic 1] done "); channel.basicAck(envelope.getDeliveryTag(),false); } } }; //關閉自動應答 boolean autoAck=false; channel.basicConsume(QUEUE_NAME,autoAck,consumer); } } |
d.路由模式
模型
消息根據routingkey來匹配到底發送到哪一個隊列
public class Sender { private static final String EXCHANGE_NAME = "test_exchange_direct";
public static void main(String[] args) throws IOException, TimeoutException { Connection connection = ConnectionUtil.getConnection(); Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "direct"); String msg = "hello exchange"; // String routingKey = "error"; String routingKey = "info"; channel.basicPublish(EXCHANGE_NAME, routingKey, null, msg.getBytes()); System.out.println("send msg: " + msg); channel.close(); connection.close(); } } |
public class Receiver1 { private static final String QUEUE_NAME = "test_queue_direct"; private static final String EXCHANGE_NAME = "test_exchange_direct";
public static void main(String[] args) throws IOException, TimeoutException { Connection connection = ConnectionUtil.getConnection(); final Channel channel = connection.createChannel(); channel.queueDeclare(QUEUE_NAME,false,false,false,null); channel.basicQos(1); channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"error");
DefaultConsumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { String msg = new String(body, StandardCharsets.UTF_8); System.out.println("[direct 1] receive msg :"+msg);
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }finally { System.out.println("[direct 1] done "); channel.basicAck(envelope.getDeliveryTag(),false); } } }; boolean autoAck=true; channel.basicConsume(QUEUE_NAME,autoAck,consumer); } } |
public class Receiver2 { private static final String QUEUE_NAME = "test_queue_direct2"; private static final String EXCHANGE_NAME = "test_exchange_direct";
public static void main(String[] args) throws IOException, TimeoutException { Connection connection = ConnectionUtil.getConnection(); final Channel channel = connection.createChannel(); channel.queueDeclare(QUEUE_NAME,false,false,false,null); channel.basicQos(1); channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"error"); channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"info"); channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"warning"); DefaultConsumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { String msg = new String(body, StandardCharsets.UTF_8); System.out.println("[direct 2] receive msg :"+msg);
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }finally { System.out.println("[direct 2] done "); channel.basicAck(envelope.getDeliveryTag(),false); } } }; boolean autoAck=true; channel.basicConsume(QUEUE_NAME,autoAck,consumer); } } |
因爲隊列根據routingKey來消費消息,耦合程度較高
e.Topic
模型
將路由鍵和某模式匹配
# 匹配一個或多個
* 匹配一個
Eg:Good.#
public class Sender { private static final String EXCHANGE_NAME = "test_exchange_topic";
public static void main(String[] args) throws IOException, TimeoutException { Connection connection = ConnectionUtil.getConnection(); Channel channel = connection.createChannel(); channel.exchangeDeclare(EXCHANGE_NAME,"topic");
String msg = "商品。。。。。"; // channel.basicPublish(EXCHANGE_NAME,"goods.add",null,msg.getBytes()); channel.basicPublish(EXCHANGE_NAME,"goods.delete",null,msg.getBytes()); System.out.println("send msg :"+msg); channel.close(); connection.close(); } } |
public class Receiver1 { private static final String QUEUE_NAME = "test_queue_topic_1"; private static final String EXCHANGE_NAME = "test_exchange_topic";
public static void main(String[] args) throws IOException, TimeoutException { Connection connection = ConnectionUtil.getConnection(); final Channel channel = connection.createChannel(); channel.queueDeclare(QUEUE_NAME,false,false,false,null); channel.basicQos(1); channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"goods.add");
DefaultConsumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { String msg = new String(body, StandardCharsets.UTF_8); System.out.println("[topic 1] receive msg :"+msg);
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }finally { System.out.println("[topic 1] done "); channel.basicAck(envelope.getDeliveryTag(),false); } } }; boolean autoAck=false; channel.basicConsume(QUEUE_NAME,autoAck,consumer); } } |
public class Receiver2 { private static final String QUEUE_NAME = "test_queue_topic_2"; private static final String EXCHANGE_NAME = "test_exchange_topic";
public static void main(String[] args) throws IOException, TimeoutException { Connection connection = ConnectionUtil.getConnection(); final Channel channel = connection.createChannel(); channel.queueDeclare(QUEUE_NAME,false,false,false,null); channel.basicQos(1); channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"goods.#");
DefaultConsumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { String msg = new String(body, StandardCharsets.UTF_8); System.out.println("[topic 2] receive msg :"+msg);
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }finally { System.out.println("[topic 2] done "); channel.basicAck(envelope.getDeliveryTag(),false); } } }; boolean autoAck=false; channel.basicConsume(QUEUE_NAME,autoAck,consumer); } } |
RabbitMQ的消息確認機制(事務+confirm)
解決消息傳遞的安全性問題:
事務機制
特點:請求重複發送,降低服務器的吞吐量
TxSelect:用戶將當前channel設置成transaction模式
txCommit:用於提交事務
txRollback:回滾事務
public class Sender { private static final String QUEUE_NAME = "test_queue_tx";
public static void main(String[] args) throws IOException, TimeoutException { Connection connection = ConnectionUtil.getConnection(); Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
String msg = "hello tx message"; try {
channel.txSelect(); channel.basicPublish("", QUEUE_NAME, null, msg.getBytes()); // int i = 1 / 0; channel.txCommit(); } catch (Exception e) { channel.txRollback(); e.printStackTrace(); }
} } |
public class Receicer { private static final String QUEUE_NAME = "test_queue_tx";
public static void main(String[] args) throws IOException, TimeoutException { Connection connection = ConnectionUtil.getConnection(); Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.basicConsume(QUEUE_NAME, true, new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println("tx receiver msg:"+new String(body, StandardCharsets.UTF_8)); } }); } } |
Confirm模式
生產者端Confirm模式的實現原理
生產者將信道設置成confirm模式,一旦信道進入confirm模式,所有在該信道上面發佈的消息都會被指派一個唯一的ID(從1開始),一旦消息被投遞到所匹配的隊列後,broker就發送一個確認給生產者(包含消息的唯一ID),使得生產者知道消息已經正確到達目的隊列,如果消息是可持久化的,那麼消息將會寫入磁盤後發出,broker回傳給生產者的確認消息中deliver-tag域包含確認消息的序列號,此外broker也可設置basic.ack的multiple域,表示到這個序列號之前的消息都已得到處理。
編程模式:
1.普通 發一條 waitForConfirms()
public class Sender { private static final String QUEUE_NAME = "test_queue_confirm1";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException { Connection connection = ConnectionUtil. getConnection(); Channel channel = connection.createChannel(); channel.queueDeclare(QUEUE_NAME,false,false,false,null);
channel.confirmSelect(); String msg="hello confirm mode "; channel.basicPublish("", QUEUE_NAME,null,msg.getBytes());
if (!channel.waitForConfirms()){ System.out.println("message send failed"); }else{ System.out.println("message send successfully"); } channel.close(); connection.close(); } } |
public class Receicer { private static final String QUEUE_NAME = "test_queue_confirm1";
public static void main(String[] args) throws IOException, TimeoutException { Connection connection = ConnectionUtil.getConnection(); Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.basicConsume(QUEUE_NAME, true, new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println("confirm receiver msg:"+new String(body, StandardCharsets.UTF_8)); } }); } } |
2.批量 waitForConfirms()
批量發送,接收的方式還是一樣
3.異步Confirm模式:提供一個回調方法
Channel對象提供的ConfirmListener()回調方法只包含deliveryTag(當前Channel發出的消息序號),需要自己爲每一個Channel維護一個unconfirm的消息序號集合,每publish一條數據,集合中元素加1,每次回調handleAck方法,unconfirm集合刪除相應的一條(multiple=false)或(multiple=true)記錄,從程序運行效率上看,unconfirm集合最好採用有序集合sortedSet存儲結構
總結
消息傳遞的方式:
- 匿名發送(不綁定交換機,直接使用隊列,單對單)
- Fanout(不處理路由鍵,使用交換機,生產者與消費者解耦,通過交換機向消費者分發消息)
- Direct(處理路由鍵)