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

news2024/11/17 12:16:25

我们的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.coloradmin.cn/o/1884342.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MacOS BurpSuite安装指南

burpsuite破解 用户家目录中创建文件夹burp 两个文件&#xff1a; burp最新版jar包 burpsuite_pro_v2024.3.1.4.jar 在哪下载&#xff1f; 官网&#xff1a;Professional / Community 2024.3.1.4 | Releases 百度云盘&#xff1a;链接: 百度网盘 请输入提取码 提取码: sgsk …

线性代数|机器学习-P21概率定义和Markov不等式

文章目录 1. 样本期望和方差1.1 样本期望 E ( X ) \mathrm{E}(X) E(X)1.2 样本期望 D ( X ) \mathrm{D}(X) D(X) 2. Markov 不等式&Chebyshev不等式2.1 Markov不等式公式 概述2.2 Markov不等式公式 证明&#xff1a;2.3 Markov不等式公式 举例&#xff1a;2.4 Chebyshev不…

从架构设计的角度分析ios自带网络库和AFNetworking

总结&#xff08;先说明文章分析出的一些‘认知’&#xff09; 从本文中&#xff0c;我们可以总结出一些框架设计上的“认知”&#xff1a; 对于通用的常规配置信息方面的设计&#xff0c;我们可以通过定义一个“类似于NSURLSessionConfiguration、NSURLRequest”的类来完成设…

41、web基础和http协议

web基础与http协议 一、web web&#xff1a;就是我们所说得页面&#xff0c;打开网页展示得页面。&#xff08;全球广域网&#xff0c;万维网&#xff09; world wide webwww 分布式图形信息系统 http&#xff1a;超文本传输协议 https&#xff1a;加密的超文本传输协议…

上位机图像处理和嵌入式模块部署(mcu 项目1:固件编写)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 说完了上位机的开发&#xff0c;接下来就是固件的开发。前面我们说过&#xff0c;目前使用的开发板是极海apm32f103的开发板。它自身包含了iap示例…

人工智能--目标检测

欢迎来到 Papicatch的博客 文章目录 &#x1f349;引言 &#x1f349;概述 &#x1f348;目标检测的主要流程通常包括以下几个步骤 &#x1f34d;数据采集 &#x1f34d;数据预处理 &#x1f34d;特征提取 &#x1f34d;目标定位 &#x1f34d;目标分类 &#x1f348;…

千益畅行,旅游卡,如何赚钱?

​ 赚钱这件事情&#xff0c;只有自己努力执行才会有结果。生活中没有幸运二字&#xff0c;每个光鲜亮丽的背后&#xff0c;都是不为人知的付出&#xff01; #旅游卡服务#

【分布式数据仓库Hive】Hive的安装配置及测试

目录 一、数据库MySQL安装 1. 检查操作系统是否有MySQL安装残留 2. 删除残留的MySQL安装&#xff08;使用yum&#xff09; 3. 安装MySQL依赖包、客户端和服务器 4. MySQL登录账户root设置密码&#xff0c;密码值自定义&#xff0c;这里是‘abc1234’ 5. 启动MySQL服务 6…

element plus 日期组件中英文切换

现在的项目需要做中英文切换功能&#xff0c;我发现element plus 只有日期组件不能转换&#xff0c;然后上网查了一下并结合自己的方法写了出来。 代码&#xff1a; <template><!-- 日期框组件 --><div class"time-box">//:locale"locale&qu…

嵌入式UI开发-lvgl+wsl2+vscode系列:5、事件(Events)

一、前言 这节进行事件的总结&#xff0c;通过事件回调方式将用户和ui的交互行为绑定组合起来。 二、事件示例 1、示例1&#xff08;点击事件&#xff09; #include "../lv_examples.h" #if LV_BUILD_EXAMPLES && LV_USE_SWITCHstatic void event_cb(lv_…

解锁机器学习算法面试挑战课程

