RabbitMQ

在这里插入图片描述
在这里插入图片描述

RabbitMQ

一. 简介

MQ(MessageQueue)消息队列,一个队列(是一个逻辑上的存在)中存放了很多的消息,在同一个队列中这些消息是有序的。它的作用有以下几点:

  1. 削峰填谷;
  2. 系统间解耦;
  3. 控制订单的超时;

常用的MQ框架:ActiveMQ、ZeroMQ、QMQ(去哪儿网)、RabbitMQ、RocketMQ(阿里巴巴)、Kafka、Pulsar.

二. RabbitMQ的安装

docker run -p 15672:15672 -p 5672:5672 -d rabbitmq:3.9-management

说明:15672是rabbitmq提供的web服务器的接口;5672是rabbitmq的端口,在浏览器中输入:

http://ip:15672,使用 guest/guest 访问即可

三. RabbitMQ的运行原理

  1. RabbitMQ的一个服务,我们习惯性的将其称作为一个 Broker(所有的MQ都是这么来取名一个MQ的服务器的)
  2. RabbitMQ最核心就是Queue, 消息最终都是放到 Queue中的,每个队列都要有名字;
  3. 消息要达到队列必须要经过交换机,队列必须要通过binding_key绑定到交换机;
  4. 发送消息的时候,每个消息要指定 routing_key,交换机就根据routing_key将消息分发到对应的队列中。
  5. 消息的消费者,直接对应到队列。

MQ的出现就是为了解决上下游的处理速度不均衡问题的。

四. RabbitMQ的5种工作模型

4.1 简单模式

简单模型,一个消息的生产者对应着一个消息的消费者,所采用的交换机使用RabbitMQ提供的默认交换机。

public class Producer {
    public static void main(String[] args) throws Exception{
       Connection connection = ConnectionUtils.getConnection();

        // 获取一个通道
        Channel channel = connection.createChannel();

        /**
         * basicPublish 是发布消息:
         *    第一个参数是:交换机的名字,如果是空字符串,那么使用的是 RabbitMQ自动一个默认交换机
         *    第二个参数是: routing_key;
         *    第三个参数是:消息的属性:后面讲
         *    第四个参数是:具体的消息
         */
        channel.basicPublish("", "first-queue", null, "Hello RabbitMQ".getBytes(StandardCharsets.UTF_8));
        System.out.println("消息发送完毕");
    }
}
public class Consumer {
    public static void main(String[] args) throws Exception {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();
        /**
         * 第一个参数:队列的名称
         * 第二个参数:队列是否持久化, true就是队列持久化,就是写入到磁盘中;false表示队列只存在于内容中,rabbitmq挂了,队列没了;都设置true
         * 第三个参数:队列是否为排他队列,就是队列只作用与当前连接断掉了,队列就没了; 都设置为 false
         * 第四个参数:表示是否自动删除, 如果队列中的消息都被消费过(不管是否确认),那么连接断开之后,那么队列就会删除;都设置为false
         * 第五个参数:队里属性;
         *
         * 如果声明了一个队列,并且没有指定绑定到哪个交换机,那么这个队列会绑定到默认交换机,并且它的 binding_key 就是队列的名称
         */
        channel.queueDeclare("first-queue", true, false, false, null);// 声明一个队列

        /**
         * 第一个参数是队列的名词,表示当前想消费哪个队列中的消息;
         * 第二个参数如果为true, 就表示自动确认,那么队列收到消费者的确认之后,就会将消息删除掉; 往后我们都是自动确认;
         * 第三个参数就是处理消息的。
         */
        channel.basicConsume("first-queue", true, new DefaultConsumer(channel){
            // 处理消息, 最后一个参数就是我们的消息
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println(new String(body, Charset.defaultCharset()));

                /**
                System.out.println(envelope.getDeliveryTag());
//                // 自己手动确认,第一个参数表示消息在MQ中的编号;第二个表示是否确认多个
                channel.basicAck(envelope.getDeliveryTag(), false);
                 */
            }
        });
    }
}
4.2 工作模式

