SpringBoot2--RabbitMQ訊息

nsxrj發表於2020-12-15

前言

大多應用中,可通過訊息服務中介軟體來提升系統非同步通訊、擴充套件解耦能力

一、概述

1、概述

1)訊息服務中兩個重要概念:
訊息代理(message broker)和目的地(destination)
當訊息傳送者傳送訊息以後,將由訊息代理接管,訊息代理保證訊息傳遞到指定目的地。
2)非同步訊息主要有兩種形式的目的地
點對點式(direct):
– 訊息傳送者傳送訊息,訊息代理將其放入一個佇列中,訊息接收者從佇列中獲取訊息內容,訊息讀取後被移出佇列
– 訊息只有唯一的傳送者和接受者,但並不是說只能有一個接收者(多個接收者只有一個能收到)
釋出訂閱式(topic):
– 傳送者(釋出者)傳送訊息到主題,多個接收者(訂閱者)監聽(訂閱)這個主題,那麼就會在訊息到達時同時收到訊息

2、JMS和AMQP

JMS(Java Message Service)JAVA訊息服務:
– 基於JVM訊息代理的規範。ActiveMQ、HornetMQ是JMS實現
AMQP(Advanced Message Queuing Protocol)
– 高階訊息佇列協議,也是一個訊息代理的規範,相容JMS
– RabbitMQ是AMQP的實現
在這裡插入圖片描述

Spring支援

– spring-jms提供了對JMS的支援
– spring-rabbit提供了對AMQP的支援
– 需要ConnectionFactory的實現來連線訊息代理
– 提供JmsTemplate、RabbitTemplate來傳送訊息
– @JmsListener(JMS)、@RabbitListener(AMQP)註解在方法上監聽訊息代理髮布的訊息
– @EnableJms、@EnableRabbit開啟支援

Spring Boot自動配置

– JmsAutoConfiguration
– RabbitAutoConfiguration

二、RabbitMQ

1、RabbitMQ簡介

RabbitMQ簡介:

RabbitMQ是一個由erlang開發的AMQP(Advanved Message Queue Protocol)的開源實現。
在這裡插入圖片描述

核心概念

Message
訊息,訊息是不具名的,它由訊息頭和訊息體組成。訊息體是不透明的,而訊息頭則由一系列的可選屬性組成,這些屬性包括routing-key(路由鍵)、priority(相對於其他訊息的優先權)、delivery-mode(指出該訊息可能需要永續性儲存)等。
Publisher
訊息的生產者,也是一個向交換器釋出訊息的客戶端應用程式。
Consumer
訊息的消費者,表示一個從訊息佇列中取得訊息的客戶端應用程式。
Broker
– 它提供一種傳輸服務,它的角色就是維護一條從生產者到消費者的路線,保證資料能按照指定的方式進行傳輸
Exchange
– 訊息交換機,用來接收生產者傳送的訊息並將這些訊息路由給伺服器中的佇列。
– Exchange有4種型別:direct(預設),fanout, topic, 和headers,不同型別的Exchange轉發訊息的策略有所區別
Queue
訊息佇列,用來儲存訊息直到傳送給消費者。它是訊息的容器,也是訊息的終點。一個訊息可投入一個或多個佇列。訊息一直在佇列裡面,等待消費者連線到這個佇列將其取走。
– 提供了FIFO的處理機制,具有快取訊息的能力。rabbitmq中,佇列訊息可以設定為持久化,臨時或者自動刪除。
– 設定為持久化的佇列,queue中的訊息會在server本地硬碟儲存一份,防止系統crash,資料丟失
– 設定為臨時佇列,queue中的資料在系統重啟之後就會丟失
– 設定為自動刪除的佇列,當不存在使用者連線到server,佇列中的資料會被自動刪除
Binding
繫結,用於訊息佇列和交換器之間的關聯。一個繫結就是基於路由鍵將交換器和訊息佇列連線起來的路由規則,所以可以將交換器理解成一個由繫結構成的路由表。
Exchange 和Queue的繫結可以是多對多的關係。
Connection
網路連線,比如一個TCP連線。
Channel
通道,多路複用連線中的一條獨立的雙向資料流通道。通道是建立在真實的TCP連線內的虛擬連線,AMQP 命令都是通過通道發出去的,不管是釋出訊息、訂閱佇列還是接收訊息,這些動作都是通過通道完成。因為對於作業系統來說建立和銷燬 TCP 都是非常昂貴的開銷,所以引入了通道的概念,以複用一條 TCP 連線。
Virtual Host
虛擬主機,表示一批交換器、訊息佇列和相關物件。虛擬主機是共享相同的身份認證和加密環境的獨立伺服器域。在rabbitmq server上可以建立多個虛擬的message broker,又叫做
virtual hosts (vhosts)。每個 vhost 本質上就是一個 mini 版的 RabbitMQ 伺服器,擁有自己的佇列、交換器、繫結和許可權機制。vhost 是 AMQP 概念的基礎,producer和consumer連線rabbit server必須指定,RabbitMQ 預設的 vhost 是 / 。
在這裡插入圖片描述

