Node端使用工作线程来解决日志开销-处理IO密集型任务

我们的BBF层很多时候会作为中间层处理后端到前端的数据,当然大部分时候都只是作为请求 / 响应的数据组装中心,但是有一个插件是怎么都绕不过去的:Log4js。
内部我们在Node层打印了很多日志。结果这周仔细分析了一下服务器处理请求到响应的中间耗时,发现log4js高居榜首。稍微有一点难蚌就是说。
在这里插入图片描述
经过揣摩推测,可能是因为日志内容需要写入文件,其中存在IO开销,而且日志请求量很大,导致了程序阻塞。

方案一:批处理,批量写入

使用bufferSize属性,但是跑过命令后发现并没有明显改善,不知为何。
在这里插入图片描述

方案二:日志移交工作线程处理

使用worker_threads创建子线程,其实并没有做什么额外处理,只是子线程中初始化log4js的包,并且接收日志消息发送。
主线程中监听子线程的error / exit事件并且重启 / 错误处理。
大概如下:

// 主线程
const { Worker } = require('worker_threads');
const worker = new Worker(path.join(__dirname, '../worker/loggerWorker.js'));

  worker.on('error', (err) => {
    console.error('【logger worker error】', err);
  });

  // 工作线程结束时的相关处理
  worker.on('exit', (code) => {
    console.error(`【logger worker exit】${code}`);
    if (code !== 0) {
      // 异常退出
      // 重试
    } else {
      // 正常退出
    }
  });

子线程loggerWorker

// loggerWorker.js
const { parentPort } = require('worker_threads');
const log4js = require('log4js');

// 配置 log4js
log4js.configure({
  // ... 日志配置
});

// 使用 log4js 的 logger
const logger = log4js.getLogger();

// 接收来自主线程的消息
parentPort.on('message', (msg) => {
  logger[msg.level] ('msg.data 要发送的消息')
});

工作线程的相关问题记录

1. worker_threads和child_process、cluster区别在哪里。

worker_threadschild_processcluster 在 Node.js 中都提供了在多个 CPU 核心上运行代码的能力,但它们的工作方式和使用场景有所不同。

worker_threads

一般而言我们项目中使用的线程都是这个类型。

适合需要执行计算密集型任务且希望避免进程间通信开销的场景。

例如,在后台执行 CPU 密集型任务,或者在处理图像、执行大量的数学计算时,使用 worker_threads 可以在不同的线程中并行处理以提高效率。

worker_threads 模块允许 Node.js 程序创建一个工作线程池并分派任务给线程来执行。与 child_process 不同的是,worker_threads 使用同一进程的不同线程来运行代码,并且这些线程可以共享某些资源(例如 TypedArray 数据)。

child_process

child_process 模块允许 Node.js 程序异步地产生新的进程,并与它们进行通信。使用 child_process 可以执行系统命令、运行其他应用或者运行另外的 Node.js 进程。它适用于需要与操作系统交互或运行不同程序的场景。

常用于需要新的进程环境(例如执行不同程序或需要完全隔离的环境)的场景。例如,如果需要在 Node.js 应用程序中执行一个 Python 脚本,你可以使用 child_process.spawn 来启动一个新的 Python 进程并运行这个脚本。

child_process 提供了几种创建子进程的函数,包括:

  • exec:用于执行命令,缓冲输出到内存,适用于输出量不大的场合。
  • spawn:用于执行命令,以流的形式提供输出,适用于输出量大的场合。
  • execFile:类似 exec,但直接执行文件而不是通过 shell,安全性更高。
  • fork:专门用于运行 Node.js 模块,它在父子进程之间建立了一个通信管道,便于消息传递。

cluster

适用于希望扩展网络服务的性能的场景。

cluster 模块允许简单地创建共享单个服务器端口的 Node.js 进程的子进程(称为工作进程)。当 Node.js 运行在多核处理器的机器上时,使用 cluster 可以让不同的工作进程运行在不同的 CPU 核心上,从而更好地利用多核资源。

例如,如果你有一个Node.js的HTTP服务器,并且你想让它能够在多核服务器上运行,那么你可以使用 cluster 模块轻松地创建多个工作进程,每个进程都监听相同的端口,以便于分摊请求负载。

2. 使用worker_threads如何传递共享数据、如何资源上锁等处理。

Node.js中有几种方式来实现线程共享资源的保护和上锁,以及进程间共享资源的保护和上锁。

