rabbitMq
结构
- 核心概念
- 业务场景
- 工作模式
- 动手实践
一、核心概念
rabbitMq 架构模型
- 客户端
- 生产者
- 消费者
- 服务端
- 虚拟主机
- 交换器
- 队列
客户端和服务端通过 连接 和 信道 进行通信
整体流程
1、生产者(producer)将消息发送到服务端(broker)
2、消费者(consumer)从服务端获取对应的消息
当然,生产者在发送消息前需要先确认发送给那个虚拟主机(virtual host)的那个交换器(exchange),再有交换器通过路由键(routing key)将消息转发给与之绑定(bingding)的队列(queue)。最后消费者到指定的队列中获取自己的消息进行消费。
1.1、客户端
消费者和生产者属于客户端,是需要我们用代码实现具体逻辑的部分。
结构
- 生产者
- 消费者
生产者
生产者是消息的发送方,将要发送的信息封装成一定的格式,发送给服务端。消息通常包括消息体(payLoad)和标签(label)
消费者
消费者是消息的接收方,负责消费消息体。
1.2、客户端
结构
- 虚拟主机
- 交换器
- 队列
虚拟主机
虚拟主机用来对交换器和队列进行逻辑隔离。同一个虚拟主机下,交换器和队列的名称不能重复。这一点类似于java的package,同一个package下,不能出现相同名称的类和接口。
交换器
交换器负责接收生产者发送来的消息,并根据规则分配给对应的队列,它不生产消息,它只是消息的搬运工。
队列
队列负责存储消息。生产者发送的消息会被存储在这里,而消费者从这里消费消息。
1.3、连接和信道
连接和信道(connection & channel) 是客户端和服务端通信的桥梁。在发送和接收消息的时候,都需要通过连接和信道于服务端通信。连接和信道的关系,就是一个连接包含多个信道,连接就是tcp连接(amqp 连接是通过tcp实现的)。在连接的基础上可以创建信道,连接是线程共享的,但信道是私有的。
为什么不直接用连接,而加了一个信道的概念:
其实主要是因为操作系统的资源成本问题,tcp连接对于操作系统来说还是比较重要的资源,建立一个tcp连接需要三次握手,而销毁一个tcp连接,需要四次挥手。所以当遇到高并发的情况,如果每个线程都需要向 rabbitmq 服务端发送/接收消息都要建立一个tcp连接,就会造成极大的资源消耗,而如果让线程共享一个tcp连接,又无法保证线程之间的私密性,就会导致线程之间互相干扰,所以,“tcp + 信道” 的模式就应运而生,即避免了不必要的系统开销,又保证了线程之间的私密性。
二、业务场景
消息队列的主要功能
- 异步处理
- 作为系统之间沟通的桥梁,且不受技术栈的影响(异步通信)
- 给高并发的业务提供缓冲(缓冲削峰)
异步处理
异步处理就是为了提高业务的响应
异步通信
实现系统之间的解耦,通过异步通信
缓冲削峰
通过mq来减少对接口的并发量,保证服务的稳定性
三、工作模式
rabbiitMq 支持7种工作模式
结构
- 简单模式
- 工作队列模式
- 广播模式
- 路由模式
- 动态路由模式
- 远程模式
- 生产者确认模式
可以分为有无交换器参与
有无交换器参与
- 无交换器参与
- 简单模式
- 工作队列模式
- 有交换器参与
- 广播模式
- 路由模式
- 动态路由模式
3.1、无交换器参与
结构
- 简单模式
- 工作队列模式
3.1.1、简单模式
简单模式就是:生产者将消息发送给队列,消费者从队列中获取消息即可。
例子:
生活中,我们网购,快递员(生产者)将快递(消息)放在快递站(队列),然后你(消费者)凭借取件码取件(消费消息)。
3.1.2、工作队列模式
工作队列模式:
工作队列模式和简单模式基本一致,只是消费者由一个变成多个,默认情况下每个消费者是对队列中的消息进行平均分配的。我们可以通过配置rabbitmq的配置实现 能者多劳,消费能力强的消费者多消费。
3.2、有交换器参与
有交换器参与
- 广播模式
- 路由模式
- 动态路由模式
3.2.1、广播模式
广播模式开始有交换器的参与,同时与工作队列模式相比,广播模式更加公平;在工作队列模式下,只有消息条数是消费者数量的整数倍时才能做到公平分配。广播模式下,所有的消费者都会收到生产者的消息。
例子:
广播模式就像收音机听广播,只要调到对应的频率,就可以收到电台的节目。
3.2.2、路由模式
路由模式下,交换器会根据不同的路由键(routing key)将消息发送给指定的队列,从而被特定的消费者消费。一个队列可以拥有一个或者多个 routing key,一个routing key 可以一个或者多个队列。
3.2.3、动态路由模式
动态路由模式可以看作路由模式的升级版,路由模式需要指定 routing key,而动态路由模式可以看作支持带通配符的 routing key。
*代表一个词;#代表零个或者多个词;词之间用.
四、动手实践
结构
- 安装rabbitMq,配置虚拟主机
- 代码实战
4.1、安装rabbitMq
docker rabbitmq
结构
- 查看镜像
- 拉取镜像
- 启动容器
- 安装插件
- 访问管理界面
- 配置虚拟主机,服务连接测试
查看镜像
docker search rabbitmq
拉取镜像
docker pull rabbitmq
启动容器
docker run -d --hostname my-rabbit --name rabbit -p 6021:15672 -p 6001:5672 rabbitmq
#6021 对应管理页面的访问端口,需绑定15672
#6001 对应服务连接端口,需绑定5672
安装插件
#1、docker ps
#2、docker exec -it 容器id /bin/bash
#3、执行 rabbitmq-plugins enable rabbitmq_management
访问管理页面
http://ip:6021
#账户:guest
#密码:guest
配置虚拟主机,服务连接测试
客户端连接端口:5672
4.2、代码实战
结构
- 添加依赖
- 添加配置
- 代码实战
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
添加配置
spring:
...
rabbitmq:
host: ip
port: 6001
username: guest
password: guest
virtual-host: springBoot-mq
RabbitTemplate
springboot 封装的 rabbitMq 的工具类,就像redis 的 RedisTemplate
4.2.1、简单模式
结构
- 生产者
- 消费者
- 测试
生产者
@Resource
private RabbitTemplate rabbitTemplate;
@ApiOperation("简单模式")
@PostMapping("/sendSimple")
public void sendSimple(String routingKey, String message) {
rabbitTemplate.convertAndSend(routingKey, message);
}
消费者
@Slf4j
@Component
@RabbitListener(queuesToDeclare = @Queue("demo1"))
public class RabbitMqConsumer {
@RabbitHandler
public void receive(String message) {
log.info("简单模式-消费-{}",message);
}
}
4.2.2、工作队列模式
结构
- 生产者
- 消费者
- 改造工作队列模式
生产者
@ApiOperation("工作队列模式")
@PostMapping("/workModel")
public void workModel(String routingKey, String message) {
for (int i = 0; i < 10; i++) {
rabbitTemplate.convertAndSend(routingKey, "第" + i + "条" + message);
}
}
消费者
@Slf4j
@Component
public class WorkConsumer {
@RabbitListener(queuesToDeclare = @Queue("work"))
public void receiveOne(String message) {
log.info("{}被 receiveOne 消费",message);
}
@RabbitListener(queuesToDeclare = @Queue("work"))
public void receiveTwo(String message) {
log.info("{}被 receiveTwo 消费",message);
}
}
改造工作队列模式
- 自定义RabbitListenerContainerFactory
- 生产者
- 消费者
自定义RabbitListenerContainerFactory
@Configuration
public class RabbitConfig {
@Bean
public RabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
//MANUAL 手动确认
//AUTO 消费完成后自动确认(spring 确认)
//NONE 消费完成后就确认 (RabbitMq 确认)
factory.setAcknowledgeMode(AcknowledgeMode.AUTO);
//拒绝策略 为true 回到队列 false 时丢弃 默认true
factory.setDefaultRequeueRejected(true);
//默认的 prefetchCount 250 , 为0时启动能者多劳模式
factory.setPrefetchCount(0);
return factory;
}
}
生产者
@ApiOperation("工作队列模式")
@PostMapping("/workModel")
public void workModel(String routingKey, String message) {
for (int i = 0; i < 10; i++) {
rabbitTemplate.convertAndSend(routingKey, "第" + i + "条" + message);
}
}
消费者
@RabbitListener(containerFactory = "rabbitListenerContainerFactory",queuesToDeclare = @Queue("work2"))
public void receiveThree(String message) {
try {
Thread.sleep(1000);
}catch (Exception e) {
e.printStackTrace();
}
log.info("{}被 receiveThree 消费",message);
}
@RabbitListener(containerFactory = "rabbitListenerContainerFactory",queuesToDeclare = @Queue("work2"))
public void receiveFour(String message) {
try {
Thread.sleep(4000);
}catch (Exception e) {
e.printStackTrace();
}
log.info("{}被 receiveFour 消费",message);
}
4.2.3、广播模式
结构
- 生产者
- 消费者
生产者
@ApiOperation("广播模式")
@PostMapping("/fanout")
public void fanout(String exchange, String message) {
rabbitTemplate.convertAndSend(exchange, "", message);
}
消费者
@Slf4j
@Component
public class FanoutConsumer {
@RabbitListener(bindings = @QueueBinding(value = @Queue,exchange = @Exchange(name = "fanout",type = "fanout")))
public void receiveOne(String message) {
log.info("receiveOne:{}",message);
}
@RabbitListener(bindings = @QueueBinding(value = @Queue,exchange = @Exchange(name = "fanout",type = "fanout")))
public void receiveTwo(String message) {
log.info("receiveTwo:{}",message);
}
}
4.2.4、路由模式
结构
- 生产者
- 消费者
生产者
@ApiOperation("路由模式")
@PostMapping("/direct")
public void direct(String exchange,String routingKey, String message) {
rabbitTemplate.convertAndSend(exchange, routingKey, message);
}
消费者
@Slf4j
@Component
public class DirectConsumer {
@RabbitListener(bindings = @QueueBinding(value = @Queue,key = {"one"},exchange = @Exchange(name = "direct",type = "direct")))
public void receiverOne(String message) {
log.info("reviceOne:{}",message);
}
@RabbitListener(bindings = @QueueBinding(value = @Queue,key = {"two"},exchange = @Exchange(name = "direct",type = "direct")))
public void receiverTwo(String message) {
log.info("receiverTwo:{}",message);
}
}
4.2.5、动态路由模式
结构
- 生产者
- 消费者
生产者
@ApiOperation("路由模式")
@PostMapping("/direct")
public void direct(String exchange,String routingKey, String message) {
rabbitTemplate.convertAndSend(exchange, routingKey, message);
}
消费者
@Slf4j
@Component
public class TopicConsumer {
@RabbitListener(bindings = @QueueBinding(value = @Queue,key = {"one.one"},exchange = @Exchange(name = "topic",type = "topic")))
public void receiveOne(String message) {
log.info("receiveOne : {}",message);
}
@RabbitListener(bindings = @QueueBinding(value = @Queue,key = {"one.receiveTwo.*"},exchange = @Exchange(name = "topic",type = "topic")))
public void receiveTwo(String message) {
log.info("receiveTwo : {}",message);
}
@RabbitListener(bindings = @QueueBinding(value = @Queue,key = {"one.*.receiveThree"},exchange = @Exchange(name = "topic",type = "topic")))
public void receiveThree(String message) {
log.info("receiveThree : {}",message);
}
@RabbitListener(bindings = @QueueBinding(value = @Queue,key = {"*.receiveFour"},exchange = @Exchange(name = "topic",type = "topic")))
public void receiveFour(String message) {
log.info("receiveFour : {}",message);
}
}