RabbitMQ

rabbitMq

结构

  • 核心概念
  • 业务场景
  • 工作模式
  • 动手实践

一、核心概念

image-20230216153320425

rabbitMq 架构模型

  • 客户端
    • 生产者
    • 消费者
  • 服务端
    • 虚拟主机
    • 交换器
    • 队列

客户端和服务端通过 连接 和 信道 进行通信

整体流程

​ 1、生产者(producer)将消息发送到服务端(broker)

​ 2、消费者(consumer)从服务端获取对应的消息

当然,生产者在发送消息前需要先确认发送给那个虚拟主机(virtual host)的那个交换器(exchange),再有交换器通过路由键(routing key)将消息转发给与之绑定(bingding)的队列(queue)。最后消费者到指定的队列中获取自己的消息进行消费。

1.1、客户端

消费者和生产者属于客户端,是需要我们用代码实现具体逻辑的部分。

结构

  • 生产者
  • 消费者

生产者

​ 生产者是消息的发送方,将要发送的信息封装成一定的格式,发送给服务端。消息通常包括消息体(payLoad)和标签(label)

消费者

​ 消费者是消息的接收方,负责消费消息体。


1.2、客户端

结构

  • 虚拟主机
  • 交换器
  • 队列

虚拟主机

​ 虚拟主机用来对交换器和队列进行逻辑隔离。同一个虚拟主机下,交换器和队列的名称不能重复。这一点类似于java的package,同一个package下,不能出现相同名称的类和接口。

交换器

​ 交换器负责接收生产者发送来的消息,并根据规则分配给对应的队列,它不生产消息,它只是消息的搬运工。

队列

​ 队列负责存储消息。生产者发送的消息会被存储在这里,而消费者从这里消费消息。


1.3、连接和信道

image-20230216173953443

连接和信道(connection & channel) 是客户端和服务端通信的桥梁。在发送和接收消息的时候,都需要通过连接和信道于服务端通信。连接和信道的关系,就是一个连接包含多个信道,连接就是tcp连接(amqp 连接是通过tcp实现的)。在连接的基础上可以创建信道,连接是线程共享的,但信道是私有的。

为什么不直接用连接,而加了一个信道的概念:

其实主要是因为操作系统的资源成本问题,tcp连接对于操作系统来说还是比较重要的资源,建立一个tcp连接需要三次握手,而销毁一个tcp连接,需要四次挥手。所以当遇到高并发的情况,如果每个线程都需要向 rabbitmq 服务端发送/接收消息都要建立一个tcp连接,就会造成极大的资源消耗,而如果让线程共享一个tcp连接,又无法保证线程之间的私密性,就会导致线程之间互相干扰,所以,“tcp + 信道” 的模式就应运而生,即避免了不必要的系统开销,又保证了线程之间的私密性。


二、业务场景

消息队列的主要功能

  • 异步处理
  • 作为系统之间沟通的桥梁,且不受技术栈的影响(异步通信)
  • 给高并发的业务提供缓冲(缓冲削峰)

异步处理

​ 异步处理就是为了提高业务的响应

异步通信

​ 实现系统之间的解耦,通过异步通信

缓冲削峰

​ 通过mq来减少对接口的并发量,保证服务的稳定性

三、工作模式

rabbiitMq 支持7种工作模式

结构

  • 简单模式
  • 工作队列模式
  • 广播模式
  • 路由模式
  • 动态路由模式
  • 远程模式
  • 生产者确认模式

可以分为有无交换器参与

有无交换器参与

  • 无交换器参与
    • 简单模式
    • 工作队列模式
  • 有交换器参与
    • 广播模式
    • 路由模式
    • 动态路由模式

3.1、无交换器参与

结构

  • 简单模式
  • 工作队列模式
3.1.1、简单模式

image-20230216182120819

简单模式就是:生产者将消息发送给队列,消费者从队列中获取消息即可。

例子:

​ 生活中,我们网购,快递员(生产者)将快递(消息)放在快递站(队列),然后你(消费者)凭借取件码取件(消费消息)。

3.1.2、工作队列模式

image-20230216182809733

工作队列模式:

​ 工作队列模式和简单模式基本一致,只是消费者由一个变成多个,默认情况下每个消费者是对队列中的消息进行平均分配的。我们可以通过配置rabbitmq的配置实现 能者多劳,消费能力强的消费者多消费。


3.2、有交换器参与

有交换器参与

  • 广播模式
  • 路由模式
  • 动态路由模式
3.2.1、广播模式

image-20230216183648241

广播模式开始有交换器的参与,同时与工作队列模式相比,广播模式更加公平;在工作队列模式下,只有消息条数是消费者数量的整数倍时才能做到公平分配。广播模式下,所有的消费者都会收到生产者的消息。

例子:

​ 广播模式就像收音机听广播,只要调到对应的频率,就可以收到电台的节目。


3.2.2、路由模式

image-20230217103618478

路由模式下,交换器会根据不同的路由键(routing key)将消息发送给指定的队列,从而被特定的消费者消费。一个队列可以拥有一个或者多个 routing key,一个routing key 可以一个或者多个队列。


3.2.3、动态路由模式

image-20230217110107710

动态路由模式可以看作路由模式的升级版,路由模式需要指定 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

image-20230217111139430


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);
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值