继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Netflix 的时间序列数据抽象层介绍

宝慕林4294392
关注TA
已关注
手记 337
粉丝 36
获赞 149

由编写Rajiv ShringiVinay ChellaKaidan FullertonOleksii TkachukJoey Lynch的作者。

介绍

随着 Netflix 继续扩展并多元化进入诸如 点播视频游戏 等各个领域,能够获取和存储大量的时间序列数据——这些数据往往达到数 petabytes——并在毫秒级的访问延迟内进行访问变得越来越重要。在之前的博客文章中,我们介绍了 键值数据抽象层数据网关平台,这两个组件对 Netflix 的数据架构至关重要。键值数据抽象层提供了一个灵活且可扩展的解决方案,用于存储和访问结构化键值数据,而数据网关平台则提供了必要的基础设施来防护、配置及部署数据层。

在此基础上,我们利用这些基础抽象开发了时序数据抽象——一个灵活且易于扩展的解决方案,旨在高效地存储和查询大量具有毫秒级延迟的时间序列数据,并以经济高效的方式适用于各种应用场景。

在这篇文章里,我们将深入探讨时间序列抽象的架构、设计原则及其实际应用情况,展示它是如何增强我们平台的大规模时间序列数据的管理能力。

注意:与名称可能暗示的相反,此系统并非作为一个通用的时间序列数据库来构建。我们不使用它来处理指标、直方图或计时器,或任何类似的接近实时的分析用例。这些用例由Netflix的 Atlas遥测系统很好地满足。相反,我们专注于存储和访问极高吞吐量、不可变的时间事件数据,以低延迟和低成本的方式处理。

挑战

在 Netflix,随着时间推移生成的数据(如视频播放事件等)来自用户交互,以及复杂的微服务网络交互,这些数据不断被使用。有效地管理和利用这些数据来获取有价值的信息,对于确保最佳用户体验和系统可靠性至关重要。

然而,存储和查询这类数据会带来一系列独特的挑战:

  • 高吞吐量:每秒支持高达1000万次写入,同时保持高可用性。
  • 大数据集中的高效查询:存储PB级的数据,确保主键读取返回结果在几十毫秒内,并支持在多个次级属性上进行搜索和聚合。
  • 全球读写:在世界任何地方执行读写操作,支持可调的一致性模型。
  • 可调配置:提供将数据集分区为单租户或多租户的存储的能力,并提供调整各种数据集方面(如保留期和一致性)的选项。
  • 处理突发流量:管理在高需求事件期间出现的显著流量高峰,例如新内容发布或区域故障转移。
  • 成本效率:降低每字节的和每操作的成本,以优化长期保留并最小化基础设施费用,这可能对Netflix来说节省数百万美元。
时间序列的概念

时间序列抽象模型是为满足这些要求而开发的,围绕以下核心设计准则进行构建。

  • 分区数据:数据通过独特的时序分区策略与事件桶化技术相结合来管理突发性工作负载并优化查询性能。
  • 灵活存储:该服务设计为可以与多种存储后端集成,包括Apache CassandraElasticsearch,使Netflix能够根据特定用例需求定制存储解决方案。
  • 配置性:TimeSeries为每个数据集提供一系列可调选项,以适应各种应用场景。
  • 可扩展性:该架构支持水平和垂直扩展,使系统能够处理随着Netflix用户群和服务的增长而不断增长的吞吐量和数据量。
  • 分片式基础设施:利用数据网关平台,我们可以部署单租户和/或多租户基础设施,并提供访问权限和流量隔离。

让我们来看看这个抽象的各种方面。

数据模型

我们遵循一个独特的事件数据模型,这个模型涵盖了我们希望捕获的所有事件数据,并使我们能够高效地查询数据。

