RabbitMQ多種消息模型實戰
前面我們學習了RabbitMQ的核心基礎組件,瞭解了基本消息模型由隊列、交換機、路由構成。而在RabbitMQ的核心組件體系中,主要有4種消息模型:
基於HeadersExchange、DirectExchange、FanoutExchange、TopicExchange的消息模型;在實際生產環境中應用最廣泛的莫過於後3中消息模型。
本篇文章主要介紹DirectExchange消息模型。
DirectExchange消息模型實戰
DirectExchange,顧名思義也是一種交換機,具有直連傳輸消息的作用,即當消息進入交換機這個中轉站時,交換機會檢查哪個路由和自己綁定在一起,並根據生產者發送消息指定的路由進行匹配,如果能找到對應的綁定模型,則將消息直接路由傳輸到指定的隊列,最終由隊列對應的消費者進行監聽消費。
此模型在RabbitMQ多種消費模型中可以說是比較正規的了,因爲它需要嚴格意義上的綁定,即需要且必須指定特定的交換機和路由,並綁定到指定的隊列中。這種嚴格意義的要求使得該消息模型在生產環境中具有很廣泛的運用。如圖爲該消息模型的結構圖:
下面結合業務場景說明該消息模型的使用。
業務場景:將實體對象信息當做消息,併發送到基於DirectExchange構成的消息模型中,根據綁定的路由,將消息路由至對應綁定的隊列中,最終由對應的消費者進行監聽消費處理。
1.在自定義注入配置類RabbitmqConfig中創建交換機、多條隊列及其綁定
package com.debug.middleware.server.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
/**
* @className:
* @PackageName: com.debug.middleware.server.config
* @author: youjp
* @create: 2020-04-06 16:39
* @description: TODO RabbitMQ自定義注入配置Bean相關組件
* @Version: 1.0
*/
@Configuration
public class RabbitmqConfig {
//定義日誌
private static final Logger logger=LoggerFactory.getLogger(RabbitmqConfig.class);
//自動裝配RabbitMQ的鏈接工廠實例
@Autowired
private CachingConnectionFactory connectionFactory;
//自動裝配消息監聽器所在的容器工廠配置類實例
@Autowired
private SimpleRabbitListenerContainerFactoryConfigurer factoryConfigurer;
/**
* 下面爲單一消費者實例的配置
* @return
*/
@Bean("singleListenerContainer")
public SimpleRabbitListenerContainerFactory listenerContainer(){
//定義消息監聽器所在的容器工廠
SimpleRabbitListenerContainerFactory factory=new SimpleRabbitListenerContainerFactory();
//設置容器工廠所用的實例
factory.setConnectionFactory(connectionFactory);
//設置消息在傳輸中的格式,在這裏採用json格式進行傳輸
factory.setMessageConverter(new Jackson2JsonMessageConverter());
//設置併發消費者實例的初始數量。在這裏爲1個
factory.setConcurrentConsumers(1);
//設置併發消費者實例的最大數量。在這裏爲1個
factory.setMaxConcurrentConsumers(1);
//設置併發消費者實例中每個實例拉取到的消息數量-在這裏爲1個
factory.setPrefetchCount(1);
return factory;
}
/**
* 多個消費者:主要針對高併發業務場景的配置
* @return
*/
@Bean(name = "multiListenerContainer")
public SimpleRabbitListenerContainerFactory multiListenerContainer(){
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factoryConfigurer.configure(factory,connectionFactory);
factory.setMessageConverter(new Jackson2JsonMessageConverter());
factory.setAcknowledgeMode(AcknowledgeMode.NONE);
factory.setConcurrentConsumers(10);
factory.setMaxConcurrentConsumers(15);
factory.setPrefetchCount(10);
return factory;
}
/**
* RabbitMQ發送消息的操作組件實例
* @return
*/
@Bean
public RabbitTemplate rabbitTemplate(){
//設置發現消息後進行確認
connectionFactory.setPublisherConfirms(true);
//設置發現消息後返回確認信息
connectionFactory.setPublisherReturns(true);
//構造發送消息組件的實例對象
RabbitTemplate rabbitTemplate=new RabbitTemplate(connectionFactory);
rabbitTemplate.setMandatory(true);
//發送消息後,如果發送成功,則輸出"消息發送成功"的反饋信息
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
logger.info("消息發送成功:correlationData({}),ack({}),cause({})",correlationData,ack,cause);
}
});
//發送消息後,如果發送失敗,則輸出"消息發送失敗-消息丟失"的反饋信息
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
logger.info("消息丟失:exchange({}),route({}),replyCode({}),replyText({}),message:{}",exchange,routingKey,replyCode,replyText,message);
}
});
return rabbitTemplate;
}
//定義讀取配置文件的環境變量實例
@Autowired
private Environment env;
/** 創建DirectExchange消息模型**/
//創建隊列1
@Bean("directQueueOne")
public Queue directQueueOne(){
return new Queue(env.getProperty("mq.direct.queue.one.name"),true);
}
//隊列2
@Bean("directQueueTwo")
public Queue directQueueTwo(){
return new Queue(env.getProperty("mq.direct.queue.two.name"),true);
}
//創建直連交換機
@Bean
public DirectExchange directExchange(){
return new DirectExchange(env.getProperty("mq.direct.exchange.name"));
}
//創建綁定1
@Bean
public Binding directBindingOne(){
return BindingBuilder.bind(directQueueOne()).to(directExchange()).with(env.getProperty("mq.direct.routing.key.one.name"));
}
//創建綁定2
@Bean
public Binding directBindingTwo(){
return BindingBuilder.bind(directQueueTwo()).to(directExchange()).with(env.getProperty("mq.direct.routing.key.two.name"));
}
}
application.yml文件配置交換機、隊列名稱
mq:
env: local
#定義直連式消息模型-directExchange: 創建隊列1,2, 路由1,2 交換機
direct:
queue:
one:
name: ${mq.env}.middleware.mq.direct.one.queue
two:
name: ${mq.env}.middleware.mq.direct.two.queue
exchange:
name: ${mq.env}.middleware.mq.direct.exchange
routing:
key:
one:
name: ${mq.env}.middleware.mq.direct.routing.key.one.name
two:
name: ${mq.env}.middleware.mq.direct.routing.key.two.name
2.創建對象實體信息EventInfo
package com.debug.middleware.server.rabbitmq.entity;
import lombok.Data;
import lombok.ToString;
import java.io.Serializable;
/**
* @ClassName:
* @PackageName: com.debug.middleware.server.rabbitmq.entity
* @author: youjp
* @create: 2020-04-08 20:21
* @description: TDOO 實體對象信息
* @Version: 1.0
*/
@Data
@ToString
public class EventInfo implements Serializable {
private Integer id; //id標識
private String module; //模塊
private String name; //名稱
private String desc;// 描述
public EventInfo(){
}
public EventInfo(Integer id, String module, String name, String desc) {
this.id = id;
this.module = module;
this.name = name;
this.desc = desc;
}
}
3.創建對象實體生產者,這裏我們創建了兩個路由、隊列的綁定。
package com.debug.middleware.server.rabbitmq.publisher;
import com.debug.middleware.server.rabbitmq.entity.EventInfo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
/**
* @ClassName:
* @PackageName: com.debug.middleware.server.rabbitmq.publisher
* @author: youjp
* @create: 2020-04-08 20:26
* @description: TODO 消息生產者
* @Version: 1.0
*/
@Component
public class ModelPublisher {
private Logger logger = LoggerFactory.getLogger(ModelPublisher.class);
@Autowired
private RabbitTemplate rabbitTemplate;
//json序列化和反序列化組件
@Autowired
private ObjectMapper objectMapper;
//定義讀取配置文件的環境變量實例
@Autowired
private Environment env;
/**
* 使用DirectExchange消息模型:發送消息-one
* @param eventInfo
*/
public void sendMsgOneByDirectExchange(EventInfo eventInfo) {
if (eventInfo != null) {
//定義消息傳輸格式爲json
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
//定義交換機爲直連式交換機
rabbitTemplate.setExchange(env.getProperty("mq.direct.exchange.name"));
//定義路由1
rabbitTemplate.setRoutingKey(env.getProperty("mq.direct.routing.key.one.name"));
try {
//創建消息
Message message=MessageBuilder.withBody(objectMapper.writeValueAsBytes(eventInfo)).build();
logger.info("消息模型DirectExchange-one-生產者-發送消息:{}", eventInfo);
//發送消息
rabbitTemplate.convertAndSend(message);
} catch (JsonProcessingException e) {
logger.error("消息模型DirectExchange-one-生產者-發送消息發送異常:{}", eventInfo, e.fillInStackTrace());
e.printStackTrace();
}
}
}
/**
* 使用DirectExchange消息模型:發送消息-two
* @param eventInfo
*/
public void sendMsgTwoByDirectExchange(EventInfo eventInfo) {
if (eventInfo != null) {
//定義消息傳輸格式爲json
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
//定義交換機爲直連式交換機
rabbitTemplate.setExchange(env.getProperty("mq.direct.exchange.name"));
//定義路由1
rabbitTemplate.setRoutingKey(env.getProperty("mq.direct.routing.key.two.name"));
try {
//創建消息
Message message=MessageBuilder.withBody(objectMapper.writeValueAsBytes(eventInfo)).build();
logger.info("消息模型DirectExchange-two-生產者-發送消息:{}", eventInfo);
//發送消息
rabbitTemplate.convertAndSend(message);
} catch (JsonProcessingException e) {
logger.error("消息模型DirectExchange-two-生產者-發送消息發送異常:{}", eventInfo, e.fillInStackTrace());
e.printStackTrace();
}
}
}
}
4.開發用於監聽消費消息的消費者方法。由於我們創建了兩個路由、隊列及其綁定,因爲需要開發兩個消費者方法,用於監聽不同隊列中的消息。
package com.debug.middleware.server.rabbitmq.consumer;
import com.debug.middleware.server.rabbitmq.entity.EventInfo;
import com.debug.middleware.server.rabbitmq.publisher.ModelPublisher;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* @ClassName:
* @PackageName: com.debug.middleware.server.rabbitmq.consumer
* @author: youjp
* @create: 2020-04-08 20:41
* @description: TODO 消息消費者
* @Version: 1.0
*/
@Component
public class ModelConsumer {
private Logger logger=LoggerFactory.getLogger(ModelPublisher.class);
//json序列化和反序列化組件
@Autowired
private ObjectMapper objectMapper;
/** 使用DirectExchange消息模型的消費案例**/
/**
* @Param [bytes]
* @return void 使用DirectExchange消息模型的消費方法
* @Author youjp
* @Description //TODO 監聽並消費隊列中消息:directExchange-one
* @throw
**/
@RabbitListener(containerFactory = "singleListenerContainer",queues = "${mq.direct.queue.one.name}")
public void consumerDirectMsgOne(@Payload byte[] bytes){
try {
EventInfo eventInfo= objectMapper.readValue(bytes,EventInfo.class);
logger.info("消息模型-directExchange-one-消費者-監聽到消息:{}",eventInfo);
} catch (IOException e) {
logger.error("消息模型-directExchange-one-消費者-監聽到消息異常:{}",e.fillInStackTrace());
e.printStackTrace();
}
}
/**
* @Param [bytes]
* @return void 使用DirectExchange消息模型的消費方法
* @Author youjp
* @Description //TODO 監聽並消費隊列中消息:directExchange-two
* @throw
**/
@RabbitListener(containerFactory = "singleListenerContainer",queues = "${mq.direct.queue.two.name}")
public void consumerDirectMsgTwo(@Payload byte[] bytes){
try {
EventInfo eventInfo= objectMapper.readValue(bytes,EventInfo.class);
logger.info("消息模型-directExchange-two-消費者-監聽到消息:{}",eventInfo);
} catch (IOException e) {
logger.error("消息模型-directExchange-two-消費者-監聽到消息異常:{}",e.fillInStackTrace());
e.printStackTrace();
}
}
}
5.編寫單元測試,觸發生產者生產消息
package com.debug.middleware.server;
import com.debug.middleware.server.entity.Student;
import com.debug.middleware.server.rabbitmq.entity.EventInfo;
import com.debug.middleware.server.rabbitmq.publisher.ModelPublisher;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @className:
* @PackageName: com.debug.middleware.server
* @author: youjp
* @create: 2020-04-06 20:58
* @description: TODO rabbitMQ 的java單元測試
* @Version: 1.0
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class RabbitmqBasicTest {
//定義日誌
private static final Logger logger = LoggerFactory.getLogger(RabbitmqBasicTest.class);
//定義Json序列化和反序列化實例
@Autowired
private ObjectMapper objectMapper;
//定義fanoutExchange消息模型中的發生消息的生產者
@Autowired
private ModelPublisher modelPublisher;
//測試直連式消息模型
@Test
public void testDirect(){
//構造第一個實體對象
EventInfo eventInfoOne =new EventInfo(1,"使用DirectExchange模型","測試Direct模塊1","基於DirectExchange消息模型-1");
//第一個生產者發送消息
modelPublisher.sendMsgOneByDirectExchange(eventInfoOne);
//構造第二個實體對象
EventInfo eventInfoTwo =new EventInfo(2,"使用DirectExchange模型","測試Direct模塊2","基於DirectExchange消息模型-2");
//第二個生產者發送消息
modelPublisher.sendMsgTwoByDirectExchange(eventInfoTwo);
}
}
點擊運行測試,控制檯查看測試結果:
打開瀏覽器,查看RabbitMQ客戶端控制檯。可查看相應的隊列和交換機列表
選擇其中一條隊列點擊,即可查看其詳細信息,包括隊列名稱、持久化策略及綁定信息。
至此,基於DirectExchange消息模型的大概使用已經講解完畢。此種模型適用於業務數據需要直接傳輸並消費的場景,比如業務模塊之間的消息交互,一般業務服務直接的通信是直接的、實時的,因而可以藉助基於DirectExchange的消息模型進行通信。 事實上,在實際應用系統中,有90%的業務場景都可以採用直連式消息模型實現。
源碼下載:
https://gitee.com/yjp245/middleware_study.git