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。有两个地方需要注意
- 查看 RabbitMQ 后台,
http://localhost:15672/,可以看到 Unacked 一直为1,说明每次只从队列里取出一条数据。
- 查看 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)不符,具体原因有待查证。
