您好,欢迎来到华拓网。
搜索
您的当前位置:首页Netflix单key存储的架构演进

Netflix单key存储的架构演进

来源:华拓网
1. Overview
2. 单表数据模型
    - 数据结构
    - 写操作
    - 读操作
      - 读延迟
      - 缓存层
3. 实时数据与历史数据分离模型
    - 数据结构
    - LiveVH
      - 写操作
      - 读操作
    - CompressedVH
      - 写操作
      - 读操作
    - 分块实现自动扩展
      - 写操作
      - 读操作
      - 改进缓存层
4. 结果对比

Overview

Netflix 的云原生存储架构使用了 Cassandra存储观看历史数据,考虑如下,

  • 支持对时序数据的建模
  • 在当前业务数据上,读写操作比是1:9。而Cassandra提供了高效写操作API,适用于当前的写密集型业务应用
  • 权衡,当前业务更偏向于C。而Cassandra支持可调整的一致性,有助于实现CAP上的权衡

演进路线总结,

v1:一个用户一个key,key后面跟一连串的用户观影记录信息data。当data少时,可以快速定位<userId, data[record1, ..., recordN]>;水平扩展性好。但是当data很大,查询需要低效的O(N)来遍历整个data。LRU缓存可以改善查询低效,但是空间换时间。

v2:既然v1的问题是累积data的太大引起,那么可以根据自定义阈值T1将data切分为两部分,一部分是实时数据;一部分是历史数据。小的实时数据可以按照v1的方式一列一个record;大的历史数据都压缩在一起成为一列(多个历史列合并成一个列)。如果合并压缩后的历史数据还是太大,那么依照阈值T2对其切分。

个人感觉是分库分表/MapReduce/冷热数据分离的思想,将一个读操作或者一个写操作的数据模型,切开成多个小的数据模型,然后在其上实现并发读写。


单表数据模型(Version1)

数据结构

(`customerId`, record1, record2, record3, ..., recordN)
  • 。随着时间的推移,这将导致存储和操作的成本增大。
单表数据模型/v1模型的读、写操作

写操作

。。在 Cassandra 中,对单一列值的写操作是快速和高效的。

读操作

。。。读取一个具有大量列的数据行,会对 Cassandra 造成了额外压力,进而对读操作延迟产生负面影响。

时间范围查询。这同样会导致上面所说的性能不一致问题。因为查询性能依赖于给定时间范围内的观看记录数/列数。

如果要查看的历史数据规模很大,需要做分页才能进行整行读操作。分页对 Cassandra 更好,因为查询不需要等待所有数据都就绪,就能返回给用户。分页也避免了客户超时问题。但是,随着观看记录的增长,分页增加了读取整行的整体延迟。

读延迟

缓存层

为优化读操作延迟,考虑了以增加写路径上的工作为代价,在Cassandra存储前增加了一个内存中的分片缓存层(即EVCache)。缓存实现为一种基本的键-值存储,键是customerId,值是观看历史数据的二进制压缩表示。每次Cassandra的读操作,将额外生成一次缓存查找操作。一旦缓存命中,直接给出缓存中的已有值。对于观看历史记录的读操作,首先使用缓存提供的服务。一旦缓存没有命中,再从Cassandra读取条目,压缩后插入到缓存中。


实时数据与历史数据分离模型(Version2)

数据结构

为进一步实现存储的规模化,分析了数据的特征使用模式,重新定义了观看历史存储。给出了两个主要目标,

  • 更小的存储空间

  • 实时/近期观看历史记录(LiveVH,Live or Recent Viewing History):一小部分频繁更新的近期观看记录。LiveVH 数据以非压缩形式存储
  • 历史/归档观看历史记录(CompressedVH,Compressed or Archival Viewing History):大部分很少更新的历史观看记录。该部分数据将做压缩,以降低存储空间。压缩观看历史作为一列,按键值存储在一行中

为提供更好的性能,LiveVH 和 CompressedVH 存储在不同的数据库表中,并做了不同的优化。

冷数据太多,单机memory放不下,就将冷数据打包,然后切片分段,缓存到不同的单机memory上。在查找时再根据meta data的routing来查。

LiveVH

写操作

读操作

读取实时/近期观看历史:在大多数情况下,近期观看历史仅需从LiveVH读取。这限制了数据的规模,进而给出了更低的延迟。

CompressedVH

写操作

在从LiveVH读取观看历史记录时,如果记录数量超过了一个预设的阈值,那么最近观看记录将由后台任务打包(roll up)、压缩并存储在CompressedVH 中。

打包数据存储在一个行标识为 customerId 的新行中。新打包的数据在写入后会给出一个版本,用于读操作检查数据的一致性。只有验证了新版本的一致性后,才会删除旧版本的打包数据。

CompressedVH的打包行中还存储了元数据信息,其中包括最新版本信息对象规模分块信息

实时数据与历史数据/v2模型的读、写操作

新行记录中具有一个版本列,指向最新版本的打包数据。这样,读取 customerId 总是会返回最新打包的数据。
为降低存储的压力,只使用了一个列存储归档数据。

打包后,其余的记录在打包期间会与 CompressedVH中已有的记录归并

读操作

读取完整观看历史:实现为对 LiveVH 和CompressVH的并行读操作(实时与历史同时读,与下文的分块的并行不一样)。考虑到数据是压缩的,并且CompressedVH 具有更少的列,因此读取操作涉及更少的数据,这显著地加速了读操作。

分块实现自动扩展

。即从一行中读取CompressedVH的性能很低。

为解决这个问题,如果数据规模大于一个预先设定的阈值,就将打包的压缩数据切分为多个分块,并存储在不同的 Cassandra节点中。并行读写也会将读写延迟控制在设定的上限内。

数据分块实现自动扩展(类似join倾斜的prefix)

写操作

打包压缩数据基于一个预先设定的分块大小切分为多个分块。各个分块使用标识CustomerId$Version$ChunkNumber并行写入到不同的行中。

在成功写入分块数据后,元数据会写入一个标识为 customerId 的单独行中。

对非常大的归档观看数据,这一做法将写延迟限制为两次写操作。这时,元数据行为一个不具有数据列的行,这种实现支持对元数据的快速读操作。

读操作

在读取时,首先会使用行标识customerId读取元数据行

  • 对于通常情况,分块数是1,元数据行中包括了打包压缩观看数据的最新版本
  • 对于罕见情况,存在多个压缩观看数据的分块。使用了元数据信息(例如版本和分块数)对不同分块生成不同的行标识即CustomerId$Version$ChunkNumber并行读取所有的分块。这将读延迟限制为两次读操作。

改进缓存层


结果对比

v1 vs. v2

Copyright © 2019- huatuo3.cn 版权所有

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务