我们从抽象中最小的数据单位开始吧,逐步向上。

  • 事件项:事件项是一对键值,用户用来为给定事件存储数据(例如,{“device_type”: “ios”})。
  • 事件:事件是由一个或多个事件项组成的结构化集合。事件发生在特定的时间点,并通过客户端生成的时间戳和事件标识符(如UUID)来唯一标识。event_timeevent_id 的组合也构成了事件的唯一幂等键的一部分,从而使用户能够安全地重试请求。
  • 时间序列IDtime_series_id 是在数据集保留期内一个或多个事件的集合(例如,一个 device_id 会存储给定设备在保留期内的所有事件)。所有事件都是不可变的,并且TimeSeries服务只会向给定的时间序列ID追加事件,不会修改已存在的事件。
  • 命名空间:命名空间是时间序列ID和事件数据的集合,表示完整的TimeSeries数据集。用户可以根据各自的用例创建一个或多个命名空间。该抽象在命名空间级别应用了各种可调选项,我们将在接下来探讨服务的控制平面时详细讨论这些选项。
API

注释:API (应用程序编程接口)

该抽象层提供了以下接口,用于与事件数据交互。

WriteEventRecordsSync:这个端点会写入一批事件记录,并将持久性确认发送给客户端。这在用户需要确保事件记录持久性的情况下使用。

WriteEventRecords :这是上述端点的一次性版本。它将一批事件加入队列,而不提供持久性确认。这通常用于日志记录或跟踪等场景,在这种情况下,用户更关心吞吐量并可以容忍少量的数据丢失。

{  
  "namespace": "my_dataset",  
  "events": [  
    {  
      "timeSeriesId": "profile100",  
      "eventTime": "2024-10-03T21:24:23.988Z",  
      "eventId": "550e8400-e29b-41d4-a716-446655440000",  
      "eventItems": [  
        {  
          "eventItemKey": "ZGV2aWNlVHlwZQ==",    
          "eventItemValue": "aW9z"  
        },  
        {  
          "eventItemKey": "ZGV2aWNlTWV0YWRhdGE=",  
          "eventItemValue": "c29tZSBtZXRhZGF0YQ=="  
        }  
      ]  
    },  
    {  
      "timeSeriesId": "profile100",  
      "eventTime": "2024-10-03T21:23:30.000Z",  
      "eventId": "123e4567-e89b-12d3-a456-426614174000",  
      "eventItems": [  
        {  
          "eventItemKey": "ZGV2aWNlVHlwZQ==",    
          "eventItemValue": "YW5kcm9pZA=="  
        }  
      ]  
    }  
  ]  
}

ReadEventRecords :根据一个命名空间、一个时间序列ID、一个时间段以及可选的事件筛选条件,该端点将返回所有匹配的事件,按时间降序排列,延迟仅为毫秒。

{
  "namespace": "my_dataset",
  "timeSeriesId": "profile100",
  "timeInterval": {
    "start": "2024-10-02T21:00:00.000Z",
    "end": "2024-10-03T21:00:00.000Z"
  },
  "eventFilters": [
    {
      "matchEventItemKey": "ZGV2aWNlVHlwZQ==",
      "matchEventItemValue": "aW9z"
    }
  ],
  "pageSize": 100,
  "totalRecordLimit": 1000
}

SearchEventRecords :给定一个搜索条件和时间范围,此端点会返回所有符合条件的事件。这些场景可以接受最终一致性读取的效果。

{  
  "namespace": "my_dataset",  
  "timeInterval": {  
    "时间区间": {  
      "start": "2024-10-02T21:00:00.000Z",  
      "end": "2024-10-03T21:00:00.000Z"  
    }  
  },  
  "searchQuery": {  
    "搜索查询": {  
      "booleanQuery": {  
        "searchQuery": [  
          {  
            "等于": {  
              "eventItemKey": "ZGV2aWNlVHlwZQ==",  
              "eventItemValue": "aW9z"  
            }  
          },  
          {  
            "范围": {  
              "eventItemKey": "ZGV2aWNlUmVnaXN0cmF0aW9uVGltZXN0YW1w",  
              "下界": {  
                "eventItemValue": "MjAyNC0xMC0wMlQwMDowMDowMC4wMDBa",  
                "inclusive": true  
              },  
              "上界": {  
                "eventItemValue": "MjAyNC0xMC0wM1QwMDowMDowMC4wMDBa"  
              }  
            }  
          }  
        ],  
        "operator": "与"  
      }  
    }  
  },  
  "pageSize": 100,  
  "totalRecordLimit": 1000  
}

