1 前言
本文要分享的是 iOS 版微信内部正在推广和使用的一个高性能通用 key-value 组件的技术实践过程, 该组件在微信内部被命名为 MMKV(以下简称 MMKV)
MMKV 是基于 mmap 内存映射的 key-value 组件, 底层序列化 / 反序列化使用 protobuf 实现, 性能高, 稳定性强希望对于有高性能 key-value 组件或类似技术需求的 IM 同行, 能通过本文获得一定的启发
学习交流:
- 即时通讯开发交流群: 320837163[推荐]
- 移动端 IM 开发入门文章: 新手入门一篇就够: 从零开发移动端 IM
2MMKV 源起
在 iOS 微信的日常运营中, 时不时就会爆发特殊文字引起 iOS 系统的 crash, 微信团队分享: iOS 版微信是如何防止特殊字符导致的炸群 APP 崩溃的? 一文里面设计的技术方案是在关键代码前后进行计数器的加减, 通过检查计数器的异常, 来发现引起闪退的异常文字
微信团队分享: iOS 版微信是如何防止特殊字符导致的炸群 APP 崩溃的? 里设计的技术方案大致原理就是:
1)在会话列表会话界面等有大量 cell 的地方, 希望新加的计时器不会影响滑动性能;
2)这些计数器还要永久存储下来因为闪退随时可能发生
这就需要一个性能非常高的通用 key-value 存储组件, 我们考察了 NSUserDefaultsSQLite 等常见组件, 发现都没能满足如此苛刻的性能要求考虑到这个防 crash 方案最主要的诉求还是实时写入, 而 mmap 内存映射文件刚好满足这种需求, 我们尝试通过它来实现一套 key-value 组件
3MMKV 原理
3.1 内存准备
通过 mmap 内存映射文件, 提供一段可供随时写入的内存块, App 只管往里面写数据, 由 iOS 负责将内存回写到文件, 不必担心 crash 导致数据丢失
3.2 数据组织
数据序列化方面我们选用 protobuf 协议, pb 在性能和空间占用上都有不错的表现考虑到我们要提供的是通用 kv 组件, key 可以限定是 string 字符串类型, value 则多种多样 (int/bool/double 等) 要做到通用的话, 考虑将 value 通过 protobuf 协议序列化成统一的内存块(buffer), 然后就可以将这些 KV 对象序列化到内存中
更多有关 Protobuf 的文章请见:
Protobuf 通信协议详解: 代码演示详细原理介绍等
强列建议将 Protobuf 作为你的即时通讯应用数据传输格式
全方位评测: Protobuf 性能到底有没有比 JSON 快 5 倍?
一个基于 Protocol Buffer 的 Java 代码演示
详解如何在 NodeJS 中使用 Google 的 Protobuf
3.3 写入优化
标准 protobuf 不提供增量更新的能力, 每次写入都必须全量写入
考虑到主要使用场景是频繁地进行写入更新, 我们需要有增量更新的能力:
1)将增量 kv 对象序列化后, 直接 append 到内存末尾;
2)这样同一个 key 会有新旧若干份数据, 最新的数据在最后;
3)那么只需在程序启动第一次打开 mmkv 时, 不断用后读入的 value 替换之前的值, 就可以保证数据是最新有效的
3.4 空间增长
使用 append 实现增量更新带来了一个新的问题, 就是不断 append 的话, 文件大小会增长得不可控例如同一个 key 不断更新的话, 是可能耗尽几百 M 甚至上 G 空间, 而事实上整个 kv 文件就这一个 key, 不到 1k 空间就存得下这明显是不可取的
我们需要在性能和空间上做个折中:
1)以内存 pagesize 为单位申请空间, 在空间用尽之前都是 append 模式;
2)当 append 到文件末尾时, 进行文件重整 key 排重, 尝试序列化保存排重结果;
3)排重后空间还是不够用的话, 将文件扩大一倍, 直到空间足够
3.5 数据有效性
考虑到文件系统操作系统都有一定的不稳定性, 我们另外增加了 crc 校验, 对无效数据进行甄别在 iOS 微信现网环境上, 我们观察到有平均约 70w 日次的数据校验不通过
4MMKV 使用
4.1 快速上手
MMKV 提供一个全局的实例, 可以直接使用:
可以看到, MMKV 在使用上还是比较简单的如果不同业务需要区别存储, 也可以单独创建自己的实例:
4.2 支持的数据类型
支持以下 C 语语言基础类型:
boolint32int64uint32uint64floatdouble
支持以下 ObjC 类型:
NSStringNSDataNSDate
5MMKV 性能
写了个简单的测试, 将 MMKVNSUserDefaults 的性能进行对比(循环写入 1w 次数据, 测试环境: iPhone X 256G, iOS 11.2.6, 单位: ms)
可见 MMKV 性能远远优于 iOS 自带的 NSUserDefaults 另外, 在测试中发现, NSUserDefaults 在每 2-3 次测试, 就会有 1 次比较耗时的操作, 怀疑是触发了数据 synchronize 重整写入对比之下, MMKV 即使触发数据重整, 也保持了性能的稳定高效
目前 MMKV 已经在鹅厂内部开源(http://git.code.oa.com/wechat-team/mmkv), 反馈比较好的话会考虑对外开源
(原文链接: https://mp.weixin.qq.com/s/cZQ3FQxRJBx4px1woBaasg, 本文略有改动)
来源: http://www.jianshu.com/p/f0d13193da4c