nodejs操作rabbitMQ amqplib库 消息持久化

config.js

javascript 复制代码
const { MQ_HOST, HOST, MQ_PORT  } = process.env;
const mqHost = MQ_HOST || HOST || "127.0.0.1";
const mqPort = MQ_PORT || 5672;
const mqUsername = "root";
const mqPassword = "password";
const mqProtocol = "amqp";

const exchangeName = 'exchange_direct_saas'; //交换机
const queueName = 'queue_direct_saas';
const routingKey = 'saasIsolution';//路由key

const config = { mqHost, mqPort, mqUsername, mqPassword, mqProtocol, exchangeName, queueName, routingKey };
module.exports = config;

生产者端:

javascript 复制代码
const amqp = require('amqplib');
const { getInitParams } = require('../../lib')
const params = getInitParams();
const { mqHost, mqPort, mqUsername, mqPassword, mqProtocol, exchangeName, queueName, routingKey } = require("./config");

async function product(msg) {
    const connection = await amqp.connect({protocol: mqProtocol, hostname: mqHost, port: mqPort, username: mqUsername, password: mqPassword}); 
    connection.on('error', console.error)
    const channel = await connection.createConfirmChannel();
    channel.on('error', console.error)
    //关联交换机,交换机设置类型,并将消息持久化  { durable: true } 参数为将消息持久化
    await channel.assertExchange(exchangeName, 'direct', { durable: true });
    //设置公平调度  将prefetch count项的值配置为1,这将会指示 RabbitMQ 在同一时间不要发送超过一条消息给每个消费者。换句话说,直到消息被处理和应答之前都不会发送给该消费者任何消息。取而代之的是,它将会发送消息至下一个比较闲的消费者或工作进程。
    await channel.prefetch(1, false); //true 为 channel 上做限制,false 为消费端上做限制,默认为 false
    await channel.publish(exchangeName, routingKey, Buffer.from(msg));
    // console.log("生产者消息发送完毕");
    channel.waitForConfirms().then(async results => { //等待confirm完毕
        // console.log(results);
        const errors = results.filter(Boolean); //去掉假值
        if (errors.length) console.error('Errors', errors);
        else console.log(new Date().toISOString(), `Broker confirmed ${results.length} messages`);
    }).catch(e => {
        console.error(e, "error");
    }).finally( async()=>{
        //关闭通道
         channel.close();
        //关闭连接
         connection.close();
    } )
}

product(JSON.stringify(params));

消费者端

javascript 复制代码
const amqp = require('amqplib');
const { getDoIsolation } = require("../../lib");
const { mqHost, mqPort, mqUsername, mqPassword, mqProtocol, exchangeName, queueName, routingKey } = require("./config");

async function consumer() {
    const connection = await amqp.connect({protocol: mqProtocol, hostname: mqHost, port: mqPort, username: mqUsername, password: mqPassword}); 
    connection.on('error', console.error)
    const channel = await connection.createConfirmChannel();
    channel.on('error', console.error)
    //关联交换机,设置交换机类型,并将消息持久化 { durable: true } 参数为将消息持久化
    await channel.assertExchange(exchangeName, 'direct', { durable: true });
    //关联消息队列  autoDelete:true 设置队列为空时自动删除  队列持久化
    await channel.assertQueue(queueName,{autoDelete:false, durable: true});
    //绑定关系(队列,交换机,路由键)
    await channel.bindQueue(queueName, exchangeName, routingKey);
    //设置公平调度  将prefetch count项的值配置为1,这将会指示 RabbitMQ 在同一时间不要发送超过一条消息给每个消费者。换句话说,直到消息被处理和应答之前都不会发送给该消费者任何消息。取而代之的是,它将会发送消息至下一个比较闲的消费者或工作进程。
    await channel.prefetch(1, false);//true 为 channel 上做限制,false 为消费端上做限制,默认为 false
    //消费队列消息
    await channel.consume(queueName, async (msg) => {
        try{
            const params = JSON.parse(msg.content.toString());
            const doIsolation = getDoIsolation(params);
            console.log(params, doIsolation);
            await doIsolation();
            channel.ack(msg);
        }catch(e){
            console.error(e);
        }
    }, { noAck: false });//手动ack应答
    console.log("消费端启动成功")
}
for(i=0; i<=2; i++){
    consumer();
}

消息持久化 避免丢失

1 .避免broker消息中间件维护的消息丢失

