消息队列RabbitMQ核心:简单(Hello World)模式、队列(Work Queues)模式、发布订阅模式

news2024/11/23 7:40:57

在这里插入图片描述

文章目录

  • 一、简单模式(Hello World)
    • 代码实现
  • 二、队列模式(Work Queues)
    • 轮训分发消息
      • 代码实现
    • 消息应答
      • 概述
    • RabbitMQ持久化
    • 不公平分发
  • 三、发布订阅模式
    • 原理概述
    • 发布确认策略
      • 单个确认发布
      • 批量确认发布
      • 异步确认发布
      • 三种发布确认速度对比


上篇文章:消息队列 RabbitMQ入门:Linux(Docker)中安装和卸载RabbitMQ服务


消息队列RabbitMQ提供了六种工作模式:简单模式、work queues、发布订阅模式、路由模式、主题模式、发布确认模式。本文将介绍前三种工作模式。所有的案例代码都是使用Java语言实现。

一、简单模式(Hello World)

本工作模式主要设计三个角色:生产者、MQ,消费者。由生产者将数据发送到MQ消息队列中,再通过MQ将消息数据转发到消费者,完成一次整体消息数据的通信。

在这里插入图片描述

代码实现

1.创建Java maven工程,添加如下依赖

  <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <!--rabbitmq 依赖客户端-->
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.8.0</version>
        </dependency>
        <!--操作文件流的一个依赖-->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>
    </dependencies>

2.消息数据生产者

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("xxx.xxx.xxx.xxx");
        //  用户名
        factory.setUsername("admin");
        // 密码
        factory.setPassword("123");
        // 创建连接
        Connection connection = factory.newConnection();
        // 获取信道
        Channel channel = connection.createChannel();
        /**
        * 生成一个队列
        * 1.队列名称
        * 2.队列中的消息是否持久化(磁盘),默认存储在内存
        * 3.该队列是否只供一个消费者进行消费,是否进行消息共享 true 多个消费者 false 一个消费者
        * 4.是否自动删除 最后一个消费者断开连接后,该队列是否自动删除 true 自动删除 false 不删除
        * 5.其他参数 死信队列。。。
        */
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        // 发送消息
        String message = "hello_world";
        /**
        * 1.发送到那个交换机
         * 2.路由的key值 队列名称
         * 3.其他参数
         * 4.发送消息的消息体
        */
        channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
        System.out.println("消息发送完毕");

        channel.close();
        connection.close();
    }
}

测试运行,进入后台管理页面,名为hello的队列中总共有一条消息,且已经准备好等待消费!接下来编写消费者代码进行消费消息数据。

在这里插入图片描述

在这里插入图片描述
3.消费者

public class Consumer {

    public static final String QUEUE_NAME = "hello";

    public static void main(String[] args) throws IOException, TimeoutException {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // 工厂IP
        factory.setHost("xxx.xxx.xxx.xxx");
        //  用户名
        factory.setUsername("admin");
        factory.setPassword("123");

        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        // 消费消息
        // 声明 接收消息
        DeliverCallback deliverCallback = (consumerTag, message) ->{
            System.out.println(new String(message.getBody()));
        };
        // 取消消息回调
        CancelCallback cancelCallback = consumerTag ->{
            System.out.println("消息消费中断");
        };
        /**
        * 1.消费那个队列
         * 2.消费成功之后是否要自动应答 true 自动应答 false 手动应答
         * 3.消费者未成功消费的回调
         * 4.消费者取消消费的回调
        **/
        channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
    }
}

测试运行,进入后台管理页面,队列中的消息数据已经被成功消费。

在这里插入图片描述
在这里插入图片描述
至此,简单工作模式一次完整的通信就完成啦。

二、队列模式(Work Queues)

工作队列的主要思想是避免立即执行资源密集型任务,而不得不等待它完成。相反我们安排任务在之后执行。我们把任务封装为消息并将其发送到队列。在后台运行的工作进程将弹出任务并最终执行作业。当有多个工作线程时,这些工作线程将一起处理这些任务。

