[译]FaceBook出品:开始用FlatBuffers替换老旧的Json吧

原文地址

人们通过FaceBook关注家人朋友的动态更新,浏览他们上传的照片。我们的后端存储了组成社交媒介的数据结构。在移动手机端,我们不能拉取整个数据结构,所以只拉取某个节点和其相关的联系构成的树状结构。

下图说明了一条带图朋友圈的工作原理。John发了一条朋友圈,之后他的朋友进行了点赞和评论,左边的图是社交图,用于后端的关系展示。当Android应用查询这条朋友圈时,就可以拉取到树状结构,包括作者信息,反馈和图片信息(右图展示)。

tree structure

应用的关键在于展现和存储信息。使用SQLite数据库把全部数据持久化是不现实的,因为有很多方法从后端获取到某个节点的树状数据。其中一个可行的方法是使用Json来存储树状信息,但是UI展示数据时,需要额外的开销把Json解析成为Java对象,包括时间,我们在Android上使用Jackson Json解析器,有如下缺点:

  • 解析速度:20KB的Json数据流(一个典型的FaceBook数据包)需要35ms来解析,超出了UI帧的刷新频率16.6ms。所以,即使是加载硬盘缓存数据,也无法做到顺滑如丝的滚动效果。
  • 初始化解析:在解析开始前,Json解析器需要构造映射表,大概需要100~200ms,大幅降低了应用的启动速度。
  • 垃圾回收:在Json解析过程中会产生很多小对象,调查中发现,解析20KB的数据流时大概分配100KB的临时内存,给垃圾回收器带来巨大的压力。

我们希望有一个更好的存储格式来提高Android应用的体验。

FlatBuffers

在可行的替代格式的探索中,我们碰到了 FlatBuffers,Google的一个开源项目。FlatBuffers是协议缓冲区的一个演化,它包括了元数据,可以在不反序列化整个对象的前提下,直接取到这个对象中的某个子数据(例如上述的树状结构)。

想象一个简单的类,有四个变量:姓名,关系,配偶,朋友列表。其中配偶和朋友也是Person对象,所以构成了一个树状结构。下面简单地分析,如何使用FlatBuffers来表示一个人,John,他的配偶,Mary。

class Person {
    String name;
    int friendshipStatus;
    Person spouse;
    List<Person>friends;
}
Show FlatBuffers

注意上述结构的几个方面:

  • 每个对象的描述分为两部分,左中心点的元数据(虚函数表),右中心点的真正数据;
  • 每个虚函数表中的格子代表一个变量,存储着真正数据的下标。例如,John的虚函数表的第一个格子有个1,指向在右中心点中,John名字的这个字节。
  • 对于对象变量,虚函数表的偏移量会指向子对象的中心点。例如,John的虚函数表的第三个格子(12)指向Mary的中心点(4)。
  • 在虚函数表中使用0表示没有其他数据可以展示了。

下面的代码片段展示了如何在这个数据结构中取得John的配偶信息:

// Root object position is normally stored at beginning of flatbuffer.
int johnPosition = FlatBufferHelper.getRootObjectPosition(flatBuffer);
int maryPosition = FlatBufferHelper.getChildObjectPosition(
   flatBuffer,
   johnPosition, // parent object position
   2 /* field number for spouse field */);
String maryName = FlatBufferHelper.getString(
   flatBuffer,
   johnPosition, // parent object position
   2 /* field number for name field */);

注意并没有涉及中间对象,节省了内存分配。更进一步,可以使用FlatBuffers数据进行存储,直接映射到内存中。这意味着我们只需要加载我们需要的部分数据,大大减少了过度的数据加载。

更重要的是,在读取变量之前不需要反序列整个对象。这减少了UI层和数据层之间的交互时间,提高了性能。

FlatBuffers的动态更新

数据是随着时间改变的,所以要更新FlatBuffers中存储的数据。因为FlatBuffers是设计为不可变的,所以没有直接的方法实现动态更新。解决方案是使用原始FlatBuffers数据进行比较。

FlatBuffer中每一块数据拥有独一无二的绝对位置,为了不需要每次都下载整个数据块,希望实现动态更新——例如关系的变更,下面举例子说明如何进行比较的:

  • John的好友状态在FlatBuffer中是虚函数表的第二个格子(6),为了改变好友状态,我们需要记录指向的数值现在是1(说明是朋友),而不是2(非朋友,但添加请求已发送)。
friend status
  • Mary的名字("Mary")在虚函数表的第13格子,相似地,修改Mary的名字需要把第13个格子指向新的字符串。

当查询FlatBuffer数据时,可以计算出数据的绝对位置,查看Mutation Buffer看是否有动态更新,如果没有则返回Base Buffer

扁平化Models

FlatBuffer可以作为应用的内存格式来处理数据存储和网络请求。它消除了从后端数据转化成前端UI展示的额外开销,也让我们转向于更为简洁的扁平化Models的架构,减少了UI层和数据层之间的复杂性。

使用Json作为数据格式时,为了用户体验,通常会在反序列化的时候做一些缓存,UI层和数据层之间添加了应用和网络的逻辑。

Json Architecture

iOS和桌面应用采用这种三层架构很普遍,在Android上就有缺点了:

  • 内存缓存意味着需要在内存中保存UI上的数据,很多Android设备都对应用有一个最大内存使用上限48MB或者更少。当开销增加时会触发垃圾回收机制,引起卡顿;
  • 应用逻辑需要处理内存缓存,UI,存储,特别是UI和存储相关的使用多线程处理,将会是个巨大的难题;
  • UI层的数据来源可能是缓存数据,网络数据,本地数据等等,当切换数据源时可能会引起UI层的过度绘制。

使用扁平化Models,UI层和数据层的交互就更为简单了,像下面图示:


Flat Models
  • UI层位于最高一层,使用标准Android的Cursor,在大多数Android应用中是通过路径进行存储的,保证了及时响应。
  • 应用和网络逻辑被数据层屏蔽了,在后台线程中运行逻辑处理并反映到数据层,使用标准的Android content provider通知,可以使UI层进行重绘。
  • 更好地分离了UI层与应用逻辑层,UI层仅仅需要对数据层的变更做出反应,应用逻辑层只需要把数据写到数据层中。UI层与应用逻辑层各自在工作线程做处理,互不干扰。

结论

FlatBuffers减少了UI层和数据层之间数据转换的花销,同样驱动了应用的架构升级。动态更新策略可以随时了解后端数据的更新,而本地状态统统存储在一个小小的数据结构中,大大减少了额外的花销。

我们用六个月时间完成了大部分Facebook的Android应用使用FlatBuffers作为存储格式,发现:

  • 加载本地缓存的时间从35ms下降到4ms;
  • 临时内存分配减少大约75%;
  • 冷启动时间减少10~15%
  • 存储空间减少了15%

很兴奋使用一个新的数据结构,让用户在看到朋友的动态如此迅速,感谢FlatBuffers

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容