【RabbitMQ】- 其他知识点

第九章 RabbitMQ 其他知识点

9.1. 幂等性

9.1.1. 概念

​ 用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。 举个最简单的例子,那就是支付,用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常, 此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额发现多扣钱了,流水记录也变成了两条。在以前的单应用系统中,我们只需要把数据操作放入事务中即可,发生错误立即回滚,但是在响应客户端的时候也有可能出现网络中断或者异常等等。

9.1.2. 消息重复消费

​ 消费者在消费 MQ 中的消息时,MQ 已把消息发送给消费者,消费者在给 MQ 返回 ack 时网络中断, 故 MQ 未收到确认信息,该条消息会重新发给其他的消费者,或者在网络重连后再次发送给该消费者,但实际上该消费者已成功消费了该条消息,造成消费者消费了重复的消息。

9.1.3. 解决思路

​ MQ 消费者的幂等性的解决一般使用全局 ID 或者写个唯一标识比如时间戳 或者 UUID 或者订单消费者消费 MQ 中的消息也可利用 MQ 的该 id 来判断,或者可按自己的规则生成一个全局唯一 id,每次消费消息时用该 id 先判断该消息是否已消费过。

9.1.4. 消费端的幂等性保障

​ 在海量订单生成的业务高峰期,生产端有可能就会重复发生了消息,这时候消费端就要实现幂等性, 这就意味着我们的消息永远不会被消费多次,即使我们收到了一样的消息。业界主流的幂等性有两种操作:a. 唯一 ID+指纹码机制,利用数据库主键去重, b.利用 redis 的原子性去实现。

9.1.5. 唯一 ID+指纹码机制

​ 指纹码:我们的一些规则或者时间戳加别的服务给到的唯一信息码,它并不一定是我们系统生成的,基本都是由我们的业务规则拼接而来,但是一定要保证唯一性,然后就利用查询语句进行判断这个 id 是否存在数据库中,优势就是实现简单就一个拼接,然后查询判断是否重复;劣势就是在高并发时,如果是单个数据库就会有写入性能瓶颈当然也可以采用分库分表提升性能,但也不是我们最推荐的方式。

9.1.6. Redis 原子性

​ 利用 redis 执行 setnx 命令,天然具有幂等性。从而实现不重复消费。

9.2. 优先级队列

9.2.1. 使用场景

​ 在我们系统中有一个订单催付的场景,我们的客户在天猫下的订单,淘宝会及时将订单推送给我们,如果在用户设定的时间内未付款那么就会给用户推送一条短信提醒,很简单的一个功能对吧,但是,tmall 商家对我们来说,肯定是要分大客户和小客户的对吧,比如像苹果,小米这样大商家一年起码能给我们创造很大的利润,所以理应当然,他们的订单必须得到优先处理,而曾经我们的后端系统是使用 redis 来存放的定时轮询,大家都知道 redis 只能用 List 做一个简简单单的消息队列,并不能实现一个优先级的场景,所以订单量大了后采用 RabbitMQ 进行改造和优化,如果发现是大客户的订单给一个相对比较高的优先级, 否则就是默认优先级。

9.2.2. 如何添加

a.控制台页面添加:

点击添加队列,点击参数中的Maximum proority选项,在Number栏输入10(10就已经够用了)
在这里插入图片描述
b.队列中代码添加优先级
在这里插入图片描述
c.消息中代码添加优先级
在这里插入图片描述
d.注意事项

要让队列实现优先级需要做的有如下事情:队列需要设置为优先级队列,消息需要设置消息的优先级,消费者需要等待消息已经发送到队列中才去消费因为,这样才有机会对消息进行排序。

9.2.3. 实战

演示

修改代码(消费者):
在这里插入图片描述

package com.atguigu.rabbitmq.one;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;

/**
 * @author lishuai
 * @date 2023/6/7-16:39
 * 生产者:发消息
 */
