1. 程式人生 > >NodeJS研究筆記,利用目錄來實現跨平臺檔案鎖

NodeJS研究筆記,利用目錄來實現跨平臺檔案鎖

檔案加鎖是多執行緒或多程序開發時,確保檔案資源不被併發讀寫所汙染的有效措施,NodeJS提供了一些辦法確保在競爭性環境下,檔案資料的一致性。

NodeJS保證檔案讀取一致性的辦法有兩種,一是讀取檔案時設定隔離標誌,隔離標誌是指,當使用fs模組開啟檔案時,在開啟模式中,新增一個x標記。例如下面的程式碼將以獨佔的方式去讀寫一個檔案:

fs.open('config.loc', 'wx', function(err) {
    if (err) return console.error(err);

    //這裡開始可以放心的讀寫檔案
});

在open呼叫中,第二個引數中所包含的’x’,指的就是要以獨佔的方式開啟指定檔案, 如果指定檔案已經被別的程序或執行緒打開了,那麼if(err)的判斷成立,函式就會直接列印錯誤資訊並返回。

利用隔離標誌實現檔案的原子操作有一個缺陷,並不是所有的平臺都會支援該標誌,例如要讀取網路硬碟上的檔案,那麼該標誌很可能會被忽略掉。因此使用隔離標誌讀取跨平臺的檔案時,隔離作用很可能會失效。

為了彌補這個缺陷,更好的辦法是使用目錄來實現檔案鎖。mkdir是標準的POSIX系統呼叫,根據標準,它的實現必須是原子性的。任何支援該呼叫的平臺都必須保證它執行的原子性,利用這個特性來構建檔案鎖的話,程式碼就能得到很好的跨平臺支援,即使是讀取網路檔案,也能保證檔案的一致性不會被破壞。接下來我們看看,如何在NodeJS中使用mkdir實現檔案鎖。

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(); }); }); }

lockDir 表示目錄名,lock是一個匯出函式,當外部模組要想獲取檔案鎖,那麼呼叫函式lock即可。lock的實現原理是,呼叫mkdir生成一個目錄,如果有多個執行緒或程序同時呼叫lock,那麼在lock中,將同時呼叫mkdir去生成同一個目錄,但是由於mkdir的原子性是由實現該呼叫的平臺必須保證的,所以即使多個程序同時呼叫lock,那麼也只有一個程序可能成功,其他的都會失敗。一旦失敗,呼叫程序的回撥函式會被lock呼叫。成功的程序,它的程序pid會被寫入到目錄裡,以方便除錯,同時把hasLock標誌位設定為true, 一旦lock返回成功,程序就可以放心的讀取檔案了。

釋放檔案鎖也簡單,只要把目錄給刪除就可以了,程式碼如下:

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();
        }) ;
    });
}

利用unlink解除程序對目錄的獲取許可權,然後呼叫rmdir將目錄刪除,執行unlock後,其他執行緒或程序才有機會通過呼叫 lock獲取檔案鎖。

我們把上面的檔案鎖實現成一個單獨的模組lock.js,如下:

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();
        }) ;
    });
}

process.on('exit', function() {
    if (hasLock) {
        fs.unlinkSync(lockDir + '/' + process.pid);
        fs.rmdirSync(lockDir);
        console.log('removed lock');
    }
});

lock.js的使用方式如下:

var locker = require('./lock');
locker.lock(function(err) {
    if (err) throw err;

    /*
    這裡可以放心的讀取相關檔案
    */

    locker.unlock(function() {});
})