2、RabbitMQ執行機制

1)AMQP 中的訊息路由
• AMQP 中訊息的路由過程和 Java 開發者熟悉的 JMS 存在一些差別,AMQP 中增加了Exchange 和 Binding 的角色。生產者把訊息釋出到 Exchange 上,訊息最終到達佇列並被消費者接收,而 Binding 中的 binding key,訊息中的路由鍵(routing key)和交換機的型別 決定交換器的訊息應該傳送到那個佇列。
在這裡插入圖片描述
訊息在傳送時都會設定一個路由鍵,用來匹配和交換機繫結的訊息佇列的繫結鍵,只有匹配成功才能收到訊息
2)Exchange 型別
Exchange分發訊息時根據型別的不同分發策略有區別,目前共四種型別:direct、fanout、topic、headers 。headers 匹配 AMQP 訊息的 header 而不是路由鍵, headers 交換器和 direct 交換器完全一致,但效能差很多,目前幾乎用不到了,所以直接看另外三種型別:
訊息中的路由鍵(routing key)如果和 Binding 中的 binding key 一致, 交換器就將訊息發到對應的佇列中。路由鍵與佇列名完全匹配,如果一個佇列繫結到交換機要求路由鍵為“dog”,則只轉發 routing key 標記為“dog”的訊息,不會轉發“dog.puppy”,也不會轉發“dog.guard”等等。它是完全匹配、單播的模式。
在這裡插入圖片描述
每個發到 fanout 型別交換器的訊息都會分到所有繫結的佇列上去。fanout 交換器不處理路由鍵,只是簡單的將佇列繫結到交換器上,每個傳送到交換器的訊息都會被轉發到與該交換器繫結的所有佇列上。很像子網廣播,每臺子網內的主機都獲得了一份複製的訊息。fanout 型別轉發訊息是最快的。
在這裡插入圖片描述
topic 交換器通過模式匹配分配訊息的路由鍵屬性,將路由鍵和某個模式進行匹配,此時佇列需要繫結到一個模式上。它將路由鍵和繫結鍵的字串切分成單詞,這些單詞之間用點隔開。它同樣也會識別兩個萬用字元:符號“#”和符號“*”。#匹配0個或多個單詞,*匹配一個單詞。
在這裡插入圖片描述

三、準備工作

1、安裝RabbitMQ

我們安裝management版本

docker pull rabbitmq:3.8-management

在這裡插入圖片描述
啟動RabbitMQ,開啟兩個埠,注意阿里雲伺服器要開安全組
5672:通訊埠
15672:頁面管理埠

docker run -d -p 5672:5672 -p 15672:15672 --name myrabbitmq 41f7a13e37d3

2、RabbitMQ管理介面

通過地址http://伺服器ip:15672/訪問RabbitMQ管理介面,注意有的瀏覽器可能進不去,可以使用google瀏覽器,使用者名稱和密碼用guest即可進入
在這裡插入圖片描述
按照下面的圖新增三個交換機和四個訊息佇列,並繫結,除了exchange.topic按照下圖規則設定繫結鍵,其他繫結鍵和佇列名一樣
在這裡插入圖片描述

四、RabbitMQ整合

1、準備

1)建立工程,匯入依賴

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

2)配置
自動配置類RabbitAutoConfiguration ,它配置了
CachingConnectionFactory:連線工廠
RabbitTemplateConfigurer:配置Rabbit操作
RabbitTemplate:Rabbit操作
AmqpAdmin:系統管理元件,建立和刪除交換器,訊息佇列和繫結規則
RabbitProperties:配置封裝類

@ConditionalOnClass({ RabbitTemplate.class, Channel.class })
@EnableConfigurationProperties(RabbitProperties.class)
@Import(RabbitAnnotationDrivenConfiguration.class)
public class RabbitAutoConfiguration {

全域性配置

#配置rabbitmq
spring.rabbitmq.host=8.135.14.253
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.port=5672
#spring.rabbitmq.virtual-host=/

3)定義一個Bean,要加無參構造器和toString方法

package com.sxt.rabbitmq.bean;

public class Book {

	private String bName;
	private String author;

	public Book() {
	}

	public Book(String bName, String author) {
		this.bName = bName;
		this.author = author;
	}

	public String getbName() {
		return bName;
	}

	public void setbName(String bName) {
		this.bName = bName;
	}

	public String getAuthor() {
		return author;
	}

	public void setAuthor(String author) {
		this.author = author;
	}

	@Override
	public String toString() {
		return "Book{" +
				"bName='" + bName + '\'' +
				", author='" + author + '\'' +
				'}';
	}
}

2、傳送接收測試

1)傳送點對點訊息