AggregateEventRecords :给定一个搜索条件和一个聚合模式(例如 DistinctAggregation),此端点会在给定的时间间隔内执行给定的聚合操作。类似于搜索端点,用户可以容忍最终的一致性以及可能更高的延迟(延迟以秒计)。

    {  
      "namespace": "my_dataset",  
      "timeInterval": {  
        "start": "2024-10-02T21:00:00.000Z",  
        "end": "2024-10-03T21:00:00.000Z"  
      },  
      "查询": {...一些搜索条件...},  
      "聚合查询": {  
        "distinct": {  
          "eventItemKey": "事件项键",  
          "pageSize": 100  
        }  
      }  
    }

在接下来的部分中,我们将讨论在存储层我们如何与数据进行交互。

存储层

时间序列的存储层包括一个主数据存储库和一个可选的索引数据存储。主数据存储库确保在写入过程中的数据持久性,并用于主要的读取操作,而索引数据存储则用于搜索和聚合操作。在 Netflix,Apache Cassandra 是在高吞吐量场景下的首选,用于存储持久数据,而Elasticsearch 则是索引数据存储的首选。但是,与我们对 API 的处理方式类似,存储层并没有紧密绑定到这些特定的数据存储上。相反,我们定义了存储 API 接口,这些接口必须被实现,这使我们可以根据需要替换底层的数据存储。

主存储

在这节里,我们将讨论如何使用Apache Cassandra处理时间序列场景。

分区规划

在 Netflix 的规模下,持续涌入的事件数据可能会迅速超出传统数据库的处理能力。时间分区通过将数据按时间间隔(如每小时、每天或每月)划分为可管理的块来解决这一问题。这种方法可以在特定时间范围内高效查询,而无需扫描整个数据集。它还使 Netflix 能够高效地归档、压缩或删除旧数据,从而优化存储和查询性能。此外,这种分区方法减轻了 Cassandra 中与宽分区相关联的性能问题。通过这种策略,我们可以实现更高的磁盘利用率,因为这减少了为数据压缩预留大量磁盘空间的需求,从而节省了成本。

看起来就是这样:

时间切片: 时间切片是数据保留的单位,直接对应于一个Cassandra表。我们创建了多个这样的时间切片,每个对应特定的时间段。根据事件的event_time,事件会被分配到这些切片中的一个。这些切片之间无缝连接,操作遵循前闭后开的原则,确保所有数据都能准确地落入一个切片中。

为什么不使用行级别的 TTL(时间戳)呢?

在单个事件上使用TTL会在Cassandra中生成大量的墓碑,从而降低性能,尤其是在范围查询期间。通过使用离散的时间片并丢弃它们,我们完全避免了墓碑问题的出现。权衡是数据可能比必要的时间稍微保留得更久,因为整个表的时间范围必须完全超出保留窗口才能被丢弃。此外,之后调整TTL较为困难,而TimeSeries可以通过一次控制平面操作立即延长数据集保留时间。

时间桶:在时间区间内,数据进一步划分成时间桶。这使得我们能够针对特定的查询范围目标特定的时间桶,从而实现有效的范围扫描。权衡之处在于,如果用户希望在一个较长的时间段内读取整个数据保留范围,则我们必须扫描许多分区。我们通过并行扫描这些分区并在最后聚合数据来缓解潜在的延迟问题。通常,针对较小数据子集的优势超过了这些散列-聚集操作带来的读取放大问题。通常,用户读取的是较小的数据子集,而不是整个数据保留范围。

