SpringBoot系列—消息(RabbitMQ)(二十)

個人博客:haichenyi.com。感謝關注

  大多數應用當中,可通過消息服務中間件來提升系統的異步通信和擴展解耦能力。

簡介

消息服務中兩個重要的概念

  消息代理目的地:當消息發送者發送消息之後,將由消息代理接管,消息代理保證消息發送到指定的目的地。

消息發送的兩種方式:

  1. 隊列(Queue):點對點消息通信(point-to-point)
  2. 主題(Topic):發佈(publish)/訂閱(subscribe)式消息通信

  開始說了,消息可以提升系統的異步通信和擴展解耦能力。異步通信,我們之前講異步任務的時候已經說過了。給用戶發送郵件就是最好,最直接的例子。

  至於,擴展解耦能力,最好最直接的例子就是流量削峯,舉個例子:整點秒殺。庫存只有100件,用戶有10000個人,整點用戶講發送10000個請求,難道每個都請求數據庫嗎?這個時候,我們就可以做個限制,用戶發送的請求先到消息隊列,然後,再由消息隊列統一管理,哪些請求時可以到數據庫的,哪些請求時不可以到數據庫的,這樣就解決了數據庫的抗壓能力。

實現

  點對點式

  • 消息發送者發送消息之後,消息代理將消息放在一個隊列當中,消息接收者從隊列中獲取消息內容,消息讀取後移除隊列
  • 消息只有唯一的發送者和接收者,但並不是說只能有一個接收者

  發佈訂閱式

  • 發送者(發佈者)發送消息到主題(topic),多個接收者(訂閱者)監聽(訂閱)這個主題,那麼,就會在消息到達的同時收到消息

JMS和AMQP

  • JMS:Java message service :Java消息服務基於JVM消息代理規範,ActiveMQ,HornetMQ就是JMS的實現
  • AMQP:advanced message Queue Protocol:高級消息隊列協議,也是消息代理的規範,兼容JMS,RabbitMQ就是AMQP的實現。

對比

類型 JMS AMQP
定義 Java api 網絡線級協議
跨語言
跨平臺
model 提供兩種消息模式:peer-2-peer,pub/sub 提供五種消息模式:direct exchange,fanout exchange,topic change,headers exchange,system exchange。本質來講,後四種和JMS的pub/sub模型沒有太大差別,僅是在路由機制上做了更詳細的劃分
支持消息類型 多種消息類型:TextMessage,MapMessage,BytesMessage,StreamMessage,ObjectMessage,Message (只有消息頭和屬性) byte[]類型,當實際應用中有複雜消息時,可以序列化之後再發送
綜合評價 JMS定義了java api層面的標準,在Java體系中,多個client均可通過JMS進行交互,不需要修改代碼,但是其對跨平臺支持較差 AMQP天然具有跨平臺,跨語言特性

RabbitMQ

簡介

  RabbitMQ是一個由erlang開發的AMQP(Advanved Message Queue Protocol)的開源實現。

核心概念

Message

  消息,消息是不具名的,它由消息頭和消息體組成。消息體是不透明的,而消息頭則由一系列的可選屬性組成,這些屬性包括routing-key(路由鍵)、priority(相對於其他消息的優先權)、delivery-mode(指出該消息可能需要持久性存儲)等

Publisher

  消息的生產者,也是一個向交換器發佈消息的客戶端應用程序

Exchange

  交換器,用來接收生產者發送的消息並將這些消息路由給服務器中的隊列。Exchange有4種類型:direct(默認),fanout,topic,和headers,不同類型的Exchange轉發消息的策略有所區別。

Queue

  消息隊列,用來保存消息直到發送給消費者。它是消息的容器,也是消息的終點。一個消息可投入一個或多個隊列。消息一直在隊列裏面,等待消費者連接到這個隊列將其取走。

Binding

  綁定,用於消息隊列和交換器之間的關聯。一個綁定就是基於路由鍵將交換器和消息隊列連接起來的路由規則,所以可以將交換器理解成一個由綁定構成的路由表。Exchange 和Queue的綁定可以是多對多的關係。

