以发送邮件任务为例,第一次发送邮件失败后,延时30s后继续发送此邮件,如果如此重试3次后仍然失败,则停止发送。
- send_email: 发送邮件任务队列(死信队列)
- send_email_retry: 发送邮件任务重试队列
- send_email_fail: 发送邮件任务失败队列
生产者
- 创建send_email队列,并给队列绑定交换机
exchange
和路由routingkey
await ch.assertQueue(queue_send_email, {
durable: true
})
await ch.assertExchange(dlx_routing_exchange)
await ch.bindQueue(queue_send_email,dlx_routing_exchange, dlx_routing_key)
- 创建queue_retry队列,指定死信交换机
deadLetterExchange
和死信路由deadLetterRoutingKey
以及队列消息过期时间messageTtl
可以发现我们指定的queue_retry队列的死信交换机和死信路由和queue_email队列声明的交换机和路由一致。这样只要queue_retry队列中的消息过期后,就会自动从queue_retry队列转移到queue_email队列,而queue_email的消费者又可以消费了,这样就实现了延迟消费。而被死信交换机和死信路由绑定的队列(queue_email)就叫做 死信队列。
await ch.assertQueue(queue_retry, {
durable: true,
deadLetterExchange: dlx_routing_exchange,
deadLetterRoutingKey: dlx_routing_key,
messageTtl: 30 * 1000
})
- 创建queue_fail队列
失败队列主要就是存储重试3次后仍然失败的发邮件任务,供人工查看。
await ch.assertQueue(queue_fail, {
durable: true
})
- 生产者创建发送邮件任务到queue_email队列
注意: 在headers中指定了一个count参数,用来标识重试的次数
await ch.sendToQueue(queue, Buffer.from('发送邮件'), {
deliveryMode: true,
headers: {
count
}
})
消费者
await ch.consume(queue, (msg) => {
//获取重试的次数
let retryCount = msg.properties.headers.count
const result = sendEmail();
if (result) { //发送成功
console.log('发送成功')
}
if (!result && retryCount < 3) { //发送失败重试,且重试次数小于3
//将消息发送到重试队列,并且重试次数+1
sendToWorker(queue_retry, ++retryCount)
} else {
//将消息发送到失败队列
sendToWorker(queue_fail, retryCount)
}
//不管是否发送成功能都要ack
ch.ack(msg)
}, {
noAck: false
})
完整代码
const amqp = require('amqplib');
const moment = require('moment')
var queue_send_email = 'send_email';
var queue_retry = 'send_email_retry'
var queue_fail = 'send_email_fail'
var dlx_routing_exchange = 'dlx_routing_exchange'
var dlx_routing_key = 'dlx_routing_key'
var conn = null
async function init() {
await amqp.connect(`amqp://${user}:${password}@${host}:5672`).then(client => {
conn = client
})
}
function sendEmail() {
return false
}
async function sendToWorker(queue, count) {
// Publisher
const ch = await conn.createConfirmChannel();
await ch.assertQueue(queue_send_email, {
durable: true
})
await ch.assertExchange(dlx_routing_exchange)
await ch.bindQueue(queue_send_email,dlx_routing_exchange, dlx_routing_key)
await ch.assertQueue(queue_retry, {
durable: true,
deadLetterExchange: dlx_routing_exchange,
deadLetterRoutingKey: dlx_routing_key,
messageTtl: 30 * 1000
})
await ch.assertQueue(queue_fail, {
durable: true
})
await ch.sendToQueue(queue, Buffer.from('发送邮件'), {
deliveryMode: true,
headers: {
count
}
})
ch.close()
}
async function consumerByWorker(queue) {
// Consumer
const ch = await conn.createConfirmChannel();
await ch.assertQueue(queue_send_email, {
durable: true
})
await ch.assertExchange(dlx_routing_exchange)
await ch.bindQueue(queue_send_email,dlx_routing_exchange, dlx_routing_key)
await ch.prefetch(1)
await ch.consume(queue, (msg) => {
console.log(`${moment().format('HH:mm:ss')}--->发送邮件`)
let retryCount = msg.properties.headers.count
const result = sendEmail();
if (result) { //发送成功
console.log('发送成功')
}
if (!result && retryCount < 3) { //发送失败重试,且重试次数小于3
console.log(`发送邮件,第${retryCount}次重试`)
sendToWorker(queue_retry, ++retryCount)
} else {
console.log(`重试${retryCount}次后仍然失败,不再处理`)
sendToWorker(queue_fail, retryCount)
}
//不管是否发送成功能都要ack
ch.ack(msg)
}, {
noAck: false
})
}
(
async () => {
await init()
await consumerByWorker(queue_send_email)
await sendToWorker(queue_send_email, 0)
}
)()