public class Producer {
    // 队列名称
    public static  final String Queue_NAME = "hello";

    //发消息
    public static void main(String[] args) throws IOException, TimeoutException {
        // 创建一个连接工厂
        ConnectionFactory factory = new ConnectionFactory();

        // 设置工厂IP:连接RabbitMQ的队列
        factory.setHost("192.168.174.101");

        // 用户名
        factory.setUsername("admin");

        // 密码
        factory.setPassword("123");

        // 创建连接
        Connection connection = factory.newConnection();

        // 获取信道
        Channel channel = connection.createChannel();


        /**
         * 第九章内容:演示优先队列
         */
        Map<String, Object> arguments  = new HashMap<>();

        arguments.put("x-max-priority",10); // 官方允许是0-255之间,此处设置为10,允许优先级范围为0-10,不要设置过大,浪费CPU与内存
        channel.queueDeclare(Queue_NAME,true,false,false,arguments);

        for (int i = 1; i < 11; i++) {
            String message = "info" + i;
            if(i == 5){
                AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().priority(5).build();
                channel.basicPublish("",Queue_NAME,properties,message.getBytes());
            }else {
                channel.basicPublish("",Queue_NAME,null,message.getBytes());
            }
        }


        System.out.println("消息发送完毕");

    }
}

PS:如果先前就有hello队列,记得删除,再启动项目

可以看到队列带有Pri标志即为优先队列
在这里插入图片描述
运行Consumer,可以看到info5排在第一

package com.atguigu.rabbitmq.one;

import com.rabbitmq.client.*;

public class Consumer {

    // 队列名称
    public static final String QUEUE_NAME = "hello"; // 要与生产者发送消息的队列名称保持一致
    
    // 接受消息
    public static void main(String[] args) throws Exception{
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("192.168.174.101");
        factory.setUsername("admin");
        factory.setPassword("123");
        Connection connection = factory.newConnection();

        // 由连接创建一个信道
        Channel channel = connection.createChannel();

        /**
         * 消费者接受消息
         * basicConsume方法参数:
         * 1.消费哪个队列
         * 2.消费成功之后是否要自动应答,true:表示自动应答,false:手动应答
         * 3.消费者未成功消费的回调(因为不是所有的情况下都能消费消息或者说接受消息)
         * 4.消费者取消消费的回调
         */
        // 回调是一个接口,需要实现接口(或者匿名内部类、lambda表达式的写法都可)
        // 声明 接收消息
        DeliverCallback deliverCallback = (consumerTag, message) -> {
            System.out.println(new String(message.getBody()));
        };

        // 取消消息时的回调
        CancelCallback cancelCallback = consumerTag -> {
            System.out.println("消息消费被中断");
        };

        channel.basicConsume(QUEUE_NAME, true, deliverCallback, cancelCallback);

    }
}

在这里插入图片描述

9.3. 惰性队列

​ 指的就是消息是保存在内存中,还是在磁盘上,正常情况下,消息是保存在内存中,而在惰性队列中,消息是保存在磁盘中。

​ 但这样的处理速度就很慢,一般不采用惰性队列,只有在消费者宕机的情况下才会使用。

9.3.1. 使用场景

​ RabbitMQ 从 3.6.0 版本开始引入了惰性队列的概念。惰性队列会尽可能的将消息存入磁盘中,而在消费者消费到相应的消息时才会被加载到内存中,它的一个重要的设计目标是能够支持更长的队列,即支持更多的消息存储。当消费者由于各种各样的原因(比如消费者下线、宕机亦或者是由于维护而关闭等)而致使长时间内不能消费消息造成堆积时,惰性队列就很有必要了。

