项目背景:
通过将本地文件压缩打包,将zip转换成buffer上传到数据库
十分简单的一个操作流程,自己在测试的过程中发现再从数据库上面下载下来并且进行转换zip文件时,文件格式的不正确,为此修改了很多方法,正常单元测试都是正确的,唯独运行service的时候出现了问题。
通过一系列的操作,发现最终的问题是在压缩文件还没有完全写完时,父进程就开始进行读取压缩问题,为此,这个问题为什么会发生呢?
两个小疑问
1. 压缩文件什么时候会返回压缩成功的指令?
2. 为什么父子进程可以对一个文件进行同步的读写操作?
问题一: 压缩文件什么时候会返回压缩成功的指令?
通过官方的快速入门代码和注释说明压缩的问题在哪里 archiver - npm
项目是通过使用一个Promise函数,当监听到执行完时,resolve一个值给下一个函数继续执行。
问题就在于什么时候返回,而项目中是在end返回的,通过下述的官方注释,可以看出close和end的不同
'end' | end这个信号产生的时候就是资源用完,因为是使用流写入,简单理解就是管道pipe没有数据了 |
'close' | 这个信号是在整个文件写完时产生的,就是关闭问题 |
正常理解觉得管道没有数据和写完应该差不多概念,因为没有资源了就写完了,但是实际上并不是的,pipe资源消耗完代表你要写入的数据已经全部写入,但是不代表整个文件已经完成,因为文件自身也有属性需要写入,所以end只能代表你要写的写完了,但文件还没写完,close才是真正写完。具体体现在通过测试可以发现,end时候对文件进行读出会比close时少1KB(这个操作正常也是不存在的,需要存在父子进行,这是下面问题所在)
// listen for all archive data to be written
// 'close' event is fired only when a file descriptor is involved
output.on('close', function() {
console.log(archive.pointer() + ' total bytes');
console.log('archiver has been finalized and the output file descriptor has closed.');
});
// This event is fired when the data source is drained no matter what was the data source.
// It is not part of this library but rather from the NodeJS Stream API.
// @see: https://nodejs.org/api/stream.html#stream_event_end
output.on('end', function() {
console.log('Data has been drained');
});
问题二:为什么父子进程可以对一个文件进行同步的读写操作?
子进程使用的是 fs.createWriteStream(), 而父进程使用的是 fs.createReadStream();
在同一个进程中,我们如果同步执行这两个操作对一个问题,系统会自动报错,目前推测是fs这个module自身带有锁
而在两个进程中,也是是父进程,for() 一个子进程时,相互资源独立,那么对于一个文件,子进程写入,父进程也可以读出,并没有任何的进程锁。这是容易忽略的。如果对于两个进程之间怎么对系统资源进行加锁,开多一个进程进行管理文件系统,
当然可以加锁,加锁方案如下:
在NodeJS中利用mkdir实现文件锁。
mkdir是标准的POSIX系统调用,根据标准,它的实现必须是原子性的。任何支持该调用的平台都必须保证它执行的原子性,利用这个特性来构建文件锁的话,代码就能得到很好的跨平台支持,即使是读取网络文件,也能保证文件的一致性不会被破坏。
// 加入锁
var fs = require('fs');
var hasLock = false;
var lockDir = 'config.lock';
exports.lock = function(cb) {
if (hasLock) return cb();
fs.mkdir(lockDir, function(err) {
if (err) return cb(err);
fs.writeFile(lockDir + '/' + process.pid, function(err) {
if (err) console.error(err);
hasLock = true;
return cb();
});
});
}
// 释放锁
exports.unlock = function(cb) {
if (!hasLock) return cb();
fs.unlink(lockDir + '/' + process.pid, function(err) {
if (err) return cb(err);
fs.rmdir(lockDir, function(err) {
if (err) return cb(err);
hasLock = false;
cb();
}) ;
});
}