2020-08-23 11:31:39
文章目錄
-
-
-
-
- 存儲方式
- 如何選擇
- sp的N宗罪
- SP優化
- MMKV與SP
- MMKV原理:來源https://github.com/Tencent/MMKV
- 使用方式
- 參考
-
-
-
鑒于SP的"種種問題",萌發了想要使用寫檔案的方式替換掉sp的想法,發現騰訊開源MMKV是個不錯的選擇。
存儲方式
- SharedPreferences
- ContentProvider
- 檔案
- 資料庫
如何選擇
時間開銷 | 這裡說的時間開銷包括了CPU時間和/0時間,在I/O優化中我就多次提到相比CPU和記憶體,I/0存儲的速度是非常慢的。但是如果存儲方法中比如編解碼或者加密/解密等設計的比較複雜,整個資料存儲過程也會出現CPU時間變得更長的情況 |
正确性 | 選擇存儲方案的時候,第一個需要判斷它是否靠譜。這套存儲方案設計是否完備,有沒有支援多線程或者跨程序同步操作。内部是否健壯,有沒有考慮異常情況下資料的校驗和恢複,比如采用雙寫或者備份檔案政策,即使主檔案因為系統底層導緻損壞,也可以一定程度 上恢複大部分資料 |
空間開銷 | 即使相同的資料如果使用不同的編碼方式,最後占用的存儲空間也會有所不同。舉一個簡單的例子,相同的資料所占的空間大小是XML>JSON>ProtocolBuffer。除了編碼方式的差異,在一些場景我們可能還需要引入壓縮政策來進-步減少存儲空間,例如zip、lzma等。資料存儲的空間開銷還需要考慮記憶體空間的占用量,整個存儲過程會不會導緻應用出現大量GC、OOM等 |
安全 | 應用中可能會有一些非常敏感的資料,即使它們存儲在/data/data中,我們依然必須将它們加密。例如微信的聊天資料是存儲在加密的資料庫中,一些些賬号相關的資料我們也要單獨做加密落地。根據加密強度的不同,可以選擇RSA、AES、chacha20、 TEA這些常用的加密算法 |
開發成本 | 有些存儲方案看起來非常高大上,但是需要業務做很大改造才能接入。這裡我們當然希望能無縫的接入到業務中,在整個開發過程越簡單越好 |
相容性 | 業務不停地向前演進,我們的存儲字段或者格式有時候也會不得不有所變化。相容性首先要考慮的是向前、向後的相容性,老的資料在更新時能否遷移過來,新的資料在老版本能否降級使用。相容性另外一個需要考慮的可能是多語言的問題,不同的語言是否支援轉換 |
sp的N宗罪
- 跨程序不安全
- 加載緩慢:異步加載,但是異步加載線程沒有設定優先級,如果這時候主線程讀取資料需要等待加載線程執行完畢(也就是主線程等待低優先級線程鎖的問題)
- 全量寫入:無論是commit還是apply,即使改動一個條目,也會把全部内容寫到檔案
- 卡頓:異步落盤機制在應用崩潰時會導緻資料丢失
下面是SP操作源碼的簡介圖,來源:https://juejin.im/entry/6844903488271417351
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-DhYdjRhi-1604329094183)(/image/Android/MMKV/apply.png)]
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-1yFyfDU6-1604329094185)(/image/Android/MMKV/commit.png)]
SP優化
可以在Application中重寫getSharedPreference方法,傳回自己實作的sp。我們可以自己将多次讀寫進行合并
MMKV與SP
關鍵要素 | SP | MMKV |
---|---|---|
正确性 | 差 跨程序和apply機制導緻資料丢失 | 優 使用mmap和檔案 鎖保證資料完整 |
時間開銷 | 差 全量寫入、卡頓 | 優 1.使用mmap 2.修改插入檔案尾部,無需全量寫入 |
空間開銷 | 差 使用XML,格式比較備援 | 良 使用Protocol Buffer,但是增量更新可能會導緻部分備援 |
安全 | 差 完全明文存儲,沒有支援加密與權限校驗,不适合存放敏感資料 | 良 使用Protocol Buffer,不是完全明文。沒有支援加密與權限校驗,不适合存放敏感資料 |
開發成本 | 優 系統支援,非常簡單 | 良 需要引入單獨庫,有一定的改造成本 |
相容性 | 優 支援前後相容 | 優 支援前後相容,支援導入 SharedPreferences曆史資料,但注意 轉換後版本無法回退 |
MMKV原理:來源https://github.com/Tencent/MMKV
-
記憶體準備
通過 mmap 記憶體映射檔案,提供一段可供随時寫入的記憶體塊,App 隻管往裡面寫資料,由作業系統負責将記憶體回寫到檔案,不必擔心 crash 導緻資料丢失。
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-1s8OdDNf-1604329094187)(/image/Android/MMKV/Linux-storage-stack.png)]
簡化版
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-qGETkZXT-1604329094189)(/image/Android/MMKV/linux-io.png)]
-
資料組織
資料序列化方面我們選用 protobuf 協定,pb 在性能和空間占用上都有不錯的表現。考慮到我們要提供的是通用 kv 元件,key 可以限定是 string 字元串類型,value 則多種多樣(int/bool/double 等)。要做到通用的話,考慮将 value 通過 protobuf 協定序列化成統一的記憶體塊(buffer),然後就可以将這些 KV 對象序列化到記憶體中。
-
寫入優化
标準 protobuf 不提供增量更新的能力,每次寫入都必須全量寫入。考慮到主要使用場景是頻繁地進行寫入更新,我們需要有增量更新的能力:将增量 kv 對象序列化後,直接 append 到記憶體末尾;這樣同一個 key 會有新舊若幹份資料,最新的資料在最後;那麼隻需在程式啟動第一次打開 mmkv 時,不斷用後讀入的 value 替換之前的值,就可以保證資料是最新有效的。
-
空間增長
使用 append 實作增量更新帶來了一個新的問題,就是不斷 append 的話,檔案大小會增長得不可控。例如同一個 key 不斷更新的話,是可能耗盡幾百 M 甚至上 G 空間,而事實上整個 kv 檔案就這一個 key,不到 1k 空間就存得下。這明顯是不可取的。我們需要在性能和空間上做個折中:以記憶體 pagesize 為機關申請空間,在空間用盡之前都是 append 模式;當 append 到檔案末尾時,進行檔案重整、key 排重,嘗試序列化儲存排重結果;排重後空間還是不夠用的話,将檔案擴大一倍,直到空間足夠。
-
資料有效性
考慮到檔案系統、作業系統都有一定的不穩定性,我們另外增加了 crc 校驗,對無效資料進行甄别。在 iOS 微信現網環境上,我們觀察到有平均約 70萬日次的資料校驗不通過。
使用方式
https://github.com/Tencent/MMKV github上有對應的示例
參考
聊聊 Linux IO
磁盤I/O那些事
Linux 核心的檔案 Cache 管理機制介紹
Linux 中直接 I/O 機制的介紹
MemoryFile
MappedByteBuffer
徹底搞懂 SharedPreferences
Java 對象序列化