【MQ篇】RabbitMQ之发布订阅模式!
目录
- 引言
- 一、 回顾:简单模式与工作队列模式的局限 😔
- 二、 发布/订阅模式详解:消息的“广播站” 📻
- 三、 RabbitMQ 中的交换机类型:不同的“广播方式” 📻
- 四、 Java (Spring Boot) 代码实战
- Fanout 模式的完整示例:
- Direct 模式的完整示例:
- Topic 模式的完整示例:
- 五、 深入理解:交换机与绑定的重要性 🧐
- 六、 发布/订阅模式的优势与适用场景总结 🎯
- 七、 总结:让消息“飞”起来! 🚀
🌟我的其他文章也讲解的比较有趣😁,如果喜欢博主的讲解方式,可以多多支持一下,感谢🤗!
🌟了解 MQ 请看 : 【MQ篇】初识MQ!
其他优质专栏: 【🎇SpringBoot】【🎉多线程】【🎨Redis】【✨设计模式专栏(已完结)】…等
如果喜欢作者的讲解方式,可以点赞收藏加关注,你的支持就是我的动力
✨更多文章请看个人主页: 码熔burning
引言
在前面的文章中,我们已经探索了 RabbitMQ 的简单模式和工作队列模式。简单模式适用于点对点的消息传递,而工作队列模式则通过引入多个消费者来并行处理任务,提高了系统的吞吐量🚀。然而,在某些场景下,我们需要将同一份消息发送给多个不同的消费者,以便它们可以独立地执行各自的处理逻辑。这时,RabbitMQ 的发布/订阅模式就派上了用场。本文将深入讲解 RabbitMQ 的发布/订阅模式,通过生动的比喻和详尽的 Java (Spring Boot) 代码示例,让你彻底掌握这种强大的消息分发机制!📢📻✨
一、 回顾:简单模式与工作队列模式的局限 😔
在简单模式中,消息直接发送到队列,由一个消费者接收。即使有多个消费者监听同一个队列,每个消息也只会被其中的一个消费者处理,而在工作队列模式中,虽然可以有多个消费者并行处理任务,但每个任务(消息)仍然只会被一个消费者处理。
这两种模式都无法满足一个生产者将同一份消息同时发送给多个消费者的需求。例如,一个电子商务网站,当用户成功下单后,需要同时发送邮件通知用户、更新库存信息、生成物流记录等等。这些操作需要由不同的服务来完成,而发布/订阅模式可以很好地解决这个问题。
了解简单模式请看:【MQ篇】RabbitMQ之简单模式!
了解工作队列模式请看:【MQ篇】RabbitMQ之工作队列模式!
二、 发布/订阅模式详解:消息的“广播站” 📻
-
概念:消息的“广播站” 📢
发布/订阅模式的核心思想是生产者将消息发送到交换机(Exchange),而不是直接发送到队列。交换机根据 绑定规则(Binding) 将消息路由到一个或多个队列,然后由监听这些队列的消费者接收和处理消息。
你可以把交换机想象成一个“广播站”,生产者是“播音员”,负责发布消息(“节目”),队列是不同的“收音机频道”,而消费者则是“听众”,可以选择收听自己感兴趣的频道。当“播音员”发布一条消息后,“广播站”会根据“频道”的设置,将消息“广播”给所有收听了该“频道”的“听众”。 🎶
-
角色:消息传递中的“中转站”和“听众”
- 生产者(Producer): 消息的发送者,它将消息发送到交换机,但不关心消息会被发送到哪些队列。就像“播音员”,只负责发布“节目”,不关心有多少人收听。 🎙️
- 交换机(Exchange): 接收生产者发送的消息,并根据绑定规则将消息路由到一个或多个队列。它是消息的“中转站”和“广播站”,负责将“节目”广播给所有感兴趣的“听众”。 🏢
- 队列(Queue): 存储被交换机路由过来的消息,等待消费者进行处理。就像不同的“收音机频道”,存储着特定类型的“节目”。 📥
- 消费者(Consumer): 消息的接收者,它监听一个或多个队列,并从中获取消息进行处理。就像“听众”,选择收听自己感兴趣的“频道”,获取“节目”的内容。 🎧
- 绑定(Binding): 连接交换机和队列的规则,它指定了交换机如何将消息路由到特定的队列。就像“频道”的设置,决定了哪些“听众”可以收听到哪些“节目”。 🔗
-
工作流程:消息是如何被“广播”的? 流程图可以更清晰哦! 🗺️
- 连接(Connect): 生产者和消费者都连接到 RabbitMQ 服务器。 🔗
- 声明交换机(Declare Exchange): 生产者和消费者都需要声明他们将要使用的交换机,并指定交换机的类型。 🏷️
- 声明队列(Declare Queue): 消费者需要声明一个或多个队列,用于接收消息。 📥
- 绑定队列到交换机(Bind Queue to Exchange): 消费者需要将声明的队列通过绑定规则绑定到交换机。 🔗
- 发送消息(Publish): 生产者将消息发送到交换机,并指定路由键(Routing Key)。 ✉️
- 交换机路由消息: 交换机根据消息的路由键和绑定规则,将消息路由到一个或多个队列。 ➡️
- 消费者接收消息(Consume): 消费者监听绑定的队列,并接收被路由过来的消息。 👂
- 处理消息(Process): 消费者接收到消息后,执行相应的业务逻辑。 💻
- 确认(Acknowledge): 消费者在成功处理消息后,向 RabbitMQ 发送确认。 ✅
-
核心特点:消息的“分发”与“广播” 📢
- 消息分发: 生产者将消息发送到交换机,由交换机负责将消息分发到一个或多个队列。 📦➡️🏢
- 消息广播: 同一份消息可以被多个消费者接收和处理,实现消息的“广播”效果。 📻
- 松耦合: 生产者和消费者之间通过交换机进行通信,无需直接交互,降低了系统的耦合度。 🤝
- 灵活性: 可以根据不同的业务需求,配置不同的交换机类型和绑定规则,实现灵活的消息路由。 ⚙️
-
适用场景:需要将同一份消息发送给多个消费者的“通知”与“事件” 🔔
发布/订阅模式非常适合需要将同一份消息发送给多个消费者进行处理的场景,例如:
- 实时通知: 例如,当用户发布文章后,需要同时通知关注者、推送消息、更新排行榜等。 📧
- 事件驱动: 例如,当用户下单成功后,需要触发订单服务、库存服务、物流服务等执行相应的操作。 🎫
- 日志处理: 例如,将服务器日志发送到多个不同的日志分析系统进行处理。 📝
- 数据同步: 例如,将数据库的变更同步到缓存系统、搜索引擎等。 🔄
三、 RabbitMQ 中的交换机类型:不同的“广播方式” 📻
RabbitMQ 提供了多种不同类型的交换机,以支持不同的消息路由策略。常见的交换机类型包括:
-
Fanout Exchange(扇出交换机):
- 将接收到的所有消息广播到所有与其绑定的队列,而忽略路由键。
- 就像一个“广播电台”,将所有“节目”发送给所有“收音机频道”。 📻
- 适用于需要将同一份消息发送给所有消费者的场景,例如,服务器日志收集。
-
Direct Exchange(直连交换机):
- 将消息发送到与其绑定的队列,且队列的绑定键(Binding Key)与消息的路由键(Routing Key)完全匹配。
- 就像一个“单播电台”,只将“节目”发送给指定“频道”的“听众”。 🎯
- 适用于需要根据消息的类型或优先级进行路由的场景,例如,订单服务可以将订单创建消息发送到订单队列,将订单支付消息发送到支付队列。 🏷️
-
Topic Exchange(主题交换机):
- 将消息发送到与其绑定的队列,且队列的绑定键可以采用通配符的方式与消息的路由键进行匹配。
- 就像一个“多播电台”,可以根据“节目”的主题,将消息发送给订阅了相关主题的“听众”。 📰
- 适用于需要根据消息的主题或模式进行灵活路由的场景,例如,可以根据日志的级别(info、warning、error)和模块(user、order、product)进行路由。 🔍
-
Headers Exchange(头交换机):
- 不依赖于路由键,而是根据消息的头部信息(Headers)进行路由。
- 提供了更灵活的路由方式,但性能相对较低。 🧮
- 在实际应用中较少使用。 🤷♀️
四、 Java (Spring Boot) 代码实战
Fanout 模式的完整示例:
1. 添加依赖
在 pom.xml
文件中添加以下依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2. 配置类 (RabbitMQConfig.java)
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class RabbitMQConfig {// 交换机名称public static final String FANOUT_EXCHANGE = "fanout.exchange";// 队列名称public static final String FANOUT_QUEUE_1 = "fanout.queue.1";public static final String FANOUT_QUEUE_2 = "fanout.queue.2";// 声明 Fanout 交换机@Beanpublic FanoutExchange fanoutExchange() {return new FanoutExchange(FANOUT_EXCHANGE);}// 声明队列 1@Beanpublic Queue fanoutQueue1() {return new Queue(FANOUT_QUEUE_1);}// 声明队列 2@Beanpublic Queue fanoutQueue2() {return new Queue(FANOUT_QUEUE_2);}// 将队列 1 绑定到 Fanout 交换机@Beanpublic Binding bindingExchangeQueue1(Queue fanoutQueue1, FanoutExchange fanoutExchange) {return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);}// 将队列 2 绑定到 Fanout 交换机@Beanpublic Binding bindingExchangeQueue2(Queue fanoutQueue2, FanoutExchange fanoutExchange) {return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);}
}
3. 生产者 (FanoutProducer.java)
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
public class FanoutProducer {@Autowiredprivate AmqpTemplate rabbitTemplate;public void send(String message) {System.out.println("生产者发送消息: " + message);rabbitTemplate.convertAndSend(RabbitMQConfig.FANOUT_EXCHANGE, "", message); // 注意:Fanout 模式忽略路由键}
}
4. 消费者 (FanoutConsumer.java)
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;@Component
public class FanoutConsumer {@RabbitListener(queues = RabbitMQConfig.FANOUT_QUEUE_1)public void receiveMessage1(String message) {System.out.println("队列 1 接收到消息: " + message);}@RabbitListener(queues = RabbitMQConfig.FANOUT_QUEUE_2)public void receiveMessage2(String message) {System.out.println("队列 2 接收到消息: " + message);}
}
5. 测试类 (FanoutTest.java)
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
public class FanoutTest {@Autowiredprivate FanoutProducer fanoutProducer;@Testpublic void testFanout() {fanoutProducer.send("Hello, Fanout Exchange!");fanoutProducer.send("This is another message for Fanout.");}
}
6. Spring Boot 启动类 (Application.java)
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}
说明:
- RabbitMQConfig.java: 配置类,定义了交换机、队列和绑定关系。
FanoutExchange
是关键,它指定了使用 Fanout 交换机类型。 - FanoutProducer.java: 生产者,使用
AmqpTemplate
发送消息到 Fanout 交换机。 注意,convertAndSend
方法的第二个参数(路由键)在这里被忽略,因为 Fanout 交换机不使用路由键。 - FanoutConsumer.java: 消费者,使用
@RabbitListener
注解监听指定的队列。 每个消费者监听一个不同的队列。 - FanoutTest.java: 测试类,用于发送消息到 Fanout 交换机,触发消息的广播。
运行结果:
这表明生产者发送的消息被广播到了所有绑定到 Fanout 交换机的队列,并且每个消费者都收到了相同的消息。
这个示例提供了一个完整的 Fanout 模式的实现,你可以根据自己的需求进行修改和扩展。 例如,你可以添加更多的消费者和队列,或者修改消息的内容和格式。
Direct 模式的完整示例:
1. 添加依赖
在 pom.xml
文件中添加以下依赖(如果已经添加过,则跳过):
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2. 配置类 (RabbitMQConfig.java)
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class RabbitMQConfig {// 交换机名称public static final String DIRECT_EXCHANGE = "direct.exchange";// 队列名称public static final String DIRECT_QUEUE_ERROR = "direct.queue.error";public static final String DIRECT_QUEUE_INFO = "direct.queue.info";// 路由键public static final String ROUTING_KEY_ERROR = "error";public static final String ROUTING_KEY_INFO = "info";// 声明 Direct 交换机@Beanpublic DirectExchange directExchange() {return new DirectExchange(DIRECT_EXCHANGE);}// 声明 Error 队列@Beanpublic Queue directQueueError() {return new Queue(DIRECT_QUEUE_ERROR);}// 声明 Info 队列@Beanpublic Queue directQueueInfo() {return new Queue(DIRECT_QUEUE_INFO);}// 将 Error 队列绑定到 Direct 交换机,使用 "error" 路由键@Beanpublic Binding bindingExchangeQueueError(Queue directQueueError, DirectExchange directExchange) {return BindingBuilder.bind(directQueueError).to(directExchange).with(ROUTING_KEY_ERROR);}// 将 Info 队列绑定到 Direct 交换机,使用 "info" 路由键@Beanpublic Binding bindingExchangeQueueInfo(Queue directQueueInfo, DirectExchange directExchange) {return BindingBuilder.bind(directQueueInfo).to(directExchange).with(ROUTING_KEY_INFO);}
}
3. 生产者 (DirectProducer.java)
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
public class DirectProducer {@Autowiredprivate AmqpTemplate rabbitTemplate;public void sendError(String message) {System.out.println("生产者发送 Error 消息: " + message);rabbitTemplate.convertAndSend(RabbitMQConfig.DIRECT_EXCHANGE, RabbitMQConfig.ROUTING_KEY_ERROR, message);}public void sendInfo(String message) {System.out.println("生产者发送 Info 消息: " + message);rabbitTemplate.convertAndSend(RabbitMQConfig.DIRECT_EXCHANGE, RabbitMQConfig.ROUTING_KEY_INFO, message);}
}
4. 消费者 (DirectConsumer.java)
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;@Component
public class DirectConsumer {@RabbitListener(queues = RabbitMQConfig.DIRECT_QUEUE_ERROR)public void receiveError(String message) {System.out.println("Error 队列接收到消息: " + message);}@RabbitListener(queues = RabbitMQConfig.DIRECT_QUEUE_INFO)public void receiveInfo(String message) {System.out.println("Info 队列接收到消息: " + message);}
}
5. 测试类 (DirectTest.java)
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
public class DirectTest {@Autowiredprivate DirectProducer directProducer;@Testpublic void testDirect() {directProducer.sendError("This is an error message.");directProducer.sendInfo("This is an info message.");directProducer.sendError("Another error message.");}
}
说明:
- RabbitMQConfig.java: 配置类,定义了交换机、队列、路由键和绑定关系。
DirectExchange
是关键,它指定了使用 Direct 交换机类型。 每个队列都通过特定的路由键绑定到交换机。 - DirectProducer.java: 生产者,使用
AmqpTemplate
发送消息到 Direct 交换机。convertAndSend
方法的第二个参数是路由键,它决定了消息将被发送到哪个队列。 - DirectConsumer.java: 消费者,使用
@RabbitListener
注解监听指定的队列。 每个消费者监听一个不同的队列,这些队列通过不同的路由键绑定到交换机。 - DirectTest.java: 测试类,用于发送不同类型的消息到 Direct 交换机,验证消息是否被正确路由。
运行结果:
这表明生产者发送的消息被正确地路由到了与其路由键匹配的队列,并且每个消费者只收到了特定类型的消息。
这个示例提供了一个完整的 Direct 模式的实现,你可以根据自己的需求进行修改和扩展。 例如,你可以添加更多的队列和路由键,或者修改消息的内容和格式。
Topic 模式的完整示例:
1. 添加依赖
在 pom.xml
文件中添加以下依赖(如果已经添加过,则跳过):
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2. 配置类 (RabbitMQConfig.java)
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class RabbitMQConfig {// 交换机名称public static final String TOPIC_EXCHANGE = "topic.exchange";// 队列名称public static final String TOPIC_QUEUE_NEWS = "topic.queue.news";public static final String TOPIC_QUEUE_WEATHER = "topic.queue.weather";// 路由键public static final String ROUTING_KEY_NEWS = "news.#"; // 匹配所有以 "news." 开头的路由键public static final String ROUTING_KEY_WEATHER = "weather.*"; // 匹配所有以 "weather." 开头,后跟一个单词的路由键// 声明 Topic 交换机@Beanpublic TopicExchange topicExchange() {return new TopicExchange(TOPIC_EXCHANGE);}// 声明 News 队列@Beanpublic Queue topicQueueNews() {return new Queue(TOPIC_QUEUE_NEWS);}// 声明 Weather 队列@Beanpublic Queue topicQueueWeather() {return new Queue(TOPIC_QUEUE_WEATHER);}// 将 News 队列绑定到 Topic 交换机,使用 "news.#" 路由键@Beanpublic Binding bindingExchangeQueueNews(Queue topicQueueNews, TopicExchange topicExchange) {return BindingBuilder.bind(topicQueueNews).to(topicExchange).with(ROUTING_KEY_NEWS);}// 将 Weather 队列绑定到 Topic 交换机,使用 "weather.*" 路由键@Beanpublic Binding bindingExchangeQueueWeather(Queue topicQueueWeather, TopicExchange topicExchange) {return BindingBuilder.bind(topicQueueWeather).to(topicExchange).with(ROUTING_KEY_WEATHER);}
}
3. 生产者 (TopicProducer.java)
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
public class TopicProducer {@Autowiredprivate AmqpTemplate rabbitTemplate;public void sendNews(String message) {String routingKey = "news.local.china"; // 模拟国内新闻System.out.println("生产者发送 News 消息 (路由键: " + routingKey + "): " + message);rabbitTemplate.convertAndSend(RabbitMQConfig.TOPIC_EXCHANGE, routingKey, message);}public void sendWeather(String message) {String routingKey = "weather.us"; // 模拟美国天气System.out.println("生产者发送 Weather 消息 (路由键: " + routingKey + "): " + message);rabbitTemplate.convertAndSend(RabbitMQConfig.TOPIC_EXCHANGE, routingKey, message);}public void sendOther(String message) {String routingKey = "other.something"; // 模拟其他消息System.out.println("生产者发送 Other 消息 (路由键: " + routingKey + "): " + message);rabbitTemplate.convertAndSend(RabbitMQConfig.TOPIC_EXCHANGE, routingKey, message);}
}
4. 消费者 (TopicConsumer.java)
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;@Component
public class TopicConsumer {@RabbitListener(queues = RabbitMQConfig.TOPIC_QUEUE_NEWS)public void receiveNews(String message) {System.out.println("News 队列接收到消息: " + message);}@RabbitListener(queues = RabbitMQConfig.TOPIC_QUEUE_WEATHER)public void receiveWeather(String message) {System.out.println("Weather 队列接收到消息: " + message);}
}
5. 测试类 (TopicTest.java)
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
public class TopicTest {@Autowiredprivate TopicProducer topicProducer;@Testpublic void testTopic() {topicProducer.sendNews("Breaking news from China!");topicProducer.sendWeather("Sunny weather in the US.");topicProducer.sendOther("This message will not be received by any queue.");}
}
说明:
- RabbitMQConfig.java: 配置类,定义了交换机、队列、路由键和绑定关系。
TopicExchange
是关键,它指定了使用 Topic 交换机类型。 每个队列都通过带有通配符的路由键绑定到交换机。#
:匹配一个或多个单词。*
:只匹配一个单词。
- TopicProducer.java: 生产者,使用
AmqpTemplate
发送消息到 Topic 交换机。convertAndSend
方法的第二个参数是路由键,它决定了消息将被发送到哪些队列。 - TopicConsumer.java: 消费者,使用
@RabbitListener
注解监听指定的队列。 每个消费者监听一个不同的队列,这些队列通过不同的路由键绑定到交换机。 - TopicTest.java: 测试类,用于发送不同类型的消息到 Topic 交换机,验证消息是否被正确路由。
运行结果:
这表明:
news.local.china
路由键的消息被发送到了News
队列,因为News
队列的绑定键是news.#
,可以匹配所有以news.
开头的路由键。weather.us
路由键的消息被发送到了Weather
队列,因为Weather
队列的绑定键是weather.*
,可以匹配所有以weather.
开头,后跟一个单词的路由键。other.something
路由键的消息没有被任何队列接收,因为没有队列的绑定键可以匹配它。
这个示例提供了一个完整的 Topic 模式的实现,你可以根据自己的需求进行修改和扩展。 例如,你可以添加更多的队列和路由键,或者修改消息的内容和格式。 你可以尝试不同的路由键和绑定键组合,以更好地理解 Topic 交换机的灵活性。
五、 深入理解:交换机与绑定的重要性 🧐
-
交换机:消息路由的“大脑” 🧠
交换机是 RabbitMQ 中消息路由的核心组件。它接收生产者发送的消息,并根据一定的规则将消息路由到一个或多个队列。不同的交换机类型采用不同的路由策略,以满足不同的业务需求。 🔀
-
绑定:连接交换机与队列的“桥梁” 🌉
绑定是连接交换机和队列的规则,它指定了交换机如何将消息路由到特定的队列。绑定通常包含一个路由键(Binding Key),用于在 Direct Exchange 和 Topic Exchange 中进行消息匹配。 🔗
-
Fanout Exchange 的特点 📢
Fanout Exchange 是一种最简单的交换机类型,它将接收到的所有消息广播到所有与其绑定的队列,而忽略路由键。因此,所有绑定到 Fanout Exchange 的队列都会接收到相同的消息,从而实现消息的“广播”效果。 📻
六、 发布/订阅模式的优势与适用场景总结 🎯
- 实现消息的“广播”: 同一份消息可以被多个消费者接收和处理,满足了需要将消息同时发送给多个接收者的需求。 📣
- 降低系统耦合度: 生产者和消费者之间通过交换机进行通信,无需直接交互,实现了生产者和消费者之间的解耦,提高了系统的可维护性和可扩展性。 🤝
- 提高系统灵活性: 可以根据不同的业务需求,配置不同的交换机类型和绑定规则,实现灵活的消息路由,从而更好地适应业务变化。 ⚙️
发布/订阅模式非常适合需要将同一份消息发送给多个消费者进行处理的场景,例如实时通知 🔔、事件驱动 🎫、日志处理 📝、数据同步 🔄 等。
七、 总结:让消息“飞”起来! 🚀
RabbitMQ 的发布/订阅模式通过引入交换机和绑定,实现了消息的“广播”功能,使得同一份消息可以被多个消费者同时接收和处理。掌握了发布/订阅模式,你就能更好地应对需要将消息分发给多个接收者的场景,让你的 RabbitMQ 应用更加灵活和强大!💪✨
相关文章:
【MQ篇】RabbitMQ之发布订阅模式!
目录 引言一、 回顾:简单模式与工作队列模式的局限 😔二、 发布/订阅模式详解:消息的“广播站” 📻三、 RabbitMQ 中的交换机类型:不同的“广播方式” 📻四、 Java (Spring Boot) 代码实战Fanout 模式的完整…...
如何批量为多张图片(JPG、PNG、BMP、WEBP 等格式)添加自定义水印保护
「鹰迅批量处理工具箱」提供了强大的批量水印添加功能,支持常见的图片格式,如 JPG、JPEG、PNG、BMP、GIF、WEBP 等。用户不仅可以选择添加文字水印或图片水印,还能自定义设置水印的样式、位置和透明度等参数,操作简单而高效&#…...
LeetCode每日一题4.23
题目 问题分析 计算每个数字的数位和:对于从 1 到 n 的每个整数,计算其十进制表示下的数位和。 分组:将数位和相等的数字放到同一个组中。 统计每个组的数字数目:统计每个组中有多少个数字。 找到并列最多的组:返回数…...
Kafka简介
简介 基本概念 Kafka是分布式发布 - 订阅消息系统,最初由LinkedIn开发,后成为Apache项目一部分,可类比为放鸡蛋的篮子,生产者产蛋放入,消费者从中取蛋 。 消息系统 优势:分布式系统,易扩展&am…...
大数据利器:Kafka与Spark的深度探索
在大数据领域,Kafka和Spark都是极为重要的工具。今天就来和大家分享一下我在学习和使用它们过程中的心得。 Kafka作为分布式消息系统,优势显著。它吞吐量高、延迟低,能每秒处理几十万条消息,延迟最低仅几毫秒;可扩展性…...
使用logrotate实现日志轮转
logrotate 是一个强大的 Linux 工具,用于自动化管理日志文件的轮转、压缩、删除和归档。它能有效防止日志文件无限增长,节省磁盘空间,同时保持日志的可追溯性。以下是详细讲解 logrotate 的用法,涵盖安装、配置、测试、自动化、常…...
第52讲:农业AI + 区块链——迈向可信、智能、透明的未来农业
目录 一、为什么农业需要“AI+区块链”? 二、核心应用场景解读 1. 农产品溯源系统 2. 农业信贷与保险精准评估 3. 农业碳足迹追踪与碳汇交易 三、案例实战分享:智能溯源 + 区块链合约 四、面临挑战与展望 五、总结 在数字农业时代,“AI” 和 “区块链” 是两股不容忽…...
视频智能分析平台EasyCVR无线监控:全流程安装指南与功能应用解析
在当今数字化安防时代,无线监控系统的安装与调试对于保障各类场所的安全至关重要。本文将结合EasyCVR视频监控的强大功能,为您详细阐述监控系统安装过程中的关键步骤和注意事项,帮助您打造一个高效、可靠的监控解决方案。 一、调试物资准备与…...
Spring Cloud Eureka 与 Nacos 深度解析:从架构到对比
一、Eureka:经典微服务注册中心 (一)核心定位与特性 Spring Cloud Eureka 是 Netflix 开源的服务注册与发现组件,在微服务架构中扮演 "大脑" 角色,负责服务的注册、发现与状态管理。其核心优势在于通过心跳…...
深入详解Java中的@PostConstruct注解:实现简洁而高效初始化操作
🧑 博主简介:CSDN博客专家、CSDN平台优质创作者,高级开发工程师,数学专业,10年以上C/C, C#, Java等多种编程语言开发经验,拥有高级工程师证书;擅长C/C、C#等开发语言,熟悉Java常用开…...
【Unity笔记】Unity 编辑器扩展:一键查找场景中组件引用关系(含完整源码)(组件引用查找工具实现笔记)
摘要: 本文介绍了如何在 Unity 编辑器中开发一款实用的编辑器扩展工具 —— ComponentReferenceFinder,用于查找场景中对某个自定义组件的引用关系。该工具特别适用于大型项目、多人协作或引入外部插件后,快速定位组件间的耦合关系。 本文从需…...
实体店的小程序转型之路:拥抱新零售的密码-中小企实战运营和营销工作室博客
实体店的小程序转型之路:拥抱新零售的密码-中小企实战运营和营销工作室博客 在当今数字化浪潮的冲击下,实体店面临着前所未有的挑战,但小程序的出现为实体店转型新零售带来了新的曙光。先来看一组惊人的数据,据相关统计ÿ…...
Mysql安装与备份配置分析
若之前存有msqyl的数据缓存,建议用以下命令将数据文件删除干净 mysql-server:主程序 mysql:客户端工具 mysql-devel:开发库 mysql-libs:共享库文件 /var/lib/mysql:数据目录 /etc/my.cnf : 主配置文件 …...
Android APP 爬虫操作
工具 夜神模拟器、charles、mitm 等 mitm的使用参考:Mitmproxy对Android进行抓包(真机)_mitmproxy 安卓-CSDN博客 charles的使用参考:【全网最详细】手把手教学Charles抓包工具详细自学教程,完整版安装教程,详细介绍…...
与Ubuntu相关命令
windows将文件传输到Ubuntu 传输文件夹或文件 scp -r 本地文件夹或文件 ubuntu用户名IP地址:要传输到的文件夹路径 例如: scp -r .\04.py gao192.168.248.129:/home/gao 如果传输文件也可以去掉-r 安装软件 sudo apt-get update 更新软件包列表 sudo apt insta…...
Unity常用内置变换矩阵
Unity引擎提供了一系列内置的变换矩阵,这些矩阵在着色器中用于处理物体、摄像机和光照的坐标变换,是游戏开发中不可或缺的工具。它们帮助开发者在顶点着色器和片段着色器中实现坐标转换、光照计算等功能。 主要变换矩阵类型 模型矩阵 (Model Matrix) /…...
算法题-图论
图的表示 207.课程表 127.单词接龙 图的遍历 DFS 递归。。。 200.岛屿数量 239.矩阵中的最长递增路径 BFS 102.二叉树的层序遍历 看到最短,首先想到的是BFS 542.01矩阵 207.课程表 127.单词接龙 拓扑排序 对于一个有向无环图G进行拓扑排序,是将G中…...
Java 8(Ubuntu 18.04.6 LTS)安装笔记
一、前言 本文与【MySQL 8(Ubuntu 18.04.6 LTS)安装笔记】同批次:先搭建数据库,再安装JVM,后面肯定就是部署Web应用了——典型的单机部署,真可谓“麻雀虽小五脏俱全”。 二、准备 (1ÿ…...
unity编辑器的json验证及格式化
UNITY编辑器的json格式化和验证工具资源-CSDN文库https://download.csdn.net/download/qq_38655924/90676188?spm1001.2014.3001.5501 反复去别的网站验证json太麻烦了 用这个工具能方便点 # Unity JSON工具 这是一个Unity编辑器扩展,用于验证、格式化和压缩JSO…...
C语言中小写字母转大写字母
一、题目引入 这一题运行结果是什么? 二、代码分析 在这个代码中 首先 -> 定义了一个字符数组空间内存是80 里面存储的是字符串123abcdEFG*& 接着 -> 定义了一个整型变量j 后面的循环会用到 然后 -> 使用了<stdio.h>中的库函数puts(ch)原样打印…...
深度学习--卷积神经网络调整学习率
文章目录 前言一、学习率1、什么学习率2、什么是调整学习率3、目的 二、调整方法1、有序调整1)有序调整StepLR(等间隔调整学习率)2)有序调整MultiStepLR(多间隔调整学习率)3)有序调整ExponentialLR (指数衰减调整学习率)4)有序调整…...
桥接模式:分离抽象与实现的独立进化
桥接模式:分离抽象与实现的独立进化 一、模式核心:解耦抽象与实现的多层变化 在软件设计中,当抽象(如 “手机品牌”)和实现(如 “操作系统”)都可能独立变化时,使用多层继承会导致…...
配置kafka与spark连接
一、配置kafka 首先进到software目录当中,如下图所示: 安装包上传/解压/重命名/解压过后的目录如下图所示: 修改配置: cd config vi server.properties 全部修改语句如下所示(以node01为样例)࿱…...
WebXR教学 05 项目3 太空飞船小游戏
准备工作 自动创建 package.json 文件 npm init -y 安装Three.js 3D 图形库,安装现代前端构建工具Vite(用于开发/打包) npm install three vite 启动 Vite 开发服务器(推荐)(正式项目开发) …...
【leetcode】3524 求出数组的X值1
题目链接 题目描述 给你一个正整数数组 nums 和一个正整数 k。 你可以对数组执行一次操作:移除不重叠的前缀和后缀(可以为空),留下一个连续非空子数组。 对于每一种留下的子数组,计算: (该子数组的乘积…...
配电室安全用电漏电保护装置的安全用电措施
配电室作为电力分配与控制的关键场所,其安全用电装置的重要性不言而喻。 防触电、防漏电、防火灾、安全防护、保障生命财产; 当用电设备发生短路、过载或漏电等异常时能迅速切断电源,精准定位问题。及时报警,防止触电 在配电室中…...
数据库-基本概述 和 SQL 语言
标题目录 基本概述DB和DBMS关系数据库库与表的概念表库 数据库在项目中的角色如何操作数据库 SQL 语言SQL 分类DDL 语言数据库操作创建数据库查看数据库删除数据库切换数据库 表的操作创建表查看表修改表名删除表修改表结构 DML 语言插入数据修改数据删除数据 基本概述 DB和DB…...
C语言中的递归1.0
一、递归函数概念引入 简单来说就是自己调用自己 递归函数满足的两个条件: 1.每次调用函数本身,必须一次又一次接近最终结果 2.必须有停止条件 二、代码展示 用递归求1到4的和 代码如下 三、代码分析 首先进入main主函数入口 int sum getsum(4); 就这个具体题目来看 我…...
解锁webpack:对html、css、js及图片资源的抽离打包处理
面试被问到webpack,可别只知道说 HtmlWebpackPlugin 了哇。 前期准备 安装依赖 npm init -y npm install webpack webpack-cli --save-dev配置打包命令 // package.json {"scripts": {// ... 其他配置信息"build": "webpack --mode pr…...
[特殊字符] 大模型对话风格微调项目实战——模型篇 [特殊字符]✨
📜 目录 🎯 背景介绍 🔍 这篇文章的任务 🤖 模型选型 📊 模型评测 ⚙️ 模型训练 🔄 模型转换 🧪 模型训练效果评估 🎉 总结 🎯 背景介绍 本文是《大模型对话风…...
lerobot[act解析]
ACT是具身智能模仿学习中重要的一个算法,本文会先从这个算法是是什么,这个算法如何工作的,到这个算法为什么有效,也就是what->how->why的这么一个顺序来进行解析 ACT 是什么?(What) 核心…...
使用Python创建带边框样式的Word表格
引言 在生成Word文档时,表格的边框样式是提升专业度的重要细节。本文将通过一个实例,展示如何使用python-docx库为表格添加上下边框加粗和内部边框隐藏的复杂样式。代码将实现以下效果: 表格位于页面底部表格首行和末行的上下边框加粗隐藏内…...
GPLT-2025年第十届团体程序设计天梯赛总决赛题解(共计266分)
今天偶然发现天梯赛的代码还保存着,于是决定写下这篇题解,也算是复盘一下了 L1本来是打算写的稳妥点,最后在L1-6又想省时间,又忘记了insert,replace这些方法怎么用,也不想花时间写一个文件测试,…...
基于SpringBoot的课程管理系统
前言 今天给大家分享一个基于SpringBoot的课程管理系统。 1 系统介绍 课程管理系统是一种专门为学校设计的软件系统,旨在帮助学校高效地管理和组织各类课程信息。 该系统通常包括学生、教师和管理员三大角色。 他们可以通过系统进行选课、查看课程表、考试、进…...
新品发布 | 6 秒全谱成像,VIX-N320 内置推扫式高光谱相机重磅发布
深圳市中达瑞和科技有限公司正式发布全新一代VIX-N320内置推扫式可见光近红外高光谱相机,一款集高速成像、高精度光谱分析与便携性于一体的革命性产品。以突破性技术重新定义光谱成像效率与精度,开启智能感知新纪元。作为国内唯一同时掌握凝采式、推扫式…...
手写深拷贝函数
在 JavaScript 中,深拷贝是指创建一个对象或数组的完全独立副本,包括其嵌套的对象或数组。这意味着修改副本不会影响原始对象。 以下是手写一个通用的深拷贝函数的实现: 深拷贝函数实现 function deepClone(target, map new WeakMap()) {//…...
智能电网第3期 | 配电房巡检机器人通信升级方案
随着电力系统智能化发展,配电房巡检机器人是保障电力设备安全稳定运行的重要工具,其通信稳定性关乎巡检效率与质量。配电房巡检智能化升级面临着多项挑战: 电磁干扰大:配电房电气设备密集,电磁干扰强,易造成…...
阿里云 AI 搜索开放平台:RAG智能化工作流助力 AI 搜索
——已获知乎作者【小小将】授权转载 最近AI圈的变化可谓是日新月异,随着大模型的技术突飞猛进,大模型的能力日益增强。这些都驱动着我们的搜索技术快速演进到了下一代,也就是 AI 搜索的技术。大模型的快速发展不仅重塑了搜索技术的基础&…...
同z科技面经
同z科技-2025-4-23 1.自我介绍 个人信息 校园经历 实习经历 项目经历 个人技能掌握 目前学习技术 2.封装缓存工具类怎么封装的 先介绍使用缓存的问题 解决的逻辑 封装的逻辑 应用 缓存穿透: 缓存雪崩: 缓存击穿: https://www…...
制作一款打飞机游戏19:碰撞检测
在这一章中,我们致力于解决碰撞检测问题,但它并不如我们所愿工作。 碰撞检测问题 今天我想解决的是碰撞检测问题,这个令人畏惧的碰撞检测。我理解,这里有很多复杂的if语句,但我们可以做到。 不过,在此之…...
python后端程序部署到服务器 Ubuntu并配合 Vue 前端页面运行
将 PyCharm 研发的 Web 后端系统程序部署到 Ubuntu 24.04 服务器并配合 Vue 前端页面运行,可按以下步骤操作: 1. 服务器环境准备 在开始部署之前,需要在 Ubuntu 24.04 服务器上安装必要的软件。 # 更新系统软件包 sudo apt update sudo ap…...
9N60-ASEMI无人机专用功率器件9N60
编辑:LL 9N60-ASEMI无人机专用功率器件9N60 型号:9N60 品牌:ASEMI 封装:TO-220F 最大漏源电流:9A 漏源击穿电压:600V 批号:最新 RDS(ON)Max:1.00Ω …...
Java单例模式详解:实现线程安全的全局访问点
精心整理了最新的面试资料和简历模板,有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 一、什么是单例模式? 单例模式(Singleton Pattern)是一种创建型设计模式,它保证一个类仅有一个实例ÿ…...
【无人机】无人机光流模块Optical Flow设置(三),光流测距一体传感器的配置。凌启科技的光流测距一体模块的测试。
目录 1、光流测距一体模块的配置,详细步骤 1.1、连接 1.2、上位机使用 1.3、切换为PX4协议 2、适配PX4飞控,QGC上参数配置 2.0、安装连接 2.1、串口配置: 2.2、启用光流辅助功能 2.3、启用测距辅助功能 2.4、高度参考设置 2.5、重…...
电路中的DGND、GROUND、GROUND_REF的区别,VREF、VCC、VDD、VEE和VSS的区别?
目录 1 DGND、GROUND、GROUND_REF的区别 1.1 DGND(Digital Ground) 1.2 GROUND(Ground) 1.3 GROUND_REF(Ground Reference) 1.4 区别 2 VREF、VCC、VDD、VEE和VSS的区别 2.1 VREF(Refere…...
VSFTPD+虚拟用户+SSL/TLS部署安装全过程(踩坑全通)
Author : Spinach | GHB Link : http://blog.csdn.net/bocai8058文章目录 前言准备配置虚拟用户1.创建虚拟用户列表文件2.生成数据库文件3.设置虚拟用户独立访问权限 配置PAM认证1.创建PAM配置文件2.测试PAM认证 创建虚拟用户映射的系统用户生成SSL/TLS证书配置VSFTPD服务1…...
Java-File类详解(一篇讲透)
一:File类的实例化及常用方法 1.1 定义 1.2 构造器 (1)File(String pathname) 文件的路径表示方式 测试: (2)File(String parent,String child) 在父路径下创建子文件(没后缀是目录,…...
Representation Flow for Action Recognition论文笔记
原文笔记: What: 在本文中,我们提出了一种受光流算法启发的CNN层,用于学习动作识别的运动表示,而无需计算光流。我们的表示流层是一个完全可微分的层,旨在捕获模型中任何表示通道的“流”。其迭代流量优化…...
云计算领域需掌握的核心技术
云计算作为现代信息技术的核心基础设施,涵盖从基础资源管理到上层应用开发的完整技术栈。它依靠强大的计算能力,使得成千上万的终端用户不担心所使用的计算技术和接入的方式等都能够进行有效的依靠网络连接起来的硬件平台的计算能力来实施多种应用。 一、…...
Android仿今日头条Kotlin版本
软件信息 gradle-8.0Sdk信息 //编译版本 compileSdk33 //最小版本 minSdk24 //目标版本 targetSdk31Android Studio Giraffe | 2022.3.1 Patch 2(建议版本不要太低)MVVMAndroid Jetpack 项目注意 没有服务器,用的是Apifox模拟服务器返回&a…...