1. worker_threads

在使用worker_threads模块时,我们可以创建多线程,这些线程可以共享部分资源。例如,SharedArrayBuffer允许多个worker共享同一内存。然而,分享资源需要注意同步问题和并发访问问题。

为了保护共享资源,可以使用Atomics API来进行上锁和同步操作。Atomics提供了一系列原子操作来确保在多个线程读写共享内存时的正确性。使用原子操作可以确保一个时间点只有一个线程在修改共享资源,这样可以防止竞态条件和数据不一致。

  • workerData
    可以在初始化创建线程时传入,工作线程通过引入workerData对象拿到传递的数据使用。(类似环境变量?)

  • SharedArrayBuffer:
    在worker中创建一个可以被多个线程共享的内存区域。通过使用SharedArrayBuffer类型化数组Int32Array)来实现的。
    SharedArrayBuffer(4) 代表了一个可以在多个线程之间共享的固定大小的二进制数据缓存区。这个共享内存buffer的大小是4字节。SharedArrayBuffer不能直接读写,而是需要通过类型化数组或者DataView对象来操作其中的数据。

  • Int32Array
    Int32Array会把之前创建的共享内存buffer作为底层存储结构。它是一种类型化数组,用于表示一个32位整数数组。由于每个Int32元素占用4字节,共享内存也只有4字节,因此这个特定的Int32Array只能包含一个整数元素。

可以在一个worker线程中修改sharedArray中的元素,然后该修改会立即对主线程或其他worker线程可见。这允许多个线程能够同时读写相同的数据,从而实现了线程间的并发操作。

  • Atomics
    Atomics 是一个全局对象,提供了一组静态方法来进行原子操作。这些方法可以在 SharedArrayBuffer 的视图(如 Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array等)上执行,确保了在多线程环境中对共享内存的读写操作是原子性的,也就是说,这些操作是不可中断的,保证了线程安全。
  1. Atomics.add(typedArray, index, value)
    对位于 typedArray[index] 的元素执行原子加法操作。

  2. Atomics.load(typedArray, index)
    对位于 typedArray[index] 的元素执行原子读取操作。

  3. Atomics.store(typedArray, index, value)
    对位于 typedArray[index] 的元素执行原子存储操作。

  4. Atomics.exchange(typedArray, index, value)
    对位于 typedArray[index] 的元素执行原子交换操作。

const { Worker, isMainThread, parentPort, WorkerData, Atomics, SharedArrayBuffer } = require('worker_threads');

 // 主线程
  const sharedBuffer = new SharedArrayBuffer(4); // 创建一个共享内存Buffer
  const sharedArray = new Int32Array(sharedBuffer); // 创建TypedArray来操作共享内存

  const worker = new Worker(__filename, {
    workerData: sharedBuffer
  });

  Atomics.store(sharedArray, 0, 1); // 在位置0写入1
  console.log('The initial value is:', Atomics.load(sharedArray, 0));

  worker.on('message', () => {
    console.log('The value now is:', Atomics.load(sharedArray, 0));
  });


// 子线程
  const { workerData } = require('worker_threads');
  const sharedArray = new Int32Array(workerData);

  Atomics.add(sharedCArray, 0, 1); // 原子操作添加
  parentPort.postMessage('Worker completed');

2. cluster

当使用cluster模块时,主进程可以创建多个工作进程,这些工作进程可以共享同一个TCP服务器绑定的端口。但是,工作进程之间的内存资源是隔离的,他们无法直接共享内存资源。

进程间通信(IPC)通常是通过父进程与子进程之间传递消息来实现的。如果需要在多个工作进程之间共享资源,通常是通过外部的存储(例如Redis、数据库等)来实现的。

// 在 cluster 模式下使用消息传递示例
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
  console.log(`Master ${process.pid} is running`);

  // 添加需要共享的资源
  let sharedResource = { ... };

  // Fork workers.
  for (let i = 0; i < numCPUs; i++) {
    const worker = cluster.fork();

    // 发送共享资源给每个worker
    worker.send({ sharedResource });
  }

  cluster.on('exit', (worker, code, signal) => {
    console.log(`worker ${worker.process.pid} died`);
  });
} else {
  process.on('message', (msg) => {
    // 接收共享资源
    console.log('Worker received message:', msg);
  });

  http.createServer((req, res) => {
    res.writeHead(200);
    res.end('hello world\n');
  }).listen(8000);

  console.log(`Worker ${process.pid} started`);
}