在这里插入图片描述

换句话说:生产者发送大量的消息数据到MQ,此时造成很多数据堆积在队列中无法及时处理,若消费者仅仅只有一个工作线程时,无法及时的处理接受大量的消息,一个一个处理效率太低,此时需要多个工作线程同时去处理消息数据,提高处理消息的效率。

注意:生产者发出的消息只能被消费者处理一次,不能处理多次。

轮训分发消息

消费者多个工作线程处理消息数据时,当其中一个工作线程在处理其中一条消息数据时,其他工作线程不能在处理这条消息,而是处理其他消息数据,最终实现一条消息数据只能被一个工作线程所消费,避免处理多次造成数据重复消费。多个工作线程之间是彼此竞争的,当其中一个工作线程抢到消息,其他的工作线程就无法抢到该消息数据。

代码实现

在简单模式的代码环境基础上,编写轮训的方式,由于创建工厂建立通道的代码都是一样的,接下来将其抽取为工具类。

1.抽取工具类

public class RabbitMQUtils {
    public static Channel getChannel() throws Exception{
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("xxx.xxx.xxx.xxx");
        factory.setUsername("admin");
        factory.setPassword("123");

        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        return channel;
    }
}

2.生产者

public class Producer{
    public static final String QUEUE_NAME = "hello";

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMQUtils.getChannel();
        // 队列声明
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        Scanner input = new Scanner(System.in);
        while (input.hasNext()){
            String message = input.next();
            channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
            System.out.println("消息发送完成:"+message);
        }
    }
}

3.工作线程(消费者)

public class Work {

    public static final String QUEUE_NAME = "hello";

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMQUtils.getChannel();
        // 声明 接收消息
        DeliverCallback deliverCallback = (consumerTag, message) -> {
            System.out.println("接收到的消息:" + new String(message.getBody()));
        };
        // 取消消息回调
        CancelCallback cancelCallback = consumerTag -> {
            System.out.println(consumerTag + "消息消费取消");
        };
        System.out.println("C1等待接收消息......");
        channel.basicConsume(QUEUE_NAME, true, deliverCallback, cancelCallback);
    }
}

启动两个工作线程接收消息,模拟轮询方式,由于代码是一样的,使用IDEA自带工具,模拟两个工作线程。

在这里插入图片描述
在这里插入图片描述
启动两个工作线程接收消息。

在这里插入图片描述
在这里插入图片描述
4.测试一下
生产者发送消息:

在这里插入图片描述
查看是否接收成功

在这里插入图片描述
在这里插入图片描述
两个工作线程分别接收到了消息数据,而且是分别处理其中一条数据,满足轮训处理数据机制,本次通信模拟成功。

消息应答

概述

消费者完成一个任务可能需要一段时间,如果其中一个消费者处理一个长的任务并仅只完成了部分突然它挂掉了,会发生什么情况。RabbitMQ 一旦向消费者传递了一条消息,便立即将该消息标记为删除。在这种情况下,突然有个消费者挂掉了,我们将丢失正在处理的消息。以及后续发送给该消费这的消息,因为它无法接收到。为了保证消息在发送过程中不丢失,rabbitmq 引入消息应答机制。

消息应答就是:消费者在接收到消息并且处理该消息之后,告诉 rabbitmq 它已经处理了,rabbitmq 可以把该消息删除了。

自动应答

消息发送后立即被认为已经传送成功,这种模式需要在高吞吐量和数据传输安全性方面做权衡,因为这种模式如果消息在接收到之前,消费者那边出现连接或者 channel 关闭,那么消息就丢失了,当然另一方面这种模式消费者那边可以传递过载的消息,没有对传递的消息数量进行限制,当然这样有可能使得消费者这边由于接收太多还来不及处理的消息,导致这些消息的积压,最终使得内存耗尽,最终这些消费者线程被操作系统杀死,所以这种模式仅适用在消费者可以高效并以某种速率能够处理这些消息的情况下使用。