工作模型就是相对与简单模型而言,就是多了一个消费者而已。

4.3 发布订阅模型(fanout模型)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s3SAJLrd-1680076110918)(images/python-three.png)]

发布订阅模型,无关路由键,发送到对应交换机的消息会到达所有绑定到该交换机的队列上。

4.4 直连模型(routing 模型)

RabbitMQ的直连模型是最标准的一种工作模型。

4.5 topic模式

fanout是到达所有的队列;direct只能到达某个队列;如果想让消息到达部分队列,那么就使用 topic模型。topic模型中有两个特殊符号:

  1. #,表示0到多个;
  2. *,表示有且只有一个;

五. RabbitMQ与spring整合

第一步,引入依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

第二步,引入该依赖之后,会在IOC容器中存在一个 RabbitTemplate 这样一个 bean,用来发送消息的;

5.1 简单模型与工作模型
// 简单和工作模型,不需要指定交换机
@RabbitListener(queuesToDeclare = @Queue(name = "work-queue"))
public void consume(String msg) throws InterruptedException {
    System.out.println("One: " + msg + "; " + new Date());
}
5.2 fanout模型
@RabbitListener(bindings = @QueueBinding(
    exchange = @Exchange(name = "fanout-exchange", type = ExchangeTypes.FANOUT),
    value = @Queue("error-queue")
))
public void consume(String msg) {
        System.out.println("Error: " + msg);
    }
}
5.3 direct模型

RabbitMQ的交换机默认类型就是 direct 模型

@RabbitListener(bindings = @QueueBinding(
    exchange = @Exchange(name = "direct-exchange", type = ExchangeTypes.DIRECT),  // 交换的类型默认是 direct模式,可以不用写
    value = @Queue("error-queue"),
    key = {"error", "fatal"}
))
public void consume(String msg) {
    System.out.println("Error: " + msg);
    // insert user
}
5.4 topic模型
@RabbitListener(bindings = @QueueBinding(
    exchange = @Exchange(name = "topic-exchange", type = ExchangeTypes.TOPIC),
    value = @Queue("company-queue"),
    key = "company.#"
))
public void consume(String msg) {
    System.out.println("Company: " + msg);
}
@RabbitListener(bindings = @QueueBinding(
    exchange = @Exchange(name = "topic-exchange", type = ExchangeTypes.TOPIC),
    value = @Queue("java-queue"),
    key = "company.java.*"
))
public void consume(String msg) {
    System.out.println("Java: " + msg);
}
5.5 springboot整合MQ的消息确认方式

springboot在整合MQ的时候,使用的是手动确认的方式。当消息的消费方执行完方法之后,如果没有抛出异常,发送一个 ack 的确认,MQ收到确认之后,会删除对应的消息;如果方法执行抛出了异常,那么就不会发送ack确认,会重新拉去MQ中消息。消息的确认是一条条确认的。在消息消费的方法中,不能捕获异常。

5.6 消息的抓取以及重试机制

在消费消息的时候,默认每次抓取 250 条数据,这个要依据实际情况来配置每次抓取的数据量;

因为消息在消费失败时候,会反复的去抓取数据,这会极大的影响MQ的性能,所以需要配置重试的机制;

spring: 
  rabbitmq:
    host: 192.168.50.53
    port: 5672
    username: admin
    password: admin
    listener:
      simple:
        # 表示每次从 Queue中抓取几条数据
        prefetch: 5
        # retry是重试的意思
        retry:
          # 开启重试机制,如果一旦开启,在达到重试的次数之后,如果消息还是没有被消费成功,那么消息就会丢失;但是可以
          # 将其放入到死信队列。
          enabled: true
          # 初始化时间间隔
          initial-interval: 3000
          # 最大时间间隔
          max-interval: 60000
          # 最大尝试的次数
          max-attempts: 4
          # 重试时间的乘法因此, 第一次间隔3s, 第二次间隔9秒,第三次间隔27s
          multiplier: 3

六. 消息的重复消费