事件桶:为了管理极高的写入吞吐量需求,在短时间内对某个时间序列可能产生大量写入,我们将时间桶进一步细分为事件桶。这可以防止同一分区在给定时间范围内过载,并进一步减小分区大小,尽管这会稍微增加读取放大情况。

注意:从 Cassandra 4.x 版本开始,扫描宽分区中数据范围的性能有了显著提升。请参阅文末的未来改进部分,了解旨在利用这一点即利用宽分区扫描的性能提升的动态事件桶分配工作。

存储表格

我们用两种表

  • 数据表:这些是存储实际事件数据的时间片段。
  • 元数据表:该表存储每个时间片段的配置信息按命名空间配置。
数据表格:数据展示表

分区键能够将time_series_idtime_bucket(s)event_bucket(s)范围内拆分事件数据,从而缓解热分区,而聚集键则允许我们以我们通常需要读取的顺序在磁盘上保持数据排序。value_metadata列存储event_item_value的元数据,例如压缩信息。

向数据表写入

用户写入的数据将在给定的时间片(由时间戳决定的特定时间段)、时间桶和事件桶中分配位置。这依据的是事件所附带的event_time。该因素由特定命名空间的控制平面设置来决定。

比如说:

在这个过程中,作者在开始写作之前就如何处理数据的决定做出选择,例如是否需要压缩数据。value_metadata 列记录了任何此类后续处理步骤,确保读者能准确理解数据。

查看数据表:

下面的说明以高层次概述了我们如何从多个分区中读取数据并散列聚集,并在最后将这些结果合并以返回最终结果集。

元数据表格

这张表存储了特定命名空间的时间分段配置信息。

请注意:

  • 无时间间隙:给定时间片段的结束时间与其下一个时间片段的开始时间紧密相连,确保所有事件都有其位置。
  • 数据保留:状态指示哪些表处于保留窗口内,哪些不在保留窗口内。
  • 灵活:此元数据可以在每个时间片段中进行调整,允许我们根据当前时间片段的数据模式调整未来时间片段的分区设置。

元数据列还可以存储更多的信息(例如,表的压缩参数),但我们这里只展示了分区设置,以节省篇幅。

索引存储

为了支持通过非主键属性的次级访问模式,我们索引数据到Elasticsearch中。用户可以在每个命名空间中配置他们希望用来搜索或聚合的数据属性列表。服务在事件流式传输时,会从这些事件中提取这些字段,并将这些提取的内容索引到Elasticsearch中。根据吞吐量,我们可能会用Elasticsearch作为反向索引,从Cassandra中获取完整数据,或者直接将整个源数据存储在Elasticsearch中。

注意:用户不会直接使用Elasticsearch,就像他们不会直接接触Cassandra一样。相反,他们通过搜索和聚合API端点进行交互,这些端点会将给定的查询转换为适合底层数据存储的形式。

接下来的部分,我们来聊聊如何为不同的数据集设置这些数据仓库。

控制面

数据平面主要负责执行读写操作,而控制平面则负责配置命名空间的所有行为。数据平面与TimeSeries控制层通信,该层管理此配置信息。反过来,TimeSeries控制层与分片的 Data Gateway Platform Control Plane 进行交互,该平台负责管理所有抽象和命名空间的控制设置。

将数据平面和控制平面的责任分开有助于确保我们数据平面的高度可用性,因为控制平面负责处理需要底层数据存储之间达成某种模式一致性任务。

命名空间设置