Connection

  網絡連接,比如一個TCP連接。

Channel

  信道,多路複用連接中的一條獨立的雙向數據流通道。信道是建立在真實的TCP連接內的虛擬連接,AMQP 命令都是通過信道發出去的,不管是發佈消息、訂閱隊列還是接收消息,這些動作都是通過信道完成。因爲對於操作系統來說建立和銷燬TCP都是非常昂貴的開銷,所以引入了信道的概念,以複用一條 TCP 連接。

Consumer

  消息的消費者,表示一個從消息隊列中取得消息的客戶端應用程序。

Virtual Host

  虛擬主機,表示一批交換器、消息隊列和相關對象。虛擬主機是共享相同的身份認證和加密環境的獨立服務器域。每個 vhost 本質上就是一個 mini 版的 RabbitMQ 服務器,擁有自己的隊列、交換器、綁定和權限機制。vhost 是 AMQP 概念的基礎,必須在連接時指定,RabbitMQ 默認的 vhost 是 / 。

Broker

  表示消息隊列服務器實體

RabbitMQ流程圖.png

流程

  AMQP的消息路由過程跟JMS存在一些差異,增加了Exchange和Binding的角色

JMS流程:

  1. 生產者(publisher)生成某個消息(Message),發送到某個隊列(Queue)上
  2. 消費者(Consumer)監聽這個隊列(Queue),消費消息

RabbitMQ流程:

  1. 生產者(publisher)生成某個消息(Message),把這個消息發送給我們的消息代理服務器上(Broker)
  2. 服務器收到消息之後,把這個消息給到一個合適的交換器(Exchange),(服務器有非常多的交換器)
  3. 交換器(Exchange)收到這個消息之後,根據路由鍵(Binding綁定關係)把這個消息給一個或者多個消息隊列(Queue)(服務器有很多個消息隊列)
  4. 消費者(Consumer)連接上隊列之後取出消息

重點就是:交換器和隊列的綁定

重點就是:交換器和隊列的綁定

重點就是:交換器和隊列的綁定

  我們上面說了Exchange有4種,不同類型轉發的消息策略不同,那麼,這個策略是什麼呢?其中,header和direct交換器完全一致,但是header性能上差很多,基本上不用了