​ 消息的消费方和MQ服务宕机的情况下,某个已经消费过的消息,没有及时的发送 ack确认,这个消息还存在于队列中,当服务恢复之后再次消费该消息,导致消息的重复消费。解决的方式是两点:

  1. 每个消息必须要有一个唯一值;
  2. 消息的消费方做消息的幂等设计;
@Component
@Transactional
public class Reconsume {
    @Resource
    private MqTestMapper mapper;
    @Resource
    private MsgInfoMapper msgInfoMapper;

    @RabbitListener(bindings = @QueueBinding(
            exchange = @Exchange(name = "other-exchange"),  // 交换的类型默认是 direct模式,可以不用写
            value = @Queue("user-queue"),
            key = "user"
    ))
    public void consume(Message message) throws InterruptedException {
        String correlationId = message.getMessageProperties().getCorrelationId();
//        String messageKey = RABBITMQ_MESSAGE_KEY_PREFIX + correlationId;

        QueryWrapper qw = new QueryWrapper();
        qw.eq("msg_id", correlationId);

        Object msgInfo = msgInfoMapper.selectOne(qw);

        if(null == msgInfo) {
            String msg = new String(message.getBody(), Charset.defaultCharset());
            MqTest mqTest = JSONObject.parseObject(msg, MqTest.class);

            mapper.insert(mqTest);  // 业务操作,可能有很多代码
            //为了解决消息的重复消费问题的
            msgInfoMapper.insert(MsgInfo.builder().msgId(correlationId).build());
        }
    }
}

七. 死信队列

私信队列它依然是一个队列,只是它的作用决定我们将其称作一个死信队列;死信队列我们不会直接往这个队列中投递消息。

public class DeadLetterProducer {
    private static final String DEAD_LETTER_QUEUE = "dead_letter_queue";
    private static final String DEAD_LETTER_ROUTING_KEY = "dead_letter_routing_key";
    private static final String DEAD_LETTER_EXCHANGE = "dead_letter_exchange";


    private static final String COMMON_EXCHANGE = "common_exchange";
    private static final String COMMON_QUEUE = "common_queue";
    private static final String COMMON_ROUTING_KEY = "common_routing_key";

    public static void main(String[] args) throws Exception {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();

        // 创建一个死信队列
        channel.queueDeclare(DEAD_LETTER_QUEUE, true, false, false, null);
        // 创建一个死信交换机
        channel.exchangeDeclare(DEAD_LETTER_EXCHANGE, BuiltinExchangeType.DIRECT);
        // 将死信队列绑定到死信交换机上
        channel.queueBind(DEAD_LETTER_QUEUE, DEAD_LETTER_EXCHANGE, DEAD_LETTER_ROUTING_KEY);


        // 创建一个常规的交换机
        channel.exchangeDeclare(COMMON_EXCHANGE, BuiltinExchangeType.DIRECT);
        Map<String, Object> params = ImmutableMap.of(
                "x-dead-letter-exchange", DEAD_LETTER_EXCHANGE,
                "x-dead-letter-routing-key", DEAD_LETTER_ROUTING_KEY
        );
        // 创建一个常规队列
        channel.queueDeclare(COMMON_QUEUE, true, false, false, params);
        // 将常规队列绑定到常规交换机上
        channel.queueBind(COMMON_QUEUE, COMMON_EXCHANGE, COMMON_ROUTING_KEY);

        AMQP.BasicProperties bp = new AMQP.BasicProperties.Builder()
                .deliveryMode(2)  // 2表示持久化
                .expiration("15000")  // 表示消息在15s之内没有被消费,就自动进入到死信队列
                .build();


        channel.basicPublish(COMMON_EXCHANGE, COMMON_ROUTING_KEY, bp,"消息".getBytes(StandardCharsets.UTF_8));
    }
}
7.1 死信队列的应用场景
  1. 创建订单的时候,将订单投递到队列中中,设置一个超时时间,但是不要消费该队列中的消息;
  2. 给队列设置死信队列;
  3. 当订单超时后,消息会自动达到死信队列,在死信队列中用一个消费者;
  4. 死信队列的消费者,在获取到订单之后,判断订单是否支付,如果支付了啥都不做;如果没之后,取消订单;