以下配置片段展现了服务的极大灵活性,以及我们如何利用控制平面为每个命名空间调整多个参数。

    "persistence_configuration": [  
      {  
        "id": "PRIMARY_STORAGE",  
        "physical_storage": {  
          "type": "CASSANDRA",                  // 主存储类型  
          "cluster": "cass_dgw_ts_tracing",     // 物理集群名称  
          "dataset": "tracing_default"          // 对应的 keyspace  
        },  
        "config": {  
          "timePartition": {  
            "secondsPerTimeSlice": "129600",    // 时间片的宽度  
            "secondPerTimeBucket": "3600",      // 时间桶的宽度  
            "eventBuckets": "4"                 // 时间桶的数量  
          },  
          "queueBuffering": {  
            "coalesce": "1s",                   // 写入合并的时间  
            "bufferCapacity": 4194304           // 队列的容量,以字节为单位  
          },  
          "consistencyScope": "LOCAL",          // 单区域或多区域  
          "consistencyTarget": "EVENTUAL",      // 最终一致性  
          "acceptLimit": "129600s"              // 允许写入的最远时限  
        },  
        "lifecycleConfigs": {  
          "lifecycleConfig": [                  // 主存储的数据保留  
            {  
              "type": "retention",  
              "config": {  
                "close_after": "1296000s",      // 读写关闭时间  
                "delete_after": "1382400s"      // 删除时间片后的时间  
              }  
            }  
          ]  
        }  
      },  
      {  
        "id": "INDEX_STORAGE",  
        "physicalStorage": {  
          "type": "ELASTICSEARCH",              // 索引存储的类型  
          "cluster": "es_dgw_ts_tracing",       // ES 集群名称  
          "dataset": "tracing_default_useast1"  // 基础索引名称  
        },  
        "config": {  
          "timePartition": {  
            "secondsPerSlice": "129600"         // 索引切片的大小  
          },  
          "consistencyScope": "LOCAL",  
          "consistencyTarget": "EVENTUAL",      // 读写一致性目标  
          "acceptLimit": "129600s",             // 允许写入的最远时限  
          "indexConfig": {  
            "fieldMapping": {                   // 要索引的字段  
              "tags.nf.app": "KEYWORD",  
              "tags.duration": "INTEGER",  
              "tags.enabled": "BOOLEAN"  
            },  
            "refreshInterval": "60s"            // 索引相关的设置  
          }  
        },  
        "lifecycleConfigs": {  
          "lifecycleConfig": [  
            {  
              "type": "retention",              // 索引保留规则  
              "config": {  
                "close_after": "1296000s",  
                "delete_after": "1382400s"  
              }  
            }  
          ]  
        }  
      }  
    ]
配置基础设施

由于有这么多不同的参数,我们需要自动化的配置工作流来推导出给定工作负载的最佳设置。当用户想要创建他们的命名空间时,他们指定一个 工作负载描述 列表,自动化将这些描述转化为具体的基础设施和相关的控制平面设置。我们强烈建议您观看我们的一位同事 Joey LynchApacheCon 分享,了解我们如何做到这一点。我们可能在未来的博客文章中详细讨论这个主题。

一旦系统创建了初始基础设施,它就会根据用户的工作负载进行扩展和调整。下一节将介绍这是如何完成的。

可伸缩性

我们的用户在为其命名空间设置时可能仅拥有有限的信息,从而导致了初步的配置估计。此外,随着应用场景的变化,可能会随着时间的推移产生新的吞吐量需求。我们是这样处理这些问题的:

  • 水平扩展:TimeSeries服务器实例能够根据附带的扩展策略自动调整数量以满足流量需求。我们可以使用我们的容量规划器调整存储服务器容量以适应不断变化的需求。
  • 垂直扩展:我们还可以选择通过垂直扩展来提升TimeSeries服务器实例或存储实例的CPU、RAM和/或存储容量。
  • 扩展磁盘:若容量规划器偏好成本更低的大容量存储而非低延迟SSD,我们可以将EBS附加到存储数据。在这种情况下,当磁盘存储达到一定百分比阈值时,我们将部署作业以扩展EBS卷。
  • 重新分区数据:不准确的工作负载估计可能导致数据集过度或不足分区。一旦我们了解实际数据的特征,TimeSeries控制平面可以调整即将的时间段的分区配置。未来,我们计划支持对旧数据的重新分区以及对当前数据的动态调整。
设计的基本原则

到目前为止,我们已经看到了TimeSeries如何存储和配置事件数据集并与其进行交互。让我们看看如何应用不同的技术来提高操作的性能,并提供更好的保障。

