1.首先我們簡單瞭解一下消息中間件的應用場景
異步處理
場景說明:用戶註冊後,需要發註冊郵件和註冊短信,傳統的做法有兩種1.串行的方式;2.並行的方式
(1)串行方式:將註冊信息寫入數據庫後,發送註冊郵件,再發送註冊短信,以上三個任務全部完成後才返回給客戶端。 這有一個問題是,郵件,短信並不是必須的,它只是一個通知,而這種做法讓客戶端等待沒有必要等待的東西.
(2)並行方式:將註冊信息寫入數據庫後,發送郵件的同時,發送短信,以上三個任務完成後,返回給客戶端,並行的方式能提高處理的時間。
假設三個業務節點分別使用50ms,串行方式使用時間150ms,並行使用時間100ms。雖然並性已經提高的處理時間,但是,前面說過,郵件和短信對我正常的使用網站沒有任何影響,客戶端沒有必要等着其發送完成才顯示註冊成功,英愛是寫入數據庫後就返回.
(3)消息隊列
引入消息隊列後,把發送郵件,短信不是必須的業務邏輯異步處理
由此可以看出,引入消息隊列後,用戶的響應時間就等於寫入數據庫的時間+寫入消息隊列的時間(可以忽略不計),引入消息隊列後處理後,響應時間是串行的3倍,是並行的2倍。
應用解耦
場景:雙11是購物狂節,用戶下單後,訂單系統需要通知庫存系統,傳統的做法就是訂單系統調用庫存系統的接口.
這種做法有一個缺點:
- 當庫存系統出現故障時,訂單就會失敗。
-
訂單系統和庫存系統高耦合.
引入消息隊列
訂單系統:用戶下單後,訂單系統完成持久化處理,將消息寫入消息隊列,返回用戶訂單下單成功。
庫存系統:訂閱下單的消息,獲取下單消息,進行庫操作。
就算庫存系統出現故障,消息隊列也能保證消息的可靠投遞,不會導致消息丟失。
流量削峯
流量削峯一般在秒殺活動中應用廣泛
場景:秒殺活動,一般會因爲流量過大,導致應用掛掉,爲了解決這個問題,一般在應用前端加入消息隊列。
作用:
1.可以控制活動人數,超過此一定閥值的訂單直接丟棄(我爲什麼秒殺一次都沒有成功過呢^^)
2.可以緩解短時間的高流量壓垮應用(應用程序按自己的最大處理能力獲取訂單)
1.用戶的請求,服務器收到之後,首先寫入消息隊列,加入消息隊列長度超過最大值,則直接拋棄用戶請求或跳轉到錯誤頁面.
2.秒殺業務根據消息隊列中的請求信息,再做後續處理.
以上內容的來源是:https://blog.csdn.net/whoamiyang/article/details/54954780,在此感謝
2.各種消息中間件性能的比較:
TPS比較 一ZeroMq 最好,RabbitMq 次之, ActiveMq 最差。
持久化消息比較—zeroMq不支持,activeMq和rabbitMq都支持。持久化消息主要是指:MQ down或者MQ所在的服務器down了,消息不會丟失的機制。
可靠性、靈活的路由、集羣、事務、高可用的隊列、消息排序、問題追蹤、可視化管理工具、插件系統、社區—RabbitMq最好,ActiveMq次之,ZeroMq最差。
高併發—從實現語言來看,RabbitMQ最高,原因是它的實現語言是天生具備高併發高可用的erlang語言。
綜上所述:RabbitMQ的性能相對來說更好更全面,是消息中間件的首選。
3.接下來我們在springboot當中整合使用RabbitMQ
需要提前安裝RabbitMQ 可參考:https://blog.csdn.net/Keith003/article/details/82386210
第一步:導入maven依賴
<!-- rabbitmq依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
第二步:在application.yml文件中進行RabbitMQ的相關配置
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest # guest 3.0以後默認只能本地登陸
password: guest
publisher-confirms: true # 消息發送到交換機確認機制,是否確認回調
publisher-returns: true
virtual-host: /
connection-timeout: 6000
server:
port: 8080
注:使用場景:
- 如果消息沒有到exchange,則confirm回調,ack=false
- 如果消息到達exchange,則confirm回調,ack=true
- exchange到queue成功,則不回調return
- exchange到queue失敗,則回調return(需設置mandatory=true,否則不回回調,消息就丟了)
第三步:進行相關配置
ExchangeConfig 消息交換機配置
package com.space.rabbitmq.config;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 消息交換機配置 可以配置多個
*/
@Configuration
public class ExchangeConfig {
/**
* 1.定義direct exchange,綁定queueTest
* 2.durable="true" rabbitmq重啓的時候不需要創建新的交換機
* 3.direct交換器相對來說比較簡單,匹配規則爲:如果路由鍵匹配,消息就被投送到相關的隊列
* fanout交換器中沒有路由鍵的概念,他會把消息發送到所有綁定在此交換器上面的隊列中。
* topic交換器你採用模糊匹配路由鍵的原則進行轉發消息到隊列中
* key: queue在該direct-exchange中的key值,當消息發送給direct-exchange中指定key爲設置值時,
* 消息將會轉發給queue參數指定的消息隊列
*/
@Bean
public DirectExchange directExchange(){
DirectExchange directExchange = new DirectExchange(RabbitMqConfig.EXCHANGE,true,false);
return directExchange;
}
}
QueueConfig 隊列配置
package com.space.rabbitmq.config;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 隊列配置 可以配置多個隊列
*/
@Configuration
public class QueueConfig {
@Bean
public Queue firstQueue() {
/**
durable="true" 持久化 rabbitmq重啓的時候不需要創建新的隊列
auto-delete 表示消息隊列沒有在使用時將被自動刪除 默認是false
exclusive 表示該消息隊列是否只在當前connection生效,默認是false
*/
return new Queue("first-queue",true,false,false);
}
@Bean
public Queue secondQueue() {
return new Queue("second-queue",true,false,false);
}
}
RabbitMqConfig RabbitMq配置
package com.space.rabbitmq.config;
import com.space.rabbitmq.mqcallback.MsgSendConfirmCallBack;
import com.space.rabbitmq.mqcallback.MsgSendReturnCallback;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* RabbitMq配置
*/
@Configuration
public class RabbitMqConfig {
/** 消息交換機的名字*/
public static final String EXCHANGE = "exchangeTest";
/*對列名稱*/
public static final String QUEUE_NAME1 = "first-queue";
public static final String QUEUE_NAME2 = "second-queue";
/*
*
* key: queue在該direct-exchange中的key值,當消息發送給direct-exchange中指定key爲設置值時,
* 消息將會轉發給queue參數指定的消息隊列
*/
/** 隊列key1*/
public static final String ROUTINGKEY1 = "queue_one_key1";
/** 隊列key2*/
public static final String ROUTINGKEY2 = "queue_one_key2";
@Autowired
private QueueConfig queueConfig;
@Autowired
private ExchangeConfig exchangeConfig;
/**
* 連接工廠
*/
@Autowired
private ConnectionFactory connectionFactory;
/**
* 將消息隊列1和交換機進行綁定,指定隊列key1
*/
@Bean
public Binding binding_one() {
return BindingBuilder.bind(queueConfig.firstQueue()).to(exchangeConfig.directExchange()).with(RabbitMqConfig.ROUTINGKEY1);
}
/**
* 將消息隊列2和交換機進行綁定,指定隊列key2
*/
@Bean
public Binding binding_two() {
return BindingBuilder.bind(queueConfig.secondQueue()).to(exchangeConfig.directExchange()).with(RabbitMqConfig.ROUTINGKEY2);
}
/**
* queue listener 觀察 監聽模式
* 當有消息到達時會通知監聽在對應的隊列上的監聽對象
* @return
*/
/*@Bean
public SimpleMessageListenerContainer simpleMessageListenerContainer_one(){
SimpleMessageListenerContainer simpleMessageListenerContainer = new SimpleMessageListenerContainer(connectionFactory);
simpleMessageListenerContainer.addQueues(queueConfig.firstQueue());
simpleMessageListenerContainer.setExposeListenerChannel(true);
simpleMessageListenerContainer.setMaxConcurrentConsumers(5);
simpleMessageListenerContainer.setConcurrentConsumers(1);
simpleMessageListenerContainer.setAcknowledgeMode(AcknowledgeMode.MANUAL); //設置確認模式手工確認
return simpleMessageListenerContainer;
}*/
/**
* 自定義rabbit template用於數據的接收和發送
* 可以設置消息確認機制和回調
* @return
*/
@Bean
public RabbitTemplate rabbitTemplate() {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
// template.setMessageConverter(); 可以自定義消息轉換器 默認使用的JDK的,所以消息對象需要實現Serializable
/**若使用confirm-callback或return-callback,
* 必須要配置publisherConfirms或publisherReturns爲true
* 每個rabbitTemplate只能有一個confirm-callback和return-callback
*/
template.setConfirmCallback(msgSendConfirmCallBack());
/**
* 使用return-callback時必須設置mandatory爲true,或者在配置中設置mandatory-expression的值爲true,
* 可針對每次請求的消息去確定’mandatory’的boolean值,
* 只能在提供’return -callback’時使用,與mandatory互斥
*/
template.setReturnCallback(msgSendReturnCallback());
template.setMandatory(true);
return template;
}
/* 關於 msgSendConfirmCallBack 和 msgSendReturnCallback 的回調說明:
1.如果消息沒有到exchange,則confirm回調,ack=false
2.如果消息到達exchange,則confirm回調,ack=true
3.exchange到queue成功,則不回調return
4.exchange到queue失敗,則回調return(需設置mandatory=true,否則不回回調,消息就丟了)
*/
/**
* 消息確認機制
* Confirms給客戶端一種輕量級的方式,能夠跟蹤哪些消息被broker處理,
* 哪些可能因爲broker宕掉或者網絡失敗的情況而重新發布。
* 確認並且保證消息被送達,提供了兩種方式:發佈確認和事務。(兩者不可同時使用)
* 在channel爲事務時,不可引入確認模式;同樣channel爲確認模式下,不可使用事務。
* @return
*/
@Bean
public MsgSendConfirmCallBack msgSendConfirmCallBack(){
return new MsgSendConfirmCallBack();
}
@Bean
public MsgSendReturnCallback msgSendReturnCallback(){
return new MsgSendReturnCallback();
}
}
消息回調
package com.space.rabbitmq.mqcallback;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.support.CorrelationData;
/**
* 消息發送到交換機確認機制
*/
public class MsgSendConfirmCallBack implements RabbitTemplate.ConfirmCallback {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println("MsgSendConfirmCallBack , 回調id:" + correlationData);
if (ack) {
System.out.println("消息消費成功");
} else {
System.out.println("消息消費失敗:" + cause+"\n重新發送");
}
}
}
package com.space.rabbitmq.mqcallback;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
public class MsgSendReturnCallback implements RabbitTemplate.ReturnCallback {
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
System.out.println("回饋消息:"+message);
}
}
生產者/消息發送者
package com.space.rabbitmq.sender;
import com.space.rabbitmq.config.RabbitMqConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.UUID;
/**
* 消息發送 生產者1
*/
@Slf4j
@Component
public class FirstSender {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 發送消息
* @param uuid
* @param message 消息
*/
public void send(String uuid,Object message) {
CorrelationData correlationId = new CorrelationData(uuid);
rabbitTemplate.convertAndSend(RabbitMqConfig.EXCHANGE, RabbitMqConfig.ROUTINGKEY2,
message, correlationId);
}
}
消費者
package com.space.rabbitmq.receiver;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* 消息消費者1
*/
@Component
public class FirstConsumer {
@RabbitListener(queues = {"first-queue","second-queue"}, containerFactory = "rabbitListenerContainerFactory")
public void handleMessage(String message) throws Exception {
// 處理消息
System.out.println("FirstConsumer {} handleMessage :"+message);
}
}
測試
package com.space.rabbitmq.controller;
import com.space.rabbitmq.sender.FirstSender;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
@RestController
public class SendController {
@Autowired
private FirstSender firstSender;
@GetMapping("/send")
public String send(String message){
String uuid = UUID.randomUUID().toString();
firstSender.send(uuid,message);
return uuid;
}
}
此時,我們就可以啓動項目,進行測試了
轉載:https://blog.csdn.net/zhuzhezhuzhe1/article/details/80454956