换句话说:自动应答强依赖于一个良好的环境,它只要接到消息,立即就会给队列反馈完成,实际上它并没有处理完成,以接收到消息为准,虽然应答成功但是在后续处理中可能会存在问题,这种方式不可取,后续使用较少。

手动应答

手动应答的好处是可以批量应答并且减少网络拥堵。

手动应答方法:

  • void basicAck(long deliveryTag, boolean multiple) throws IOException;肯定确认,MQ 已知道该消息并且成功的处理消息,可以将其丢弃了
  • void basicNack(long deliveryTag, boolean multiple, boolean requeue) throws IOException;否定确认
  • void basicReject(long deliveryTag, boolean requeue) throws IOException;否定确认,不处理该消息了直接拒绝,可以将其丢弃了

multiple参数的解释:
true 表示批量应答
如说 channel 上有传送 tag 的消息 5,6,7,8 当前 tag 是 8 那么此时5-8 的这些还未应答的消息都会被确认收到消息应答

在这里插入图片描述

false 表示不批量应答
只会应答 tag=8 的消息 5,6,7 这三个消息依然不会被确认收到消息应答

在这里插入图片描述

在实际开发中推荐不批量应答消息,如果批量应答时,在处理消息7或者6时,突然宕机消息处理不完整会导致消息丢失。

消息自动重新入队

如果消费者由于某些原因失去连接(其通道已关闭,连接已关闭或 TCP 连接丢失),导致消息未发送 ACK 确认RabbitMQ 将了解到消息未完全处理,并将对其重新排队。如果此时其他消费者可以处理,它将很快将其重新分发给另一个消费者。这样,即使某个消费者偶尔死亡,也可以确保不会丢失任何消息。

在这里插入图片描述

消息手动应答代码实现:消息在手动应答时是不丢失的,放回队列重新消费。

1.生产者

public class Producer{
    public static final String TASK_QUEUE_NAME = "ack_queue";

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMQUtils.getChannel();
        // 声明队列
        channel.queueDeclare(TASK_QUEUE_NAME,false,false,false,null);
        Scanner input = new Scanner(System.in);
        while (input.hasNext()){
            String message = input.next();
            channel.basicPublish("",TASK_QUEUE_NAME,null,message.getBytes());
            System.out.println("消息发送完成:"+message);
        }
    }
}

2.消费者

public class Work01 {

    public static final String TASK_QUEUE_NAME = "ack_queue";

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMQUtils.getChannel();
        System.out.println("C1等待消息处理,时间较短");

        DeliverCallback deliverCallback =( consumerTag,  message) ->{
            // 沉睡1 秒
            try {
                Thread.sleep(1*1000);
                System.out.println("接收到的消息:"+new String(message.getBody()));
                
                // 手动应答
                /**
                * 1.消息标识 tag
                 * 2.是否批量应答
                **/
                channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        // 手动应答
        boolean autoAck = false;
        channel.basicConsume(TASK_QUEUE_NAME, autoAck,deliverCallback,(consumerTag ->{
            System.out.println(consumerTag+"消费者取消消费");
        }));
    }
}
public class Work02 {

    public static final String TASK_QUEUE_NAME = "ack_queue";

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMQUtils.getChannel();
        System.out.println("C2等待消息处理,时间较长");