需要指定exchange和routeKey,還有傳送的資料

	@Autowired
	RabbitTemplate  rabbitTemplate;
	
	@Test
	void contextLoads() {
		//message需要自己構造;定製訊息體內容
		//rabbitTemplate.send(exchange,routeKey,message);
        //只需要傳入要傳送的物件,自動序列化
		//rabbitTemplate.convertAndSend(exchange,routeKey,object);
		Map<String,Object> map = new HashMap<>();
		map.put("aaa","hhhh");
		map.put("msg", Arrays.asList("dddddd","gggggggggg"));
		rabbitTemplate.convertAndSend("exchange.direct","atguigu",map);
	}

交換機exchange.direct繫結的atguigu訊息佇列收到一條訊息
在這裡插入圖片描述
在這裡插入圖片描述
因為預設使用java-serialized序列化,所以是一串位元組碼

2)接收訊息

	@Autowired
	RabbitTemplate  rabbitTemplate;
	
	@Test
	void receive() {
		//message需要自己構造;定製訊息體內容
		//rabbitTemplate.send(exchange,routeKey,message);

		//只會接收到訊息體
		//rabbitTemplate.receiveAndConvert(訊息佇列名稱);

		Object atguigu = rabbitTemplate.receiveAndConvert("atguigu");
		System.out.println(atguigu);
	}

在這裡插入圖片描述
接收到訊息佇列atguigu的訊息,佇列訊息清零
在這裡插入圖片描述
3)序列化機制
RabbitTemplate 類預設使用jdk序列化

public class RabbitTemplate ...{
    private MessageConverter messageConverter = new SimpleMessageConverter();

而我們在定義MessageConverter 的時候有多種選擇
在這裡插入圖片描述
自定義MessageConverter ,使用Jackson2JsonMessageConverter

@Configuration
public class myRabbitConfig {

	@Bean
	public MessageConverter myMessageConverter(){

         return new Jackson2JsonMessageConverter();
	}
}

以下原始碼在建立RabbitTemplate時會把自定義的MessageConverter 加上


public class RabbitTemplateConfigurer {
     	public void configure(RabbitTemplate template, ConnectionFactory connectionFactory) {
		    PropertyMapper map = PropertyMapper.get();
		    template.setConnectionFactory(connectionFactory);
		    if (this.messageConverter != null) {
			    template.setMessageConverter(this.messageConverter);
		   }

再次傳送訊息
在這裡插入圖片描述
4)傳送廣播訊息
傳送給一個設定為fanout的交換器即可

	/*
	 * 傳送訊息
	 * 2.廣播
	 * */
	@Test
	void sendMsg() {
		rabbitTemplate.convertAndSend("exchange.fanout","",new Book("西遊記","吳承恩"));
	}

3、監聽測試

通過監聽@RabbitListener訊息佇列來做出反應

@Service
public class BookService {

	//監聽的訊息佇列,queues可以以陣列的方式,同時監聽多個
	@RabbitListener(queues = "atguigu")
	public void receive(Book book){
		System.out.println("收到訊息"+book);
	}

	@RabbitListener(queues = "atguigu.news")//監聽的訊息佇列atguigu.news
	public void receive02(Message massage){
	    //獲取訊息體
		System.out.println(massage.getBody());
		
		System.out.println(massage.getMessageProperties());
	}
}

必須開啟基於註解的rabbit模式@EnableRabbit

/*
* 自動配置類
*    RabbitAutoConfiguration
* 1.配置了連線工廠CachingConnectionFactory
* 2.RabbitTemplate
* 3.AmqpAdmin:系統管理元件,交換器,訊息佇列和繫結規則
* 4.RabbitTemplateConfigurer
* 配置封裝類RabbitProperties
* */
@EnableRabbit//開啟基於註解的rabbit模式
@SpringBootApplication
public class Application {

	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}

}

啟動主程式,通過以下程式碼發訊息

	@Test
	void contextLoads() {
		rabbitTemplate.convertAndSend("exchange.direct","atguigu",new Book("三國演義","羅貫中"));
	}

在這裡插入圖片描述
給訊息佇列atguigu.news發訊息,得到Message如下
在這裡插入圖片描述

3、AmqpAdmin管理元件

AmqpAdmin用來建立、刪除和繫結Exchange和Queue

	@Autowired
	AmqpAdmin  amqpAdmin;


	@Test
	public void create(){
		//建立一個DirectExchange
		amqpAdmin.declareExchange(new DirectExchange("dExchange",true,true));
		//建立一個Fanoutexchange
		//amqpAdmin.declareExchange(new FanoutExchange("fExchange"));
		//System.out.println("建立完成");


		//建立一個Queue
		amqpAdmin.declareQueue(new Queue("queue01",true));

		//建立繫結規則
		amqpAdmin.declareBinding(new Binding("queue01", Binding.DestinationType.QUEUE,"dExchange","key1",null));
		
		//刪除名字為fExchange的Exchange
		amqpAdmin.deleteExchange("fExchange");

	}

可以建立的Exchange型別
在這裡插入圖片描述

相關文章