重點start

  • direct交換器:當我們發送消息時的路由鍵和綁定中的key完全一致的時候,交換器就將消息發送到該隊列當中。它時完全匹配單播模式
  • fanout交換器:當我們消息發送到fanout交換器時,不管交換器與隊列綁定的路由鍵時什麼,fanout交換器都會把這個消息發送給每一個隊列,跟UDP廣播類似,fanout交換器發送消息最快。
  • topic交換器:該交換器允許我們對路由鍵做模糊匹配,有選擇性的發送給某一個或者多個隊列。兩個通配符:井號(#)和星號(*)。其中:井號:匹配0個或者多個單詞。星號:匹配一個單詞。

重點end

安裝

  首先,打開我們的虛擬機,用SecureCRT連接我們的虛擬機,我用的SecureCRT,至於你用的啥連接虛擬機,隨便你。

  然後,用docker安裝帶manager版本的rabbit,帶manager的版本自帶圖形化界面,容易操作。從docker hub上面搜索,我安裝的是

docker pull rabbitmq:3.8.1-management

  接着,新建容器,記得帶端口號,-d後臺運行,映射兩個端口號,起自己的名字,加上鏡像id

docker run -d -p 5672:5672 -p 15672:15672 --name myrabbitmq 鏡像id

  接下來,就可以訪問了,通過虛擬機的ip地址加上端口號,賬號密碼都是guest

虛擬機ip地址:15672

RabbitMQ管理界面圖.png

  我們在圖上,就能看到我們前面說過的:Connection,Channel,Exchange,Queue等等。

  最後面那個admin,我們能夠設置用戶名和密碼,就是我們前面登錄的guest,並且,能夠設置訪問的Virtual Hosts。

  我們看一下最上面的流程圖和消息發送流程
,我就舉一個例子:

  1. 首先,我們先創建一個交換器名字叫:haichenyi

創建交換器.png

  1. 其次,我們再創建一個隊列,名字也叫:haichenyi

創建消息隊列.png

  1. 接着,我們將這交換器和隊列綁定到一起

交換器與隊列綁定.png

  1. 然後,我們隨便發送一條消息

  2. 最後,查看消息隊列

發送消息之後的queue.png

PS:

  • 我們在創建Exchange和Queue的時候,有一個選項:Durability,意思是是否可持久化,也就是,服務器重啓之後這個東西是否還存在。就選默認的durable就行了,可持久化的
  • 我們在Exchange和Queue綁定的時候,發送消息的時候,都要填一個Routing key,就是上文我們說的綁定規則。

  這就是整個流程,這都是頁面操作,下面說一下代碼裏面怎麼寫,很簡單。

用法

  首先,添加依賴:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>

  然後,就是配置:兩個可能出錯的位置,我已經註釋標明瞭

spring.rabbitmq.host=192.168.113.22
#這裏端口號要用5672,不能用15672,15672是後臺管理頁面的端口號
spring.rabbitmq.port=5672
#這裏的用戶民和密碼還有virtual-host要對應上,新建的賬號要記得給權限
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtual-host=/

  代碼怎麼寫呢?

@SpringBootTest
class SellApplicationTests {

    @Autowired
    RabbitTemplate rabbitTemplate;

    @Test
    void contextLoads() {
        //message需要自己構造一個;定義消息體內容和消息頭
        //rabbitTemplate.send(exchange,routingKey,message);

        //Object默認當成消息體,只需傳入要發送的對象,自動序列化發送給RabbitMQ
        //rabbitTemplate.convertAndSend(exchange,routingKey,Object);
        
        //對象會被默認序列化之後發送
        User user = new User("海晨憶",25);
        rabbitTemplate.convertAndSend("haichenyi","haichenyi",user);
    }

    @Test
    void getMsg(){
        //queueName:需要從哪個隊列中收消息
        User user = (User) rabbitTemplate.receiveAndConvert("haichenyi");
        System.out.println(user.getName());
        System.out.println(user.getAge());
    }
    
}

  可以向上面這樣測試,發送和接收。實際應用中,我們要向下面這樣寫:

  • 啓動類上添加@EnableRabbit註釋,開啓Rabbit監聽功能
  • 在我們接收的方法添加@RabbitListener註解,queues是一個數組,方法的參數是發送的數據類型。
@Service
public class UserService {

    @RabbitListener(queues = "haichenyi")
    public void receive(User user){
        System.out.println("收到消息:"+user);
        System.out.println(user.getName());
        System.out.println(user.getAge());
    }
}

  以上,就是RabbitMQ的簡單使用了,上面的Exchange,Queue都是在管理界面創建綁定的,代碼裏面怎麼創建綁定呢?

@Autowired
    RabbitAdmin rabbitAdmin;

    @Test
    void createExchange(){
        //以declare開頭的都是創建,這裏是創建一個Exchange,需要傳一個Exchange對象
        //我們點擊過去看,是一個接口,我們就看它的實現類。可以看到5種實現類
        rabbitAdmin.declareExchange(new DirectExchange("wang.exchange"));
        //創建一個queue隊列
        rabbitAdmin.declareQueue(new Queue("wang.queue"));
        //destination:目的地
        //Binding.DestinationType destinationType:類型,枚舉類:隊列和交換器兩種
        //exchange:交換器
        //routingKey:路由鍵
        //arguments:頭信息
        //綁定有兩種綁定方式,一種是把交換器往隊列上面綁定,一種是把隊列往交換器上面綁定
        rabbitAdmin.declareBinding(new Binding("wang.queue",Binding.DestinationType.QUEUE,"wang.exchange","wang.key",null));
    }

  然後,發消息的操作就跟前面寫的一樣了

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