小程式 LRU 儲存設計

CPPAlien發表於2018-07-12

寫在前面

為了解決小程式生成分享到朋友圈圖片的問題,我們開啟了畫家計劃--- 一個小程式圖片生成庫。該計劃已開源,可移步:github.com/Kujiale-Mob… 。 大家知道 Canvas 的繪製有很多很蛋疼的坑,其中一個是它的 drawImage 方法,該方法在 IDE 中可以直接設定為網路圖片的 url 進行繪製,但在真機上無法這樣做。有這個坑後,我們就需要先把圖片通過 download 下載到本地後,才能進行繪製。所以你的小程式中,如果有頻繁繪製一些圖片的需求,而又需要用到網路圖片素材,這就導致每次繪製都要重新下載素材圖片,產生很大的繪製效能問題。

小程式本身是未提供檔案 LRU 之類的快取機制的。為了讓我們的畫家計劃圖片生成的更快,我們自己開發了的小程式檔案進行 LRU 儲存的相關程式碼。這樣我們就無需重複下載可能會頻繁使用到的繪圖素材,大大增加了繪圖速度。

介紹下小程式的快取系統

小程式的快取分為資料快取,和檔案快取兩部分。而檔案快取又分為臨時檔案快取,和本地檔案儲存。其中本地檔案儲存的大小限制為 10M。

資料快取

我們可以使用小程式提供的一套非同步和同步的方法來增刪查結構化資料。同一個微信使用者,同一個小程式的儲存上限為 10MB。如果空間不足時會在小程式級別進行 LRU,也就是不經常使用的小程式的資料快取區域會被全部清空。

詳細見微信官方文件:

developers.weixin.qq.com/miniprogram…

注:資料快取區在體驗版、開發版、和線上版都共用一套,並不會隔離。

檔案臨時快取

我們在呼叫 wx.downloadFile 或者 wx.chooseImage 等獲取檔案或圖片的方式成功後,我們會得到這個檔案或圖片的臨時儲存路徑。文件上寫的是臨時路徑的生命週期是在本次小程式啟動期間內

不過沒有對儲存大小的限制進行說明,所以理論上不管多大檔案都可以進行臨時快取,當然如果太大肯定會造成某些神奇的錯誤吧。

本地儲存

我們在獲得臨時檔案後,可以通過呼叫介面 wx.saveFile 把臨時檔案儲存到本地空間中,本地空間儲存限制為 10M。如果儲存滿了後,後面的檔案就無法儲存成功了,會報超出最大儲存上限的錯誤。

而我們現在需要做的就是在這個本地儲存空間上,開闢一個空間,作為我們下載檔案的儲存空間,因空間有限,所以我們需要對這塊空間進行 LRU 管理。

有關本地儲存相關的介面可看以下文件:

developers.weixin.qq.com/miniprogram…

注:把臨時檔案通過呼叫 saveFile 成功後,這個臨時檔案路徑就無效了。切記切記。

檔案 LRU 儲存實現

小程式端的本地儲存有 10M 限制,但卻無 LRU,現在我們需要結合上面提到的小程式三種儲存方式來實現一套小程式檔案下載的 LRU 機制。

資料結構設計

{
'key': {
        'path': // 檔案的儲存路徑
        'time': // 時間戳,用來記錄檔案的最後訪問時間,當儲存不夠時,會選擇最遠未被訪問的檔案進行刪除
        'size': // 檔案大小
       }
....
'totalSize': // 所有儲存檔案的當前總大小
}
複製程式碼

其中我們用下載的 url 作為 key。 以上資料結構會存在在資料快取區(後續我們會把這個區域稱為 storage 區),並且在下載器構建之時會從 storage 中讀取到記憶體中。以後的檔案操作,也會實時同步到 storage 中記錄的檔案資訊。

你可以理解為,storage 中儲存了檔案的基本資訊,而 path 就相當於指向這個實際檔案的指標。

總體流程設計

小程式 LRU 儲存設計

容錯

因為 storage 的儲存,和檔案操作都是非同步的,所以有可能存在兩者不一致的情況。此處的不一致情況分兩種

第一種,storage 的某檔案資訊被刪除了,但檔案本身卻因為出現神奇錯誤而未被刪除。另外檔案新增成功了,但 storage 中卻未新增成功也屬於此情況。

第二種,storage 中檔案資訊刪除失敗,但是檔案卻被刪除了。

以上兩種性質不同,所以也需要區別對待。針對第一種會導致檔案的儲存空間和 storage 中記錄的檔案資訊不一致,也即出現了遊離的檔案(未被 storage 跟蹤)。

而第二種,相當於存在了空指標,此種情況是絕對需要避免的,因為這會導致你在拿出一個不存在的檔案使用。會直接導致嚴重bug。

針對以上兩種特殊情況,做了以下容錯的處理。首先我們要保證檔案的刪除操作一定要在 storage 成功之後進行。這樣保證了第二種不會出錯。

而針對第一種遊離檔案的情況。我們這邊會在 saveFile 的時機進行兜底處理。如果存在了遊離檔案,最終會導致我們空間總大小計算不一致,這可能最終會導致,我們外部邏輯認為可以儲存,但實際儲存空間已經滿了,這樣就會導致 saveFile 報錯,在 saveFile 出錯後,不管啥原因,我們都把涉及到本策略儲存相關的內容全部清空掉,重新來過。因為我們一直有 tempFilePath 兜底,所以即使這種情況出現,也不會影響使用者正常使用。只是會影響一點使用者體驗(畢竟一下子沒有以前的快取了)。

注:之所以不像保證第二種情況的方式來保證第一種情況,是因為我覺得不需要為處理極少會出現的錯誤場景而去浪費效能,影響使用者體驗。只要我們做好兜底,即使這種錯誤情況萬一真的出現,整個系統也不會因此出問題,還是會正常使用。

寫在後面

小程式有很多的坑。目前市面上很多小程式效能體驗並不是很好。所以為了做一款高效能的小程式,是需要我們花大量的時間去試錯,琢磨的。踩坑不止,生命不惜。

目前這一套 LRU 的檔案儲存機制已經在我們開源的 Painter 庫中使用,如有興趣請移步: github.com/Kujiale-Mob…

相關文章