八. RabbitMQ的消息的可靠性投递

  1. 在投递消息之前,将消息在本地存储一份;
  2. 开头投递消息;
  3. 如果MQ通过消息确认机制告知我们,如果成功到达队列就将消息删除;如果没有成功到达队列,就啥也不干;
  4. 引入消息补偿系统,其实就是通过定时任务,扫描消息表,进行消息的重投;

九. 接口的幂等设计

其他

  1. 系统永远以管理身份运行:https://baijiahao.baidu.com/s?id=1725635393301181756&wfr=spider&for=pc

bitMQ的消息的可靠性投递

### RabbitMQ 使用指南和教程 #### 安装 RabbitMQ RabbitMQ 是一种基于 AMQP 协议的消息中间件,用于实现分布式系统的可靠消息传递。以下是安装 RabbitMQ 的基本流程: 1. **安装 Erlang** RabbitMQ 基于 Erlang 编程语言开发,因此需要先安装 Erlang 运行环境。可以通过包管理器或者下载官方二进制文件完成安装。 2. **安装 RabbitMQ Server** 下载并安装 RabbitMQ Server 软件包。对于 Linux 用户,可以使用以下命令: ```bash sudo apt-get install rabbitmq-server ``` 3. **启动服务** 启动 RabbitMQ 服务后,默认监听端口为 `5672`(AMQP 协议),Web 管理界面默认运行在 `15672` 端口上。 ```bash sudo systemctl start rabbitmq-server ``` 4. **启用 Web 管理插件** 可通过以下命令启用 RabbitMQ 提供的 Web 管理工具: ```bash sudo rabbitmq-plugins enable rabbitmq_management ``` --- #### 配置用户与权限 为了安全访问 RabbitMQ 实例,通常需要创建自定义用户并分配相应权限。 - 创建新用户: ```bash rabbitmqctl add_user rabbitmq 211314 ``` 此操作会新增名为 `rabbitmq` 的用户,并将其密码设为 `211314`[^1]。 - 设置用户角色: ```bash rabbitmqctl set_user_tags rabbitmq administrator ``` 将该用户的标签设定为管理员角色,使其拥有完全控制权[^1]。 - 授予用户权限: ```bash rabbitmqctl set_permissions -p "/" rabbitmq ".*" ".*" ".*" ``` 上述命令授予用户对根虚拟主机 `/` 中所有资源的操作权限[^1]。 - 查看现有用户及其角色: ```bash rabbitmqctl list_users ``` --- #### 集群配置 RabbitMQ 支持多种集群模式来提升可用性和性能。主要分为两类:普通模式和镜像模式。 - **普通模式** 在这种模式下,各节点独立存储队列中的数据和其他元信息(如交换机)。当客户端尝试消费某个不在当前连接节点上的消息时,目标节点会被请求转发所需的数据[^2]。 - **镜像模式** 对比之下,在镜像模式中,指定队列的内容将在多个节点间保持一致副本。即使部分成员失效,剩余存活节点仍能继续提供完整的服务功能[^2]。 > 注意事项:尽管镜像模式提高了可靠性,但也带来了额外开销——网络流量增加以及写入延迟上升等问题需被充分考虑进去。 --- #### 应用集成示例 假设要在一个 Java 或 Python 应用程序里利用 RabbitMQ 来发送/接收消息,则可能涉及以下几个步骤: 1. **声明交换器 (Exchange)** 和绑定关系: ```java channel.exchangeDeclare(exchangeName, "direct", true); channel.queueDeclare(queueName, true, false, false, null); channel.queueBind(queueName, exchangeName, routingKey); ``` 如此一来便完成了持久化队列及路由键关联工作[^3]。 2. 发布一条测试消息至上述已建立好的通道路径之中; 3. 订阅对应主题下的事件流以便实时捕获最新动态更新情况; --- #### 总结 以上涵盖了从基础安装到高级特性使用的整个过程概述。希望这些指导能够帮助您快速掌握如何部署与维护属于自己的 RabbitMQ 平台实例! 问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值