当前位置:

首页 > 编程开发 > Java RabbitMQ如何实现持久化和发布确认

Java RabbitMQ如何实现持久化和发布确认

1.持久化当RabbitMQ服务停掉以后消息生产者发送过的消息不丢失。默认情况下RabbitMQ退出或者崩溃时,会忽视掉队列和消息。为了保证消息不丢失需要将队列和消息都标记为持久化。1.1实现持久化1.队列持久化:在创建队列时将channel.queueDeclare();第二个参数改为true。2.消息持久化:在使用信道发送消息时channel.basicPublish();将第三个参数改为:MessageProperties.PERSISTENT_TEXT_PLAIN表示持久化消息。/***@Desc

    1. 持久化

    当RabbitMQ服务停掉以后消息生产者发送过的消息不丢失。默认情况下RabbitMQ退出或者崩溃时,会忽视掉队列和消息。为了保证消息不丢失需要将队列和消息都标记为持久化。

    1.1 实现持久化

    1.队列持久化:在创建队列时将channel.queueDeclare();第二个参数改为true。

    2.消息持久化:在使用信道发送消息时channel.basicPublish();将第三个参数改为:MessageProperties.PERSISTENT_TEXT_PLAIN表示持久化消息。

    /**
     * @Description 持久化MQ
     * @date 2022/3/7 9:14
     */
    public class Producer3 {
        private static final String LONG_QUEUE = "long_queue";
        public static void main(String[] args) throws Exception {
            Channel channel = RabbitMQUtils.getChannel();
            // 持久化队列
            channel.queueDeclare(LONG_QUEUE,true,false,false,null);
            Scanner scanner = new Scanner(System.in);
            int i = 0;
            while (scanner.hasNext()){
                i++;
                String msg = scanner.next() + i;
                // 持久化消息
                channel.basicPublish("",LONG_QUEUE, MessageProperties.PERSISTENT_TEXT_PLAIN,msg.getBytes(StandardCharsets.UTF_8));
                System.out.println("发送消息:'" + msg + "'成功");
            }
        }
    }

    但是存储消息还有存在一个缓存的间隔点,没有真正的写入磁盘,持久性保证不够强,但是对于简单队列而言也绰绰有余。

    1.2 不公平分发

    轮询分发的方式在消费者处理效率不同的情况下并不适用。所以真正的公平应该是遵循能者多劳的前提。

    在消费者处修改channel.basicQos(1);表示开启不公平分发

    /**
     * @Description 不公平分发消费者
     * @date 2022/3/7 9:27
     */
    public class Consumer2 {
        private static final String LONG_QUEUE = "long_queue";
        public static void main(String[] args) throws Exception {
            Channel channel = RabbitMQUtils.getChannel();
            DeliverCallback deliverCallback = (consumerTag, message) -> {
                // 模拟并发沉睡三十秒
                try {
                    Thread.sleep(30000);
                    System.out.println("线程B接收消息:"+ new String(message.getBody(), StandardCharsets.UTF_8));
                    channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            };
            // 设置不公平分发
            channel.basicQos(1);
            channel.basicConsume(LONG_QUEUE,false,deliverCallback,
                    consumerTag -> {
                        System.out.println(consumerTag + "消费者取消消费");
                    });
        }
    }

    1.3 测试不公平分发

    测试目的:是否能实现能者多劳。

    测试方法:两个消费者睡眠不同的事件来模拟处理事件不同,如果处理时间(睡眠时间)短的能够处理多个消息就代表目的达成。

    先启动生产者创建队列,再分别启动两个消费者。

    生产者按照顺序发四条消息:

    Java RabbitMQ如何实现持久化和发布确认

    睡眠时间短的线程A接收到了三条消息

    Java RabbitMQ如何实现持久化和发布确认

    而睡眠时间长的线程B只接收到的第二条消息:

    Java RabbitMQ如何实现持久化和发布确认

    因为线程B在处理消息时消耗的时间较长,所以就将其他消息分配给了线程A。

    实验成功!

    1.4 预取值

    消息的发送和手动确认都是异步完成的,因此就存在一个未确认消息的缓冲区,开发人员希望能够限制缓冲区的大小,用来避免缓冲区里面无限制的未确认消息问题。

    这里的预期值就值得是上述方法channel.basicQos();里面的参数,如果在当前信道上存在等于参数的消息就不会在安排当前信道进行消费消息。

    1.4.1 代码测试

    测试方法:

    1.新建两个不同的消费者分别给定预期值5个2。

    2.给睡眠时间长的指定为5,时间短的指定为2。

    3.假如按照指定的预期值获取消息则表示测试成功,但并不是代表一定会按照5和2分配,这个类似于权重的判别。

    代码根据上述代码修改预期值即可。

    2. 发布确认

    发布确认就是生产者发布消息到队列之后,队列确认进行持久化完毕再通知给生产者的过程。这样才能保证消息不会丢失。

    需要注意的是需要开启队列持久化才能使用确认发布。
    开启方法:channel.confirmSelect();

    2.1 单个确认发布

    是一种同步发布的方式,即发送完一个消息之后只有确认它确认发布后,后续的消息才会继续发布,在指定的时间内没有确认就会抛出异常。缺点就是特别慢。

    /**
     * @Description 确认发布——单个确认
     * @date 2022/3/7 14:49
     */
    public class SoloProducer {
        private static final int MESSAGE_COUNT = 100;
        private static final String QUEUE_NAME = "confirm_solo";
        public static void main(String[] args) throws Exception {
            Channel channel = RabbitMQUtils.getChannel();
            // 产生队列
            channel.queueDeclare(QUEUE_NAME,true,false,false,null);
            // 开启确认发布
            channel.confirmSelect();
            // 记录开始时间
            long beginTime = System.currentTimeMillis();
            for (int i = 0; i < MESSAGE_COUNT; i++) {
                String msg = ""+i;
                channel.basicPublish("",QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN,msg.getBytes(StandardCharsets.UTF_8));
                // 单个发布确认
                boolean flag = channel.waitForConfirms();
                if (flag){
                    System.out.println("发送消息:" + i);
                }
            }
            // 记录结束时间
            long endTime = System.currentTimeMillis();
            System.out.println("发送" + MESSAGE_COUNT + "条消息消耗:"+(endTime - beginTime) + "毫秒");   }
    }

    2.2 批量确认发布

    一批一批的确认发布可以提高系统的吞吐量。但是缺点是发生故障导致发布出现问题时,需要将整个批处理保存在内存中,后面再重新发布。

    /**
     * @Description 确认发布——批量确认
     * @date 2022/3/7 14:49
     */
    public class BatchProducer {
        private static final int MESSAGE_COUNT = 100;
        private static final String QUEUE_NAME = "confirm_batch";
        public static void main(String[] args) throws Exception {
            Channel channel = RabbitMQUtils.getChannel();
            // 产生队列
            channel.queueDeclare(QUEUE_NAME,true,false,false,null);
            // 开启确认发布
            channel.confirmSelect();
            // 设置一个多少一批确认一次。
            int batchSize = MESSAGE_COUNT / 10;
            // 记录开始时间
            long beginTime = System.currentTimeMillis();
            for (int i = 0; i < MESSAGE_COUNT; i++) {
                String msg = ""+i;
                channel.basicPublish("",QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN,msg.getBytes(StandardCharsets.UTF_8));
                // 批量发布确认
                if (i % batchSize == 0){
                    if (channel.waitForConfirms()){
                        System.out.println("发送消息:" + i);
                    }
                }
            }
            // 记录结束时间
            long endTime = System.currentTimeMillis();
            System.out.println("发送" + MESSAGE_COUNT + "条消息消耗:"+(endTime - beginTime) + "毫秒");
        }
    }

    显然效率要比单个确认发布的高很多。

    2.3 异步确认发布

    在编程上比上述两个要复杂,但是性价比很高,无论是可靠性还行效率的都好很多,利用回调函数来达到消息可靠性传递的。

    /**
     * @Description 确认发布——异步确认
     * @date 2022/3/7 14:49
     */
    public class AsyncProducer {
        private static final int MESSAGE_COUNT = 100;
        private static final String QUEUE_NAME = "confirm_async";
        public static void main(String[] args) throws Exception {
            Channel channel = RabbitMQUtils.getChannel();
            // 产生队列
            channel.queueDeclare(QUEUE_NAME,true,false,false,null);
            // 开启确认发布
            channel.confirmSelect();
            // 记录开始时间
            long beginTime = System.currentTimeMillis();
            // 确认成功回调
            ConfirmCallback ackCallback = (deliveryTab,multiple) ->{
                System.out.println("确认成功消息:" + deliveryTab);
            };
            // 确认失败回调
            ConfirmCallback nackCallback = (deliveryTab,multiple) ->{
                System.out.println("未确认的消息:" + deliveryTab);
            };
            // 消息监听器
            /**
             * addConfirmListener:
             *                  1. 确认成功的消息;
             *                  2. 确认失败的消息。
             */
            channel.addConfirmListener(ackCallback,nackCallback);
            for (int i = 0; i < MESSAGE_COUNT; i++) {
                String msg = "" + i;
                channel.basicPublish("",QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN,msg.getBytes(StandardCharsets.UTF_8));
            }
    
            // 记录结束时间
            long endTime = System.currentTimeMillis();
            System.out.println("发送" + MESSAGE_COUNT + "条消息消耗:"+(endTime - beginTime) + "毫秒");
        }
    }

    2.4 处理未确认的消息

    最好的处理方式把未确认的消息放到一个基于内存的能被发布线程访问的队列。

    例如:ConcurrentLinkedQueue可以在确认队列confirm callbacks与发布线程之间进行消息的传递。

    处理方式:

    1.记录要发送的全部消息;

    2.在发布成功确认处删除;

    3.打印未确认的消息。

    使用一个哈希表存储消息,它的优点:

    可以将需要和消息进行关联;轻松批量删除条目;支持高并发。

    ConcurrentSkipListMap map = new ConcurrentSkipListMap<>();
    /**
     * @Description 异步发布确认,处理未发布成功的消息
     * @date 2022/3/7 18:09
     */
    public class AsyncProducerRemember {
        private static final int MESSAGE_COUNT = 100;
        private static final String QUEUE_NAME = "confirm_async_remember";
        public static void main(String[] args) throws Exception {
            Channel channel = RabbitMQUtils.getChannel();
            // 产生队列
            channel.queueDeclare(QUEUE_NAME,true,false,false,null);
            // 开启确认发布
            channel.confirmSelect();
            // 线程安全有序的一个hash表,适用与高并发
            ConcurrentSkipListMap< Long, String > map = new ConcurrentSkipListMap<>();
            // 记录开始时间
            long beginTime = System.currentTimeMillis();
            // 确认成功回调
            ConfirmCallback ackCallback = (deliveryTab, multiple) ->{
                //2. 在发布成功确认处删除;
                // 批量删除
                if (multiple){
                    ConcurrentNavigableMap confirmMap = map.headMap(deliveryTab);
                    confirmMap.clear();
                }else {
                    // 单独删除
                    map.remove(deliveryTab);
                }
                System.out.println("确认成功消息:" + deliveryTab);
            };
            // 确认失败回调
            ConfirmCallback nackCallback = (deliveryTab,multiple) ->{
                // 3. 打印未确认的消息。
                System.out.println("未确认的消息:" + map.get(deliveryTab) + ",标记:" + deliveryTab);
            };
            // 消息监听器
            /**
             * addConfirmListener:
             *                  1. 确认成功的消息;
             *                  2. 确认失败的消息。
             */
            channel.addConfirmListener(ackCallback,nackCallback);
            for (int i = 0; i < MESSAGE_COUNT; i++) {
                String msg = "" + i;
                channel.basicPublish("",QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN,msg.getBytes(StandardCharsets.UTF_8));
                // 1. 记录要发送的全部消息;
                map.put(channel.getNextPublishSeqNo(),msg);
            }
    
            // 记录结束时间
            long endTime = System.currentTimeMillis();
            System.out.println("发送" + MESSAGE_COUNT + "条消息消耗:"+(endTime - beginTime) + "毫秒");
        }
    }
    本文内容来源于互联网,如有侵权请联系删除。
    作者最新文章
    编程开发
    相关文章 更多
    C++动态数组初始化怎么写?常用语句与代码示例
    C++动态数组初始化怎么写?常用语句与代码示例

    深入解析C++中动态数组的初始化机制,涵盖new操作符的不同用法、基本类型与类对象的初始化差异,以及为何在现代C++开发中应优先使用std::vector。

    谷歌浏览器Mac版入口
    谷歌浏览器Mac版入口

    谷歌浏览器Mac版官方安装指南 谷歌浏览器Mac版官方安装入口是https://www.google.com/chrome/,需macOS 12+系统、500MB空间,下载.dmg后拖入应用程序安装,支持多设备同步、性能优化与隐私保护功能。 苹果电脑Chrome的安装入口究竟在哪里?这个问题最近可是

    Chrome浏览器JS脚本不运行怎么办
    Chrome浏览器JS脚本不运行怎么办

    Chrome中JavaScript未执行需依次检查:一、移除站点级禁用并添加允许域名;二、开启全局JavaScript开关;三、禁用干扰扩展;四、在开发者工具中启用JavaScript;五、重置内容设置为默认。 有时在Chrome里打开网页,会发现交互按钮点了没反应,数据加载不出来,页面仿佛“静止”

    IE浏览器怀旧版在线网址
    IE浏览器怀旧版在线网址

    IE浏览器怀旧版在线网址:一次精准的技术时光回溯 最近,不少老用户和怀旧爱好者在反复搜索一个问题:那个经典的Internet Explorer,如今还能在哪里原汁原味地体验到?答案指向一个特定的地址:https://ie.microsoft.com/legacy/。 这个网站远不止是一个简单的“皮肤

    火狐浏览器有哪些设置功能
    火狐浏览器有哪些设置功能

    火狐浏览器五大核心设置功能:解锁高效、安全与个性化体验 火狐浏览器功能强大,但如果不仔细挖掘,很多能大幅提升效率和安全性的设置可能就“藏着掖着”了。这就好比拥有一台高性能设备,却只用了基础模式。那么,如何把它调整到最顺手、最安全的状态?接下来,我们就聚焦于当前版本(截至2025年末)最关键的五大设置

    chrome搜索免验证入口
    chrome搜索免验证入口

    Chrome官方免验证入口为https://www.google.cn/chrome/,提供全平台安装包、免登录即用、本地化安全机制及引擎级性能优化。 到底该去哪里找正版、免费且无需繁琐验证的Chrome浏览器入口?这个问题困扰了不少网友。今天,我们就来直通核心,为大家详细拆解Chrome引擎的官方

    java heap space 选型思路:使用场景与区别整理
    java heap space 选型思路:使用场景与区别整理

    Java堆是JVM存储对象的核心内存区域,配置需结合场景:单体应用适中设置;大数据处理需大堆并关注GC停顿;微服务强调快速启动;高并发需精细划分堆区域。关键参数-Xms和-Xmx建议等值以稳定性能。垃圾回收器选择影响效率,如G1适用于大堆,ZGC可实现低停顿。内存错误时需监控堆状态。

    java heap space 使用中遇到的问题怎么解决
    java heap space 使用中遇到的问题怎么解决

    Java堆内存溢出错误通常因内存泄漏、数据处理需求过大或JVM参数配置不当引起。排查时可借助jmap、堆转储及MAT等工具定位问题。解决方案包括调整JVM内存参数(如-Xmx)、修复代码中的内存泄漏、优化大数据处理逻辑,并建立持续监控与预防机制,以保障应用稳定运行。

    java xml 选型思路:使用场景与区别整理
    java xml 选型思路:使用场景与区别整理

    XML在Java开发中用于配置、数据交换等场景。解析方式主要有DOM、SAX、StAX及第三方库。DOM适合操作小文件,SAX/StAX适合处理大文件流,JAXB用于对象与XML映射。选型需结合数据大小、内存、性能及团队熟悉度,现代框架常封装底层解析。

    using namespace 使用中遇到的问题怎么解决
    using namespace 使用中遇到的问题怎么解决

    命名空间的基本概念与常见引入问题在C++等编程语言中,命名空间(namespace)是一种将代码标识符(如变量、函数、类名)封装在特定名称下的机制,其主要目的是避免命名冲突,尤其是在大型项目或使用多个第三方库时。使用“using namespace”指令可以将指定命名空间中的所有名称引入当前作用域,

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    macOS软件
    macOS软件

    正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

    Mac软件 更多
    灵活计算器
    灵活计算器
    macOS/iOS/Android

    灵活计算器是一款笔记式算数应用,支持实时计算、动态关联和云端同步功能。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

    赤友清理大师
    赤友清理大师
    macOS

    赤友清理大师是一款为 Mac 设计的智能清理优化工具,可精准扫描垃圾、大文件、重复文件等,释放磁盘空间。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

    极度公式
    极度公式
    Windows/macOS/Linux

    极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

    WINDOWS 更多
    Windows 10
    Windows 10
    Windows

    Windows 10 是一款微软推出的经典操作系统,拥有硬件兼容性与多任务处理能力。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。

    极度公式
    极度公式
    Windows/macOS/Linux

    极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

    密码键盘
    密码键盘
    Windows/macOS/iOS/Android

    密码键盘是一款兼具安全性与便捷性的高效密码管理器。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。