日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

使用腾讯云 SCF 云函数压缩 COS 对象存储文件

發布時間:2024/3/13 编程问答 21 豆豆
生活随笔 收集整理的這篇文章主要介紹了 使用腾讯云 SCF 云函数压缩 COS 对象存储文件 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

歡迎大家前往騰訊云技術社區,獲取更多騰訊海量技術實踐干貨哦~

作者:騰訊云Serverless團隊

在使用騰訊云 COS 對象存儲的過程中,我們經常有想要把整個 Bucket 打包下載的需求,但是 COS 并沒有提供整個 Bucket 打包下載的能力。這時,我們可以利用騰訊云的 SCF 無服務器云函數,完成 COS Bucket 的打包,并重新保存壓縮后的文件到 COS 中,然后通過 COS 提供的文件訪問鏈接下載文件。

但是在使用 SCF 云函數進行 COS Bucket 打包的過程中,偶爾會碰到這樣的問題:我期望將某個 COS Bucket 內的文件全部下載下來然后打包壓縮,把壓縮文件再上傳到 COS 中進行備份;但是在這個過程中,COS Bucket 內的文件可能數量多體積大,而 SCF 云函數的運行環境,實際只有 512MB 的 /tmp 目錄是可以讀寫的。這樣算上下載的文件,和生成的 ZIP 包,可能僅支持一定體積的文件處理,滿足不了所需。怎么辦?

在這種情況下,可能有的同學會想到使用內存,將內存轉變為文件系統,即內存文件系統,或者直接讀取文件并放置在內存中,或者在內存中生成文件。這種方法能解決一部分問題,但同時也帶來了些其他問題:

  • SCF 云函數的內存配置也是有上限的,目前上限是 1.5GB。
  • SCF 云函數的收費方式是按配置內存*運行時間。如果使用配置大內存的方法,實際是在為可能偶爾碰到的極端情況支付不必要的費用,不符合我們使用 SCF 云函數就是要精簡費用的目的。
  • 我們在這里嘗試了一種流式文件處理的方式,通過單個文件壓縮后數據立即提交 COS 寫的方法,一次處理一個文件,使得被壓縮文件無需在 SCF 的緩存空間內堆積,壓縮文件也無需放在緩存或內存中,而是直接寫入 COS。在這里,我們實際利用了兩種特性:ZIP 文件的數據結構特性和 COS 的分片上傳特性。

    zip 文件的數據結構

    在官方文檔中給出的 zip 文件格式如下:

    Overall .ZIP file format:[local file header 1][file data 1][data descriptor 1]. ..[local file header n][file data n][data descriptor n][archive decryption header] (EFS)[archive extra data record] (EFS)[central directory][zip64 end of central directory record][zip64 end of central directory locator] [end of central directory record]

    可以看到,實際的 zip 文件格式基本是[文件頭+文件數據+數據描述符]{此處可重復n次}+核心目錄+目錄結束標識
    組成的,壓縮文件的文件數據和壓縮數據是在文件頭部,相關的目錄結構,zip文件信息存儲在文件尾部。這樣的結構,為我們后續 COS 分片上傳寫入帶來了方便,可以先寫入壓縮數據內容,再寫入最終文件信息。

    COS 分片上傳

    COS 分片上傳按照如下操作即可進行:

  • 初始化分片上傳:通過初始化動作,獲取到此次上傳的唯一標識ID。此ID需要保存在本地并在后續上傳分片時使用。
  • 上傳分片:通過初始化時獲取到的ID,配合文件分片的順序編號,依次上傳文件分片,獲取到每個分片的ETag;COS 會通過 ID 和分片順序編號,拼接文件。
  • 結束上傳:通過初始化時獲取到的ID,結合分片的順序編號和ETag,通知 COS 分片上傳已經完成,可以進行拼接。
  • 在上傳過程中,還隨時可以查詢已上傳分片,或結束取消分片上傳。

    文件壓縮處理流程設計

    利用 zip 文件數據結構中文件壓縮數據在前目錄和額外標識在后的特性,和 COS 支持分片上傳的特性,我們可以利用流式文件處理方式來依次處理文件,并且做到處理完成一個文件壓縮就上傳處理后的壓縮數據分片。這種處理流程可以簡化為如下說明:
    1. 初始化 zip 文件數據結構,并將數據結構保存在內存中。
    2. 初始化 COS 分片上傳文件,保存好分片上傳 ID。
    3. 下載要放入壓縮包的文件至本地,使用 zip 算法,生成壓縮文件的數據內容并保存在內存中,并根據目錄格式,更新zip數據格式中的目錄標識。
    4. 將壓縮后的文件數據使用 COS 上傳分片,上傳至 COS 中。
    5. 清理刪除下載至本地的需壓縮文件。
    6. 根據需要,重復 3~5 步驟,增加壓縮包內的文件。
    7. 在壓縮文件處理完成后,使用分片上傳,將內存中的 zip 文件數據結構最后的目錄結構部分上傳至 COS。
    8. 通知 COS 結束上傳,完成最終 zip 文件的自動拼接。

    在這個處理流程中,一次只處理一個文件,對本地緩存和內存使用都只這一個文件的占用,相比下載全部文件再處理,大大減小了本地緩存占用和內存占用,這種情況下,使用少量緩存和內存就可以完成 COS 中大量文件的壓縮打包處理。

    使用SCF進行 COS 文件壓縮處理實現

    流式壓縮文件庫 archiver

    我們這里使用 node.js 開發語言來實現 COS 文件壓縮處理。我們這里使用了 cos-nodejs-sdk-v5 sdk 和 archiver 模塊。其中 archiver 模塊是實現zip和tar包壓縮的流式處理庫,能夠通過 append 輸入欲壓縮文件,通過 stream 輸出壓縮后的文件流。archiver的簡單用法如下:

    // require modules var fs = require('fs'); var archiver = require('archiver');// create a file to stream archive data to. var output = fs.createWriteStream(__dirname + '/example.zip'); var archive = archiver('zip', {zlib: { level: 9 } // Sets the compression level. });// pipe archive data to the file archive.pipe(output);// append a file from stream var file1 = __dirname + '/file1.txt'; archive.append(fs.createReadStream(file1), { name: 'file1.txt' });// append a file archive.file('file1.txt', { name: 'file4.txt' });// finalize the archive (ie we are done appending files but streams have to finish yet) // 'close', 'end' or 'finish' may be fired right after calling this method so register to them beforehand archive.finalize();

    archiver 將會在每次 append 文件的時候,將文件的壓縮數據輸出到 pipe 指定的輸出流上。因此,我們在這里可以通過實現我們自身的 WriteStream,獲取到 archiver 的寫請求,并把寫入內容轉移到 COS 模塊的分片上傳能力上。在這里,我們實現的 WriteStream 為:

    var Writable = require('stream').Writable; var util = require('util');module.exports = TempWriteStream;let handlerBuffer;function TempWriteStream(options) {if (!(this instanceof TempWriteStream))return new TempWriteStream(options);if (!options) options = {};options.objectMode = true;handlerBuffer = options.handlerBuffer;Writable.call(this, options); }util.inherits(TempWriteStream, Writable);TempWriteStream.prototype._write = function write(doc, encoding, next) {handlerBuffer(doc);process.nextTick(next) };

    通過集成 nodejs 中的 Writable stream,我們可以將寫操作轉移到我們所需的 handle 上去,handle 可以對接 COS 的分片上傳功能。

    COS 分片上傳

    COS 分片上傳功能的實現如下,我們將其封裝為 Upload 模塊:

    const cos = require('./cos')let Duplex = require('stream').Duplex; function bufferToStream(buffer) {let stream = new Duplex();stream.push(buffer);stream.push(null);return stream; }// 大于4M上傳 const sliceSize = 4 * 1024 * 1024function Upload(cosParams) {this.cosParams = cosParams;this.partNumber = 1;this.uploadedSize = 0;this.Parts = []this.tempSize = 0;this.tempBuffer = new Buffer('') }Upload.prototype.init = function (next) {const _this = this;cos.multipartInit(this.cosParams, function (err, data) {_this.UploadId = data.UploadIdnext()}); } Upload.prototype.upload = function(partNumber, buffer) {const _this = this;const params = Object.assign({Body: bufferToStream(buffer),PartNumber: partNumber,UploadId: this.UploadId,ContentLength: buffer.length}, this.cosParams);cos.multipartUpload(params, function (err, data) {if (err) {console.log(err)} else {_this.afterUpload(data.ETag, buffer, partNumber)}}); }Upload.prototype.sendData = function (buffer) {this.tempSize += buffer.length;if (this.tempSize >= sliceSize) {this.upload(this.partNumber, Buffer.concat([this.tempBuffer, buffer]))this.partNumber++;this.tempSize = 0;this.tempBuffer = new Buffer('')} else {this.tempBuffer = Buffer.concat([this.tempBuffer, buffer]);} }Upload.prototype.afterUpload = function (etag, buffer, partNumber) {this.uploadedSize += buffer.lengththis.Parts.push({ ETag: etag, PartNumber: partNumber })if (this.uploadedSize == this.total) {this.complete();} }Upload.prototype.complete = function () {this.Parts.sort((part1, part2) => {return part1.PartNumber - part2.PartNumber});const params = Object.assign({UploadId: this.UploadId,Parts: this.Parts,}, this.cosParams);cos.multipartComplete(params, function (err, data) {if (err) {console.log(err)} else {console.log('Success!')}}); }Upload.prototype.sendDataFinish = function (total) {this.total = total;this.upload(this.partNumber, this.tempBuffer); }module.exports = Upload;

    對于 COS 本身已經提供的 SDK,我們在其基礎上封裝了相關查詢,分片上傳初始化,分片上傳等功能如下:

    const COS = require('cos-nodejs-sdk-v5');const cos = new COS({AppId: '125xxxx227',SecretId: 'AKIDutrojxxxxxxx5898Lmciu',SecretKey: '96VJ5tnlxxxxxxxl5To6Md2', }); const getObject = (event, callback) => {const Bucket = event.Bucket;const Key = event.Key;const Region = event.Regionconst params = {Region,Bucket,Key};cos.getObject(params, function (err, data) {if (err) {const message = `Error getting object ${Key} from bucket ${Bucket}.`;callback(message);} else {callback(null, data);}}); };const multipartUpload = (config, callback) => {cos.multipartUpload(config, function (err, data) {if (err) {console.log(err);}callback && callback(err, data);}); };const multipartInit = (config, callback) => {cos.multipartInit(config, function (err, data) {if (err) {console.log(err);}callback && callback(err, data);}); };const multipartComplete = (config, callback) => {cos.multipartComplete(config, function (err, data) {if (err) {console.log(err);}callback && callback(err, data);}); };const getBucket = (config, callback) => {cos.getBucket(config, function (err, data) {if (err) {console.log(err);}callback && callback(err, data);}); };module.exports = {getObject,multipartUpload,multipartInit,multipartComplete,getBucket };

    在具體使用時,需要將文件中 COS 相關登錄信息的APPId,SecretId,SecretKey等替換為自身可用的真實內容。

    功能入口實現函數

    我們在最終入口函數 index.js 中使用各個組件來完成最終的目錄檢索,文件壓縮打包上傳。在這里,我們利用函數入參來確定要訪問的 bucket 名稱和所屬地域,期望壓縮的文件夾和最終壓縮后文件名。云函數入口函數仍然為 main_handler。

    // require modules const fs = require('fs'); const archiver = require('archiver');const cos = require('./cos');const Upload = require('./Upload')const TempWriteStream = require('./TempWriteStream')const start = new Date();const getDirFileList = (region, bucket, dir, next) => {const cosParams = {Bucket: bucket,Region: region,}const params = Object.assign({ Prefix: dir }, cosParams);cos.getBucket(params, function (err, data) {if (err) {console.log(err)} else {let fileList = [];data.Contents.forEach(function (item) {if (!item.Key.endsWith('/')) {fileList.push(item.Key)}});next && next(fileList)}}) }const handler = (region, bucket, source, target) => {const cosParams = {Bucket: bucket,Region: region,}const multipartUpload = new Upload(Object.assign({ Key: target}, cosParams));const output = TempWriteStream({ handlerBuffer: multipartUpload.sendData.bind(multipartUpload) })var archive = archiver('zip', {zlib: { level: 9 } // Sets the compression level.});output.on('finish', function () {multipartUpload.sendDataFinish(archive.pointer());});output.on('error', function (error) {console.log(error);});archive.on('error', function (err) {console.log(err)});archive.pipe(output);multipartUpload.init(function () {getDirFileList(region, bucket, source, function(fileList) {let count = 0;const total = fileList.length;for (let fileName of fileList) {((fileName) => {let getParams = Object.assign({ Key: fileName }, cosParams)cos.getObject(getParams, (err, data) => {if (err) {console.log(err)return}var buffer = data.Body;console.log("download file "+fileName);archive.append(buffer, { name: fileName.split('/').pop() });console.log("zip file "+fileName);count++;if (count == total) {console.log("finish zip "+count+" files")archive.finalize();}})})(fileName)}})}) }exports.main_handler = (event, context, callback) => {var region = event["region"];var bucket = event["bucket"];var source = event["source"];var zipfile = event["zipfile"];//handler('ap-guangzhou', 'testzip', 'pic/', 'pic.zip');handler(region, bucket, source, zipfile) }

    測試及輸出

    最終我們將如上的代碼文件及相關依賴庫打包為zip代碼包,創建函數并上傳代碼包。同時我們準備好一個 COS Bucket命名為 testzip, 在其中創建 pic 文件夾,并在文件夾中傳入若干文件。通過函數頁面的測試功能,我們使用如下模版測試函數:

    { "region":"ap-guangzhou", "bucket":"testzip", "source":"pic/", "zipfile":"pic.zip" }

    函數輸出日志為:

    ... 2017-10-13T12:18:18.579Z 9643c683-b010-11e7-a4ea-5254001df6c6 download file pic/DSC_3739.JPG 2017-10-13T12:18:18.579Z 9643c683-b010-11e7-a4ea-5254001df6c6 zip file pic/DSC_3739.JPG 2017-10-13T12:18:18.689Z 9643c683-b010-11e7-a4ea-5254001df6c6 download file pic/DSC_3775.JPG 2017-10-13T12:18:18.690Z 9643c683-b010-11e7-a4ea-5254001df6c6 zip file pic/DSC_3775.JPG 2017-10-13T12:18:18.739Z 9643c683-b010-11e7-a4ea-5254001df6c6 download file pic/DSC_3813.JPG 2017-10-13T12:18:18.739Z 9643c683-b010-11e7-a4ea-5254001df6c6 finish zip 93 files 2017-10-13T12:18:56.887Z 9643c683-b010-11e7-a4ea-5254001df6c6 Success!

    可以看到函數執行成功,并從 COS Bucket 根目錄看到新增加的 pic.zip 文件。

    項目源代碼及改進方向

    目前項目所有源代碼已經放置在 Github 上,路徑為 https://github.com/qcloud-scf/demo-scf-compress-cos。可以通過下載或 git clone 項目,獲取到項目源代碼,根據自身帳號信息,修改 cos 文件內的帳號 APPId、SecretId、SecretKey這些認證信息,然后將根目錄下所有文件打包至 zip 壓縮包后,通過 SCF 創建函數并通過 zip 文件上傳代碼來完成函數創建,根據上面所屬的“測試及輸出”步驟來測試函數的可用性。

    函數在此提供的仍然只是個demo代碼,更多的是為大家帶來一種新的思路及使用騰訊云 SCF 無服務器云函數和 COS 對象存儲。基于此思路,Demo本身后續還有很多可以改進的方法,或根據業務進行變化的思路:

  • 文件的處理目前還是下載一個處理一個,其實我們可以使用多線程和隊列來加速處理過程,使用若干線程持續下載文件,使用隊列對已經下載完成待處理的文件進行排隊,然后使用一個壓縮線程從隊列中讀取已下載的文件后進行壓縮上傳處理。這種方式可以進一步加快大量文件的處理速度,只是需要小心處理好緩存空間被使用占滿后的等待和文件處理完成后的刪除釋放空間。
  • 目前 Demo 從入參接受的是單個地域、Bucket、目錄和輸出文件,我們完全可以改造為從多個地域或Bucket拉取文件,也可以傳遞指定的文件列表而不是僅一個目錄,同時函數執行觸發可以使用 COS 觸發或 CMQ 消息隊列觸發,能夠形成更加通用的壓縮處理函數。
  • 后續對于此 Demo 如果有更多疑問,想法,或改進需求,歡迎大家提交 git pr 或 issue。項目地址:https://github.com/qcloud-scf/demo-scf-compress-cos

    相關閱讀

    使用騰訊云 CDN 、COS 以及萬象優圖實現HTTP/2樣例
    如何利用云對象存儲 COS 免費托管靜態網站
    Serverless 初探


    此文已由作者授權騰訊云技術社區發布,轉載請注明文章出處
    原文鏈接:https://cloud.tencent.com/community/article/810260

    總結

    以上是生活随笔為你收集整理的使用腾讯云 SCF 云函数压缩 COS 对象存储文件的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。