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); // 创建一个共享内存Bufferconst sharedArray = new Int32Array(sharedBuffer); // 创建TypedArray来操作共享内存const worker = new Worker(__filename, {workerData: sharedBuffer});Atomics.store(sharedArray, 0, 1); // 在位置0写入1console.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();// 发送共享资源给每个workerworker.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.mzph.cn/bicheng/38892.shtml

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

相关文章

Linux中vim文本编辑器你了解多少?

在Linux中&#xff0c;vim&#xff08;Vi IMproved&#xff09;是一个非常强大的文本编辑器&#xff0c;它基于更古老的vi编辑器&#xff0c;并添加了许多增强功能。vim有几种不同的模式&#xff0c;包括命令模式、插入模式和可视模式&#xff0c;每种模式都有特定的功能和快捷…

音乐:触动心灵的艺术语言

Enjoy your music 音 乐 作为一种跨越时空和文化的艺术形式&#xff0c;拥有着无穷的魅力和力量。 它不仅能够带给我们愉悦的听觉享受&#xff0c;还对我们的身心健康、认知发展和社会交往产生着深远的影响。 一、音乐的基本元素 音乐由多个基本元素构成&#xff0c;包括…

【CentOS7.6】yum 报错:Could not retrieve mirrorlist http://mirrorlist.centos.org

一、报错 1.报错内容如下 在使用 yum makecache 命令时报错&#xff0c;在 yum install -y xxx 的时候报错等等 [roothcss-ecs-a901 yum.repos.d]# yum makecache Loaded plugins: fastestmirror Determining fastest mirrors Could not retrieve mirrorlist http://mirrorl…

Linux 常见的几种编辑器的操作步骤

在大多数命令行文本编辑器中&#xff0c;保存并关闭文件的操作方式基本相似。以下是常见的几种编辑器的操作步骤&#xff1a; 使用 vi 编辑器保存并关闭文件 编辑文件&#xff1a; sudo vi /path/to/file 编辑内容&#xff1a; 按 i 进入插入模式&#xff0c;编辑文件内容。 …

Go 语言条件语句

Go 语言条件语句 在编程语言中&#xff0c;条件语句用于根据特定条件执行不同的代码路径。Go 语言提供了几种条件语句&#xff0c;包括 if、switch 和 select。这些语句使得程序可以根据不同的条件执行不同的操作&#xff0c;从而实现程序的逻辑分支。 1. if 语句 if 语句是…

【web APIs】快速上手Day03

目录 Web APIs - 第3天全选文本框案例事件流事件捕获事件冒泡阻止冒泡解绑事件on事件方式解绑addEventListener方式解绑 注意事项-鼠标经过事件的区别两种注册事件的区别 事件委托综合案例-tab栏切换改造 其他事件页面加载事件元素滚动事件页面滚动事件-获取位置页面滚动事件-滚…

巴西东南湾乌巴图巴 ANTARES 监测站数据

ANTARES monitoring station in Ubatuba, Southeast Brazilian Bight 巴西东南湾乌巴图巴 ANTARES 监测站 简介 ANTARES 区域网络由分布在拉丁美洲的沿岸时间序列站组成。主要目的是研究气候和人为影响引起的长期变化&#xff0c;以及用于卫星匹配和算法开发的海洋颜色。Uba…