3. child_process

child_process模块允许你创建子进程,并与之通信。与cluster模块类似,子进程的内存也是隔离的,资源共享需要通过IPC来实现。
你可以使用child_process.fork()来创建子进程,并通过process.send()child.on('message', callback)进行父子进程间通讯。

总结

在Node.js中,实现不同线程或进程间的资源共享和上锁,通常需要针对场景选择合适的机制。worker_threads提供了共享内存和原子操作,而clusterchild_process主要依靠消息传递和外部存储解决资源共享问题。记住多线程和多进程编程都需要考虑同步和竞态条件等问题,合理设计代码以确保线程安全和数据的一臀性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/768968.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

excel数据大小显示竟然有最大限制,限制32,767,实际限制32759

Excel 单元格在显示数据时确实存在一些限制&#xff0c;这些限制主要与单元格的宽度和高度有关&#xff0c;而不是存储数据的大小。以下是一些主要的限制&#xff1a; 1. **列宽和行高**&#xff1a;Excel 单元格的显示大小取决于列宽和行高。如果单元格中的数据超出了设定的列…

C# Winform项目中简单使用Sqlite并在DataGridview中显示

1. SQLite概述 1.1 什么是 SQLite&#xff1f; SQLite是一个进程内的库&#xff0c;实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。它是一个零配置的数据库&#xff0c;这意味着与其他数据库不一样&#xff0c;您不需要在系统中配置。 1.2 为什么要用 …

vmware虚拟机安装openEuler

一、openEuler简介 openEuler是一款开源操作系统。当前openEuler内核源于Linux&#xff0c;支持鲲鹏及其它多种处理器&#xff0c;能够充分释放计算芯片的潜能&#xff0c;是由全球开源贡献者构建的高效、稳定、安全的开源操作系统&#xff0c;适用于数据库、大数据、云计算、…

游戏AI的创造思路-技术基础-自然语言处理

自然语言处理-可以对游戏AI特别是RPG类、语言类游戏进行“附魔”&#xff0c;开发出“随机应变”和你聊天的“女友”、“队友”或者是根据你定义的文本库来用接近自然语言的生成“语言”&#xff0c;推动游戏情景在受控范围内前进 目录 1. 自然语言处理定义 2. 发展历史 3. …

k8s部署单节点redis

一、configmap # cat redis-configmap.yaml apiVersion: v1 kind: ConfigMap metadata:name: redis-single-confignamespace: redis data:redis.conf: |daemonize nobind 0.0.0.0port 6379tcp-backlog 511timeout 0tcp-keepalive 300pidfile /data/redis-server.pidlogfile /d…

高考服务系统

摘 要 每年有大批考生在进行填写高考志愿时并不很清楚自己的高考分数适合那些高校以及专业。高考考生面临着未被高校录取&#xff0c;被调剂专业&#xff0c;甚至可能复读的问题。若能让考生轻松查询到高校录取、高校专业、高校招生等相关信息&#xff0c;能减少很大一部分考生…

《后端程序猿 · Caffeine 本地缓存》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻一周&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

SolrCloud Autoscaling 自动添加副本

SolrCloud Autoscaling 自动添加副本 前言 问题描述 起因是这样的&#xff0c;我在本地调试 Solr 源码&#xff08;版本 7.7.3&#xff09;&#xff0c;用 IDEA 以 solrcloud 方式启动了 2 个 Solr 服务&#xff0c;如下所示&#xff1a; 上图的启动参数 VM Options 如下&am…

QT控制comboBox切换方法

目录 1. 效果2. 操作 1. 效果 如下图&#xff1a; 点击全切换雨天模式按钮 则 comboBox 文本显示为 “雨天模式”点击全切换正常模式按钮 则 comboBox 文本显示为 “雨天模式” 切换到 雨天模式 切换到 正常模式 2. 操作 使用 “setCurrentIndex” 方法&#xff0c;切换 combo…

vmware虚拟机增加磁盘容量

概述 当初始分配给虚拟机的磁盘空间不够时&#xff0c;需要从外部的主系统增加配给。 具体操作分为两步&#xff1a;一&#xff1a;通过虚拟机界面添加分配的磁盘配给&#xff1b;二&#xff1a;将新分配的配给给使用起来。 操作 添加磁盘配给 在虚拟机内部添加新分配的配给…

