android网络开发首页投稿(暂停使用,暂停投稿)

用FlatBuffers提升Android平台上Facebook

2016-09-28  本文已影响149人  hanpfei

在Facebook上,人们可以通过阅读状态更新和查看照片同他们的家人和朋友来往。在我们的后端,我们保存了组成这些连接的社交图谱的所有数据。在我们的移动客户端,我们不能下载完整的图谱,而是以一个本地的树结构的形式下载一个节点及它的一些连接。

下面的图片描述了在一个含有照片附件的story中这是如何工作的。在这个例子中,John创建了一个story,他的朋友们很喜欢它并加了评论。图片的左边是社交图谱,用来描述Facebook后端的人际关系。当Android app查询story时,我们获得一个以story开始的树结构,包含了参与者的信息,反馈,和附件(如图片的右边所展示的那样)。

我们要处理的一个重要问题是如何在app中表示并存储数据。规格化所有这些数据为不同的表并放进SQLite数据库不太现实,因为我们查询节点并关联来自于后端的树结构的方式非常非常多,因此我们直接存储树结构。一个解决方案是将树结构存储为JSON,但那将需要我们在它能够被用于UI展示之前先解析JSON,并转换为一个Java对象。而且,JSON解析耗费时间。我们过去常常在Android平台上使用Jackson JSON解析器,但我们发现了它的一些问题:

我们想要找到一个更好的存储格式来提升我们的Android app的性能。

FlatBuffers

在我们对可选的格式的探索中,我们找到了FlatBuffers,来自于Google的一个开源项目。FlatBuffers是protocol buffers(protobuf)的一个进化版,后者已经包含了对象元数据,允许直接地访问数据的独立的子部件,而不必须先反序列化整个对象(在这个例子中,是一棵树)。

想象一下,我们有一个简单的person类对象,它有四个字段: name,friendship status,spouse,及friends列表。spouse和friends字段还包含了person对象,这些形成了一个树结构。这是一个说明了这样一个对象,一个person,John和他的妻子,Mary,在FlatBuffer中是如何布局的简化的图解。

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

在上面的布局中,你需要注意:

下面的代码片段展示了我们可以如何在上面展示的结构中找到Jonh的妻子的名字。

// 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,
   maryPosition, // parent object position
   0 /* field number for name field */);

注意,不需要创建中间对象,这节省了瞬时内存的分配。我们甚至可以通过直接地把FlatBuffer数据存储在文件中,然后把它mmap进内存中来做进一步的优化。这意味着我只需要加载文件中我们需要读取的部分,这将进一步降低总的内存消耗。

而且,在读取字段之前,无需反序列化对象树。这减少了存储层和UI之间的延迟,它将提升总的性能。

FlatBuffers上的变化

有时我们需要修改FlatBuffers里的值。由于FlatBuffers有意地设计为不可变的,而没有一个直接的方法来做到这一点。我们想到的一个方案是与最初的FlatBuffer一并追踪变化。

FlatBuffer中的每个数据片段可以通过它在FlatBuffer中的绝对位置来唯一的标识。我们支持变化,因而我们不一定要为很小的更新重新下载整个story——比如friendship status的改变。由于只是例子,这是一个概念的可视化,关于如何使用它来追踪两个变化:

最后,我们可以把所有的变化打包进变化缓冲区。变化缓冲区由两部分组成:变化索引和变化数据。变化索引记录了从base buffer中绝对索引到新数据的位置的映射。变化数据以FlatBuffer的格式存储了新的数据。

当查询FlatBuffers中的一个数据片段时,我们可以计算出数据的绝对位置,然后查询变化缓冲区来查看是否发生了变化并返回它,否则返回base buffer中的数据。

平坦模型

FlatBuffers不仅可以被用于存储,也可以被用于网络,以app中的in-memory的格式。这消除了服务器响应的数据到UI显示之间的转换。这已经允许我们走向一个更干净平坦模型架构,这将消除UI和存储层之间额外的复杂性。

当使用JSON作为存储格式时,我们需要添加一个内存缓存来迂回地处理反序列化的性能问题。我们最终也在UI和存储层之间添加应用和网络逻辑。


尽管这个三层架构在iOS和桌面上已经相当的流行了,它在Android上依然有一些重要问题:

通过 平坦模型 方法,UI和存储层可以被更简单地集成,如下面的图片所展示的那样。

结论

FlatBuffers是一个数据格式,它使得存储和UI之间的数据转换变得不必要了。采用这种技术,我们也已经驱动了我们的app的额外的架构提升,如平坦模型。我们在FlatBuffers之上构建的mutation扩展使我们可以在一个单独的结构中,追踪服务器数据,变化,和本地状态,这可以简化我们的数据模型,并暴露一个一致的API给我们的UI组件。

在过去的六个月,我们已经将Android平台上的Facebook的大部分过度到使用FlatBuffers作为存储格式了。一些性能提升数字包括:

看到数据格式的选择使得人们可以只花费一点点的时间就读到他们的朋友的更新及查看他们的家人的照片令人感到兴奋。感谢你,FlatBuffers!

原文链接

上一篇 下一篇

猜你喜欢

热点阅读