個人博客:haichenyi.com。感謝關注
大多數應用當中,可通過消息服務中間件來提升系統的異步通信和擴展解耦能力。
簡介
消息服務中兩個重要的概念
消息代理和目的地:當消息發送者發送消息之後,將由消息代理接管,消息代理保證消息發送到指定的目的地。
消息發送的兩種方式:
- 隊列(Queue):點對點消息通信(point-to-point)
- 主題(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
表示消息隊列服務器實體
流程
AMQP的消息路由過程跟JMS存在一些差異,增加了Exchange和Binding的角色
JMS流程:
- 生產者(publisher)生成某個消息(Message),發送到某個隊列(Queue)上
- 消費者(Consumer)監聽這個隊列(Queue),消費消息
RabbitMQ流程:
- 生產者(publisher)生成某個消息(Message),把這個消息發送給我們的消息代理服務器上(Broker)
- 服務器收到消息之後,把這個消息給到一個合適的交換器(Exchange),(服務器有非常多的交換器)
- 交換器(Exchange)收到這個消息之後,根據路由鍵(Binding綁定關係)把這個消息給一個或者多個消息隊列(Queue)(服務器有很多個消息隊列)
- 消費者(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
我們在圖上,就能看到我們前面說過的:Connection,Channel,Exchange,Queue等等。
最後面那個admin,我們能夠設置用戶名和密碼,就是我們前面登錄的guest,並且,能夠設置訪問的Virtual Hosts。
我們看一下最上面的流程圖和消息發送流程
,我就舉一個例子:
- 首先,我們先創建一個交換器名字叫:haichenyi
- 其次,我們再創建一個隊列,名字也叫:haichenyi
- 接着,我們將這交換器和隊列綁定到一起
-
然後,我們隨便發送一條消息
-
最後,查看消息隊列
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));
}
然後,發消息的操作就跟前面寫的一樣了