UNPKG

sonofs

Version:

sono nodejs distributed file system

687 lines (597 loc) 25 kB
const cluster = require('cluster'); const net = require('net'); const fs = require('fs'); const path = require('path'); const { formatFile, pad, parseFileName, getBundlePath } = require('./util'); const socketUtils = require('./socket-utils'); const { startSlave } = require('./slave'); const MAX_BUNDLE_ID = 256 * 256 * 256 - 1; const MAX_FILE_SIZE = 1024 * 1024 * 1024 - 1; const MAX_CALLBACK_ID = 256 * 256 * 256 * 256 - 100; function startMaster(cfg, readyFn) { if (!cluster.isMaster) throw new Error('startMaster must in cluster master'); const { serverId, root, groupId, logger = console, isSlave = false, port, registry } = cfg; const serverCfgPath = path.join(root, 'server.cfg'); const filesIdxPath = path.join(root, 'files.idx'); let currentBundleId; let currentBundleSize; let savedBundleSize; let idxFileSize; function start() { loadCfg(serverCfgPath, (err, cfgFd) => { if (err) { readyFn && readyFn(err); return; } fs.open(filesIdxPath, 'a', (err, filesIdxFd) => { if (err) { readyFn && readyFn(err); return; } fs.stat(filesIdxPath, (err, stats) => { if (err) { readyFn && readyFn(err); return; } readyFn && readyFn(null); idxFileSize = stats.size; registerWorkerListener(cfgFd, filesIdxFd); startServerRegistryHb(); if (isSlave) { startSlave({ logger, cfg, cfgFd, bundle: { get currentBundleId() { return currentBundleId; }, set currentBundleId(val) { currentBundleId = val; }, get currentBundleSize() { return currentBundleSize; }, set currentBundleSize(val) { currentBundleSize = val; } } }); } }); }); }); } start(); function loadCfg(serverCfgPath, complete) { fs.open(serverCfgPath, 'wx', (err, fd) => { if (err) { if (err.code === 'EEXIST') { fs.readFile(serverCfgPath, (err, buf) => { if (err) { return complete(err); } console.log(buf); // buf[0] : serverId // buf[1~3] : bundleId // buf[4~7] : bundleSize const sid = buf.readUInt8(); if (sid !== serverId) { return complete(new Error('服务器ID错误!')); } currentBundleId = buf.readUIntBE(1, 3); savedBundleSize = currentBundleSize = buf.readUIntBE(4, 4); fs.open(serverCfgPath, fs.constants.O_WRONLY | fs.constants.O_CREAT, (err, fd) => { if (err) { complete(err); } else { complete(null, fd); } }); }); } else { complete(err); } } else { fs.write(fd, Buffer.from([serverId, 0, 0, 0, 0, 0, 0, 0]), (err) => { if (err) { return complete(err); } currentBundleId = 0; savedBundleSize = currentBundleSize = 0; complete(null, fd); }); } }); } const uploadingFiles = []; function registerWorkerListener(cfgFd, filesIdxFd) { const messageListener = (msg) => { if (!msg || !msg.workerId || !msg.callbackId) { logger.info('bad message:', msg); return; } // logger.info('proccess handler:', msg); const { type, workerId, callbackId } = msg; const worker = cluster.workers[workerId]; if (isSlave) { worker.send({ success: false, callbackId, error: 'slave机不可上传文件!' }); return; } if (type === 'GET_CURRENT_BUNDLE') { worker.send({ success: true, callbackId, bundleSize: savedBundleSize, bundleId: currentBundleId }); } else if (type === "ALLOC_FILE_SPACE") { const { fileSize } = msg; let newFileSize = currentBundleSize + fileSize; if (currentBundleSize < MAX_FILE_SIZE) { currentBundleSize = newFileSize; } else { currentBundleId++; currentBundleSize = newFileSize = fileSize; if (currentBundleId > MAX_BUNDLE_ID) { worker.send({ success: false, callbackId, error: '文件数量大于' + MAX_BUNDLE_ID + ',请更换磁盘!' }); return; } } const bundleId = currentBundleId; const idxFileOffest = idxFileSize; idxFileSize += 12; const buf = Buffer.alloc(8); buf.writeUIntBE(serverId, 0, 1); buf.writeUIntBE(bundleId, 1, 3); buf.writeUIntBE(newFileSize, 4, 4); logger.info("serverId", serverId); logger.info("newFileSize", newFileSize); logger.info("write", buf); // 将文件信息写入cfg文件 fs.write(cfgFd, buf, 0, 8, 0, (err) => { if (err) { worker.send({ success: false, callbackId, error: err }); return; } // files.idx: 文件索引,每个文件索引占12byte : dir:1 subdir:1 file:1 mime:1 fileStart:4 fileSize:4 const idxBuf = Buffer.alloc(12); const dirId = bundleId >> 16; const subDirId = (bundleId & 65535) >> 8; const fileId = bundleId & 255; const { mime } = msg; const fileStart = newFileSize - fileSize; idxBuf.writeUIntBE(dirId, 0, 1); idxBuf.writeUIntBE(subDirId, 1, 1); idxBuf.writeUIntBE(fileId, 2, 1); idxBuf.writeUIntBE(mime, 3, 1); idxBuf.writeUIntBE(fileStart, 4, 4); idxBuf.writeUIntBE(fileSize, 8, 4); fs.write(filesIdxFd, idxBuf, 0, 12, idxFileOffest, (err) => { if (err) { worker.send({ success: false, callbackId, error: err }); return; } const dir = pad(dirId.toString(16), 2); const subDir = pad(subDirId.toString(16), 2); const file = pad(fileId.toString(16), 2); const fileDir = path.join(root, dir, subDir); const filePath = path.join(fileDir, file + '.snf'); fs.mkdir(fileDir, { recursive: true }, () => { const fileName = formatFile({ groupId, serverId, dir, subDir, file, mime, fileStart, fileSize }); uploadingFiles.push(fileName); worker.send({ success: true, callbackId, fileStart, fileName, fileSize, bundleId, mime, dir, subDir, file, filePath }); }); }); }); } else if (type === 'UPLOAD_SUCCESS') { const { fileName } = msg; for (let i = uploadingFiles.length; i >= 0; i--) { if (uploadingFiles[i] == fileName) { uploadingFiles.splice(i, 1); } } if (uploadingFiles.length == 0) { logger.info('all upload success - oldBundleSize:', savedBundleSize, 'currentBundleSize:', currentBundleSize); savedBundleSize = currentBundleSize; } } }; logger.info('currentBundleId:', currentBundleId, 'currentBundleSize:', currentBundleSize, 'idxFileSize:', idxFileSize); for (const id in cluster.workers) { cluster.workers[id].on('message', messageListener); } } /** * 启动服务器注册服务 */ function startServerRegistryHb() { let hbTimeout; let registryClient; function registerServer() { if (hbTimeout) { clearTimeout(hbTimeout); hbTimeout = null; } const serverBuf = Buffer.from([1, groupId, serverId, 0, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3]); serverBuf.writeUInt8(isSlave ? 1 : 0, 3); serverBuf.writeUInt32BE(port, 4); serverBuf.writeUIntBE(currentBundleId, 8, 3); serverBuf.writeUInt32BE(currentBundleSize, 11); if (!registryClient) { registryClient = net.createConnection({ host: registry.host, port: registry.port }, () => { socketUtils.sendBlock(registryClient, 1, serverBuf); }) .on('timeout', () => { registryClient.end(); }) .on('error', handleRegistryUnkowError) .on('end', handleRegistryUnkowError) .on('close', handleRegistryUnkowError) .on('data', (buf) => { let timeout; // 失败 if (buf.length == 1 && buf.readUInt8() === 0) { timeout = 3000; } else { timeout = 5000; } hbTimeout = setTimeout(() => { registerServer(); }, timeout); }); } else { socketUtils.sendBlock(registryClient, 1, serverBuf); } } function handleRegistryUnkowError() { registryClient = null; if (!hbTimeout) { logger.info('disconnected from register!'); hbTimeout = setTimeout(registerServer, 5000); } } registerServer(); } } exports.startMaster = startMaster; function startWorker(cfg) { if (!cluster.isWorker) throw new Error('startWorker必须在子线程中运行!'); const { root, port, logger = console } = cfg; const { allocFileSpace, getCurrentBundle } = createMessageChannel(); let filePromise; const server = net.createServer((socket) => { logger.info('client connected! workerId:', cluster.worker.id); let uploadingFileName; // 操作类型: enum { 0: '读', 1: '写', 2: '断点续传', 3: '同步文件给slave' } let type; let allBuf; let total; let reading = false; let readingSize = false; socket .on('end', () => { if (type == 1 || type == 2) { process.send({ type: 'UPLOAD_SUCCESS', fileName: uploadingFileName, callbackId: -1, workerId: cluster.worker.id, }); } logger.info('client disconnected! workerId:', cluster.worker.id); }) .on('error', (e) => { logger.error('server error! workerId:', cluster.worker.id, 'error:', e); }); function readBuffer(chunk) { if (!reading) { reading = true; type = chunk.readUInt8(0); allBuf = chunk; readingSize = false; if (type == 0) { // 文件信息18字节 total = 18; } else if (type == 3) { // slave服务器信息12字节 total = 12; } else if (allBuf.length < 5) { readingSize = true; return; } else { total = allBuf.readUInt32BE(1); } } else { allBuf = Buffer.concat([allBuf, chunk]); } if (readingSize && allBuf.length > 5) { readingSize = false; total = allBuf.readUInt32BE(1); } if (allBuf.length >= total) { const buf = allBuf; reading = false; allBuf = null; if (buf.length > total) { handleBuffer(buf.slice(0, total)); readBuffer(buf.slice(total)); } else { handleBuffer(buf); } } } function handleBuffer(buf) { if (type == 0) { // 获取文件 const fileName = buf.toString('utf8', 1); const { dir, subDir, file, fileStart, fileSize } = parseFileName(fileName); const filePath = path.join(root, dir, subDir, file); fs.open(filePath, 'r', (err, fd) => { if (err) { logger.error(err); socket.write(Buffer.from([0])); return; } const readBuf = Buffer.alloc(fileSize); fs.read(fd, readBuf, 0, fileSize, fileStart, (err, bytesRead, buffer) => { if (err) { logger.error(err); socket.write(Buffer.from([0])); return; } socket.cork(); socket.write(Buffer.from([1])); socket.write(buffer); socket.uncork(); }); }); } else if (type === 1) { // 小文件(小于256*256*256)直接保存 const CLIENT_HEAD_LENGTH = 9; const mime = buf.readUInt8(5); const fileSize = buf.readUIntBE(6, 3); const headBuf = Buffer.from([0, 0, 0, 0, 0]); headBuf.writeUInt32BE(fileSize, 1); allocFileSpace(mime, fileSize) .then(({ fd, msg }) => { const { fileStart, fileName } = msg; uploadingFileName = fileName; fs.write(fd, buf, CLIENT_HEAD_LENGTH, buf.length - CLIENT_HEAD_LENGTH, fileStart, (err) => { if (err) { socket.write(headBuf); return; } headBuf.writeUInt8(1, 0); socket.write(Buffer.concat([headBuf, Buffer.from(fileName, 'utf8')])); }); }) .catch(() => { socket.write(headBuf); }); } else if (type === 2) { // 较大文件断点续传 const mime = buf.readUInt8(5); const fileSize = buf.readUIntBE(6, 4); if (!filePromise) { filePromise = allocFileSpace(mime, fileSize); } let sliceSize = 0; do { const offset = buf.readUIntBE(sliceSize + 10, 4); const rangeSize = buf.readUIntBE(sliceSize + 14, 3); const rangeOffset = sliceSize + 17; const headBuf = Buffer.from([0, 0, 0, 0, 0]); headBuf.writeUInt32BE(rangeSize, 1); filePromise .then(({ fd, msg }) => { const { fileStart, fileName } = msg; uploadingFileName = fileName; fs.write(fd, buf, rangeOffset, rangeSize, fileStart + offset, (err) => { if (err) { socket.write(headBuf); return; } headBuf.writeUInt8(1, 0); socket.write(Buffer.concat([headBuf, Buffer.from(fileName, 'utf8')])); }); }) .catch(() => { socket.write(headBuf); }); sliceSize = rangeOffset + rangeSize; } while (sliceSize < buf.length); } else if (type === 3) { // 同步文件给slave const slaveBundleId = buf.readUIntBE(1, 3); const slaveBundleSize = buf.readUInt32BE(4); const callbackSyncId = buf.readUInt32BE(8); getCurrentBundle((currentBundle) => { if (slaveBundleId == currentBundle.bundleId) { // 未生成新bundle if (slaveBundleSize == currentBundle.bundleSize) { // 无新文件 socket.write(Buffer.from([0])); } else { // 当前bundle有新文件 sendBundleToSlave(callbackSyncId, currentBundle.bundleId, getBundlePath(root, slaveBundleId), slaveBundleSize, currentBundle.bundleSize, socket); } } else if (slaveBundleId < currentBundle.bundleId) { // 已有新bundle const bundlePath = getBundlePath(root, slaveBundleId); fs.stat(bundlePath, (err, stats) => { if (err) { socket.write(Buffer.from([0])); return; } if (slaveBundleSize >= stats.size) { // 读取新bundle const newBundleId = slaveBundleId + 1; if (newBundleId === currentBundle.bundleId) { sendBundleToSlave(callbackSyncId, newBundleId, getBundlePath(root, newBundleId), 0, currentBundle.bundleSize, socket); } else { sendNewBundleToSlave(callbackSyncId, newBundleId, getBundlePath(root, newBundleId), socket); } } else { // 同步slave bundle sendBundleToSlave(callbackSyncId, slaveBundleId, bundlePath, slaveBundleSize, stats.size, socket); } }); } else { // slave 的 bundleId 不应该大于 master 的 bundleId socket.write(Buffer.from([0])); } }); } } socket.on('data', readBuffer); }); server.on('error', (err) => { logger.error(err); }); server.listen(port, () => { logger.info('server bound workerId:', cluster.worker.id); }); } function sendNewBundleToSlave(syncId, newBundleId, newBundlePath, socket) { fs.stat(newBundlePath, (err, stats) => { if (err) { socket.write(Buffer.from([0])); return; } sendBundleToSlave(syncId, newBundleId, newBundlePath, 0, stats.size, socket); }); } function sendBundleToSlave(syncId, syncingBundleId, syncingBundlePath, slaveBundleSize, bundleFileSize, socket) { const head = Buffer.alloc(12); head.writeUInt8(1); head.writeUIntBE(syncingBundleId, 1, 3); head.writeInt32BE(bundleFileSize, 4); head.writeInt32BE(syncId, 8); socket.write(head); const rs = fs.createReadStream(syncingBundlePath, { start: slaveBundleSize, end: bundleFileSize - 1, autoClose: true }); rs.on('data', (chunk) => { socket.write(chunk); }); } function createMessageChannel() { const callbacks = {}; let cid = 0; // 写入前先向主线程申请文件空间 function allocFileSpace(mime, fileSize) { return new Promise((resolve, reject) => { if (cid >= MAX_CALLBACK_ID) { cid = 0; } const callbackId = ++cid; callbacks[callbackId] = (msg) => { console.log('allocFileSpace callback:', msg, 'workerId:', cluster.worker.id); const { success, filePath } = msg; if (success) { fs.open(filePath, fs.constants.O_WRONLY | fs.constants.O_CREAT, (err, fd) => { if (err) return reject(err); fs.fstat(fd, (err, stats) => { if (err) return reject(err); if (stats.size === 0) { // 预先分配1GB空间 fs.write(fd, Buffer.from([1]), 0, 1, MAX_FILE_SIZE, (err) => { if (err) return reject(err); resolve({ msg, fd }); }); } else { resolve({ msg, fd }); } }); }); } else { reject(msg); } }; process.send({ type: 'ALLOC_FILE_SPACE', callbackId, workerId: cluster.worker.id, mime, fileSize }); }); } function getCurrentBundle(cb) { if (cid >= MAX_CALLBACK_ID) { cid = 0; } const callbackId = ++cid; callbacks[callbackId] = cb; process.send({ type: 'GET_CURRENT_BUNDLE', callbackId, workerId: cluster.worker.id, }); } process.on('message', (msg) => { if (!msg || !msg.callbackId) { return; } const { callbackId } = msg; callbacks[callbackId](msg); delete callbacks[callbackId]; }); return { allocFileSpace, getCurrentBundle }; } exports.startWorker = startWorker;