开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第32天,点击查看活动详情
[论文阅读]FlightTracker-Facebook跨网在线存储读一致性优化(2)
第44篇,终于到论文正文,这个章节介绍了FlightTracker基本信息 请牢记这个流程图:为了实现RYW,首先为用户42的请求获取ticket,而后写完成,为42appendWrite一个T1,读的时候,约定好我需要读取 T+T1的数据,这样,我们就实现了RYW。请注意,这里要求存储支持类似HLC的设计,例如对read(T+T1...)的正确回应。这个并不容易,如果你的存储不能做到这一点,先别考虑FT
论文见:www.usenix.org/conference/…
3. FlightTracker
FlightTracker的主要思想是将RYW一致性的问题分解成三个部分。(1) Ticket abstraction,一种在独立开发的系统和API中代表写集的灵活和可扩展的方式;(2) FlightTracker服务,每个网络请求查询一次的通用基础设施,以获得用户最近的写元数据;(3) Ticket-inclusive reads,系统特定的机制,确保指定的写反映在查询结果中。
我们对FlightTracker的目标是保留通信模式,以利于最终一致的读取优化的存储。FlightTracker利用了这些数据存储的现有信息。大多数的读取查询可以通过一个简单的本地RPC来实现,从而保持高效率和低延迟。FlightTracker不限制数据存储发送读取RPC的位置,这使它们能够利用每个查询重试和故障转移来实现高可用性。FlightTracker支持先进的多级缓存,数据存储使用这种缓存来容忍热点。大部分确保写入对读取可见的工作由异步管道处理,这保留了底层数据存储的理想隔离和松散耦合。这种 "搭便车 "的方法也使得在现有的成熟系统中逐步增加FlightTracker的支持变得可行,而且开销很小。
图3显示了数据存储与FlightTracker集成所需实施的API扩展。写入成功后,数据存储会返回一个识别写入结果的Ticket;读取查询需要一个Ticket参数,并保证Ticket中的任何相关写入内容都会反映在结果中。
3.1 一个例子
考虑一个假设的社交媒体产品,使用TAO的版本节点和边的图模型,有用户节点、媒体节点、用户喜欢某个特定媒体实例时的边,以及用户信任他人品味时的边。假设爱丽丝喜欢莫扎特的《安魂曲》;鲍勃最近表示他信任爱丽丝的艺术品味,然后将他对爱丽丝的信任扩大到了音乐。由此产生的子图如图5所示,Bob最近写给TAO的信息将是
为了获得RYW的一致性,我们只需要确保Bob随后的数据存储查询包括这些写入的效果。我们通过在每个网络请求中计算一次Bob的最近写入集,将其附加到他的所有查询中,然后确保数据存储在查询结果中反映出附加写入。
3.2 Tickets
我们将写入元数据存储在一个数据类型中,我们称之为Ticket。识别一个写的元数据包括事务ID和产生的节点或边缘版本等信息,但不包括数据本身。如果Wi是识别Bob上面第i次写的元数据,我们可能有:
鲍勃的Ticket会是*{W1*... .**..W4}.
请注意,你会发现这里的Blob的Ticket会膨胀的很厉害,下文还有一些优化动作,称之为compaction
作为写集合,Ticket可以通过集合联合来连接。此外,Ticket在许多独立部署的系统之间被处理和传递;因此,它们需要被封装、可扩展,并且向前和向后兼容。在一个Ticket中,写可以被列举出来,或者用一个低水位标记来表示,这个标记隐含了所有前面的写。第4节描述了Ticket的内容、语义和实现。
3.3 FlightTracker 服务
鲍勃的逻辑用户会话跨越了许多网络请求,所以我们需要在其他地方存储他最近写的元数据。为此,我们建立了FlightTracker服务,其API类似于用户ID到最近写的哈希图(图4)。例如,Bob的条目将是17 |-> {W1,W2,W3,W4}。客户端库在向数据存储成功写入后立即调用appendWrite,然后再向应用程序确认成功;getMergedWrites返回某个特定用户的最近写入数据。
图1显示了Bob在浏览网站的音乐部分时可能出现的RPC模式。一旦Web请求确定Bob是登录用户,它就通过调用getMergedWrites(17)从FlightTracker获取他的RYW Ticket,并将其放入Web请求上下文中。当鲍勃执行写操作时,客户端库将其元数据加入到网络请求上下文中的Ticket中,并使用appendWrite立即将写元数据发送到FlightTracker。客户端库隐含地将网络请求上下文中的票证附在每个读取查询中。一个网络请求会执行许多这样的查询,这就为摊薄最初的Ticket获取量提供了充足的机会。大多数开发人员不会明确地观察或操作Ticket。
3.4 Ticket-inclusive reads
画了一个图,标注方便理解,请注意,这里的RYW,是可以在不同的终端完成的,也就是说FlightTracker支持用户级别的session一致性。也就是说这里的RYW比教科书上的扩展了些。
数据存储客户有责任为每个查询附加一个确保RYW的Ticket,每个查询服务组件有责任确保Ticket中的所有写内容都包含在查询结果中。
我们的应用程序并不期望独家访问社交图或从快照中读取;读取总是被允许返回比预期更新鲜的数据。在包含Ticket的读取中,Ticket指定了一个应该可见的写入的下限。编码另一个超集的Ticket总是可以在读取时被安全地替换掉,因为超集Ticket所带来的任何东西都可能是作为正常异步复制的一部分而可见的。
**缓存查询。**在获得RYW Ticket后,Bob的网络请求对TAO的缓存进行了两次查询。TAO API的简单性使得缓存可以直接验证其缓存内容的新鲜度--TAO副本将有关数据的版本与Ticket中指定的版本进行比较。例如,如果请求正在读取Bob所信任的所有用户的列表 (GetEdges(<17,TRUSTS,>)),那么W3就意味着目前Alice的边缘版本>=2
在图1中,第一个TAO查询是一个未受Ticket影响的缓存命中。这是很常见的情况。第二个TAO查询显示了一个一致性缺失,即本地缓存中的边是过时的。在这种情况下,缓存会向上游移动,在响应之前将新的边缘合并到本地列表中。请注意,上游的查询有相同的Ticket,它递归地确保了Bob最近写的东西的可见性。
**索引查询。**如果Bob正在浏览ID为55的歌曲,我们希望显示Bob的信任用户也喜欢这首歌。这涉及到寻找所有17,TRUSTS["music"], xi^ h55,ENJOYED_BY, xi。 这种两跳查询不太适合TAO,因为TRUSTS和ENJOYED_BY边缘列表都可能太大,无法完全缓存。我们可以优化这种类型通过具体化一个全局的二级索引来实现查询的目的。具体来说,我们可以使用一个列表交叉索引,其边缘列表为包括 "音乐 "的TRUSTS边缘和 "音乐 "MEDIA节点的ENJOYED_BY边缘。
索引叶(读取服务器)没有足够的信息来准确识别丢失的写入,因为被更新管道过滤的写入永远不会到达。例如,W1可以被上游过滤掉,因为它不是一个包括 "音乐 "的TRUSTS边,因此不会改变任何物化列表。如果没有额外的信息,索引叶会认为W1永远丢失。任何这样的索引叶都不能满足对包含W1的Bob的Ticket的读取。我们通过跟踪最近写入的交付信息来解决这个问题,包括更新管道最近的路由和过滤选择,以及对索引叶的交付状态。这个FlightTracker-ReverseIndex(FT-RI)组件为索引更新管道采取的行动建立了一个最近写入的索引(§6.3)。可通过以下方式查询根据Ticket中的元数据,交付信息被索引客户库用来确定索引读取结果是否足够新鲜。如果是陈旧的,客户库就会使用诸如读取修复或重试等策略来获得一个新的结果。
第6节描述了我们的全部策略,以确保包含Ticket的读取结果反映Ticket中的所有写入。