在nodejs中使用RabbitMQ(七)实现生产者确认

  • 生产者 :批量发送消息(每批10条),每条消息附带唯一 correlationId,并监听确认队列(ackQueue)。

  • 消费者 :处理消息后,通过 ackQueue 返回确认消息(携带原 correlationId)。

  • 超时重试:若某批消息在指定时间内未全部确认,未确认的消息会重新加入待发送队列。

producer.ts

javascript 复制代码
import amqp from 'amqplib';


async function start() {
  const connection = await amqp.connect('amqp://admin:admin1234@localhost:5672//mirror?heartbeat=60');
  const channel = await connection.createChannel();
  const queue = 'queue11';
  const ackQueue = 'queue11_ack';

  await channel.assertQueue(queue, { durable: true });
  await channel.assertQueue(ackQueue, { durable: true });

  async function produce(limit: number, data: string[], timeout: number = 10000) {
    let message = [...data];
    if (message.length > limit) {
      message = message.slice(0, limit);
    } else if (message.length < limit) {
      limit = message.length;
    }

    // 消息确认
    let cache: Array<{
      correlationId: string,
      message: string,
      isDelete: boolean,
    }> = new Array(limit)
      .fill(null)
      .map((_, index) => {
        return {
          correlationId: Math.random().toString().slice(2, -1),
          message: message[index],
          isDelete: false,
        };
      });

    for (let i = 0; i < limit; ++i) {
      channel.sendToQueue(queue, Buffer.from(cache[i].message), {
        correlationId: cache[i].correlationId,
        replyTo: ackQueue
      });
    }

    const consume = await channel.consume(ackQueue, (message) => {
      if (!message) {
        console.error('message is null', message);
        return;
      }

      let index = cache.findIndex((item) => item.correlationId === message.properties.correlationId);

      if (index !== -1) {
        cache[index].isDelete = true;
        console.log('confirmed success:', `"${message.content.toString()}"`, cache.every(item => item.isDelete));
      } else {
        console.log('confirmed fail:', `"${message.content.toString()}"`, cache, cache.every(item => item.isDelete), message.properties.correlationId);
      }

      channel.ack(message);
    });

    const sleep = (time: number) => {
      return new Promise<void>(resolve => setTimeout(() => resolve(), time));
    }

    let stop = false;
    const interval = async () => {
      await sleep(0);
      if (cache.every(item => item.isDelete) || stop) {
        return;
      } else {
        await interval();
      }
    }

    await Promise.race([
      interval(), // 监听本批次消息是否已经处理完成
      sleep(timeout), // 本批次消息最长处理时间
    ]);

    stop = true;

    await channel.cancel(consume.consumerTag);

    // 没有收到确认的消息返回下一批处理继续处理
    return cache.filter(item => !item.isDelete).map(item => item.message);
  }

  // 发送1000条数据,分100批,每批10个
  let msg = new Array(100).fill(null).map((_, index) => `${index} message ${Math.random().toString().slice(2, -1)}`);

  while (msg.length) {
    let res = await produce(10, msg.slice(0, 10), 6000);
    msg = [...res, ...msg.slice(10, msg.length)];
    console.log('完成一批:', msg.length, '发送结果:', res.length, res);
  }
}

start();

consumer.ts

javascript 复制代码
import amqp from 'amqplib';


async function produce() {
  const connection = await amqp.connect('amqp://admin:admin1234@localhost:5672//mirror?heartbeat=60');
  const channel = await connection.createChannel();
  const queue = 'queue11';
  const ackQueue = 'queue11_ack';

  await channel.assertQueue(queue, { durable: true });
  await channel.assertQueue(ackQueue, { durable: true });

  
  channel.consume(queue, (message) => {
    if (message) {
      console.log(message?.content.toString(), message?.properties?.replyTo, message?.properties?.correlationId);

      // 消息处理完后,向 ackQueue 发送确认消息
      channel.sendToQueue(ackQueue, message?.content, {
        // 使用相同的 correlationId 来标识确认消息
        correlationId: message?.properties?.correlationId,
        // 将原 replyTo 信息传递回来
        // replyTo: queue,
      });

      // 确认 queue11 中的消息
      channel.ack(message);
    } else {
      console.error('message is null', message);
    }
  }, { noAck: false });
}

produce();
相关推荐
TTBIGDATA3 小时前
【Atlas】Atlas Hook 消费 Kafka 报错:GroupAuthorizationException
hadoop·分布式·kafka·ambari·hdp·linq·ranger
m0_687399845 小时前
telnet localhost 15672 RabbitMQ “Connection refused“ 错误表示目标主机拒绝了连接请求。
分布式·rabbitmq
陌上丨6 小时前
生产环境分布式锁的常见问题和解决方案有哪些?
分布式
新新学长搞科研6 小时前
【智慧城市专题IEEE会议】第六届物联网与智慧城市国际学术会议(IoTSC 2026)
人工智能·分布式·科技·物联网·云计算·智慧城市·学术会议
Ronin3056 小时前
日志打印和实用 Helper 工具
数据库·sqlite·rabbitmq·文件操作·uuid生成
泡泡以安6 小时前
Scrapy分布式爬虫调度器架构设计说明
分布式·爬虫·scrapy·调度器
波波0077 小时前
每日一题:中间件是如何工作的?
中间件·.net·面试题
没有bug.的程序员7 小时前
RocketMQ 与 Kafka 深度对垒:分布式消息引擎内核、事务金融级实战与高可用演进指南
java·分布式·kafka·rocketmq·分布式消息·引擎内核·事务金融
上海锟联科技8 小时前
250MSPS DAS 在地铁监测中够用吗?——来自上海锟联科技的工程实践
分布式·科技·分布式光纤传感·das解调卡·光频域反射·das
岁岁种桃花儿8 小时前
深度解析DolphinScheduler核心架构:搭建高可用Zookeeper集群
linux·分布式·zookeeper