安装Intel Realsense D435i驱动与ROS包报错

1.下载安装realsense SDK 1.1 安装依赖 sudo apt install libudev-dev pkg-config libgtk-3-dev sudo apt install libusb-1.0-0-dev pkg-config sudo apt install libglfw3-dev sudo apt install libssl-dev1.2 权限 cd librealsense/ sudo cp config/99-realsense-libusb.…

独享代理VS共享代理,新手选择攻略

随着互联网的广泛普及和应用&#xff0c;涉及网络隐私、数据安全和网络访问控制的问题变得越来越重要。代理服务器作为一种常见的网络工具&#xff0c;可以在跨境电商、海外社媒、SEO投放、网页抓取等领域发挥作用&#xff0c;实现匿名访问并加强网络安全。在代理服务器类别中&…

GoLand 2024 for Mac GO语言集成开发工具环境

Mac分享吧 文章目录 效果一、下载软件二、开始安装1、双击运行软件&#xff08;适合自己的M芯片版或Intel芯片版&#xff09;&#xff0c;将其从左侧拖入右侧文件夹中&#xff0c;等待安装完毕2、应用程序显示软件图标&#xff0c;表示安装成功3、打开访达&#xff0c;点击【文…

哪个牌子的充电宝牌子便宜好用?2024年性价比高充电宝排行榜!

在 2024 年&#xff0c;充电宝市场依旧琳琅满目&#xff0c;让人眼花缭乱。大家都在寻找那个既便宜又好用的充电宝&#xff0c;可面对众多品牌和产品&#xff0c;常常感到无从下手。别担心&#xff01;经过深入的市场调研和实际使用体验&#xff0c;我们为您精心整理出了 2024 …

掌握空投,预见未来:空投教程网,撸毛党的必备工具

随着区块链技术的发展&#xff0c;越来越多的人开始关注这一领域。对于很多人来说&#xff0c;如何有效地参与区块链项目&#xff0c;尤其是空投活动&#xff0c;成为了一个重要的问题。为了解决这一问题&#xff0c;“空投教程网”应运而生。作为一个专为撸毛党设计的工具&…

【高中数学/基本不等式】已知:x,y皆大于1,且x+2y=4 求:1/(x-1)+1/(y-1)的最小值为?

【问题来源】 https://www.ixigua.com/7025123539728466469?logTag1c2fd2e305d60e6277ab 之第一题 【问题】 已知&#xff1a;x,y皆大于1&#xff0c;且x2y4 求&#xff1a;1/(x-1)1/(y-1)的最小值为&#xff1f; 【解答】 解&#xff1a; 若将(x2y)/41代入目标式&…

【PostgreSQL】数据基石:PostgreSQL的数据类型与表结构设计

目录 核心数据类型深入探索 基础数值与文本类型 日期与时间类型 特殊与复合类型 JSON与HSTORE 表的创建、修改与管理 创建与修改表结构 索引与性能优化 约束与数据完整性 核心数据类型深入探索 PostgreSQL以其多样化的数据类型著称&#xff0c;不仅包括标准SQL数据类型…

昇思25天学习打卡营第04天 | 数据集 Dataset

昇思25天学习打卡营第04天 | 数据集 Dataset 文章目录 昇思25天学习打卡营第04天 | 数据集 Dataset数据集加载数据集迭代数据集的变换shufflemapbatch 自定义数据集可随机访问数据集对象可迭代数据集生成器 总结打卡 数据集Dataset对原始数据进行封装、变换&#xff0c;为神经网…

光照老化试验箱在化工产品暴晒测试中的应用

概述 光照老化试验箱是一种模拟自然光照条件下材料老化情况的实验设备&#xff0c;广泛应用于化工、建材、电子、汽车等行业中对材料的耐候性、耐光性能等进行测试。通过模拟日光中的紫外线和温度等环境因素&#xff0c;加速材料老化过程&#xff0c;以此评估材料在长期使用中…

C语言编程-基于单链表实现贪吃蛇游戏

基于单链表实现贪吃蛇游戏 1.定义结构体参数 蛇行走的方向 蛇行走的状态 蛇身节点类 维护蛇的结构体型 2.游戏运行前预备工作 定位光标位置 游戏欢迎界面 绘制游戏地图&#xff08;边界&#xff09; 初始化游戏中的蛇身 创建食物 3.游戏运行 下一个位置是食物&#xff0c;就吃掉…