交换机的持久化 其实就是相当于将交换机的属性在服务器内部保存。当MQ的服务器发生意外或关闭之后,重启RabbitMQ时不需要重新手动或执行代码去建立交换机,交换机会自动建立,相当于一直存在。 在声明交换器的时候,将 durable 属性设置为 true即可。

队列的持久化 也是在声明队列的时候****,将durable参数设置为true。****如果队列不设置持久化,那么 RabbitMQ服务重启之后,队列就会被删除,既然队列都不存在了,队列中的消息也会丢失。

消息持久化 要确保消息不会丢失,需要将消息设置为持久化。信息持久化则是将信息存在磁盘中。生产者在发布消息时,可以设置 options 的 deliveryMode 属性为 2,标记消息为持久化消息

可以将所有的消息都设置为持久化,但是这样会严重影响 RabbitMQ 的性能。写入磁盘的速度比写入内存的速度慢得不只一点点。对于可靠性不是那么高的消息可以不采用持久化处理,以提高整体的吞吐量。在选择是否要将消息持久化时,需要在可靠性和吞吐量之间做权衡。

一般的系统也用不到对消息进行持久化。不过交换机和队列的持久化还是要支持的。

2 .避免producer生产者 -> MQ 过程中消息丢失

生产者发送消息由于网络等原因并没有发送到RabbitMq

解决方案:

2-1、开启RabbitMq事务机制

生产者发送数据之前开启 RabbitMQ 事务channel.txSelect,然后发送消 息,如果消息没有成功被 RabbitMQ 接收到,那么生产者会收到异常报错,此时就可以回滚事务channel.txRollback,然后重试发送消息;如果收到了消息,那么可以提交事务channel.txCommit,类似我们数据库数据库事务机制。

2 -2 、开启 confirm 模式 ( 推荐 )

在生产者端设置开启 confirm 模式之后,你每次写的消息都会分配一个唯一的 ID,然后如果写入了 RabbitMQ 中,RabbitMQ 会给你回传一个 ack 消息,告诉你说这个消息已经收到。如果 RabbitMQ 没能处理这个消息,会回调你的一个 nack 接口,告诉你这个消息接收失败,你可以重试。而且可以结合这个机制在自己业务里维护每个消息 ID 的状态,如果超过一定时间还没接收到这个消息的回调,那么可以业务主动重发。

事务机制和 confirm 机制优劣:

事务机制是同步的,提交一个事务之后会阻塞,吞吐量会下来,耗性能。

confirm 机制是异步的,流程不会阻塞,吞吐量较高,性能较好。

3. 避免 consumer消费者 未处理完的消息丢失

原因:消费者自动ack配置情况下(no_ack=true),消息发送后立即被认为已经传送成功,业务代码异常或者其他故障消息并没有处理完成也会自动ack。RabbitMq消息ack后就会丢弃,这就导致异常情况下的消息丢失了。

解决方案:

关闭RabbitMq自动ack(no_ack=false),业务代码成功消费了消息手动调用Mq ack,让Mq丢弃消息;如果业务代码异常则直接nack,让Mq重新推送消息进行处理。当然,在要求比较高的情况下也可以异常数据进入死信队列,保证数据的完整性。

相关推荐
前端Hardy12 分钟前
HTML&CSS:MacBook Air 3D 动画跃然屏上
前端·javascript·css·3d·html
loey_ln1 小时前
观察者模式和发布订阅模式
javascript·观察者模式·react.js
cnsxjean3 小时前
SpringBoot集成Minio实现上传凭证、分片上传、秒传和断点续传
java·前端·spring boot·分布式·后端·中间件·架构
ZL_5673 小时前
uniapp中使用uni-forms实现表单管理,验证表单
前端·javascript·uni-app
sunly_3 小时前
Flutter:启动屏逻辑处理02:启动页
android·javascript·flutter
EasyNTS4 小时前
H5流媒体播放器EasyPlayer.js网页直播/点播播放器如果H.265视频在播放器上播放不流畅,可以考虑的解决方案
javascript·音视频·h.265
莘薪4 小时前
JQuery -- 第九课
前端·javascript·jquery
在路上`5 小时前
vue实现列表滑动下拉加载数据
javascript·vue.js·ecmascript
蒜蓉大猩猩5 小时前
Vue.js --- Vue3中其他组合式API
前端·javascript·vue.js·前端框架·node.js·html
珹洺5 小时前
从 HTML 到 CSS:开启网页样式之旅(开篇之一)——CSS 初体验与网页样式新征程
前端·javascript·css·前端框架·html