        DeliverCallback deliverCallback =( consumerTag,  message) ->{
            // 沉睡30秒
            try {
                Thread.sleep(30*1000);
                System.out.println("接收到的消息:"+new String(message.getBody()));
                // 手动应答
                /**
                * 1.消息标识 tag
                 * 2.是否批量应答
                **/
                channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        // 手动应答
        boolean autoAck = false;
        channel.basicConsume(TASK_QUEUE_NAME, autoAck,deliverCallback,(consumerTag ->{
            System.out.println(consumerTag+"消费者取消消费");
        }));
    }
}

3.测试
① 启动生产者发送消息
在这里插入图片描述
C1消费者等待一秒接收到消息

在这里插入图片描述

C2消费者等待30秒接收到消息
在这里插入图片描述
② 生产者继续发送两条消息,此时让C2处于宕机状态

在这里插入图片描述
C1处理了两条消息,由于C2宕机,消息重新入队,避免消息丢失!

在这里插入图片描述

在这里插入图片描述

RabbitMQ持久化

刚刚我们已经看到了如何处理任务不丢失的情况,但是如何保障当 RabbitMQ 服务停掉以后消息生产者发送过来的消息不丢失。默认情况下 RabbitMQ 退出或由于某种原因崩溃时,它忽视队列和消息,除非告知它不要这样做。确保消息不会丢失需要做两件事:我们需要将队列和消息都标记为持久化。

队列如何实现持久化

durable参数设置为true

        // 声明队列
        channel.queueDeclare(TASK_QUEUE_NAME,false,false,false,null);
      	Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete,
                                 Map<String, Object> arguments) throws IOException;

需要注意的就是如果之前声明的队列不是持久化的,需要把原先队列先删除,或者重新创建一个持久化的队列,不然就会出现错误

在这里插入图片描述

删除此队列重新测试,重启 rabbitmq 队列持久化也依然存在

在这里插入图片描述
消息实现持久化

修改BasicProperties props参数为MessageProperties.PERSISTENT_TEXT_PLAIN

// 没有开启消息持久化
channel.basicPublish("",TASK_QUEUE_NAME,null,message.getBytes());
// 方法源代码
void basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body) throws IOException;
// 开启消息持久化
channel.basicPublish("",TASK_QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes());

将消息标记为持久化并不能完全保证不会丢失消息。尽管它告诉RabbitMQ 将消息保存到磁盘,但是这里依然存在当消息刚准备存储在磁盘的时候 但是还没有存储完,消息还在缓存的一个间隔点。此时并没有真正写入磁盘。持久性保证并不强,但是对于我们的简单任务队列而言已经足够了。

不公平分发

RabbitMQ 分发消息采用的轮训分发,但是在某种场景下这种策略并不是很好,比方说有两个消费者在处理任务,其中有个消费者 1 处理任务的速度非常快,而另外一个消费者 2处理速度却很慢,这个时候我们还是采用轮训分发的化就会到这处理速度快的这个消费者很大一部分时间处于空闲状态,而处理慢的那个消费者一直在干活,这种分配方式在这种情况下其实就不太好,但是RabbitMQ 并不知道这种情况它依然很公平的进行分发。不公平分发体现能者多劳的思想,企业级使用此策略较多。

如何实现

更改消费者代码,设置参数 channel.basicQos(1); 参数为 0 是轮训分发。

		......
        // 设置不公平分发
        int prefetchCount = 1;
        channel.basicQos(prefetchCount);

        // 手动应答
        boolean autoAck = false;
        channel.basicConsume(TASK_QUEUE_NAME, autoAck,deliverCallback,(consumerTag ->{
            System.out.println(consumerTag+"消费者取消消费");
        }));

测试一下,由于C1处理效率很快,处理了三条消息,C2效率慢处理了一条消息,体现了能者多劳的思想。

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

三、发布订阅模式

原理概述

生产者将信道设置成 confirm 模式,一旦信道进入 confirm 模式,所有在该信道上面发布的消息都将会被指派一个唯一的 ID(从 1 开始),一旦消息被投递到所有匹配的队列之后,broker就会发送一个确认给生产者(包含消息的唯一 ID),这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么确认消息会在将消息写入磁盘之后发出,broker 回传给生产者的确认消息中 delivery-tag 域包含了确认消息的序列号,此外 broker 也可以设置basic.ack 的 multiple 域,表示到这个序列号之前的所有消息都已经得到了处理。

