本文章总体简介,会采用Docker下载安装RabbitMQ,详细介绍RabbitMQ优缺点及其相关技术知识点,SpringBoot作为整合学习的基础架构。看完本片文章,你会了解消息队列RabbitMQ,你会使用消息队列RabbitMQ。
当第一次出现在我脑中这个词语时,我会问自己什么时消息队列?我做的项目为什么没有消息队列?我做的项目可以用消息队列么?如果可以用消息队列这要怎么使用消息队列?消息队列能解决项目什么问题?等等问题会出现在我的脑海…,然而我将会在面文章中对自己慢慢摸索和讲解和答疑。
2.异步消息
应用程序的解耦也是运用的消息队列异步消息的功能,系统在没有引入消息队列时应用程序的数据流向会是用户请求(浏览器)->业务处理(订单,财务,积分等等)->数据存储(操作数据库)如下图,我们会发现大量不同的业务都会之间对数据库操作,整个应用程序的性能将受到数据量的影响,数据量过大时还会直接导致系统宕机。
而加入了消息队列的系统是怎么样的呢?每个生产者业务都会把消息放入消息队列,每个消费者都会去消息队列取自己订阅的消息。保证了上游系统能一直正常运行,不会应为上游生产者系统产生大量业务数据而导致系统卡顿,上游大量的业务数据将会被存入消息队列,等待下游系统来消费,下游系统每次从消息队列取多少数据由下游系统性能决定,能处理2000就从消息队列取2000处理,这样减轻了数据库系统的操作,也保证了上游系统正常的业务。如下图:
3.流量削锋
就目前技术而言,流量削锋的技术很多,分为两大类技术有损流量削峰和无损流量削峰。前者偏重与技术层面实现,后者侧重于物理和运行环境实现。
有损流量削峰
技术方案有消息队列(RabbitMQ、Kafka、ActiveMQ、ZeroMQ、MetaMQ、RocketMQ等)、答题(验证码、图片绘画,图片识别等)、过滤(技术层面层层过滤数据,从用户发起请求到存入数据库中所有环节对数据处理校验和筛选最终存入有效数据)。
无损流量削峰
负载均衡(可以用nginx反向代理等负载技术实现流量分发)等等技术。
市场上的MQ种类繁多,常见消息队列有RabbitMQ,RocketMQ,ActiveMQ,ZeroMQ,Kafka等,各有优缺点自行查阅资料了解相对应的优缺点。主要学习RabbitMQ,其他相关知识自行了解。
在上面所有教程中已经知道了什么是消息队列,RabbitMQRabbitMQ的核心概念(生产者(Producer):发送消息的应用;消费者(Consumer):接收消息的应用;队列(Queue):存储消息的缓存;消息(Message):由生产者通过RabbitMQ发送给消费者的信息;连接(Connection):连接RabbitMQ和应用服务器的TCP连接;通道(Channel):连接里的一个虚拟通道。当你通过消息队列发送或者接收消息时,这个操作都是通过通道进行的;交换机(Exchange):交换机负责从生产者那里接收消息,并根据交换类型分发到对应的消息列队里。要实现消息的接收,一个队列必须到绑定一个交换机;绑定(Binding):绑定是队列和交换机的一个关联连接;路由键(Routing Key):路由键是供交换机查看并根据键来决定如何分发消息到列队的一个键。路由键可以说是消息的目的地址)。RabbitMQ五种消息发送模式(简单队列、工作队列、发布/订阅模式(Publish/Subcribe)、路由模式(Routing)、主题模式(Topic))。RabbitMQ四种交换机(Fanout exchange,Direct exchange、Topic exchange、Headers exchange)等相关知识,与RabbitMQ相关详细知识请自行查阅相关资料学习。推荐学习博客地址(https://blog.csdn.net/github_37130188/article/details/115289346)。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
在application.properties配置文件中配置rabbitMQ相关配置信息
# rabbitmq配置信息
# rabbitmq启动的ip地址
spring.rabbitmq.host=192.168.147.135
# 端口
spring.rabbitmq.port=5672
# 用户名
spring.rabbitmq.username=admin
# 密码
spring.rabbitmq.password=admin
# 配置虚拟机
spring.rabbitmq.virtual-host=/
# 消息开启手动确认
spring.rabbitmq.listener.direct.acknowledge-mode=manual
以上配置整合已经完成了,接下来我们看看如何在SpringBoot中rabbitmq使用每种情况。
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.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Bean;
/**
* @author 260497
*/
@SpringBootConfiguration
public class DirectRabbitmqConfig {
/**
* 队列
* @return directQueue队列名称 true 持久化
*/
@Bean
public Queue directQueueTest(){
return new Queue("directQueue",true);
}
/**
* 新建Direct类型交换机
* @return directExchangeTest交换机名称 true 持久化 false 不自动删除
*/
@Bean
public DirectExchange directExchangeTest(){
return new DirectExchange("directExchangeTest",true,false);
}
/**
* 将directQueueTest队列绑定到DirectExchange交换机
* @return
*/
@Bean
public Binding bindingDirect(){
/**
* directQueueTest() Queue队列
* directExchangeTest() DirectExchange交换机
* directRoutingKey 路由键
*/
return BindingBuilder.bind(directQueueTest()).to(directExchangeTest()).with("directRoutingKey");
}
/**
* 没有绑定任何队列的交换机Exchange
* @return
*/
@Bean
DirectExchange lonelyDirectExchange() {
return new DirectExchange("lonelyDirectExchange");
}
}
书写接口生产数据调用生产者
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author 260497
*/
@RestController
public class DirectRabbitmqController {
@Autowired
RabbitTemplate rabbitTemplate;
/**
* Direct测试
* @return
*/
@GetMapping("/sendDirectMessage")
public String sendDirectMessage() {
rabbitTemplate.convertAndSend("directExchangeTest", "directRoutingKey", "你好!大帅哥,我是交换机Direct类型所产生的消息");
return "ok";
}
}
书写Direct类型交换机的消费者
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* @email: [email protected]
* @date: 2021/11/16 15:40
* 功能:DirectExchange交换机类型生产者和消费者测试
* 描述:
* @author: 吴帆
**/
@Component
@RabbitListener(queues = "directQueue") //
public class DirectReceiver {
@RabbitHandler
public void process(String msg){
System.out.println("消费者接收到消息:"+msg);
}
}
启动SpringBoot项目,postman或浏览器直接访问ip:端口/sendDirectMessage地址,返回ok
查看控制台如下图,消费者消费交换机类型为derect直连类型消费消息成功
上面代码可以用下面流图表示,队列和交换机直通过路由键建立关系,不像topic模式可进行匹配。
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;
/**
* @email: [email protected]
* @date: 2021/11/16 16:32
* 功能:
* 描述:
* @author: 吴帆
**/
@Configuration
public class TopicRabbitmqConfig {
@Bean
public Queue queue1(){
//配置队列名称为topic.man的队列
return new Queue("queue1");
}
@Bean
public Queue queue2(){
//配置队列名称为topic.woman的队列
return new Queue("queue2");
}
/**
* 新建TopicExchange交换机 起名topicExchange
* @return
*/
@Bean
public TopicExchange topicExchange(){
return new TopicExchange("topicExchange");
}
/**
* 绑定一个manQueue()队列到topicExchange交换机 路由键routingKey=topic.man
* @return
*/
@Bean
public Binding bindingManQueueToTopicExchange(){
return BindingBuilder.bind(queue1()).to(topicExchange()).with("topic.man");
}
/**
* womanQueue()topicExchange 路由键routingKey=topic. 这样只要是消息携带的路由键是以topic.开头,都会分发到该队列womanQueue
* @return
*/
@Bean
public Binding bindingWoManQueueToTopicExchange(){
// 这样只要是消息携带的路由键是以topic.开头,都会分发到该队列
return BindingBuilder.bind(queue2()).to(topicExchange()).with("topic.#");
}
}
新建生产者1—指定路由键为topic.man(其实等同指定队列)
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @email: [email protected]
* @date: 2021/11/16 17:04
* 功能:
* 描述:
* @author: 吴帆
**/
@RestController
public class TopicManRabbitmqController {
@Autowired
RabbitTemplate rabbitTemplate;
/**
* Direct测试
* @return
*/
@GetMapping("/sendTopicManMessage")
public String sendTopicManMessage() {
rabbitTemplate.convertAndSend("topicExchange", "topic.man", "你好!大帅哥,我是交换机Topic类型所产生的消息");
return "ok";
}
}
新建生产者2—路由键为topic.woman
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* @email: [email protected]
* @date: 2021/11/16 18:36
* 功能:
* 描述:
* @author: 吴帆
**/
@RestController
public class TopicWoManRabbitmqController {
@Resource
private RabbitTemplate rabbitTemplate;
/**
* Direct测试
* @return
*/
@GetMapping("/sendTopicWoManManMessage")
public String sendTopicWoManManMessage() {
rabbitTemplate.convertAndSend("topicExchange", "topic.woman", "你好!大帅哥,我是交换机Topic类型所产生的消息");
return "ok";
}
}
新建消费者1—TopicManReceiver —queues = “queue1”
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @email: [email protected]
* @date: 2021/11/16 17:09
* 功能:
* 描述:
* @author: 吴帆
**/
@Component
@RabbitListener(queues = "queue1")
public class TopicManReceiver {
@RabbitHandler
public void process(String msg){
System.out.println("消费者接收到消息Man--Man--Man:"+msg);
}
}
新建消费者2–TopicWoManReceiver–queues = “queue2”
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @email: [email protected]
* @date: 2021/11/16 17:26
* 功能:
* 描述:
* @author: 吴帆
**/
@Component
@RabbitListener(queues = "queue2")
public class TopicWoManReceiver {
@RabbitHandler
public void process(String msg){
System.out.println("消费者接收到消息WoMan--WoMan--WoMan:"+msg);
}
}
上面的代码就是如下图效果一致,将队列queue1绑定到交换机TopicExchange上路由键为topic.man、将队列queue绑定到交换机TopicExchange上路由键为topic.#(#代表任意一个或多个单词)。
下图描述的就是在RabbitMQ中交换机类型为TopicExchange的整体流程图。生产者生产信息会携带交换机类型、路由键、消息三个信息去配置完的RabbitMQ根据对应关系把消息塞到队列queue1或queue2上。消费者监听自己指定的队列,每个队列绑定时都有自己的路由规则,路由规则符合自己的信息将会被该队列接受。
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;
/**
* @email: [email protected]
* @date: 2021/11/17 8:44
* 功能:
* 描述:
* @author: 吴帆
**/
@Configuration
public class FanoutRabbitConfig {
@Bean
public Queue queueA(){
return new Queue("queueA");
}
@Bean
public Queue queueB(){
return new Queue("queueB");
}
@Bean
public Queue queueC(){
return new Queue("queueC");
}
@Bean
public FanoutExchange fanoutExchange() {
return new FanoutExchange("fanoutExchange");
}
/**
* 名称为queueA的队列绑定到fanout类型的交换机
* @return Binding
*/
@Bean
public Binding queueABingToExchange(){
return BindingBuilder.bind(queueA()).to(fanoutExchange());
}
/**
* 名称为queueB的队列绑定到fanout类型的交换机
* @return Binding
*/
@Bean
public Binding queueBBingToExchange(){
return BindingBuilder.bind(queueB()).to(fanoutExchange());
}
/**
* 名称为queueC的队列绑定到fanout类型的交换机
* @return Binding
*/
@Bean
public Binding queueCBingToExchange(){
return BindingBuilder.bind(queueC()).to(fanoutExchange());
}
}
新建一个消息发送,指定交换机类型为fanoutExchange,路由键为空。
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @email: [email protected]
* @date: 2021/11/17 9:04
* 功能:模拟生产者往队列queueA中推送消息
* 描述:
* @author: 吴帆
**/
@RestController
public class FanoutQueueRabbitController {
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("queue")
public String queue(){
rabbitTemplate.convertAndSend("fanoutExchange", null, "测试fanout类型的交换机消息发送");
return "ok";
}
}
新建三个消费者监听queueA,queueA,queueC。
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @email: [email protected]
* @date: 2021/11/17 9:22
* 功能:队列queueA监听 消费者端
* 描述:
* @author: 吴帆
**/
@Component
@RabbitListener(queues= "queueA")
public class QueueReceiverA {
@RabbitHandler
public void process(String msg){
System.out.println("QueueReceiverA消费者接收到的消息:"+msg);
}
}
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @email: [email protected]
* @date: 2021/11/17 9:22
* 功能:
* 描述:
* @author: 吴帆
**/
@Component
@RabbitListener(queues="queueB")
public class QueueReceiverB {
@RabbitHandler
public void process(String msg){
System.out.println("QueueReceiverB消费者接收到的消息:"+msg);
}
}
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @email: [email protected]
* @date: 2021/11/17 9:22
* 功能:
* 描述:
* @author: 吴帆
**/
@Component
@RabbitListener(queues="queueC")
public class QueueReceiverC {
@RabbitHandler
public void process(String msg){
System.out.println("QueueReceiverC消费者接收到的消息:"+msg);
}
}
重启项目访问ip:端口/queue地址,postman访问成功如下图:
查看控制台打印信息如下图:
上图得知,当交换机为fanout广播模式时,只要是与FanoutExchange交换机绑定的队列,路由键相同则所有队列都会收到消息。
RabbitMQ整合SpringBoot结束了,gitee代码地址https://gitee.com/wufanlove/mq.git,以上技术仅供学习参考,如有疑问请及时联系。