消息模型
- Publisher:生产者,不再发送消息到队列中,而是发给交换机
- Exchange:交换机,一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。
- Queue:消息队列也与以前一样,接收消息、缓存消息。不过队列一定要与交换机绑定。
- Consumer:消费者,与以前一样,订阅队列,没有变化
交换机类型
- Fanout:广播,将消息交给所有绑定到交换机的队列。我们最早在控制台使用的正是Fanout交换机
- Direct:订阅,基于RoutingKey(路由key)发送给订阅了消息的队列
- Topic:通配符订阅,与Direct类似,只不过RoutingKey可以使用通配符
- Headers:头匹配,基于MQ的消息头匹配,用的较少。
创建项目
1.引入MQ依赖 创建项目
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2.定义消费者和生产者
2.1发送方
生产者用来发送消息 消费者用来接受消息
发送方可以发送到任意交换机 如TOPIC FANOUT DIRECT
具体实现到Test包下调用查看
import cn.dsxriiiii.publisher.BaseEvent;
import cn.dsxriiiii.publisher.publish.EventPublish;
import com.alibaba.fastjson2.JSONObject;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @ProjectName: rabbit-demo
* @Author: DSXRIIIII
* @CreateDate: 2024/7/19 13:40
*/
@SpringBootTest
public class DirectExchangeTest {
private final Logger logger = LoggerFactory.getLogger(DirectExchangeTest.class);
@Resource
private AwardEvent awardEvent;
@Resource
private EventPublish eventPublish;
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void send_award_message(){
for (int i = 0; i < 10; i++) {
BaseEvent.BaseEventMessage<Long> longBaseEventMessage = awardEvent.buildBaseEventMessage((long) i);
eventPublish.publish(awardEvent.topic(),longBaseEventMessage);
logger.info("send_award_message 发送mq消息成功 for循环节点:{},发送消息为:{}",i, JSONObject.toJSONString(longBaseEventMessage));
}
}
@Test
public void testExchangeDirect_award(){
String exchangeName = "dome.direct";
String message = "hello,direct_award,Key为{award}要接受到/(ㄒoㄒ)/~~";
rabbitTemplate.convertAndSend(exchangeName,"award",message);
}
@Test
public void testExchangeDirect_rebate(){
String exchangeName = "dome.direct";
String message = "hello,direct_award,Key为{rebate}要接受到/(ㄒoㄒ)/~~";
rabbitTemplate.convertAndSend(exchangeName,"rebate",message);
}
@Test
public void testExchangeTopic_all(){
String exchangeName = "dome.topic";
String message = "hello,我是topic,dome下的都要接收到 (⊙o⊙)!";
rabbitTemplate.convertAndSend(exchangeName,"dome.#",message);
}
@Test
public void testExchangeTopic_rebate(){
String exchangeName = "dome.topic";
String message = "hello,我是topic,rebate前的都要接收到ヽ(✿゚▽゚)ノ";
rabbitTemplate.convertAndSend(exchangeName,"#.rebate",message);
}
@Test
public void testExchangeFanout(){
String exchangeName = "dome.fanout";
String message = "我是广播 都要接收到 (╯▔皿▔)╯";
rabbitTemplate.convertAndSend(exchangeName,"",message);
}
}
2.1.1定义消息模板
定义抽象方法BaseEvent 抽象出不同领域下对应不同MQ发送方
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* @ProjectName: rabbit-demo
* @Author: DSXRIIIII
* @CreateDate: 2024/7/19 9:18
*/
@Data
public abstract class BaseEvent<T> {
public abstract BaseEventMessage<T> buildBaseEventMessage();
public abstract String topic();
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public static class BaseEventMessage<T>{
private String id;
private Date timm;
private T data;
}
}
直接选择继承即可
@Component
public class AwardEvent extends BaseEvent<Long>{
@Value("${mq.server.Award}")
private String topic;
@Override
public BaseEventMessage<Long> buildBaseEventMessage(Long data) {
return BaseEventMessage.<Long>builder()
.id(RandomStringUtils.randomNumeric(11))
.time(new Date())
.data(data)
.build();
}
@Override
public String topic() {
return topic;
}
}
如果自定义消息体 使用泛型即可
import cn.dsxriiiii.publisher.BaseEvent;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang.RandomStringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* @ProjectName: rabbit-demo
* @Description:
* @Author: DSXRIIIII
* @CreateDate: 2024/7/19 9:44
* @Email: lihh53453@hundsun.com
*/
@Component
public class RebateEvent extends BaseEvent<RebateEvent.RebateMessage> {
@Value("${mq.server.rebate}")
private String topic;
@Override
public BaseEventMessage<RebateMessage> buildBaseEventMessage(RebateMessage data) {
return BaseEventMessage.<RebateMessage>builder()
.id(RandomStringUtils.random(11))
.time(new Date())
.data(data)
.build();
}
@Override
public String topic() {
return topic;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public static class RebateMessage{
private String id;
private String info;
}
}
2.1.2项目中定义接收消息方法
import cn.dsxriiiii.publisher.BaseEvent;
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @ProjectName: rabbit-demo
* @Description:
* @Author: DSXRIIIII
* @CreateDate: 2024/7/19 10:25
* @Email: lihh53453@hundsun.com
*/
@Component
public class EventPublish {
private final Logger logger = LoggerFactory.getLogger(EventPublish.class);
@Autowired
private RabbitTemplate rabbitTemplate;
public void publish(String topic, BaseEvent.BaseEventMessage<?> eventMessage){
try{
String messageJson = JSON.toJSONString(eventMessage);
rabbitTemplate.convertAndSend(topic,messageJson);
logger.info("发送MQ消息 topic:{} message:{}", topic, messageJson);
}catch (Exception e){
logger.error("发送MQ消息 topic:{} message:{}", topic, JSON.toJSONString(eventMessage),e);
throw e;
}
}
public void publish(String topic, String eventMessageJSON){
try {
rabbitTemplate.convertAndSend(topic, eventMessageJSON);
logger.info("发送MQ JSON数据 topic:{} message:{}", topic, eventMessageJSON);
} catch (Exception e) {
logger.error("发送MQ JSON数据失败 topic:{} message:{}", topic, eventMessageJSON, e);
throw e;
}
}
}
2.2接收方
2.2.1 FANOUT交换机
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @ProjectName: rabbit-demo
* @Author: DSXRIIIII
* @CreateDate: 2024/7/19 14:12
*/
@Component
public class FanoutListener {
private static final Logger log = LoggerFactory.getLogger(FanoutListener.class);
/**
* fanout交换机匹配
* 与fanout交换机绑定的消息队列都可以收到消息
* @param msg 传递的消息
*/
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "${mq.listener.fanout.award}"),
exchange = @Exchange(name = "dome.fanout",type = ExchangeTypes.FANOUT)
))
public void direct_award(String msg){
log.info("FANOUT 广播 mq.fanout.award队列接收到消息{}",msg);
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "${mq.listener.fanout.rebate}"),
exchange = @Exchange(name = "dome.fanout",type = ExchangeTypes.FANOUT)
))
public void direct_rebate(String msg){
log.info("FANOUT 广播 mq.fanout.rebate队列接收到消息{}",msg);
}
}
2.2.2 DIRECT交换机
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @ProjectName: rabbit-demo
* @Author: DSXRIIIII
* @CreateDate: 2024/7/19 13:16
*/
@Component
public class DirectListener {
private static final Logger log = LoggerFactory.getLogger(DirectListener.class);
/**
* topic交换机匹配
* 根据routingKey 匹配消息队列
* @param msg 传递的消息
*/
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "${mq.listener.direct.award}"),
exchange = @Exchange(name = "dome.direct"),
key = "award"
))
public void direct_award(String msg){
log.info("DIRECT 广播 mq.direct.award队列接收到消息{}",msg);
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "${mq.listener.direct.rebate}"),
exchange = @Exchange(name = "dome.direct"),
key = "rebate"
))
public void direct_rebate(String msg){
log.info("DIRECT 广播 mq.direct.rebate队列接收到消息{}",msg);
}
}
2.2.3 TOPIC交换机
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @ProjectName: rabbit-demo
* @Author: DSXRIIIII
* @CreateDate: 2024/7/19 13:58
*/
@Component
public class TopicListener {
private static final Logger log = LoggerFactory.getLogger(TopicListener.class);
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "${mq.listener.topic.award}"),
exchange = @Exchange(name = "dome.topic", type = ExchangeTypes.TOPIC),
key = "dome.#"
))
public void topic_award(String msg){
log.info("TOPIC 广播 mq.topic.award队列接收到消息{}",msg);
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "${mq.listener.topic.rebate}"),
exchange = @Exchange(name = "dome.topic",type = ExchangeTypes.TOPIC),
key = "#.rebate"
))
public void topic_rebate(String msg){
log.info("TOPIC 广播 mq.topic.rebate队列 ·队列接收到消息{}",msg);
}
}
2.3消息转换器
将传入的消息对象转化为JSON 实现代码如下:
@Bean
public MessageConverter messageConverter(){
// 1.定义消息转换器
Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
// 2.配置自动创建消息id,用于识别不同消息,也可以在业务中基于ID判断是否是重复消息
jackson2JsonMessageConverter.setCreateMessageIds(true);
return jackson2JsonMessageConverter;
}
在publisher和consumer两个服务中都引入依赖:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.9.10</version>
</dependency>
注意,如果项目中引入了spring-boot-starter-web依赖,则无需再次引入Jackson依赖。
2.4使用@BEAN注解定义交换机类型以及绑定队列代码
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @ProjectName: rabbit-demo
* @Description: 非注解形式绑定队列 防止Bean冲突 这里注释了注解
* @Author: DSXRIIIII
* @CreateDate: 2024/5/23 10:08
* @Email: lihh53453@hundsun.com
**/
@Configuration
public class DirectConfig {
@Value("${mq.listener.direct.award}")
private String queue_award;
@Value("${mq.listener.direct.rebate}")
private String queue_rebate;
@Bean
public DirectExchange directExchange(){
return ExchangeBuilder.directExchange("dome.direct").build();
}
// @Bean
// public TopicExchange topicExchange(){
// return ExchangeBuilder.topicExchange("dome.topic").build();
// }
//@Bean
public Queue queue_award(){
return new Queue(queue_award);
}
//@Bean
public Queue queue_rebate(){
return new Queue(queue_rebate);
}
//@Bean
public Binding QueueAwardBindingDirect(Queue queue_award, DirectExchange directExchange){
return BindingBuilder.bind(queue_award).to(directExchange).with("award");
}
//@Bean
public Binding QueueRebateBindingDirect(Queue queue_rebate, DirectExchange directExchange){
return BindingBuilder.bind(queue_rebate).to(directExchange).with("rebate");
}
}
2.5实现结果
1.启动消费者和生产者o( ̄▽ ̄)ブ 运行Application方法 注意修改mq地址和账户密码⚠ 2.调用Test方法发送消息,接收方打印日志
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.2.5)
2024-07-19T15:55:44.415+08:00 INFO 31892 --- [ntContainer#2-1] c.d.customer.exchange.FanoutListener : FANOUT 广播 mq.fanout.rebate队列接收到消息我是广播 都要接收到 (╯▔皿▔)╯
2024-07-19T15:55:44.415+08:00 INFO 31892 --- [ntContainer#3-1] c.d.customer.exchange.FanoutListener : FANOUT 广播 mq.fanout.award队列接收到消息我是广播 都要接收到 (╯▔皿▔)╯
2024-07-19T15:55:44.425+08:00 INFO 31892 --- [ntContainer#5-1] c.d.customer.exchange.TopicListener : TOPIC 广播 mq.topic.rebate队列 ·队列接收到消息hello,我是topic,rebate前的都要接收到ヽ(✿゚▽゚)ノ
2024-07-19T15:55:44.430+08:00 INFO 31892 --- [ntContainer#0-1] c.d.customer.exchange.DirectListener : DIRECT 广播 mq.direct.rebate队列接收到消息hello,direct_award,Key为{rebate}要接受到/(ㄒoㄒ)/~~
2024-07-19T15:55:44.672+08:00 INFO 31892 --- [ntContainer#4-1] c.d.customer.exchange.TopicListener : TOPIC 广播 mq.topic.award队列接收到消息hello,我是topic,dome下的都要接收到 (⊙o⊙)!
2024-07-19T15:55:44.673+08:00 INFO 31892 --- [ntContainer#5-1] c.d.customer.exchange.TopicListener : TOPIC 广播 mq.topic.rebate队列 ·队列接收到消息hello,我是topic,dome下的都要接收到 (⊙o⊙)!
2024-07-19T15:55:44.678+08:00 INFO 31892 --- [ntContainer#1-1] c.d.customer.exchange.DirectListener : DIRECT 广播 mq.direct.award队列接收到消息hello,direct_award,Key为{award}要接受到/(ㄒoㄒ)/~~