如何保证消息完全的不丢失呢?

  • 设置要求队列必须持久化
  • 设置队列中的消息必须持久化
  • 使用发布确认模式

发布确认策略

开启发布确认模式
发布确认默认是没有开启的,如果要开启需要调用方法confirmSelect,每次使用发布确认,都需要在 channel 上调用该方法

channel.confirmSelect();

单个确认发布

它是一种同步确认发布的方式,也就是发布一个消息之后只有它被确认发布,后续的消息才能继续发布。这种确认方式有一个最大的缺点就是:发布速度特别的慢,因为如果没有确认发布的消息就会阻塞所有后续消息的发布,这种方式最多提供每秒不超过数百条发布消息的吞吐量。当然对于某些应用程序来说这可能已经足够了。

代码实现

 // 发消息的个数
    public static final int MESSAGE_COUNT = 1000;
  // 单个确认
    public static void publishMessageIndividually() throws Exception {
        Channel channel = RabbitMQUtils.getChannel();
        // 声明队列
        String queueName = UUID.randomUUID().toString();
        channel.queueDeclare(queueName, false, false, false, null);
        // 开启发布确认
        channel.confirmSelect();
        // 开始时间
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < MESSAGE_COUNT; i++) {
            StringBuilder message = new StringBuilder();
            message.append(i);
            message.append(" ");
            channel.basicPublish("", queueName, null, message.toString().getBytes());
            // 单个消息发布确认
            boolean flage = channel.waitForConfirms();
            if (flage){
                System.out.println("消息发送成功");
            }
        }
        long endTime = System.currentTimeMillis();
        System.out.println("单个发布确认,发布1000条消息耗时:"+(endTime - startTime) + "ms");
    }

运行测试:

在这里插入图片描述
可以看出单个发布确认的执行时间较长,效率非常慢,但是当发生故障的时候非常容易排查,清楚的知道是哪个消息出现了问题。

批量确认发布

与单个等待确认消息相比,先发布一批消息然后一起确认可以极大地提高吞吐量,当然这种方式的缺点就是:当发生故障导致发布出现问题时,不知道是哪个消息出现问题了,我们必须将整个批处理保存在内存中,以记录重要的信息而后重新发布消息。当然这种方案仍然是同步的,也一样阻塞消息的发布。

代码实现

  // 批量发布
    public static void publishmessageBatch() throws Exception {
        Channel channel = RabbitMQUtils.getChannel();
        // 声明队列
        String queueName = UUID.randomUUID().toString();
        channel.queueDeclare(queueName, false, false, false, null);
        // 开启发布确认
        channel.confirmSelect();
        // 开始时间
        long startTime = System.currentTimeMillis();
        // 批量确认消息大小
        int batchSize = 100;

        // 批量发送,批量发布确认
        for (int i = 0; i < MESSAGE_COUNT; i++) {
            String message = i + "";
            channel.basicPublish("", queueName, null, message.getBytes());
            //  到达100条消息确认一次
            if (i % batchSize == 0) {
                System.out.println("消息发送成功");
                //  发布确认
                channel.waitForConfirms();
            }
        }

        long endTime = System.currentTimeMillis();
        System.out.println("单个发布确认,发布1000条消息耗时:" + (endTime - startTime) + "ms");
    }

测试运行:

在这里插入图片描述

可以看出相比于单个发布确认效率提升了很多。

异步确认发布

异步确认相较于之前两个策略在编程逻辑上要复杂的多,但是它的效率是最高的,如果消息出现丢失,它可以知道那些消息丢失,并且可以重新的投递,是通过函数回调来保证是否投递成功。