在这个课程中&#xff0c;我们将从基础知识出发&#xff0c;系统学习机器学习与算法的核心概念和实践技巧。通过大量案例分析和LeetCode算法题解&#xff0c;帮助您深入理解各种面试问题&#xff0c;并掌握解题技巧和面试技巧。无论是百面挑战还是LeetCode算法题&#xff0c;都…

华为智能驾驶方案剖析

华为ADS智驾方案始终坚持激光雷达毫米波雷达摄像头的多传感器融合路线&#xff0c;行业降本压力下硬件配置从超配逐步转向贴合实际需求&#xff0c;带动整体硬件成本下降。 1)单车传感器数量呈现下降趋势&#xff0c;包括激光雷达从3个减配至1个、毫米波雷达从6R减配至3R、摄像…

firewalld防火墙概念(形态、分类、区域)相关综合示例

目录 防火墙 概念 形态 内核态&#xff1a;netfilter 用户态&#xff1a;iptables、firewalld 防火墙分类 firewalld网络区域 区域划分 使用图形化界面配置防火墙 端口配置 协议配置 源端口配置 永久配置恢复默认防火墙规则 配置防火墙相关命令 示例 实验环境 …

谷粒商城篇章10 -- P262-P291/P295-P310 -- 订单服务(支付)【分布式高级篇七】

目录 1 页面环境搭建 1.1 静态资源上传到nginx 1.2 SwitchHosts增加配置 1.3 网关配置 1.4 订单模块基础配置 1.4.1 引入 thymeleaf 依赖 1.4.2 application.yml配置 1.4.3 bootstrap.properties配置 1.4.4 开启nacos注册发现和远程调用 1.5 修改各个页面的静态资源路…

Hadoop权威指南-读书笔记-01-初识Hadoop

Hadoop权威指南-读书笔记 记录一下读这本书的时候觉得有意思或者重要的点~ 第一章—初识Hadoop Tips&#xff1a; 这个引例很有哲理嘻嘻&#x1f604;&#xff0c;道出了分布式的灵魂。 1.1 数据&#xff01;数据&#xff01; 这一小节主要介绍了进入大数据时代&#xff0c;面…

使用简鹿音频格式转换器轻松将MP3转换为WAV音频

在音频处理领域&#xff0c;不同的格式有其特定的应用场景。有时&#xff0c;我们可能需要将MP3格式的音频转换为WAV格式&#xff0c;以满足特定的播放或编辑需求。简鹿音频格式转换器就是一款能够帮助我们轻松实现这一转换目标的工具。 为什么选择 WAV 格式&#xff1f; WAV …

CentOS中使用SSH远程登录

CentOS中使用SSH远程登录 准备工作SSH概述SSH服务的安装与启动建立SSH连接SSH配置文件修改SSH默认端口SSH文件传输 准备工作 两台安装CentOS系统的虚拟机 客户机&#xff08;192.168.239.128&#xff09; 服务器&#xff08;192.168.239.129&#xff09; SSH概述 Secure S…

【RabbitMQ实战】Springboot 整合RabbitMQ组件,多种编码示例,带你实践 看完这一篇就够了

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、对RabbitMQ管理界面深入了解1、在这个界面里面我们可以做些什么&#xff1f; 二、编码练习&#xff08;1&#xff09;使用direct exchange(直连型交换机)&a…

【深圳大学算法设计与分析】 实验六 最大流应用问题 FF -> EK -> Dinic

目录 一、实验目的&#xff1a; 二、内容&#xff1a;棒球赛问题 三、实验要求 四、提交要求 ———————— 问题分析解释&#xff1a; ———————— 算法简解&#xff1a; Ford–Fulkerson 增广 Edmonds–Karp 算法 Dinic算法 Dinic和EK的区别&#xff1a; …

DDPM(Denoising Diffusion Probabilistic Models)

DDPM&#xff08;Denoising Diffusion Probabilistic Models&#xff09; 笔记来源&#xff1a; 1.Denoising Diffusion Probabilistic Models 2.大白话AI | 图像生成模型DDPM | 扩散模型 | 生成模型 | 概率扩散去噪生成模型 3.pytorch-stable-diffusion 扩散模型正向过程&am…