内容简介:网络互联设备的增长带来了大量易于访问的时间序列数据。越来越多的公司对挖掘这些数据感兴趣,从而获取了有价值的信息并做出了相应的数据决策。近几年技术的进步提高了收集,存储和分析时间序列数据的效率,同时也刺激了人们对这些数据的消费欲望。然而,这种时间序列的爆炸式增长,可能会破坏大多数初始时间序列数据的体系结构。
大数据文摘作品
编译:丁慧、笪洁琼、蒋宝尚
网络互联设备的增长带来了大量易于访问的时间序列数据。越来越多的公司对挖掘这些数据感兴趣,从而获取了有价值的信息并做出了相应的数据决策。
近几年技术的进步提高了收集,存储和分析时间序列数据的效率,同时也刺激了人们对这些数据的消费欲望。然而,这种时间序列的爆炸式增长,可能会破坏大多数初始时间序列数据的体系结构。
Netflix作为一家以数据为驱导的公司,对这些挑战并不陌生,多年来致力于寻找如何管理日益增长的数据。我们将分享Netflix如何通过多次扩展来解决时间序列数据的存储架构问题。
时间序列数据——会员观看记录
Netflix会员每天观看超过1.4亿小时的内容。 而每个会员在点击标题时会产生几个数据点,这些数据点将被作为观看记录进行存储。 Netflix通过分析这些观看数据,为每位会员提供了实时准确的标签和个性化推荐服务,如这些帖子中所述:
-
如何判断你在观看一个节目?
https://medium.com/netflix-techblog/netflixs-viewing-data-how-we-know-where-you-are-in-house-of-cards-608dd61077da
-
帮助你继续观看Netflix的节目。
https://medium.com/netflix-techblog/to-be-continued-helping-you-find-shows-to-continue-watching-on-7c0d8ee4dab6
从以下3个维度累积历史观看记录:
-
随着时间的推移,每个会员的更多观看记录数据将被存储。
-
随着会员数量的增长,更多会员的观看记录数据会被存储。
-
随着会员每月观看时间的累积,每个会员的更多观看记录将被存储。
过去十年的发展,Netflix已经在全球拥有1亿名会员,其观看记录的数据亦是大幅增加。在本篇博客中,我们将重点讨论如何应对存储观看历史数据带来的巨大挑战。
从简单的开始
观看记录的第一版原生云存储架构使用Cassandra的理由如下:
-
Cassandra对时间序列数据建模提供了很好的支持,其中每行都有动态的列数。
-
观看记录数据的读写速度比约为9:1。由于Cassandra的写入效率非常高,因此Cassandra非常适合频繁写入操作的工作。
-
根据CAP定理,团队更倾向于最终的一致性。Cassandra支持通过调整一致性进行权衡。
在最初的方法中,每个成员的观看历史记录都存储在Cassandra中,并使用行键存储在一行中:CustomerId。这种水平分区的方式能够随着会员数量的增长而有效扩展,并且使得浏览会员的整个观看记录的常见用例变得简单、高效。
然而随着会员数量的增加,更重要的是,每个会员的流量越来越多,行数和整体数据量也越来越多。随着时间的推移,这导致了昂贵的操作成本,对于读写具有海量观看记录的会员数据而言性能较差。
下图说明了初始数据模型的读写流程:
图1:单个图表数据模型
写流程
当会员点击播放时,一条观看记录将作为新的列插入。点击暂停或停止后,则该观看记录被更新。可见对于单列的写入是迅速和高效的。
读流程
通过整行读取来检索一个会员的所有观看记录:当每个会员的记录数很少时,读取效率很高。但是随着一个会员点击更多标题产生更多的观看记录。此时读取具有大量列的行数据会给Cassandra带来额外的压力,并造成一定的读取延迟。
通过时间范围查询读取会员数据的时间片:将导致了与上面的性能不一致,这取决于在指定的时间范围内查看记录的数量。
通过分页整行读取大量观看记录:这对于Cassandra来说是好的,因为它并不需要等待所有的数据返回就可以加载。同时也避免了客户端超时。然而,随着观看记录数量的增加,整行读取的总延迟增加了。
放缓原因
让我们来看看Cassandra的一些内部实现,以了解为什么我们最初简单设计的性能缓慢。随着数据的增长,SSTable的数量相应增加。
由于只有最近的数据在内存中,所以在很多情况下,必须同时读取memtables和SSTable才能检索观看记录。这样就造成了读取延迟。同样,随着数据量的增加,压缩需要更多的IO和时间。由于行越来越宽,读修复和全列修复因此变得更加缓慢。
缓存层
虽说Cassandra在观看记录数据写入方面表现很好,但仍有必要改进读取延迟。为了优化读取延迟,需要以牺牲写入路径上的工作量为代价,我们通过在Cassandra存储之前增加内存分片缓存层(EVCache)来实现。
缓存是一种简单的键值对存储,键是CustomerId,值是观看记录数据的压缩二进制表示。每次写入Cassandra都会发生额外的缓存查找,并在缓存命中时将新数据与现有值合并。
读取观看记录首先由缓存提供服务。在高速缓存未命中时,再从Cassandra读取条目,压缩并插入高速缓存。
多年来随着缓存层的增加,这种单一的Cassandra表格存储方法表现良好。基于CustomerId的分区在Cassandra集群中可扩展性亦较好。
直到2012年,观看记录Cassandra集群成为Netflix最大的Cassandra集群之一。为进一步扩展,团队决定将集群规模扩大一倍。
这就意味着Netflix要冒险进入使用Cassandra的未知领域。与此同时,伴随着Netflix业务的快速增长,包括不断增加的国际会员数和即将投入的原创内容。
重新设计:实时和压缩存储方法
显然,需要采取不同的方法进行扩展来应对未来5年的预期增长。团队分析了数据特征和使用模式,重新设计了观看记录存储方式并实现了两个主要目标:
-
较小的存储空间
-
每个会员的观看记录增长与读写性能保持一致
对于每个会员,观看记录数据被分成两个集合:
-
实时或近期观看记录(LiveVH):频繁更新的最近观看记录数量较少。这样的数据以非压缩形式存储,如上面简单的设计中所述。
-
压缩或存档观看历史记录(CompressedVH):大量较早的观看记录很少更新。 这样的数据将被压缩以减少存储空间。压缩的观看历史记录存储在每行键的单个列中。
LiveVH和CompressedVH存储在不同的表格中,并通过不同的调整以获得更好的性能。由于LiveVH的频繁更新和拥有少量的观看记录,因此压缩需频繁进行,且保证gc_grace_seconds足够小以减少SSTables数量和数据大小。
只读修复和全列修复频繁进行保证数据的一致性。由于对CompressedVH的更新很少,因此手动和不频繁的全面压缩足以减少SSTables的数量。在不频繁更新期间检查数据的一致性。这样做消除了读修复以及全列维修的需要。
使用与前面所述相同的方法将新观看记录写入LiveVH。
写流程
使用与前面所述相同的方法将新观看记录写入LiveVH。
读流程
为了从新设计中获益,观看历史记录的API已更新,可以选择读取最近的或完整的数据:
-
最近观看记录:对于大多数的用例,只需从LiveVH中读取数据,通过限制数据大小降低延迟。
-
完整的观看记录:作为LiveVH和CompressedVH的并行读取实现。由于数据压缩和CompressedVH的列较少,因此通过读取较少的数据就可以显著加速读取。
CompressedVH更新流程
当从LiveVH中读取观看历史记录时,如果记录数量超过可配置的阈值,那么最近的观看记录就被汇总一次,压缩并通过后台任务存储在CompressedVH中。然后使用行键(行关键字):CustomerId将数据存储在新行中。新的汇总是版本化的,写入后会再次检查查数据的一致性。只有在验证与新版本数据一致后,旧版本的数据才会被删除。为简单起见,在汇总过程中没有加锁,Cassandra负责解决极少的重复写入操作(即最后一个写入操作获胜)。
图2:实时和压缩的数据模型
如上图所示,CompressedVH中汇总的行也存储元数据信息,如最新版本号,对象大小和块信息(稍后更多)。版本列存储对最新版本的汇总数据进行引用,以便CustomerId的读取始终只返回最新的汇总数据。
汇总起来的数据存储在一个单一的列中,以减少压缩压力。为了最大限度地减少频繁观看模式的会员的汇总频率,最后几天查看历史记录的值将在汇总后保存在LiveVH中,其余部分在汇总期间与CompressedVH中的记录合并。
通过Chunking进行扩展
对于大多数会员来说,将其整个观看记录存储在单行压缩数据中将在读取流程中提升性能。对于一小部分具有大量观看记录的会员,由于第一种体系结构中描述的类似原因,从单行中读取CompressedVH速度缓慢。不常见用例需要在读写延迟上设一个上限,才不会对常见用例造成读写延迟。
为了解决这个问题,如果数据大小大于可配置的阈值,我们将汇总起来的压缩数据分成多个块。这些块存储在不同的Cassandra节点上。即使对于非常大的观看记录数据,对这些块的并行读取和写入也最多只能达到读取和写入延迟上限。
图3:自动缩放通过组块
写流程
如图3所示,根据可配置的块大小,汇总起来的压缩数据被分成多个块。所有块都通过行键:CustomerId $ Version $ ChunkNumber并行写入不同的行。在成功写入分块数据之后,元数据通过行键:CustomerId写入到自己的行。
对于大量观看记录数据的汇总,上述方法将写入延迟限制为两种写入。在这种情况下,元数据行具有一个空数据列,以便能够快速读取元数据。
为了使常见用例(压缩观看记录小于可配置阈值)被快速读取,将元数据与同一行中的观看记录组合以消除元数据查找流程,如图2所示。
读流程
通过关键字CustomerId首次读取元数据行。对于常见用例,块数为1,元数据行也具有最新版本汇总起来的压缩观看记录。对于不常见的用例,有多个压缩的观看记录数据块。使用版本号和块数等元数据信息生成块的不同行密钥,并且并行读取所有块。上述方法将读取延迟限制为两种读取。
缓存层更改
内存缓存层的增强是为了支持对大型条目进行分块。对于具有大量观看记录的会员,无法将整个压缩的观看历史记录放入单个EVCache条目中。与CompressedVH模型类似,每个大的观看历史高速缓存条目被分成多个块,并且元数据与第一块一起被存储。
结果
利用并行,压缩和改进的数据模型,实现了所有目标:
-
通过压缩缩小存储空间。
-
通过分块和并行的读/写操作保证读/写一致性。常见用例的延迟受限于一次读操作和一次写操作,以及不常见用例的延迟受限于两次读操作和两次写操作。
图4:结果
数据大小减少了约6倍,花费在Cassandra维护上的系统时间减少了约13倍,平均读取延迟减少了约5倍,平均写入延迟减少了约1.5倍。更重要的是,它为团队提供了可扩展的架构和空间,可以适应Netflix观看记录数据的快速增长。
原文链接:https://medium.com/netflix-techblog/scaling-time-series-data-storage-part-i-ec2b6d44ba39
版权声明
本文仅代表作者观点,不代表百度立场。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 如何限制 docker run 容器执行时长?
- Java接口全链路优化:如何降低接口RT时长
- JDK 11 将引入低延迟 GC,大幅度缩短 GC 暂停时长
- 时间序列的聚类
- Pinterest 如何构建时间序列
- [译]时间序列异常检测算法
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Beginning Apache Struts
Arnold Doray / Apress / 2006-02-20 / USD 44.99
Beginning Apache Struts will provide you a working knowledge of Apache Struts 1.2. This book is ideal for you Java programmers who have some JSP familiarity, but little or no prior experience with Ser......一起来看看 《Beginning Apache Struts》 这本书的介绍吧!
HTML 编码/解码
HTML 编码/解码
XML 在线格式化
在线 XML 格式化压缩工具