在这里插入图片描述
代码实现

    // 异步发布
    public static void publishMessageAsync() throws Exception {
        Channel channel = RabbitMQUtils.getChannel();
        // 声明队列
        String queueName = UUID.randomUUID().toString();
        channel.queueDeclare(queueName, false, false, false, null);
        // 开启发布确认
        channel.confirmSelect();
        // 开始时间
        long startTime = System.currentTimeMillis();

        /**
         * 消息确认成功回调
         * 1. 消息的标识
         * 2.是否为批量确认
        **/
        ConfirmCallback ackCallback = (deliveryTag, multiple) -> {
            System.out.println("确认消息:"+deliveryTag);
        };
        // 消息确认失败回调
        ConfirmCallback nackCallback = (deliveryTag, multiple) -> {
            System.out.println("未确认消息:"+deliveryTag);
        };
        // 消息监听器
        channel.addConfirmListener(ackCallback,nackCallback);

        for (int i = 0; i < MESSAGE_COUNT; i++) {
            String message = i + " ";
            channel.basicPublish("", queueName, null, message.getBytes());
        }
        //  发布确认

        long endTime = System.currentTimeMillis();
        System.out.println("异步发布确认,发布1000条消息耗时:" + (endTime - startTime) + "ms");
    }

在这里插入图片描述

如何处理异步未确认消息?
最好的解决的解决方案就是把未确认的消息放到一个基于内存的能被发布线程访问的队列,比如说用 ConcurrentLinkedQueue 这个队列在 confirm callbacks 与发布线程之间进行消息的传递。

代码实现

 // 异步发布
    public static void publishMessageAsync() throws Exception {
        Channel channel = RabbitMQUtils.getChannel();
        // 声明队列
        String queueName = UUID.randomUUID().toString();
        channel.queueDeclare(queueName, false, false, false, null);
        // 开启发布确认
        channel.confirmSelect();

        /**
         * 线程安全有序的一个哈希表,适用于高并发的情况
         * 1.将序号与消息进行关联
         * 2.批量删除条目
         * 3.支持高并发
        **/
        ConcurrentSkipListMap<Long,Object> outstandingConfirms = new ConcurrentSkipListMap<>();


        // 开始时间
        long startTime = System.currentTimeMillis();

        /**
         * 消息确认成功回调
         * 1. 消息的标识
         * 2.是否为批量确认
        **/
        ConfirmCallback ackCallback = (deliveryTag, multiple) -> {
            if (multiple){
                // 2.删除已经确认的消息
                ConcurrentNavigableMap<Long, Object> confirmed = outstandingConfirms.headMap(deliveryTag);
                confirmed.clear();
            }else {
                outstandingConfirms.remove(deliveryTag);
            }
            System.out.println("确认消息:"+deliveryTag);
        };
        // 消息确认失败回调
        ConfirmCallback nackCallback = (deliveryTag, multiple) -> {
            // 3.打印未确认的消息
            Object message = outstandingConfirms.get(deliveryTag);
            System.out.println("未确认消息为:"+message+"标记:"+deliveryTag);
        };
        // 消息监听器
        channel.addConfirmListener(ackCallback,nackCallback);

        for (int i = 0; i < MESSAGE_COUNT; i++) {
            String message = i + " ";
            channel.basicPublish("", queueName, null, message.getBytes());
            // 1.记录要发送的所有消息
            outstandingConfirms.put(channel.getNextPublishSeqNo(),message);
        }
        //  发布确认

        long endTime = System.currentTimeMillis();
        System.out.println("异步发布确认,发布1000条消息耗时:" + (endTime - startTime) + "ms");
    }

三种发布确认速度对比

通过对三种发布确认策略的实验可以得出:

  • 单独发布消息:同步等待确认,简单,但吞吐量非常有限。
  • 批量发布消息:批量同步等待确认,简单,合理的吞吐量,一旦出现问题但很难推断出是那条消息出现了问题。
  • 异步处理:最佳性能和资源使用,在出现错误的情况下可以很好地控制,但是实现起来稍微难些

本次分享的文章到这里就结束了,希望对大家有啥帮助。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/65016.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MongoDB_实战部分(二)

目录一、MongoDB CRUD操作MongoDB 插入文档MongoDB 查询文档MongoDB 修改文档MongoDB 删除文档练习题二、Mongoose三、VSCode连接MongoDB模块化一、MongoDB CRUD操作 MongoDB 插入文档 /*向数据库插入文档db.<collection>.insert()db.<collection>.insertOne() 插…

SDK 2019.1 - GNU Debugger (GDB) 不正常工作

报错截图 报错显示 warning: Can not parse XML target description; XML support was disabled at compile time warning: No executable has been specified and target does not support determining executable automatically. Try using the “file” command. " 解…

ROS service简单使用示例

1、为什么要使用ROS service 之前写过一篇关于ROS topic的内容。对于实时性、周期性的消息&#xff0c;使用topic来传输是最佳的选择。topic是一种点对点的单向通信方式&#xff0c;这里的“点”指的是node&#xff0c;也就是说node之间可以通过topic方式来传递信息。topic要经…

详细设计阶段复习

详细设计详细设计:确定具体实现方案,得出精确描述任务:结构程序设计:三种基本控制结构(选择[if]/顺序/循环[while|for])实现任何单入单出的程序人机界面设计:属于接口设计的重要组成问题设计指南设计工具:描述处理过程的工具程序流程图(历史悠久)盒图(N-S图): 不违背结构程序设…

[附源码]计算机毕业设计JAVA疫情环境下的酒店管理系统

[附源码]计算机毕业设计JAVA疫情环境下的酒店管理系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM…

机器学习实战——股票close预测

前言 用股票历史的close预测未来的close。 另一篇用深度学习搞得&#xff0c;见&#xff1a;深度学习实战——CNNLSTMAttention预测股票 技术栈 xgboostpython 原理 都是很简单的小玩意&#xff0c;试了下发现预测的还不错&#xff0c;先上效果图&#xff1a; 有点惊讶&a…

CSS中 设置( 单行、多行 )超出显示省略号

1. 设置超出显示省略号 css设置超出显示省略号可分两种情况&#xff1a; 单行文本溢出显示省略号…多行文本溢出显示省略号… 但使用的核心代码是一样的&#xff1a;需要先使用 “overflow:hidden;” 来把超出的部分隐藏&#xff0c;然后使用“text-overflow:ellipsis;”当文…

Java进阶架构师之如何画好架构图?阿里大神手把手教你!

1、什么是架构 架构就是对系统中的实体以及实体之间的关系所进行的抽象描述&#xff0c;是一系列的决策。 架构是结构和愿景。 系统架构是概念的体现&#xff0c;是对物/信息的功能与形式元素之间的对应情况所做的分配&#xff0c;是对元素之间的关系以及元素同周边环境之间…

基于灰狼算法优化的lssvm回归预测-附代码

基于灰狼算法优化的lssvm回归预测 - 附代码 文章目录基于灰狼算法优化的lssvm回归预测 - 附代码1.数据集2.lssvm模型3.基于灰狼算法优化的LSSVM4.测试结果5.Matlab代码摘要&#xff1a;为了提高最小二乘支持向量机&#xff08;lssvm&#xff09;的回归预测准确率&#xff0c;对…

Java基础:Collection、泛型

第一章 Collection集合 1.1 集合概述 在前面使用过集合ArrayList&#xff0c;那么集合到底是什么呢&#xff1f; 集合&#xff1a;集合是java中提供的一种容器&#xff0c;可以用来存储多个数据。 集合和数组既然都是容器&#xff0c;它们有啥区别呢&#xff1f; 数组的长…

DPDK 数据传输流程

在进行正式的收发包之前&#xff0c;DPDK需要做一些初始化操作&#xff0c;包括&#xff1a; 初始化一个或多个mbuf_pool&#xff0c;用来存储从网卡中接受的数据包修改网卡配置&#xff0c;指定其接受队列的个数&#xff08;通常每个转发核一个&#xff09;&#xff0c;长度&…

【Hadoop 2.7.1】HDFS Shell操作的简单试验

【Hadoop 2.7.1】HDFS Shell操作的简单试验 HDFS提供了多种数据访问的方式&#xff0c;其中&#xff0c;命令行的形式是最简单的&#xff0c;同时也是开发者最容易掌握的方式 文章目录【Hadoop 2.7.1】HDFS Shell操作的简单试验HDFS Shell命令的使用上传文件(put)查看文件列表(…

全网最详细Centos7搭建Redis集群

1、准备三台服务器 没有服务器的话&#xff0c;虚拟机也一样 2、每台服务器安装上redis 相关网址&#xff1a; CentOS7安装Redis完整教程_长头发的程序猿的博客-CSDN博客_centos7 redis安装 3、修改“139.196.105.140&#xff08;主机&#xff09;”的配置文件 vim /etc/r…

路由策略简介、配置举例

路由策略简介、配置举例 定义 路由策略主要实现了路由过滤和路由属性等设置功能&#xff0c;他通过改变路由属性&#xff08;包括可达性&#xff09;来改变网络流量所经过的路径。 目的优势 目的 路由协议在发布、接收和引入路由信息时&#xff0c;根据实际组网需求实施一些策…

25个网络安全搜索引擎备忘录

©网络研究院 下面介绍一个包含 25 个网络安全搜索引擎的列表&#xff0c;每个网络爱好者都应该在互联网中了解这些搜索引擎。 此列表没有特定顺序&#xff0c;主要基于使用偏好。 1. 搜索连接到互联网的设备 https://www.shodan.io/ 2. 无线网络数据库&#xff0c;带…

矩阵篇(五)-- 特征值分解(EVD)和奇异值分解(SVD)

1 特征值分解&#xff08;EVD&#xff09; 设AnnA_{n \times n}Ann​有nnn个线性无关的特征向量x1,…,xn\boldsymbol{x}_{1}, \ldots, \boldsymbol{x}_{n}x1​,…,xn​&#xff0c;对应特征值分别为λ1,…,λn\lambda_{1}, \ldots, \lambda_{n}λ1​,…,λn​ A[x1⋯xn][λ1x1⋯…

基于jsp+ssm的家庭理财系统

项目介绍 在这科技不断的进步&#xff0c;让我们的生活改变了很多&#xff0c;信息技术的迅速发展&#xff0c;使各种行业在信息技术应用方面变得非常普遍。信息时代的到来&#xff0c;已成为一种必然趋势。本系统的标题是基于B/S模式的家庭理财系统的设计开发&#xff0c;其目…

公钥密码(非对称加密)

实例 投币寄物柜是这样使用的&#xff1a; 首先&#xff0c;将物品放人寄物柜中。然后&#xff0c;投入硬币并拔出钥匙&#xff0c;就可以将寄物柜关闭了。关闭后的寄物柜&#xff0c;没有钥匙是无法打开的。只要有硬币&#xff0c;任何人都可以关闭寄物柜&#xff0c;但寄物…

Locust学习记录2-locust用户类属性【HttpUser,wait_time,weight和fixed_count】

HttpUser 每个模拟用户定义的类&#xff0c;都必须继承HttpUser&#xff0c;目的时为每个用户提供一个client属性&#xff0c;该属性是一个实例HttpSession&#xff0c;可用于向我们要进行负载测试的目标系统发出HTTP请求 当测试开始时&#xff0c;locust将为它模拟的每个用户…

vue项目分环境打包的具体步骤 --- 区分测试环境与线上环境的打包引用路径

第一步&#xff1a; 安装cross-env npm install --save-dev cross-env 运行跨平台设置和使用环境变量的脚本 第二步&#xff1a;修改package.json 在package.json 里设置打包命令 --- 主要是基于使用vue-cli创建的项目&#xff0c;配置文件基于 NODE_ENVproduction 去处…