RabbitMQ踩坑

1,039 阅读2分钟

Warning,本文只为记录问题,不提供解决方案。

1. 限制消费者每次只消费一条消息,且 nack 后仍然消费上一次消费的消息。

先上代码
// lib.js

const amqp = require('amqplib');
module.exports = {
  createConfirmChannel: async function() {
    const connection = await amqp.connect('amqp://localhost/');
    return connection.createConfirmChannel();
  }
};

// client.js

'use strict';
const { createConfirmChannel } = require('./lib');
const queueId = 'test_consumer';
const exchangeId = 'test_exchange';
const consumerTag = 'unique';
const pattern = 'test';
const array = [];
let global_channel;
async function create() {
  try {
    const channel = await createConfirmChannel();
    await channel.assertExchange(exchangeId, 'direct', { durable: true });
    await channel.assertQueue(queueId, { durable: true });
    await channel.prefetch(1);
    await channel.bindQueue(queueId, exchangeId, pattern);
    return channel;
  } catch (err) {
    console.log('创建消费者异常', err);
    return false;
  }
}
async function run() {
  global_channel = await create();
  await global_channel.consume(queueId, msg => consume(msg), { noAck: false, consumerTag });
}

async function consume(msg) {
  // 先不消费,放回队列 
  if (!array.includes(msg.content.toString())) {
    array.push(msg.content.toString());
    console.log(array);
  }
  await global_channel.nack(msg);
}

run();

// server.js

'use strict';
const Promise = require('bluebird');
const queueId = 'test_producer';
const exchangeId = 'test_exchange';
const pattern = 'test';
const { createConfirmChannel } = require('./lib');

async function create() {
  try {
    const channel = await createConfirmChannel();
    await channel.assertQueue(queueId, { durable: true });
    channel.assertExchange(exchangeId, 'direct', { durable: true });
    return channel;
  } catch (err) {
    console.log(err);
    return false;
  }
}

async function run() {
  const channel = await create();
  while (true) {
    // 不断推送数据到 client
    await Promise.delay(200);
    await channel.publish(exchangeId, pattern, new Buffer(Date.now() + ''), { deliveryMode: true });
  }
}

run();

让我们先运行 node server.js,再运行node client.js。有两个地方需要注意

  1. 查看 RabbitMQ 后台,http://localhost:15672/,可以看到 Unacked 一直为1,说明每次只从队列里取出一条数据。
  2. 查看 shell,发现只有一条消息被打印出来。

综合两者可以得知,需求1完美实现。到目前为止一切顺利,没有问题。但是要注意,如果设置为 channel.prefetch(1, true) 会产生与预期不一样的结果,虽然每次也会只取一条数据,但是有可能取到的数据不是队列头的数据,具体原因有待查证。

2. 消费的暂停与重启

已知: channel.cancel() 能暂停消费,再次调用 channel.consume() 能重新开始消费。

先改变 client.js 的代码,进行频繁重启。

'use strict';
const { createConfirmChannel } = require('./lib');
const queueId = 'test_consumer';
const exchangeId = 'test_exchange';
const consumerTag = 'unique';
const pattern = 'test';
let global_channel;
async function create() {
  try {
    const channel = await createConfirmChannel();
    await channel.assertExchange(exchangeId, 'direct', { durable: true });
    await channel.assertQueue(queueId, { durable: true });
    await channel.prefetch(1);
    await channel.bindQueue(queueId, exchangeId, pattern);
    return channel;
  } catch (err) {
    console.log('创建消费者异常', err);
    return false;
  }
}
async function run() {
  global_channel = await create();
  await global_channel.consume(queueId, msg => consume(msg), { noAck: false, consumerTag });
}

async function consume(msg) {
  try {
    // 频繁重启
    await global_channel.cancel(consumerTag);
    await global_channel.consume(queueId, msg => consume(msg), { noAck: false, consumerTag });
  } catch (error) {
    console.log(error);
  }
}

run();

查看 RabbitMQ 后台,http://localhost:15672/ ,可以看到 unacked 有多个,而不是1个,与 channel.prefetch(1)不符,具体原因有待查证。