引入github:
MMKV——基于 mmap 的高性能通用 key-value 组件 MMKV 是基于 mmap 内存映射的 key-value
组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强。从 2015年中至今在微信上使用,其性能和稳定性经过了时间的验证。近期也已移植到 Android / macOS / Win32 / POSIX 平台,一并开源。MMKV 源起 在微信客户端的日常运营中,时不时就会爆发特殊文字引起系统的crash,参考文章,文章里面设计的技术方案是在关键代码前后进行计数器的加减,通过检查计数器的异常,来发现引起闪退的异常文字。在会话列表、会话界面等有大量cell 的地方,希望新加的计时器不会影响滑动性能;另外这些计数器还要永久存储下来——因为闪退随时可能发生。这就需要一个性能非常高的通用key-value 存储组件,我们考察了 SharedPreferences、NSUserDefaults、SQLite等常见组件,发现都没能满足如此苛刻的性能要求。考虑到这个防 crash 方案最主要的诉求还是实时写入,而 mmap内存映射文件刚好满足这种需求,我们尝试通过它来实现一套 key-value 组件。
MMKV 原理 内存准备 通过 mmap 内存映射文件,提供一段可供随时写入的内存块,App只管往里面写数据,由操作系统负责将内存回写到文件,不必担心 crash 导致数据丢失。 数据组织 数据序列化方面我们选用 protobuf协议,pb 在性能和空间占用上都有不错的表现。 写入优化 考虑到主要使用场景是频繁地进行写入更新,我们需要有增量更新的能力。我们考虑将增量kv 对象序列化后,append 到内存末尾。 空间增长 使用 append 实现增量更新带来了一个新的问题,就是不断 append的话,文件大小会增长得不可控。我们需要在性能和空间上做个折中。 更详细的设计原理参考 MMKV 原理。
Android使用指南
安装引入
推荐使用 Maven:
dependencies {
implementation 'com.tencent:mmkv-static:1.2.1'
// replace "1.2.1" with any available version
}
更多安装指引参考 Android Setup。
快速上手
MMKV 的使用非常简单,所有变更立马生效,无需调用 sync、apply。 在 App 启动时初始化 MMKV,设定 MMKV 的根目录(files/mmkv/),例如在 Application 里:
public void onCreate() {
super.onCreate();
String rootDir = MMKV.initialize(this);
System.out.println("mmkv root: " + rootDir);
//……
}
MMKV 提供一个全局的实例,可以直接使用:
import com.tencent.mmkv.MMKV;
//……
MMKV kv = MMKV.defaultMMKV();
kv.encode("bool", true);
boolean bValue = kv.decodeBool("bool");
kv.encode("int", Integer.MIN_VALUE);
int iValue = kv.decodeInt("int");
kv.encode("string", "Hello from mmkv");
String str = kv.decodeString("string");
MMKV 支持多进程访问,更详细的用法参考 Android Tutorial。
性能对比
循环写入随机的int 1k 次,我们有如下性能对比:
更详细的性能对比参考 Android Benchmark。
以上内容摘自MMKV ReadMe文件,接下来我们一步一步研究一下这个MMKV框架的原理,和怎么实现的。
通过上面的readme描述我们知道了mmap和protobuf这两个东西,接下来我们就去查找一下这两个东西
mmap
优点如下:
1、对文件的读取操作跨过了页缓存,减少了数据的拷贝次数,用内存读写取代I/O读写,提高了文件读取效率。
2、实现了用户空间和内核空间的高效交互方式。两空间的各自修改操作可以直接反映在映射的区域内,从而被对方空间及时捕捉。
3、提供进程间共享内存及相互通信的方式。不管是父子进程还是无亲缘关系的进程,都可以将自身用户空间映射到同一个文件或匿名映射到同一片区域。从而通过各自对映射区域的改动,达到进程间通信和进程间共享的目的。同时,如果进程A和进程B都映射了区域C,当A第一次读取C时通过缺页从磁盘复制文件页到内存中;但当B再读C的相同页面时,虽然也会产生缺页异常,但是不再需要从磁盘中复制文件过来,而可直接使用已经保存在内存中的文件数据。
4、可用于实现高效的大规模数据传输。内存空间不足,是制约大数据操作的一个方面,解决方案往往是借助硬盘空间协助操作,补充内存的不足。但是进一步会造成大量的文件I/O操作,极大影响效率。这个问题可以通过mmap映射很好的解决。换句话说,但凡是需要用磁盘空间代替内存的时候,mmap都可以发挥其功效。
缺点如下:
1.文件如果很小,是小于4096字节的,比如10字节,由于内存的最小粒度是页,而进程虚拟地址空间和内存的映射也是以页为单位。虽然被映射的文件只有10字节,但是对应到进程虚拟地址区域的大小需要满足整页大小,因此mmap函数执行后,实际映射到虚拟内存区域的是4096个字节,11~4096的字节部分用零填充。因此如果连续mmap小文件,会浪费内存空间。
对变长文件不适合,文件无法完成拓展,因为mmap到内存的时候,你所能够操作的范围就确定了。
3.如果更新文件的操作很多,会触发大量的脏页回写及由此引发的随机IO上。所以在随机写很多的情况下,mmap方式在效率上不一定会比带缓冲区的一般写快
protobuf协议
Protobuf 完整解析 - 公司最常用的数据交互协议 Google Protocol Buffer(简称
Protobuf)是一种轻便高效的结构化数据存储格式,平台无关、语言无关、可扩展,可用于通讯协议和数据存储等领域。数据交互xml、json、protobuf格式比较
1、json: 一般的web项目中,最流行的主要还是json。因为浏览器对于json数据支持非常好,有很多内建的函数支持。
2、xml:
在webservice中应用最为广泛,但是相比于json,它的数据更加冗余,因为需要成对的闭合标签。json使用了键值对的方式,不仅压缩了一定的数据空间,同时也具有可读性。3、protobuf :
是后起之秀,是谷歌开源的一种数据格式,适合高性能,对响应速度有要求的数据传输场景。因为profobuf是二进制数据格式,需要编码和解码。数据本身不具有可读性。因此只能反序列化之后得到真正可读的数据。相对于其它protobuf更具有优势1:序列化后体积相比Json和XML很小,适合网络传输
2:支持跨平台多语言
3:消息格式升级和兼容性还不错
4:序列化反序列化速度很快,快于Json的处理速速
protoBuf的优点
Protobuf 有如
XML,不过它更小、更快、也更简单。你可以定义自己的数据结构,然后使用代码生成器生成的代码来读写这个数据结构。你甚至可以在无需重新部署程序的情况下更新数据结构。只需使用
Protobuf
对数据结构进行一次描述,即可利用各种不同语言或从各种不同数据流中对你的结构化数据轻松读写。它有一个非常棒的特性,即“向后”兼容性好,人们不必破坏已部署的、依靠“老”数据格式的程序就可以对数据结构进行升级。Protobuf 语义更清晰,无需类似 XML 解析器的东西(因为 Protobuf 编译器会将 .proto
文件编译生成对应的数据访问类以对 Protobuf 数据进行序列化、反序列化操作)。使用 Protobuf 无需学习复杂的文档对象模型,Protobuf 的编程模式比较友好,简单易学,同时它拥有良好的文档和示例,对于喜欢简单事物的人们而言,Protobuf
比其他的技术更加有吸引力。ProtoBuf 的不足
Protobuf 与 XML 相比也有不足之处。它功能简单,无法用来表示复杂的概念。XML 已经成为多种行业标准的编写工具,Protobuf
只是 Google 公司内部使用的工具,在通用性上还差很多。 由于文本并不适合用来描述数据结构,所以 Protobuf
也不适合用来对基于文本的标记文档(如 HTML)建模。另外,由于 XML
具有某种程度上的自解释性,它可以被人直接读取编辑,在这一点上Protobuf 不行,它以二进制的方式存储,除非你有 .proto
定义,否则你没法直接读出 Protobuf 的任何内容
通过上面对mmap和protobuf的介绍,相信你一定是一脸懵逼。这个非应用层面的东西,门槛确实高。这里我先放两个链接,有兴趣深入的自行研读
https://blog.csdn.net/qq_33611327/article/details/81738195
https://www.jianshu.com/p/755338d11865
https://www.cnblogs.com/zhenghongxin/p/10891426.html
未完待续!!!