Node.js部署Azure Web App后下载Blob文件出现Base64/Buffer随机损坏问题咨询
问题根因
setTimeout逻辑完全错误。你是先读取临时文件得到不完整的base64后,才等待2秒再返回结果,等待操作并没有提前到读取文件之前,完全起不到等待文件写入完成的作用。- Azure Web App的
./temp临时目录是网络挂载的共享存储,IO延迟远高于本地磁盘。旧版Azure Storage SDK的getBlobToLocalFile回调触发时,仅代表SDK侧的写入操作完成,网络存储侧可能还未完成数据同步,此时立即读取文件就会大概率读到不完整的内容。本地磁盘IO速度快,写入后立即读也能拿到完整数据,所以本地测试不会触发问题。 - 重试逻辑存在缺陷,仅判断
file是否为假值,不完整的base64属于合法字符串不会触发重试,直接返回给客户端。 - 额外的本地文件读写环节引入了不必要的IO故障点,完全可以省略。
修复方案
最优方案:省略本地文件写入环节
直接调用getBlobToBuffer接口将Blob下载到内存,直接转base64,彻底规避磁盘IO问题:
storageUtils.downloadBlobFromCloud = (subDirectory, fileName) => { if (!fileName) { return Promise.resolve(null); } return new Promise( (resolve, reject) => { blobService.getBlobToBuffer( container, `${subDirectory}/${fileName}`, function (error, serverBlob, buffer) { if (error) { console.log("下载Blob失败", error) return reject(error); } const base64 = buffer.toString('base64') resolve(base64); } ); }); };
如果你必须保留本地文件写入逻辑
将等待逻辑放在读取文件之前,同时校验下载的本地文件大小和Blob的实际大小是否一致:
storageUtils.downloadBlobFromCloud = (subDirectory, fileName) => { var dir = './temp'; // 加随机后缀避免同一毫秒多请求出现重名 var tempFilePath = dir + '/' + Date.now().toString() + '-' + Math.random().toString(36).slice(2) + '.temp'; console.log(tempFilePath) if (!fs.existsSync(dir)) { fs.mkdirSync(dir); } if (!fileName) { return Promise.resolve(null); } return new Promise( (resolve, reject) => { blobService.getBlobToLocalFile( container, `${subDirectory}/${fileName}`, tempFilePath, async function (error, serverBlob) { if (error) { console.log("下载Blob失败", error) return reject(error); } try { // 等待网络存储同步完成 await new Promise(r => setTimeout(r, 2000)); // 校验文件大小和Blob实际大小是否一致,避免读到不完整文件 const fileStat = fs.statSync(tempFilePath); if (fileStat.size !== serverBlob.contentLength) { throw new Error(`文件大小不匹配,预期${serverBlob.contentLength},实际${fileStat.size}`); } let base64 = await fileStream(tempFilePath); resolve(base64); // 延迟删除文件 setTimeout(()=>{ console.log('deleting file') fs.unlinkSync(tempFilePath); },10000); } catch (e) { console.log('处理文件失败', e) // 出错也清理临时文件 fs.unlink(tempFilePath, ()=>{}); reject(e); } } ); }); };
完善重试逻辑
捕获处理失败的错误触发重试,不要设置过高的重试次数避免触发接口限流:
let downloadFile = async (directory, fileName, attempts) => { attempts++; try { let file = await storageUtils.downloadBlobFromCloud(directory, fileName); return file; } catch (e) { if(attempts < 3) { return await downloadFile(directory, fileName, attempts); } throw e; } }
内容的提问来源于stack exchange,提问作者cup cake
相关产品推荐
相关产品推荐