幂等操作

我们更倾向于在所有修改端点中内置幂等性,以便用户可以安全地重试或重发其请求。重试是指当客户端发送的原始请求在预期时间内未返回响应时,客户端会发送一个完全相同的请求到服务器。然后客户端将以最先完成的响应为准。这样可以将应用程序的尾部延迟保持在一个较低的水平。这只有在变更操作具有幂等性时才能安全地进行。对于TimeSeries,给定time_series_id的事件,其幂等性键由event_timeevent_idevent_item_key的组合形成。

基于SLO的套期保值策略

我们为TimeSeries中的不同端点设定服务等级目标(SLO),以表明在给定命名空间下的性能预期。如果响应没有在设定的时间内返回,我们就可以延迟处理该请求。

    "slos": {  
      "read": {               // 每个端点的SLO  
        "latency": {  
          "target": "0.5s",   // 目标延迟为  
          "max": "1s"         // 超时时长为  
        }  
      },  
      "write": {  
        "latency": {  
          "target": "0.01s",  
          "max": "0.05s"  
        }  
      }  
    }
部分回退

有时,客户可能对延迟敏感,并愿意接受部分结果集。一个实际的例子是实时频率调控。在这种情况下,精度并非至关重要,但如果响应出现延迟,对上游客户端来说,这样的响应几乎就失去了实际价值。因此,客户更倾向于使用已收集的数据,而不是在等待所有数据过程中超时。TimeSeries 客户端为此目的支持 SLO 范围内的部分返回。重要的是,在这种部分获取中,我们仍然会保持事件的最新顺序。

自适应分页功能

所有的读取操作都从默认的扇出因子开始,同时扫描8个分区桶。然而,如果服务层确定时间序列数据是密集的——即大多数读取都可以通过读取前几个分区桶得到满足——那么它会动态调整未来读取的扇出因子,以减少对底层数据存储的读取放大。相反,如果数据集较稀疏,我们可能希望在此合理的上限内增加此限制。

有限写窗口

通常情况下,写数据的可写范围小于读数据的范围——也就是,我们希望数据尽快变得不可更改,以便我们可以在此基础上应用优化。我们通过一个可配置的“acceptLimit”参数来控制这个情况,该参数阻止用户写入比当前时间早于acceptLimit时间的事件。比如,4小时的acceptLimit限制意味着用户不能写入比当前时间早于4小时的事件。有时我们会提高此限制来补写历史数据,但在常规写入操作中,我们会将其调整回较低的限制。一旦数据范围变得不可更改,我们就可以安全地对其缓存、压缩和整理等操作以供读取。

写入缓存

我们常常利用这项服务来处理突发的工作负载。通过让事件在短时间内(通常是几秒钟内)累积,我们将其负载均匀分布,而不是一次性将所有负载压在底层数据存储上。这些事件会在每个实例的内存队列里积累。专用的消费者则会稳定地清空这些队列,按分区键将事件分组,并将这些写入批量处理到底层数据存储。

每个数据存储的队列都是根据其特定需求量身定制的,因为它们的操作特性取决于具体写入的数据存储类型。例如,写入Cassandra的批处理大小明显小于索引到Elasticsearch的批处理大小,导致相关消费者的处理速率和批处理大小不同。

使用内存队列确实会增加JVM的垃圾收集次数,但我们通过迁移到JDK 21并采用ZGC,取得了显著的改进。举例来说,ZGC达到了将我们的尾延迟减少86%的效果。

因为使用了内存队列,如果实例崩溃,可能会丢失事件。因此,这些队列仅用于可以容忍一定数据丢失的场景,比如追踪或日志记录。对于需要保证持久性和/或写后立即读取一致性的场景,这些队列实际上会被停用,写入几乎会立即被同步到数据存储中。

动态密实化

一旦时间片离开活动写窗口,我们可以通过数据的不可变特性来提升其读取性能。这一过程可能涉及采用最有效的整理策略重新组织不可变数据,动态缩小或拆分分片以优化系统资源,以及其他类似的方法以确保快速和稳定的性能。