​ 默认情况下,当生产者将消息发送到 RabbitMQ 的时候,队列中的消息会尽可能的存储在内存之中, 这样可以更加快速的将消息发送给消费者。即使是持久化的消息,在被写入磁盘的同时也会在内存中驻留一份备份。当 RabbitMQ 需要释放内存的时候,会将内存中的消息换页至磁盘中,这个操作会耗费较长的时间,也会阻塞队列的操作,进而无法接收新的消息。虽然 RabbitMQ 的开发者们一直在升级相关的算法, 但是效果始终不太理想,尤其是在消息量特别大的时候。

9.3.2. 两种模式

​队列具备两种模式:default 和 lazy。默认的为 default 模式,在 3.6.0 之前的版本无需做任何变更。lazy 模式即为惰性队列的模式,可以通过调用 channel.queueDeclare 方法的时候在参数中设置,也可以通过 Policy 的方式设置,如果一个队列同时使用这两种方式设置的话,那么 Policy 的方式具备更高的优先级。如果要通过声明的方式改变已有队列的模式的话,那么只能先删除队列,然后再重新声明一个新的。

​在队列声明的时候可以通过“x-queue-mode”参数来设置队列的模式,取值为“default”和“lazy”。下面示例中演示了一个惰性队列的声明细节:

​ Map args = new HashMap();

​ args.put(“x-queue-mode”, “lazy”);

​ channel.queueDeclare(“myqueue”, false, false, false, args);

控制台页面添加方式:
在这里插入图片描述

9.3.3. 内存开销对比

在这里插入图片描述
在发送 1 百万条消息,每条消息大概占 1KB 的情况下,普通队列占用内存是 1.2GB,而惰性队列仅仅 占用 1.5MB。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. RabbitMQ是什么? RabbitMQ是一个开源的消息中间件,它实现了AMQP(Advanced Message Queuing Protocol)协议,用于在分布式系统中传递消息。 2. 消息队列的概念 消息队列是一种解耦应用程序之间的通信方式。应用程序可以将消息发送到队列中,然后由消费者从队列中接收和处理消息。 3. RabbitMQ的基本组件 RabbitMQ包括以下基本组件: - Producer(生产者): 发送消息的应用程序。 - Exchange(交换机): 接收生产者发送的消息,并将其路由到一个或多个队列。 - Queue(队列): 存储消息的地方,消费者从队列中接收消息并进行处理。 - Consumer(消费者): 接收并处理队列中的消息。 4. 消息传递模型 RabbitMQ支持多种消息传递模型,包括: - 简单模式(Simple Mode): 生产者将消息直接发送到队列,消费者从队列中接收消息。 - 工作模式(Work Mode): 多个消费者共享同一个队列,每个消息只会被一个消费者接收。 - 发布/订阅模式(Publish/Subscribe Mode): 生产者将消息发送到交换机,交换机将消息路由到绑定的多个队列,每个队列都有一个消费者。 - 路由模式(Routing Mode): 生产者将消息发送到交换机,并指定路由键,交换机根据路由键将消息路由到绑定的队列。 - 主题模式(Topic Mode): 生产者将消息发送到交换机,并指定主题,交换机根据主题将消息路由到匹配的队列。 5. 持久化和可靠性 RabbitMQ提供消息持久化机制,可以确保即使在消息中间件重启后,消息也不会丢失。此外,还可以配置确认机制,确保生产者发送的消息被成功接收和处理。 6. 高可用性和集群 RabbitMQ支持集群配置,可以通过在多个节点上部署RabbitMQ实例来提高可靠性和可用性。集群中的节点之间会自动进行数据同步和故障转移。 7. 消息优先级和延迟队列 RabbitMQ支持为消息设置优先级,并且可以配置延迟队列,将消息推迟到指定的时间后再进行处理。 8. Spring AMQP和RabbitMQ 对于Java开发者,Spring AMQP是一个常用的与RabbitMQ集成的框架,它提供了更方便的API和一些高级特性,使得使用RabbitMQ变得更加简单和高效。 这些是RabbitMQ的一些重要知识点,希望能对你有所帮助。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值