MMKV 是基于 mmap 內(nèi)存映射的 key-value 組件,底層序列化/反序列化使用 protobuf 實(shí)現(xiàn),性能高,穩(wěn)定性強(qiáng)。
MMKV 源起
在 iOS 微信的日常運(yùn)營中,時(shí)不時(shí)就會(huì)爆發(fā)特殊文字引起 iOS 系統(tǒng)的 crash,《iOS微信特殊字符保護(hù)方案》,文章里面設(shè)計(jì)的技術(shù)方案是在關(guān)鍵代碼前后進(jìn)行計(jì)數(shù)器的加減,通過檢查計(jì)數(shù)器的異常,來發(fā)現(xiàn)引起閃退的異常文字。在會(huì)話列表、會(huì)話界面等有大量 cell 的地方,希望新加的計(jì)時(shí)器不會(huì)影響滑動(dòng)性能;另外這些計(jì)數(shù)器還要永久存儲(chǔ)下來——因?yàn)殚W退隨時(shí)可能發(fā)生。這就需要一個(gè)性能非常高的通用 key-value 存儲(chǔ)組件,我們考察了 NSUserDefaults、SQLite 等常見組件,發(fā)現(xiàn)都沒能滿足如此苛刻的性能要求。考慮到這個(gè)防 crash 方案最主要的訴求還是實(shí)時(shí)寫入,而 mmap 內(nèi)存映射文件剛好滿足這種需求,我們嘗試通過它來實(shí)現(xiàn)一套 key-value 組件。
MMKV 原理
內(nèi)存準(zhǔn)備
通過 mmap 內(nèi)存映射文件,提供一段可供隨時(shí)寫入的內(nèi)存塊,App 只管往里面寫數(shù)據(jù),由 iOS 負(fù)責(zé)將內(nèi)存回寫到文件,不必?fù)?dān)心 crash 導(dǎo)致數(shù)據(jù)丟失。
數(shù)據(jù)組織
數(shù)據(jù)序列化方面我們選用 protobuf 協(xié)議,pb 在性能和空間占用上都有不錯(cuò)的表現(xiàn)。考慮到我們要提供的是通用 kv 組件,key 可以限定是 string 字符串類型,value 則多種多樣(int/bool/double等)。要做到通用的話,考慮將 value 通過 protobuf 協(xié)議序列化成統(tǒng)一的內(nèi)存塊(buffer),然后就可以將這些 KV 對(duì)象序列化到內(nèi)存中。

寫入優(yōu)化
標(biāo)準(zhǔn) protobuf 不提供增量更新的能力,每次寫入都必須全量寫入??紤]到主要使用場(chǎng)景是頻繁地進(jìn)行寫入更新,我們需要有增量更新的能力:將增量 kv 對(duì)象序列化后,直接 append 到內(nèi)存末尾;這樣同一個(gè) key 會(huì)有新舊若干份數(shù)據(jù),最新的數(shù)據(jù)在最后;那么只需在程序啟動(dòng)第一次打開 mmkv 時(shí),不斷用后讀入的 value 替換之前的值,就可以保證數(shù)據(jù)是最新有效的。
空間增長(zhǎng)
使用 append 實(shí)現(xiàn)增量更新帶來了一個(gè)新的問題,就是不斷 append 的話,文件大小會(huì)增長(zhǎng)得不可控。例如同一個(gè) key 不斷更新的話,是可能耗盡幾百 M 甚至上 G 空間,而事實(shí)上整個(gè) kv 文件就這一個(gè) key,不到 1k 空間就存得下。這明顯是不可取的。我們需要在性能和空間上做個(gè)折中:以內(nèi)存 pagesize 為單位申請(qǐng)空間,在空間用盡之前都是 append 模式;當(dāng) append 到文件末尾時(shí),進(jìn)行文件重整、key 排重,嘗試序列化保存排重結(jié)果;排重后空間還是不夠用的話,將文件擴(kuò)大一倍,直到空間足夠。

數(shù)據(jù)有效性
考慮到文件系統(tǒng)、操作系統(tǒng)都有一定的不穩(wěn)定性,我們另外增加了 crc 校驗(yàn),對(duì)無效數(shù)據(jù)進(jìn)行甄別。在 iOS 微信現(xiàn)網(wǎng)環(huán)境上,我們觀察到有平均約 70w 日次的數(shù)據(jù)校驗(yàn)不通過。
MMKV 使用
快速上手
MMKV 提供一個(gè)全局的實(shí)例,可以直接使用:

可以看到,MMKV 在使用上還是比較簡(jiǎn)單的。如果不同業(yè)務(wù)需要區(qū)別存儲(chǔ),也可以單獨(dú)創(chuàng)建自己的實(shí)例:

支持的數(shù)據(jù)類型
支持以下 C 語語言基礎(chǔ)類型:
bool、int32、int64、uint32、uint64、float、double
支持以下 ObjC 類型:
NSString、NSData、NSDate
MMKV 性能
寫了個(gè)簡(jiǎn)單的測(cè)試,將 MMKV、NSUserDefaults 的性能進(jìn)行對(duì)比(循環(huán)寫入1w 次數(shù)據(jù),測(cè)試環(huán)境:iPhone X 256G, iOS 11.2.6,單位:ms)。

可見 MMKV 性能遠(yuǎn)遠(yuǎn)優(yōu)于 iOS 自帶的 NSUserDefaults。另外,在測(cè)試中發(fā)現(xiàn),NSUserDefaults 在每2-3次測(cè)試,就會(huì)有1次比較耗時(shí)的操作,懷疑是觸發(fā)了數(shù)據(jù) synchronize 重整寫入。對(duì)比之下,MMKV即使觸發(fā)數(shù)據(jù)重整,也保持了性能的穩(wěn)定高效。
目前 MMKV 已經(jīng)在鵝廠內(nèi)部開源(http://git.code.oa.com/wechat-team/mmkv),反饋比較好的話會(huì)考慮對(duì)外開源。