下面的内容将展示我们一些TimeSeries(即时间序列)数据集在实际应用中的表现。

实际表现

该服务可以按几毫秒的顺序写入数据

同时保持读取延迟时间的稳定

撰写这篇博客时,该服务在全球范围内每秒处理接近_15百万_个事件,覆盖了所有不同的数据集。

时间序列的应用:在Netflix

时间序列抽象模型,在Netflix的几个重要服务中扮演着至关重要的角色。这里有几个有影响力的用例:

  • 追踪和分析: 记录 Netflix 中所有应用和微服务的日志,以理解服务间通信,帮助解决问题调试,并回答支持请求。
  • 用户交互跟踪: 跟踪数以百万计的用户交互,例如视频播放、搜索和内容互动,提供实时的洞察,提升 Netflix 的推荐算法并改善整体用户体验。
  • 功能推出及性能分析: 跟踪新功能的推出及性能,使 Netflix 工程师能够衡量用户对功能的使用情况,为未来的改进提供数据驱动的参考。
  • 资产印象跟踪与优化: 跟踪资产印象,确保内容和资产的有效交付,同时提供实时反馈以优化。
  • 计费和订阅管理: 存储与计费和订阅管理相关的历史数据,确保交易记录的准确并支持客户服务查询。

等等……

基于未来的改进

随着应用场景的演变,以及对这种抽象成本效益的需求不断增加,我们计划在未来几个月内做出多项改进。其中一些改进如下:

  • 分层存储以提高成本效益: 支持将较旧且访问较少的数据迁移到更便宜的对象存储中,虽然对象存储的首次字节响应时间较慢,但这种方法有可能为Netflix节省数百万美元。
  • 动态事件分桶: 支持实时将键分割成最合适的分区,而不是在命名空间配置时使用某种静态配置。这种策略的一大优势在于,不需要对那些实际上不需要进行分区的时间序列ID进行分区,从而节省了整体的读取放大成本。此外,使用Cassandra 4.x版本,我们注意到在宽分区中读取数据子集时有了显著的改进,这使我们可以在提前对整个数据集进行分区时更加谨慎。
  • 缓存: 利用数据的不可变性,对特定时间范围内的数据进行智能缓存。
  • 计数和其他聚合: 一些用户只关心在特定时间段内的事件计数,而不必获取所有事件数据。
结论

时间序列抽象层是 Netflix 在线数据基础设施的重要组成部分,在支持实时和长期决策中起着关键作用。无论是监控高流量时段期间的系统性能,还是通过行为数据分析优化用户参与度,时间序列抽象层都确保了 Netflix 能够在全球各地无缝且高效地运行。

随着 Netflix 持续创新并进入新领域,时间序列抽象技术将依然是我们平台的核心技术之一,帮助我们在流媒体及其他领域不断突破可能的界限。

请继续关注第二部分,我们将介绍我们团队的分布式计数器抽象,这是Netflix复合抽象中的一个重要组成部分,建立在时间序列抽象基础之上。

下致谢

特别感谢以下同事在TimeSeries Abstraction项目中的出色表现:Tom DeVoe(请参阅LinkedIn:https://www.linkedin.com/in/tomdevoe/),Mengqing Wang(请参阅LinkedIn:https://www.linkedin.com/in/mengqingwang/),Kartik Sathyanarayanan(请参阅LinkedIn:https://www.linkedin.com/in/kartik894/),Jordan West(请参阅LinkedIn:https://www.linkedin.com/in/jordan-west-8aa1731a3/),Matt Lehman(请参阅LinkedIn:https://www.linkedin.com/in/matt-lehman-39549719b/),Cheng Wang(请参阅LinkedIn:https://www.linkedin.com/in/cheng-wang-10323417/),Chris Lohfink(请参阅LinkedIn:https://www.linkedin.com/in/clohfink/)。

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP