RabbitMQ消息中間件學習心得

RabbitMQ 實戰教程

1.MQ引言

1.1 什麼是MQ

MQ(Message Quene) : 翻譯爲 消息隊列,通過典型的 生產者消費者模型,生產者不斷向消息隊列中生產消息,消費者不斷的從隊列中獲取消息。因爲消息的生產和消費都是異步的,而且只關心消息的發送和接收,沒有業務邏輯的侵入,輕鬆的實現系統間解耦。別名爲 消息中間件 通過利用高效可靠的消息傳遞機制進行平臺無關的數據交流,並基於數據通信來進行分佈式系統的集成。

1.2 MQ有哪些

當今市面上有很多主流的消息中間件,如老牌的ActiveMQRabbitMQ,炙手可熱的Kafka,阿里巴巴自主開發RocketMQ等。

1.3 不同MQ特點

# 1.ActiveMQ
		ActiveMQ 是Apache出品,最流行的,能力強勁的開源消息總線。它是一個完全支持JMS規範的的消息中間件。豐富的API,多種集羣架構模式讓ActiveMQ在業界成爲老牌的消息中間件,在中小型企業頗受歡迎!

# 2.Kafka
		Kafka是LinkedIn開源的分佈式發佈-訂閱消息系統,目前歸屬於Apache頂級項目。Kafka主要特點是基於Pull的模式來處理消息消費,
		追求高吞吐量,一開始的目的就是用於日誌收集和傳輸。0.8版本開始支持複製,不支持事務,對消息的重複、丟失、錯誤沒有嚴格要求,
		適合產生大量數據的互聯網服務的數據收集業務。

# 3.RocketMQ
		RocketMQ是阿里開源的消息中間件,它是純Java開發,具有高吞吐量、高可用性、適合大規模分佈式系統應用的特點。RocketMQ思路起
		源於Kafka,但並不是Kafka的一個Copy,它對消息的可靠傳輸及事務性做了優化,目前在阿里集團被廣泛應用於交易、充值、流計算、消
		息推送、日誌流式處理、binglog分發等場景。

# 4.RabbitMQ
		RabbitMQ是使用Erlang語言開發的開源消息隊列系統,基於AMQP協議來實現。AMQP的主要特徵是面向消息、隊列、路由(包括點對點和
		發佈/訂閱)、可靠性、安全。AMQP協議更多用在企業系統內對數據一致性、穩定性和可靠性要求很高的場景,對性能和吞吐量的要求還在
		其次。
		

RabbitMQ比Kafka可靠,Kafka更適合IO高吞吐的處理,一般應用在大數據日誌處理或對實時性(少量延遲),可靠性(少量丟數據)要求稍低的場景使用,比如ELK日誌收集。


2.RabbitMQ 的引言

2.1 RabbitMQ

基於AMQP協議,erlang語言開發,是部署最廣泛的開源消息中間件,是最受歡迎的開源消息中間件之一。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-UZH5NOMz-1587108892902)(RibbitMQ 實戰教程.assets/image-20190925215603036-9419777.png)]

官網: https://www.rabbitmq.com/

官方教程: https://www.rabbitmq.com/#getstarted

 # AMQP 協議
 		AMQP(advanced message queuing protocol)`在2003年時被提出,最早用於解決金融領不同平臺之間的消息傳遞交互問題。顧名思義,AMQP是一種協議,更準確的說是一種binary wire-level protocol(鏈接協議)。這是其和JMS的本質差別,AMQP不從API層進行限定,而是直接定義網絡交換的數據格式。這使得實現了AMQP的provider天然性就是跨平臺的。以下是AMQP協議模型:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Eq0aNMZt-1587108892907)(RibbitMQ 實戰教程.assets/image-20200311182438041.png)]

2.2 RabbitMQ 的安裝

2.2.1 下載

官網下載地址: https://www.rabbitmq.com/download.html[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-7Sscg5WK-1587108892914)(RibbitMQ 實戰教程.assets/image-20190925220115235.png)]

最新版本: 3.7.18

2.2.2 下載的安裝包[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-aIJcfuco-1587108892918)(RibbitMQ 實戰教程.assets/image-20190925220343521.png)]

注意:這裏的安裝包是centos7安裝的包

2.2.3 安裝步驟

# 1.將rabbitmq安裝包上傳到linux系統中
	erlang-22.0.7-1.el7.x86_64.rpm
	rabbitmq-server-3.7.18-1.el7.noarch.rpm

# 2.安裝Erlang依賴包
	rpm -ivh erlang-22.0.7-1.el7.x86_64.rpm

# 3.安裝RabbitMQ安裝包(需要聯網)
	yum install -y rabbitmq-server-3.7.18-1.el7.noarch.rpm
		注意:默認安裝完成後配置文件模板在:/usr/share/doc/rabbitmq-server-3.7.18/rabbitmq.config.example目錄中,需要	
				將配置文件複製到/etc/rabbitmq/目錄中,並修改名稱爲rabbitmq.config
# 4.複製配置文件
	cp /usr/share/doc/rabbitmq-server-3.7.18/rabbitmq.config.example /etc/rabbitmq/rabbitmq.config

# 5.查看配置文件位置
	ls /etc/rabbitmq/rabbitmq.config

# 6.修改配置文件(參見下圖:)
	vim /etc/rabbitmq/rabbitmq.config 

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-RVd6bPZl-1587108892922)(RibbitMQ 實戰教程.assets/image-20190925222230260-3836271.png)]

將上圖中配置文件中紅色部分去掉%%,以及最後的,逗號 修改爲下圖:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-mslwsOUl-1587108892924)(RibbitMQ 實戰教程.assets/image-20190925222329200-3836312.png)]

# 7.執行如下命令,啓動rabbitmq中的插件管理
	rabbitmq-plugins enable rabbitmq_management
	
	出現如下說明:
		Enabling plugins on node rabbit@localhost:
    rabbitmq_management
    The following plugins have been configured:
      rabbitmq_management
      rabbitmq_management_agent
      rabbitmq_web_dispatch
    Applying plugin configuration to rabbit@localhost...
    The following plugins have been enabled:
      rabbitmq_management
      rabbitmq_management_agent
      rabbitmq_web_dispatch

    set 3 plugins.
    Offline change; changes will take effect at broker restart.

# 8.啓動RabbitMQ的服務
	systemctl start rabbitmq-server
	systemctl restart rabbitmq-server
	systemctl stop rabbitmq-server
	

# 9.查看服務狀態(見下圖:)
	systemctl status rabbitmq-server
  ● rabbitmq-server.service - RabbitMQ broker
     Loaded: loaded (/usr/lib/systemd/system/rabbitmq-server.service; disabled; vendor preset: disabled)
     Active: active (running) since 三 2019-09-25 22:26:35 CST; 7s ago
   Main PID: 2904 (beam.smp)
     Status: "Initialized"
     CGroup: /system.slice/rabbitmq-server.service
             ├─2904 /usr/lib64/erlang/erts-10.4.4/bin/beam.smp -W w -A 64 -MBas ageffcbf -MHas ageffcbf -
             MBlmbcs...
             ├─3220 erl_child_setup 32768
             ├─3243 inet_gethost 4
             └─3244 inet_gethost 4
      .........

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-vQ7keNbl-1587108892926)(RibbitMQ 實戰教程.assets/image-20190925222743776-3836511.png)]

# 10.關閉防火牆服務
	systemctl disable firewalld
    Removed symlink /etc/systemd/system/multi-user.target.wants/firewalld.service.
    Removed symlink /etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service.
	systemctl stop firewalld   

# 11.訪問web管理界面
	http://10.15.0.8:15672/

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-TFepyrBQ-1587108892928)(RibbitMQ 實戰教程.assets/image-20190926194738708-3836601.png)]

# 12.登錄管理界面
	username:  guest
	password:  guest

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-hVNh7VdM-1587108892931)(RibbitMQ 實戰教程.assets/image-20190926194954822-3836665.png)]


3. RabiitMQ 配置

3.1RabbitMQ 管理命令行

# 1.服務啓動相關
	systemctl start|restart|stop|status rabbitmq-server

# 2.管理命令行  用來在不使用web管理界面情況下命令操作RabbitMQ
	rabbitmqctl  help  可以查看更多命令

# 3.插件管理命令行
	rabbitmq-plugins enable|list|disable 

3.2 web管理界面介紹

3.2.1 overview概覽

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-qOpaIdsJ-1587108892935)(RibbitMQ 實戰教程.assets/image-20191126162026720.png)]

  • connections:無論生產者還是消費者,都需要與RabbitMQ建立連接後纔可以完成消息的生產和消費,在這裏可以查看連接情況

  • channels:通道,建立連接後,會形成通道,消息的投遞獲取依賴通道。

  • Exchanges:交換機,用來實現消息的路由

  • Queues:隊列,即消息隊列,消息存放在隊列中,等待消費,消費後被移除隊列。

3.2.2 Admin用戶和虛擬主機管理

1. 添加用戶

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-arMn8OUO-1587108892936)(RibbitMQ 實戰教程.assets/image-20191126162617280.png)]

上面的Tags選項,其實是指定用戶的角色,可選的有以下幾個:

  • 超級管理員(administrator)

    可登陸管理控制檯,可查看所有的信息,並且可以對用戶,策略(policy)進行操作。

  • 監控者(monitoring)

    可登陸管理控制檯,同時可以查看rabbitmq節點的相關信息(進程數,內存使用情況,磁盤使用情況等)

  • 策略制定者(policymaker)

    可登陸管理控制檯, 同時可以對policy進行管理。但無法查看節點的相關信息(上圖紅框標識的部分)。

  • 普通管理者(management)

    僅可登陸管理控制檯,無法看到節點信息,也無法對策略進行管理。

  • 其他

    無法登陸管理控制檯,通常就是普通的生產者和消費者。

2. 創建虛擬主機
# 虛擬主機
	爲了讓各個用戶可以互不干擾的工作,RabbitMQ添加了虛擬主機(Virtual Hosts)的概念。其實就是一個獨立的訪問路徑,不同用戶使用不同路徑,各自有自己的隊列、交換機,互相不會影響。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-QXpK0cBK-1587108892939)(RibbitMQ 實戰教程.assets/image-20191126163023153.png)]

3. 綁定虛擬主機和用戶

創建好虛擬主機,我們還要給用戶添加訪問權限:

點擊添加好的虛擬主機:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Wganx4cW-1587108892942)(RibbitMQ 實戰教程.assets/image-20191126163506795.png)]

進入虛擬機設置界面:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-NgTTVMsT-1587108892944)(RibbitMQ 實戰教程.assets/image-20191126163631889.png)]


4.RabbitMQ 的第一個程序

4.0 AMQP協議的回顧

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-KXHZChwz-1587108892946)(RibbitMQ 實戰教程.assets/image-20200312140114784.png)]

4.1 RabbitMQ支持的消息模型

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Vq7lh3O0-1587108892949)(RibbitMQ 實戰教程.assets/image-20191126165434784.png)]

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-gGF9dg5E-1587108892952)(RibbitMQ 實戰教程.assets/image-20191126165459282.png)]

4.2 引入依賴

<dependency>
    <groupId>com.rabbitmq</groupId>
    <artifactId>amqp-client</artifactId>
    <version>5.7.2</version>
</dependency>

4.3 第一種模型(直連)

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-rRc9OWdG-1587108892955)(RibbitMQ 實戰教程.assets/image-20191126165840602.png)]

在上圖的模型中,有以下概念:

  • P:生產者,也就是要發送消息的程序
  • C:消費者:消息的接受者,會一直等待消息到來。
  • queue:消息隊列,圖中紅色部分。類似一個郵箱,可以緩存消息;生產者向其中投遞消息,消費者從其中取出消息。
1. 開發生產者
  //創建連接工廠
  ConnectionFactory connectionFactory = new ConnectionFactory();
  connectionFactory.setHost("10.15.0.9");
  connectionFactory.setPort(5672);
  connectionFactory.setUsername("ems");
  connectionFactory.setPassword("123");
  connectionFactory.setVirtualHost("/ems");
  Connection connection = connectionFactory.newConnection();
  //創建通道
  Channel channel = connection.createChannel();
  //參數1: 是否持久化  參數2:是否獨佔隊列 參數3:是否自動刪除  參數4:其他屬性
  channel.queueDeclare("hello",true,false,false,null);
  channel.basicPublish("","hello", null,"hello rabbitmq".getBytes());
  channel.close();
  connection.close();
2. 開發消費者
  //創建連接工廠
  ConnectionFactory connectionFactory = new ConnectionFactory();
  connectionFactory.setHost("10.15.0.9");
  connectionFactory.setPort(5672);
  connectionFactory.setUsername("ems");
  connectionFactory.setPassword("123");
  connectionFactory.setVirtualHost("/ems");
  Connection connection = connectionFactory.newConnection();
  Channel channel = connection.createChannel();
  channel.queueDeclare("hello", true, false, false, null);
  channel.basicConsume("hello",true,new DefaultConsumer(channel){
    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
      System.out.println(new String(body));
    }
  });
3. 參數的說明
  channel.queueDeclare("hello",true,false,false,null);
	'參數1':用來聲明通道對應的隊列
  '參數2':用來指定是否持久化隊列
  '參數3':用來指定是否獨佔隊列
  '參數4':用來指定是否自動刪除隊列
  '參數5':對隊列的額外配置

4.4 第二種模型(work quene)

Work queues,也被稱爲(Task queues),任務模型。當消息處理比較耗時的時候,可能生產消息的速度會遠遠大於消息的消費速度。長此以往,消息就會堆積越來越多,無法及時處理。此時就可以使用work 模型:讓多個消費者綁定到一個隊列,共同消費隊列中的消息。隊列中的消息一旦消費,就會消失,因此任務是不會被重複執行的。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-JNPZi1Op-1587108892957)(RibbitMQ 實戰教程.assets/image-20200314221002008.png)]

角色:

  • P:生產者:任務的發佈者
  • C1:消費者-1,領取任務並且完成任務,假設完成速度較慢
  • C2:消費者-2:領取任務並完成任務,假設完成速度快
1. 開發生產者
channel.queueDeclare("hello", true, false, false, null);
for (int i = 0; i < 10; i++) {
  channel.basicPublish("", "hello", null, (i+"====>:我是消息").getBytes());
}
2.開發消費者-1
channel.queueDeclare("hello",true,false,false,null);
channel.basicConsume("hello",true,new DefaultConsumer(channel){
  @Override
  public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    System.out.println("消費者1: "+new String(body));
  }
});
3.開發消費者-2
channel.queueDeclare("hello",true,false,false,null);
channel.basicConsume("hello",true,new DefaultConsumer(channel){
  @Override
  public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    try {
      Thread.sleep(1000);   //處理消息比較慢 一秒處理一個消息
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("消費者2: "+new String(body));  
  }
});
4.測試結果

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-6sKVC45Y-1587108892959)(RibbitMQ 實戰教程.assets/image-20200314223242058.png)]

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-XxmC66eP-1587108892962)(RibbitMQ 實戰教程.assets/image-20200314223302207.png)]

總結:默認情況下,RabbitMQ將按順序將每個消息發送給下一個使用者。平均而言,每個消費者都會收到相同數量的消息。這種分發消息的方式稱爲循環。

5.消息自動確認機制

Doing a task can take a few seconds. You may wonder what happens if one of the consumers starts a long task and dies with it only partly done. With our current code, once RabbitMQ delivers a message to the consumer it immediately marks it for deletion. In this case, if you kill a worker we will lose the message it was just processing. We’ll also lose all the messages that were dispatched to this particular worker but were not yet handled.

But we don’t want to lose any tasks. If a worker dies, we’d like the task to be delivered to another worker.

channel.basicQos(1);//一次只接受一條未確認的消息
//參數2:關閉自動確認消息
channel.basicConsume("hello",false,new DefaultConsumer(channel){
  @Override
  public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    System.out.println("消費者1: "+new String(body));
    channel.basicAck(envelope.getDeliveryTag(),false);//手動確認消息
  }
});
  • 設置通道一次只能消費一個消息

  • 關閉消息的自動確認,開啓手動確認消息

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-sZHFnfnw-1587108892964)(RibbitMQ 實戰教程.assets/image-20200314230412178.png)]

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-dXgmEyoz-1587108892966)(RibbitMQ 實戰教程.assets/image-20200314230423280.png)]


4.5 第三種模型(fanout)

fanout 扇出 也稱爲廣播

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-CPuSj5VV-1587108892969)(RibbitMQ 實戰教程.assets/image-20191126213115873.png)]

在廣播模式下,消息發送流程是這樣的:

  • 可以有多個消費者
  • 每個消費者有自己的queue(隊列)
  • 每個隊列都要綁定到Exchange(交換機)
  • 生產者發送的消息,只能發送到交換機,交換機來決定要發給哪個隊列,生產者無法決定。
  • 交換機把消息發送給綁定過的所有隊列
  • 隊列的消費者都能拿到消息。實現一條消息被多個消費者消費
1. 開發生產者
//聲明交換機
channel.exchangeDeclare("logs","fanout");//廣播 一條消息多個消費者同時消費
//發佈消息
channel.basicPublish("logs","",null,"hello".getBytes());
2. 開發消費者-1
//綁定交換機
channel.exchangeDeclare("logs","fanout");
//創建臨時隊列
String queue = channel.queueDeclare().getQueue();
//將臨時隊列綁定exchange
channel.queueBind(queue,"logs","");
//處理消息
channel.basicConsume(queue,true,new DefaultConsumer(channel){
  @Override
  public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    System.out.println("消費者1: "+new String(body));
  }
});
3. 開發消費者-2
//綁定交換機
channel.exchangeDeclare("logs","fanout");
//創建臨時隊列
String queue = channel.queueDeclare().getQueue();
//將臨時隊列綁定exchange
channel.queueBind(queue,"logs","");
//處理消息
channel.basicConsume(queue,true,new DefaultConsumer(channel){
  @Override
  public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    System.out.println("消費者2: "+new String(body));
  }
});
4.開發消費者-3
//綁定交換機
channel.exchangeDeclare("logs","fanout");
//創建臨時隊列
String queue = channel.queueDeclare().getQueue();
//將臨時隊列綁定exchange
channel.queueBind(queue,"logs","");
//處理消息
channel.basicConsume(queue,true,new DefaultConsumer(channel){
  @Override
  public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    System.out.println("消費者3: "+new String(body));
  }
});
5. 測試結果

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-nSptfOz0-1587108892971)(RibbitMQ 實戰教程.assets/image-20200315180653207.png)]

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Bqmmdq9v-1587108892974)(RibbitMQ 實戰教程.assets/image-20200315180708489.png)]

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-sYEiwLSO-1587108892976)(RibbitMQ 實戰教程.assets/image-20200315180728035.png)]


4.6 第四種模型(Routing)

4.6.1 Routing 之訂閱模型-Direct(直連)

在Fanout模式中,一條消息,會被所有訂閱的隊列都消費。但是,在某些場景下,我們希望不同的消息被不同的隊列消費。這時就要用到Direct類型的Exchange。

在Direct模型下:

  • 隊列與交換機的綁定,不能是任意綁定了,而是要指定一個RoutingKey(路由key)
  • 消息的發送方在 向 Exchange發送消息時,也必須指定消息的 RoutingKey
  • Exchange不再把消息交給每一個綁定的隊列,而是根據消息的Routing Key進行判斷,只有隊列的Routingkey與消息的 Routing key完全一致,纔會接收到消息

流程:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-jjLWPZNG-1587108892978)(RibbitMQ 實戰教程.assets/image-20191126220145375.png)]

圖解:

  • P:生產者,向Exchange發送消息,發送消息時,會指定一個routing key。
  • X:Exchange(交換機),接收生產者的消息,然後把消息遞交給 與routing key完全匹配的隊列
  • C1:消費者,其所在隊列指定了需要routing key 爲 error 的消息
  • C2:消費者,其所在隊列指定了需要routing key 爲 info、error、warning 的消息
1. 開發生產者
//聲明交換機  參數1:交換機名稱 參數2:交換機類型 基於指令的Routing key轉發
channel.exchangeDeclare("logs_direct","direct");
String key = "";
//發佈消息
channel.basicPublish("logs_direct",key,null,("指定的route key"+key+"的消息").getBytes());
2.開發消費者-1
 //聲明交換機
channel.exchangeDeclare("logs_direct","direct");
//創建臨時隊列
String queue = channel.queueDeclare().getQueue();
//綁定隊列和交換機
channel.queueBind(queue,"logs_direct","error");
channel.queueBind(queue,"logs_direct","info");
channel.queueBind(queue,"logs_direct","warn");

//消費消息
channel.basicConsume(queue,true,new DefaultConsumer(channel){
  @Override
  public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    System.out.println("消費者1: "+new String(body));
  }
});
3.開發消費者-2
//聲明交換機
channel.exchangeDeclare("logs_direct","direct");
//創建臨時隊列
String queue = channel.queueDeclare().getQueue();
//綁定隊列和交換機
channel.queueBind(queue,"logs_direct","error");
//消費消息
channel.basicConsume(queue,true,new DefaultConsumer(channel){
  @Override
  public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    System.out.println("消費者2: "+new String(body));
  }
});
4.測試生產者發送Route key爲error的消息時

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-oZs8lSjw-1587108892981)(RibbitMQ 實戰教程.assets/image-20200316102613933.png)]

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Nq9kLl5Q-1587108892983)(RibbitMQ 實戰教程.assets/image-20200316102627912.png)]

5.測試生產者發送Route key爲info的消息時

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-5PjJvX6F-1587108892986)(RibbitMQ 實戰教程.assets/image-20200316102925740.png)]

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-1AV1Yjky-1587108892988)(RibbitMQ 實戰教程.assets/image-20200316102947326.png)]


4.6.2 Routing 之訂閱模型-Topic

Topic類型的ExchangeDirect相比,都是可以根據RoutingKey把消息路由到不同的隊列。只不過Topic類型Exchange可以讓隊列在綁定Routing key 的時候使用通配符!這種模型Routingkey 一般都是由一個或多個單詞組成,多個單詞之間以”.”分割,例如: item.insert

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-XQYKW7WL-1587108892991)(RibbitMQ 實戰教程.assets/image-20191127121900255.png)]

# 統配符
		* (star) can substitute for exactly one word.    匹配不多不少恰好1個詞
		# (hash) can substitute for zero or more words.  匹配一個或多個詞
# 如:
		audit.#    匹配audit.irs.corporate或者 audit.irs 等
    audit.*   只能匹配 audit.irs
1.開發生產者
//生命交換機和交換機類型 topic 使用動態路由(通配符方式)
channel.exchangeDeclare("topics","topic");
String routekey = "user.save";//動態路由key
//發佈消息
channel.basicPublish("topics",routekey,null,("這是路由中的動態訂閱模型,route key: ["+routekey+"]").getBytes());
2.開發消費者-1

Routing Key中使用*通配符方式

 //聲明交換機
channel.exchangeDeclare("topics","topic");
//創建臨時隊列
String queue = channel.queueDeclare().getQueue();
//綁定隊列與交換機並設置獲取交換機中動態路由
channel.queueBind(queue,"topics","user.*");

//消費消息
channel.basicConsume(queue,true,new DefaultConsumer(channel){
  @Override
  public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    System.out.println("消費者1: "+new String(body));
  }
});
3.開發消費者-2

Routing Key中使用#通配符方式

//聲明交換機
channel.exchangeDeclare("topics","topic");
//創建臨時隊列
String queue = channel.queueDeclare().getQueue();
//綁定隊列與交換機並設置獲取交換機中動態路由
channel.queueBind(queue,"topics","user.#");

//消費消息
channel.basicConsume(queue,true,new DefaultConsumer(channel){
  @Override
  public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    System.out.println("消費者2: "+new String(body));
  }
});
4.測試結果

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-wraSj9Fl-1587108892993)(RibbitMQ 實戰教程.assets/image-20200316113935785.png)]

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-WB0I9F99-1587108892995)(RibbitMQ 實戰教程.assets/image-20200316114000459.png)]

5. SpringBoot中使用RabbitMQ

5.0 搭建初始環境

1. 引入依賴
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2. 配置配置文件
spring:
  application:
    name: springboot_rabbitmq
  rabbitmq:
    host: 10.15.0.9
    port: 5672
    username: ems
    password: 123
    virtual-host: /ems

RabbitTemplate 用來簡化操作 使用時候直接在項目中注入即可使用

5.1 第一種hello world模型使用

  1. 開發生產者
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @Test
    public void testHello(){
      rabbitTemplate.convertAndSend("hello","hello world");
    }
    
  2. 開發消費者
    @Component
    @RabbitListener(queuesToDeclare = @Queue("hello"))
    public class HelloCustomer {
    
        @RabbitHandler
        public void receive1(String message){
            System.out.println("message = " + message);
        }
    }
    

5.2 第二種work模型使用

  1. 開發生產者
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @Test
    public void testWork(){
      for (int i = 0; i < 10; i++) {
        rabbitTemplate.convertAndSend("work","hello work!");
      }
    }
    
  2. 開發消費者
    @Component
    public class WorkCustomer {
        @RabbitListener(queuesToDeclare = @Queue("work"))
        public void receive1(String message){
            System.out.println("work message1 = " + message);
        }
    
        @RabbitListener(queuesToDeclare = @Queue("work"))
        public void receive2(String message){
            System.out.println("work message2 = " + message);
        }
    }
    

    說明:默認在Spring AMQP實現中Work這種方式就是公平調度,如果需要實現能者多勞需要額外配置

5.3 Fanout 廣播模型

  1. 開發生產者
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @Test
    public void testFanout() throws InterruptedException {
      rabbitTemplate.convertAndSend("logs","","這是日誌廣播");
    }
    
  2. 開發消費者
    @Component
    public class FanoutCustomer {
    
        @RabbitListener(bindings = @QueueBinding(
                value = @Queue,
                exchange = @Exchange(name="logs",type = "fanout")
        ))
        public void receive1(String message){
            System.out.println("message1 = " + message);
        }
    
        @RabbitListener(bindings = @QueueBinding(
                value = @Queue, //創建臨時隊列
                exchange = @Exchange(name="logs",type = "fanout")  //綁定交換機類型
        ))
        public void receive2(String message){
            System.out.println("message2 = " + message);
        }
    }
    

5.4 Route 路由模型

  1. 開發生產者
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @Test
    public void testDirect(){
      rabbitTemplate.convertAndSend("directs","error","error 的日誌信息");
    }
    
  2. 開發消費者
    @Component
    public class DirectCustomer {
    
        @RabbitListener(bindings ={
                @QueueBinding(
                        value = @Queue(),
                        key={"info","error"},
                        exchange = @Exchange(type = "direct",name="directs")
                )})
        public void receive1(String message){
            System.out.println("message1 = " + message);
        }
    
        @RabbitListener(bindings ={
                @QueueBinding(
                        value = @Queue(),
                        key={"error"},
                        exchange = @Exchange(type = "direct",name="directs")
                )})
        public void receive2(String message){
            System.out.println("message2 = " + message);
        }
    }
    
    

5.5 Topic 訂閱模型(動態路由模型)

  1. 開發生產者
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    //topic
    @Test
    public void testTopic(){
      rabbitTemplate.convertAndSend("topics","user.save.findAll","user.save.findAll 的消息");
    }
    
  2. 開發消費者
    @Component
    public class TopCustomer {
        @RabbitListener(bindings = {
                @QueueBinding(
                        value = @Queue,
                        key = {"user.*"},
                        exchange = @Exchange(type = "topic",name = "topics")
                )
        })
        public void receive1(String message){
            System.out.println("message1 = " + message);
        }
    
        @RabbitListener(bindings = {
                @QueueBinding(
                        value = @Queue,
                        key = {"user.#"},
                        exchange = @Exchange(type = "topic",name = "topics")
                )
        })
        public void receive2(String message){
            System.out.println("message2 = " + message);
        }
    }
    

6. MQ的應用場景

6.1 異步處理

場景說明:用戶註冊後,需要發註冊郵件和註冊短信,傳統的做法有兩種 1.串行的方式 2.並行的方式

  • 串行方式: 將註冊信息寫入數據庫後,發送註冊郵件,再發送註冊短信,以上三個任務全部完成後才返回給客戶端。 這有一個問題是,郵件,短信並不是必須的,它只是一個通知,而這種做法讓客戶端等待沒有必要等待的東西.

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-aQBy8Npi-1587108892998)(RibbitMQ 實戰教程.assets/SouthEast-4860248.png)]

  • 並行方式:將註冊信息寫入數據庫後,發送郵件的同時,發送短信,以上三個任務完成後,返回給客戶端,並行的方式能提高處理的時間。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-nTdVkXAd-1587108893000)(RibbitMQ 實戰教程.assets/SouthEast-20191127211112660.png)]

  • 消息隊列:假設三個業務節點分別使用50ms,串行方式使用時間150ms,並行使用時間100ms。雖然並行已經提高的處理時間,但是,前面說過,郵件和短信對我正常的使用網站沒有任何影響,客戶端沒有必要等着其發送完成才顯示註冊成功,應該是寫入數據庫後就返回. 消息隊列: 引入消息隊列後,把發送郵件,短信不是必須的業務邏輯異步處理

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-tekDnyGH-1587108893006)(RibbitMQ 實戰教程.assets/592892-20190520220249900-1679743651.jpg)]

由此可以看出,引入消息隊列後,用戶的響應時間就等於寫入數據庫的時間+寫入消息隊列的時間(可以忽略不計),引入消息隊列後處理後,響應時間是串行的3倍,是並行的2倍。

6.2 應用解耦

場景:雙11是購物狂節,用戶下單後,訂單系統需要通知庫存系統,傳統的做法就是訂單系統調用庫存系統的接口.

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-exiKUVSd-1587108893009)(RibbitMQ 實戰教程.assets/SouthEast-20191127211247287.png)]

這種做法有一個缺點:

當庫存系統出現故障時,訂單就會失敗。 訂單系統和庫存系統高耦合. 引入消息隊列

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-7xFVEO6s-1587108893011)(RibbitMQ 實戰教程.assets/SouthEast-20191127211304085.png)]

  • 訂單系統:用戶下單後,訂單系統完成持久化處理,將消息寫入消息隊列,返回用戶訂單下單成功。

  • 庫存系統:訂閱下單的消息,獲取下單消息,進行庫操作。 就算庫存系統出現故障,消息隊列也能保證消息的可靠投遞,不會導致消息丟失.

6.3 流量削峯

場景: 秒殺活動,一般會因爲流量過大,導致應用掛掉,爲了解決這個問題,一般在應用前端加入消息隊列。

作用:

​ 1.可以控制活動人數,超過此一定閥值的訂單直接丟棄(我爲什麼秒殺一次都沒有成功過呢^^)

​ 2.可以緩解短時間的高流量壓垮應用(應用程序按自己的最大處理能力獲取訂單)

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-8Evex16G-1587108893013)(RibbitMQ 實戰教程.assets/SouthEast-20191127211341601.png)]

1.用戶的請求,服務器收到之後,首先寫入消息隊列,加入消息隊列長度超過最大值,則直接拋棄用戶請求或跳轉到錯誤頁面.

2.秒殺業務根據消息隊列中的請求信息,再做後續處理.


7. RabbitMQ的集羣

7.1 集羣架構

7.1.1 普通集羣(副本集羣)

All data/state required for the operation of a RabbitMQ broker is replicated across all nodes. An exception to this are message queues, which by default reside on one node, though they are visible and reachable from all nodes. To replicate queues across nodes in a cluster --摘自官網

默認情況下:RabbitMQ代理操作所需的所有數據/狀態都將跨所有節點複製。這方面的一個例外是消息隊列,默認情況下,消息隊列位於一個節點上,儘管它們可以從所有節點看到和訪問

  1. 架構圖

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-mQ2Dnfik-1587108893015)(RibbitMQ 實戰教程.assets/image-20200320094147471.png)]

​ 核心解決問題: 當集羣中某一時刻master節點宕機,可以對Quene中信息,進行備份

  1. 集羣搭建
    # 0.集羣規劃
    	node1: 10.15.0.3  mq1  master 主節點
    	node2: 10.15.0.4  mq2  repl1  副本節點
    	node3: 10.15.0.5  mq3  repl2  副本節點
    
    # 1.克隆三臺機器主機名和ip映射
    	vim /etc/hosts加入:
    		 10.15.0.3 mq1
        	10.15.0.4 mq2
        	10.15.0.5 mq3
    	node1: vim /etc/hostname 加入:  mq1
    	node2: vim /etc/hostname 加入:  mq2
    	node3: vim /etc/hostname 加入:  mq3
    
    # 2.三個機器安裝rabbitmq,並同步cookie文件,在node1上執行:
    	scp /var/lib/rabbitmq/.erlang.cookie root@mq2:/var/lib/rabbitmq/
    	scp /var/lib/rabbitmq/.erlang.cookie root@mq3:/var/lib/rabbitmq/
    
    # 3.查看cookie是否一致:
    	node1: cat /var/lib/rabbitmq/.erlang.cookie 
    	node2: cat /var/lib/rabbitmq/.erlang.cookie 
    	node3: cat /var/lib/rabbitmq/.erlang.cookie 
    
    # 4.後臺啓動rabbitmq所有節點執行如下命令,啓動成功訪問管理界面:
    	rabbitmq-server -detached 
    
    # 5.在node2和node3執行加入集羣命令:
    	1.關閉       rabbitmqctl stop_app
    	2.加入集羣    rabbitmqctl join_cluster rabbit@mq1
    	3.啓動服務    rabbitmqctl start_app
    
    # 6.查看集羣狀態,任意節點執行:
    	rabbitmqctl cluster_status
    
    # 7.如果出現如下顯示,集羣搭建成功:
    	Cluster status of node rabbit@mq3 ...
    	[{nodes,[{disc,[rabbit@mq1,rabbit@mq2,rabbit@mq3]}]},
    	{running_nodes,[rabbit@mq1,rabbit@mq2,rabbit@mq3]},
    	{cluster_name,<<"rabbit@mq1">>},
    	{partitions,[]},
    	{alarms,[{rabbit@mq1,[]},{rabbit@mq2,[]},{rabbit@mq3,[]}]}]
    
    # 8.登錄管理界面,展示如下狀態:
    

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ynTjQEDu-1587108893018)(RibbitMQ 實戰教程.assets/image-20200320095613586.png)]

    # 9.測試集羣在node1上,創建隊列
    

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-0OXc9NCv-1587108893020)(RibbitMQ 實戰教程.assets/image-20200320095743935.png)]

    # 10.查看node2和node3節點:
    

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-tqpuG1zW-1587108893022)(RibbitMQ 實戰教程.assets/image-20200320095827688.png)]

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ZvvIroPK-1587108893024)(RibbitMQ 實戰教程.assets/image-20200320095843370.png)]

    # 11.關閉node1節點,執行如下命令,查看node2和node3:
    	rabbitmqctl stop_app
    

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-zcAmEq6r-1587108893026)(RibbitMQ 實戰教程.assets/image-20200320100000347.png)]

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-fVZaa4k7-1587108893029)(RibbitMQ 實戰教程.assets/image-20200320100010968.png)]


7.1.2 鏡像集羣

This guide covers mirroring (queue contents replication) of classic queues --摘自官網

By default, contents of a queue within a RabbitMQ cluster are located on a single node (the node on which the queue was declared). This is in contrast to exchanges and bindings, which can always be considered to be on all nodes. Queues can optionally be made mirrored across multiple nodes. --摘自官網

鏡像隊列機制就是將隊列在三個節點之間設置主從關係,消息會在三個節點之間進行自動同步,且如果其中一個節點不可用,並不會導致消息丟失或服務不可用的情況,提升MQ集羣的整體高可用性。

  1. 集羣架構圖

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-w0lfto0R-1587108893032)(RibbitMQ 實戰教程.assets/image-20200320113423235.png)]

  2. 配置集羣架構
    # 0.策略說明
    	rabbitmqctl set_policy [-p <vhost>] [--priority <priority>] [--apply-to <apply-to>] <name> <pattern>  <definition>
    	-p Vhost: 可選參數,針對指定vhost下的queue進行設置
    	Name:     policy的名稱
    	Pattern: queue的匹配模式(正則表達式)
    	Definition:鏡像定義,包括三個部分ha-mode, ha-params, ha-sync-mode
               		ha-mode:指明鏡像隊列的模式,有效值爲 all/exactly/nodes
                            all:表示在集羣中所有的節點上進行鏡像
                            exactly:表示在指定個數的節點上進行鏡像,節點的個數由ha-params指定
                            nodes:表示在指定的節點上進行鏡像,節點名稱通過ha-params指定
                	 ha-params:ha-mode模式需要用到的參數
                    ha-sync-mode:進行隊列中消息的同步方式,有效值爲automatic和manual
                    priority:可選參數,policy的優先級
                    
                     
    # 1.查看當前策略
    	rabbitmqctl list_policies
    
    # 2.添加策略
    	rabbitmqctl set_policy ha-all '^hello' '{"ha-mode":"all","ha-sync-mode":"automatic"}' 
    	說明:策略正則表達式爲 “^” 表示所有匹配所有隊列名稱  ^hello:匹配hello開頭隊列
    
    # 3.刪除策略
    	rabbitmqctl clear_policy ha-all
    
    # 4.測試集羣
    

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章