scan-thread-pool 技术文档
该项目是用于扫描文件系统的 Node.js 工具,其主要目标是高效地查找并计算指定目录下所有 node_modules 文件夹的大小。项目利用多线程技术来并发执行扫描任务,从而显著提高在大型项目或磁盘上的扫描性能
- 并发扫描: 利用 Node.js 的
worker_threads模块,将文件扫描任务分配到多个线程中,避免阻塞主线程 - 响应式数据流: 使用 RxJS 的
Subject来处理和传递扫描结果,实现了模块间的解耦 - 动态任务分配: 采用轮询调度算法,将扫描任务动态、均匀地分配给线程池中的工作线程
- 目录大小计算: 集成了
get-folder-size库,用于准确计算目录的磁盘占用空间
整体架构图和技术栈说明:
text
+-----------------+ +-----------------+
| Main Thread |----->| ScanService |
| (index.ts) | | (scan.ts) |
+-----------------+ +-----------------+
^ |
| | 1. Distributes tasks
| v
+-----------------+ +-----------------+
| RxJS Subject |<-----| Worker Thread |
| (Data Stream) | | (scan.worker.ts)|
+-----------------+ +-----------------+
|
| 2. Scans filesystem
v
+-----------------+
| FileWalker |
| (file-walker.ts)|
+-----------------+各组件/模块的职责划分:
index.ts(主线程): 应用程序的入口,负责初始化ScanService,创建 RxJSSubject,并订阅最终的扫描结果ScanService(扫描服务): 核心协调器,负责创建和管理工作线程池,通过MessageChannel与工作线程通信,并使用轮询算法分发扫描任务scan.worker.ts(工作线程): 在独立的线程中执行文件扫描任务。每个工作线程包含一个FileWalker实例,并通过MessagePort与主线程进行通信FileWalker(文件遍历器): 封装了文件系统遍历的底层逻辑,使用fs.promises.opendir异步读取目录内容,并通过EventEmitter发出扫描结果
数据流向和接口设计:
- 初始化:
index.ts启动ScanService,ScanService根据 CPU 核心数创建相应数量的工作线程,并为每个线程建立一个MessageChannel - 任务分发:
ScanService将初始扫描路径作为一个任务,通过轮询算法选择一个工作线程,并通过MessagePort将任务发送出去 - 文件扫描: 工作线程接收到任务后,驱动
FileWalker开始遍历文件系统。FileWalker采用广度优先策略,将发现的子目录作为新的结果发出 - 结果回传: 工作线程监听到
FileWalker的newResult事件后,将结果通过MessagePort回传给ScanService - 结果处理:
ScanService接收到结果后,判断路径是否为node_modules。如果是,则通过 RxJSSubject将路径推送到最终结果流;如果不是,则将其作为新的扫描任务,重新分发给工作线程 - 最终输出:
index.ts中的订阅者接收到node_modules路径后,调用getSize()计算其大小并输出到控制台
3. 详细实现
-
核心功能的技术实现细节:
- 线程池管理:
ScanService的initWorkers方法利用os.cpus().length来动态创建与 CPU 核心数相等的工作线程,以最大化并行处理能力。 - 高效通信: 主线程与工作线程之间使用
MessageChannel进行通信。通过在postMessage时传递[port2],MessagePort的所有权被转移,避免了数据序列化和反序列化的开销。 - 异步文件I/O:
FileWalker使用fs.promises.opendir和dir.read()进行全异步的目录读取,确保了高吞吐量和非阻塞的执行。
- 线程池管理:
-
关键算法或业务流程说明:
- 广度优先扫描 (BFS): 项目通过任务队列实现了广度优先的文件系统遍历。这种策略确保了扫描是逐层进行的,有助于平均分配扫描负载。
- 轮询调度 (Round-Robin):
ScanService中的addJob方法通过一个简单的索引递增和取模操作,实现了轮询调度,确保了扫描任务在工作线程之间均匀分布。
5. API 文档
ScanService类:startScan(stream$: Subject<string>, path: string): 启动扫描过程stream$: 用于接收node_modules路径的 RxJSSubjectpath: 初始扫描目录
FileWalker类:enqueueTask(path: string): 将一个目录路径加入到内部任务队列以供处理events(EventEmitter):newResult事件: 当一个目录被扫描完成时触发,负载为{ results: Array<{ path: string; isTarget: boolean }> }