SoftCLT: 时间序列的软对比学习《Soft Contrastive Learning for Time Series》(时间序列、时序分类任务、软...

2024年6月25日&#xff0c;10:11&#xff0c;好几天没看论文了&#xff0c;一直在摸鱼写代码(虽然也没学会多少)&#xff0c;今天看一篇师兄推荐的。 论文&#xff1a; Soft Contrastive Learning for Time Series 或者是&#xff1a; Soft Contrastive Learning for Time Seri…

[C++][设计模式][组合模式]详细讲解

目录 1.动机(Motivation)2.模式定义3.要点总结4.代码感受 1.动机(Motivation) 软件在某些情况下&#xff0c;客户代码过多地依赖于对象容器复杂的内部实现结构&#xff0c;对象容器内部实现结构(而非抽象结构)的变化引起客户代码的频繁变化&#xff0c;带来了代码的维护性、扩…

【Android面试八股文】什么是ANR?如何分析和定位ANR?如何避免ANR?

文章目录 一、ANR概述二、触发ANR的主要场景三、Android四大组件中的潜在的ANR风险五、避免ANR的实践建议六、ANR的产生原因与出现的场景6.1 原因:6.2 出现场景:七、ANR的定位与分析7.1. ANR分析思路——traces7.2 ANR其他分析思路与相关日志7.2.1 分析logcat思路7.2.2 分析k…

解释Java中的Java IO流模型。

Java IO流模型是Java中用于处理输入和输出的基础模型。它通过一组类和接口来实现文件、网络和其他I/O设备的读写操作。Java IO流模型主要包括以下几个部分&#xff1a; 1. **InputStream和OutputStream**&#xff1a;这些类是Java IO的核心&#xff0c;它们提供了一种抽象的方…

如何避免爬取网站时IP被封?

互联网协议 (IP) 地址是识别网络抓取工具的最常见方式。IP 是每个互联网交换的核心&#xff0c;对其进行跟踪和分析可以了解很多有关连接客户端的信息。 在网络抓取中&#xff0c;IP 跟踪和分析&#xff08;又名指纹&#xff09;通常用于限制和阻止网络抓取程序或其他不需要的访…

计算两个经纬度之间的球面距离(基于Mysql和PHP实现)

计算两个经纬度之间的球面距离 1、MySQL实现方式 - 基于空间函数(ST_Distance_Sphere)实现 前置条件&#xff1a;确保您使用的是 MySQL 8.0 或更高版本&#xff0c;因为较早的版本对地理空间的支持有限。 1.1 创建表和索引 说明&#xff1a;设置 location 为 point 类型 #…

hive乱码问题完全解决方案

修改数据库编码集 SET GLOBAL character_set_client utf8; SET GLOBAL character_set_connection utf8; SET GLOBAL character_set_database utf8; SET GLOBAL character_set_results utf8; SET GLOBAL character_set_server utf8; show variables like ‘character_set%…

c++ set和unordered_set区别

一.set介绍 C 中的 set 容器是一种关联容器&#xff0c;用于存储唯一的元素&#xff0c;并能够根据特定的顺序对元素进行排列。在这里&#xff0c;我们将对 set 容器进行详细的分析。 概述 set 容器是 C标准库中的一个部分&#xff0c;位于 头文件中。它是一个关联容器&…

基于Java的智能城市解决方案

基于Java的智能城市解决方案 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;今天我们将探讨基于Java的智能城市解决方案&#xff0c;探索如何利用现代技术构建智…

【Python数据分析及环境搭建】:教程详解1(第23天)

系列文章目录 Python进行数据分析的优势常用Python数据分析开源库介绍启动Jupyter服务Jupyter Notebook的使用 文章目录 系列文章目录前言学习目标1. Python进行数据分析的优势2. 常用Python数据分析开源库介绍2.1 NumPy2.2 Pandas2.3 Matplotlib2.4 Seaborn2.5 Sklearn2.6 Ju…

[AHK V2]鼠标悬停展开窗口,鼠标离开折叠窗口

演示鼠标悬停窗口标题栏则展开窗口&#xff0c;鼠标离开窗口标题栏则折叠窗口。 ;作者&#xff1a;sunwind ;日期&#xff1a;2024年6月30日11:36:08 ;脚本&#xff1a;演示鼠标悬停窗口标题栏则展开窗口&#xff0c;鼠标离开窗口标题栏则折叠窗口。 MyGui : Gui() mytext:MyG…

第一 二章 小车硬件介绍-(全网最详细)基于STM32智能小车-蓝牙遥控、避障、循迹、跟随、PID速度控制、视觉循迹、openmv与STM32通信、openmv图像处理、smt32f103c8t6

第一篇-STM32智能小车硬件介绍 后续章节也放这里 持续更新中&#xff0c;视频发布在小B站 里面。这边也会更新。 B站视频合集: STM32智能小车V3-STM32入门教程-openmv与STM32循迹小车-stm32f103c8t6-电赛 嵌入式学习 PID控制算法 编码器电机 跟随 小B站链接:https://www.bilib…

启航IT世界:高考后假期的科技探索之旅

随着高考的落幕&#xff0c;新世界的大门已经为你们敞开。这个假期&#xff0c;不仅是放松身心的时光&#xff0c;更是为即将到来的IT学习之旅打下坚实基础的黄金时期。以下是一份专为你们准备的IT专业入门预习指南&#xff0c;希望能助你们一臂之力。 一&#xff1a;筑基篇&a…