ceph逻辑架构图

ceph后端支持多种存储引擎,以插件化的形式来进行管理使用,目前支持filestore,kvstore,memstore以及bluestore,目前默认使用的是filestore,但是目前bluestore也可以上生产。下图是ceph的逻辑架构图:

ceph-bluestore

Firestore存在的问题是:

  1. 在写数据前需要先写journal,会有一倍的写放大;

  2. 若是另外配备SSD盘给journal使用又增加额外的成本;

  3. filestore一开始只是对于SATA/SAS这一类机械盘进行设计的,没有专门针对SSD这一类的Flash介质盘做考虑。

而Bluestore的优势在于:

  1. 减少写放大;

  2. 针对FLASH介质盘做优化;

  3. 直接管理裸盘,进一步减少文件系统部分的开销。

但是在机械盘场景Bluestore与firestore在性能上并没有太大的优势,bluestore的优势在于flash介质盘。

FileStore逻辑架构

下图为ceph filestore逻辑架构图:

ceph-bluestore

  1. 首先,为了提高写事务的性能,FileStore增加了fileJournal功能,所有的写事务在被FileJournal处理以后都会立即callback(上图中的第2步)。日志是按append only的方式处理的,每次都是被append到journal文件末尾,同时该事务会被塞到FileStore op queue;

  2. 接着,FileStore采用多个thread的方式从op queue 这个 thread pool里获取op,然后真正apply事务数据到disk(文件系统pagecache)。当FileStore将事务落到disk上之后,后续的读请求才会继续(上图中的第5步)。

  3. 当FileStore完成一个op后,对应的Journal才可以丢弃这部分Journal。对于每一个副本都有这两步操作,先写journal,再写到disk,如果是3副本,就涉及到6次写操作,因此性能上体现不是很好。

Bluestore逻辑架构

下图为ceph bluestore逻辑架构图:

ceph-bluestore

  1. Bluestore实现了直接管理裸设备的方式,抛弃了本地文件系统,BlockDevice实现在用户态下使用linux aio直接对裸设备进行I/O操作,去除了本地文件系统的消耗,减少系统复杂度,更有利于Flash介质盘发挥性能优势;

  2. 为了惯例裸设备就需要一个磁盘的空间管理系统,Bluestore采用Allocator进行裸设备的空间管理,目前支持StupidAllocator和BitmapAllocator两种方式;

  3. Bluestore的元数据是以KEY-VALUE的形式保存到RockDB里的,而RockDB又不能直接操作裸盘,为此,bluestore实现了一个BlueRocksEnv,继承自EnvWrapper,来为RocksDB提供底层文件系统的抽象接口支持;

  4. 为了对接BlueRocksEnv,Bluestore自己实现了一个简洁的文件系统BlueFS,只实现RocksDB Env所需要的接口,在系统启动挂在这个文件系统的时候将所有的元数据都加载到内存中,BluesFS的数据和日志文件都通过BlockDevice保存到底层的裸设备上;

  5. BlueFS和BlueStore可以共享裸设备,也可以分别指定不同的设备,比如为了获得更好的性能Bluestore可以采用 SATA SSD 盘,BlueFS采用 NVMe SSD 盘。

2018年2月12日到2019年2月11日,刚好满一年,不知不觉间居然写了35770行代码,

2018-02-12 ~ 2019-02-11, 刚好入职EMC满一年,里程碑两件:

1,个人代码量突破3万5千行,排列第一;

2,专利公司内部通过且美国专利局审核中2个。

codeline

当前大数据处理平台存在的问题

如图1是目前大数据处理平台最常见的Lambda架构,它的优势在于实时处理与批处理统一,但是它的缺点也很明显:

  1. 实时处理一条路径,批处理另外一条路径,不同的路径采用了不同的计算组件,这就增加了系统的复杂度;
  2. 数据存储多组件化、多份化,如下图,同样的数据会被存储在ElasticSearch 里、S3对象存储系统里、Kafka里、HDFS里以及Cassandra里,而且考虑到数据的可靠性,数据还都是多份冗余的,这就极大的增加了用户的存储成本;
  3. 系统里组件太多太复杂,也增加了用户的运维成本。

lambda架构

​图1. Lambda架构

因此,为了解决Lambda架构的以上三大缺点,流式架构被提出。在流式架构里,流计算一般选用Flink作为计算组件,那么对于存储来说又意味着什么呢?为了降低系统复杂度、减少用户的存储成本与运维成本,我们推出了 流存储,目的之一就是为了重构Lambda架构里的存储栈,这样流式架构就可以由”流计算+流存储“组成。

第4种存储类型 - 流存储

首先,流式大数据处理平台里的数据一般被称之为“流数据”,流数据在百度百科里是这样被定义的:

流数据是一组顺序、大量、快速、连续到达的数据序列,一般情况下,数据流可被视为一个随时间延续而无限增长的动态数据集合。应用于网络监控、传感器网络、航空航天、气象测控和金融服务等领域。

那么目前又有哪种存储系统最适合用于“流数据”呢?正如当前技术条件下最适合“流数据”计算的是类似Flink这样的流计算应用,最适合“流数据”存储的应当是流存储系统。

如图2所示,从 存储的视角来说,每种类型的数据都有其原生的属性和需求,对应有最佳的适用场景以及最合适的存储系统。

存储类型

​ 图2. 4大存储类型

简单来说就是传统数据库这类对于IOPS要求高的业务需要块存储系统。文件共享场景下需要在用户间共享文件进行读写操作,因此适合采用分布式文件存储系统。而互联网业务文件以及图片、视频等适合采用对象存储系统。

流数据存储具有性能要求高、严格次序保证、连续而又无限、大规模租户隔离等特点,而目前市面上又没有这样一个专门针对流数据进行设计的存储系统。因此,为了满足业务需求、平衡商业成本与技术成本,也为了给流数据提供最佳最合适的存储系统,分布式流存储Pravega被推出。

I/O路径隔离

​ 图3. 日志结构

如图3所示:在Pravega里,日志是作为共享存储原语而存在的。Pravega被推出的目的之一就是为了 重构Lambda架构里的存储栈:流批统一、降低存储成本以及运维成本。 一般数据的批处理对应于处理历史数据,因此Pravega支持高吞吐量的追赶读;数据的流处理对应于处理实时数据,因此Pravega又支持低时延的尾部读取以及写入;同时Pravega通过分层存储以及资源自动伸缩降低了用户的存储成本以及运维成本。

Pravega关键架构

架构目标

  • 持久化:在客户端确认写入前,数据被复制并且写入磁盘;
  • 严格的顺序保证以及恰好一次语义:支持追赶读、尾部读以及从中间任意位置读,支持事务
  • 轻量级:一个流就如同一个文件,可以在单集群里创建千万量级起的流;
  • 可弹性:可基于负载和吞吐量智能地动态扩展或者收缩流;
  • 无限性:存储空间大小不受单个节点的容量限制;
  • 高性能:写入延迟低于10ms,吞吐量仅受网络带宽限制,读模式(例如:追赶读)不影响写性能;

逻辑架构

”技术在某种程度上一定是来自此前已有技术的新的组合“ – 《技术的本质》,布莱恩·阿瑟

Pravega为连续而又无限的数据提供了一种新的存储原语 - 流存储,然而Pravega也并不是凭空发明出来的,它是以前成熟技术与新技术的组合,例如Pravega的 范围、流、段、事件就跟Kafka的主题、分区、段、消息对应,而一层存储又用了Bookkeeper,协调器用了Zookeeper等,如图4 :Pravega的逻辑架构。

逻辑架构图

​ 图4. 逻辑架构

  1. Pravega提供了一个用Java编写的客户端库,抽象出了流协议层接口,用于支持客户端应用,例如Flink、Spark以及一些检索系统等;
  2. Pravega实现了一个流数据抽象层,用于事件流和字节流的抽象;
  3. Pravega遵循软件定义存储的设计规则,其控制面与数据面分离,控制实例组成控制面,实现了检索流信息、监控集群、收集相关指标等功能,同时为了实现高可用,通常有多个(建议至少3个)控制实例同时对外提供服务;
  4. Pravega采用Zookeeper作为集群中的协调组件;
  5. Pravega的第1层存储系统由bookkeeper实现,第2层存储系统由开源的HDFS、Ceph、GlusterFS、Swift或者商业存储产品组成。

流批统一 - 降低系统复杂度

通过使用Pravega,实现了流批统一的大数据处理架构,重构了大数据处理平台的存储栈,有效降低了系统复杂度.

存储分层 - 降低存储成本

如图4所示,在Pravega里,底层存储系统由两部分组成:第1层为低时延存储层,主要关注性能,用于存储热点数据,由bookkeeper实现,保证了存储系统的低时延、高性能。第2层为长期存储层,主要关注低成本、高吞吐量以及高可扩展性,提供数据的长期存储,由开源的或者商业的存储产品组成。随着数据的老化,第1层中的数据将自动分层流入第2层。通过这种方式,冷热数据分离有效降低了数据存储成本。

资源自动缩放 - 减少运维成本

在Pravega里,当流中的负载上升或下降时,流中段的数量会随着负载自动增长或收缩,此特性被称之为“自动缩放”,该特性无需人工干预自动完成,有效减少了系统的运维成本。当创建流时,可以使用缩放策略配置流,该策略确定流如何响应其负载变化,目前支持三种策略:1)固定,流段的数量不随负载而变化;2)基于写入的字节数,当每秒写入流的数据字节数增量超过某个目标速率时,流段的数量增加,相应的如果它低于某个流速时,流段数量减少;3)基于事件的个数,与基于字节数的扩展策略类似,不同之处在于它使用事件的个数而不是字节数。

Pravega的一些关键概念与特性

本章节将简要介绍一些Pravega的关键特性。

范围(scope):在Pravega里,范围是流的命名空间,例如可以把一台机器命名为一个范围,也可以把一个无人车命名为一个范围,还可以把整个工厂命名为一个范围。

流(stream):在同一个范围内流具有命名唯一性,所有流的名称在同一个范围内都是唯一的。在pravega里数据被组织到流中的,流是一种可持久化、可伸缩、仅附加、字节大小无限制的序列,具有高性能和强一致性的特性。

段(segment):流由段组成,段是流的分片。

事件(event): 段由事件组成,事件存储在段里,事件是流中的可以表示为一组字节的任何事物。例如:来自温度传感器的读数,它包含少量的字节且由时间戳,度量标识符和温度值组成。另外事件也可以是与用户点击网站或APP相关联的日志数据等。

写客户端(writers):写客户端是一个可以创建事件并将事件写入流中的应用,所有的事件数据都可以通过附加到流的尾部来写入。

读客户端(readers):读客户端是一个可以从流中读取事件的应用,读客户端可以从流中的任何一点读取,比如头部、尾部、中间任何一点。

读者组(readerGroups):读者组由读客户端组成,读者组本质上是为了实现同一个组内读客户端的平衡以及不同组的扇出。同一个读者组内的读客户端可以一起并行读取给定的一组流段内的事件,比如一个读客户端对应一个段。不同的应用可以定义不同的读者组实现扇出,比如定义一个Flink读者组,再定义一个检索读者组,这样二者互不影响,互不干涉,可以优雅而又和谐地一起读取同一个流段内的事件。

顺序保证:流是由段组成的,写入流的事件被写入单个段,在同一个段内的事件具有顺序性。对于读客户端来说,可以分配多个可并行读取的段,从多个段读取的也许是交错的事件,但在同一个段内读取的数据是有严格有序的。

检查点:Pravega为应用提供了在读者组上初始化检查点的功能,使用检查点的意图是通过使用检查点事件来确保每个读客户端能保存原来的使用状态。

事务: Pravega提供了事务功能,事务是写客户端可以“批处理”一堆事件并将它们作为一个处理单元原子性地提交到流中。这一堆事件要么所有都处理成功,要么所有都处理失败。在提交事务之前,发布到事务中的事件永远不会被读客户端看到。

状态同步器: Pravega也提供了在分布式计算环境中作为协调器的功能,类似Zookeeper、ETCD这样的提供分布式共识和领导者选举能力。这样的组件在Pravega里被称作“状态同步器”。状态同步器为在集群中运行的多个进程之间的共享状态提供同步机制,使用户可以轻松地构建高级服务,从而使用户更加的容易构建分布式应用。

恰好一次: Pravega确保每个事件只被处理一次,即使客户端、服务器或网络出现故障也能保证精确的处理顺序。

性能: Pravega的延迟目标为毫秒级(<10ms);

永久保留: Pravega将流的抽象与实际数据存储分离,这使得Pravega可以透明地将数据从低延迟、持久的存储层移到云存储服务层。

高效存储: Pravega统一了流(有序)数据和批量(并行)数据的访问,可以将批量和实时应用程序结合起来而无需为流式计算流水线(比如Flink)的每个步骤复制数据从而有效的提高了数据的存储效率。



## 与kafka对比

前面我们已经提到过Pravega是从 存储的视角来看待流数据,而Kafka本身的定位是消息系统而不是存储系统,它是从 消息的视角来看待流数据。消息系统与存储系统的定位是不同的,简单来说,消息系统是消息的传输系统,关注的是数据传输与生产消费的过程。而存储系统除了关注存储用的物理媒介,数据的持久化、安全、可靠性、一致性、隔离等都是它的原生属性,它关注数据的生产、传输、存放、访问等整个数据的生命周期。

这里我们把Pravega与Kafka做了对比,大体在功能上的差异如下表所示。功能上的差异也只是说明各个产品针对的业务场景不同,看待数据的视角不同,并不是说明这个产品不好,另外每个产品自身也在演进,因此本对比仅供参考。

名称 Kafka 2.1.0 Pravega GA
自动扩容缩容 部分支持 支持
完全不丢数据 不支持 支持
多协议可入 支持 支持
无限个流 不支持 支持
事务 支持 支持
恰好一次 支持 支持
顺序保证 支持 支持
兼容Kafka API 支持 支持
数据链接与汇聚 支持 部分支持
多种二层存储支持(ECS,HDFS,S3,etc) 不支持 支持
安全与加密 支持 支持
无限多租户 不支持 部分支持
服务质量保证 部分支持 部分支持
流计算应用集成 支持 支持
数据治理 不支持 支持

总结

本文讲述了推出分布式流存储Pravega的原因,介绍了一些Pravega的关键架构以及关键特性,另外还与Kafka做了简要对比。有关Pravega的更多详细信息,请参阅官方网站以及关注我们的后续文章。另作者能力有限,如有不足之处欢迎留言批评指正。

问题思考

最后给大家留一个问题:一般来说从开源项目到商业产品还是有一段距离的(注意这里的用词:开源的“项目”,商业的“产品”),那么对于设计开发人员来说应该如何弥补这段距离,从而使得开源项目产品化?

Pravega架构

”技术在某种程度上一定是来自此前已有技术的新的组合“ – 《技术的本质》,布莱恩·阿瑟

Pravega为连续而又无限的数据提供了一种新的存储原语 - 流存储,然而Pravega也并不是凭空发明出来的,它是以前成熟技术与新技术的组合,例如Pravega的 范围、流、段、事件就跟Kafka的主题、分区、段、消息对应,而一层存储又用了Bookkeeper,协调器用了Zookeeper等。

设计原则与目标

  • 持久化:在客户端确认写入前,数据被复制并且写入磁盘;

  • 保序:段内严格保序;

  • 恰好一次:支持恰好一次语义;

  • 轻量级:一个流就如同一个文件,可以在单集群里创建千万量级起的流;

  • 可弹性:可基于负载和吞吐量智能地动态扩展或者收缩流;

  • 无限性:存储空间大小不受单个节点的容量限制;

  • 高性能:写入延迟低于10ms,吞吐量仅受网络带宽限制,读模式(例如:追赶读)不影响写性能;

Pravega设计创新

  1. 支持“无限流”分层

  2. 零接触动态缩放

    • 根据负载和SLO自动调整读/写并行度

    • 没有服务中断

    • 无需手动重新配置客户端
    • 无需手动重新配置服务资源
  3. 智能工作负载分配

    • 无需为峰值负载过度配置服务器
  4. I / O路径隔离

    • 支持尾部写入
    • 支持尾部读
    • 支持追赶读
  5. 支持“恰好一次”事务

逻辑架构

下图为Pravega的逻辑架构图:


逻辑架构图

  1. 首先,Pravega提供了一个用Java编写的客户端库,抽象出了流协议层接口,用于支持客户端应用,例如Flink、Spark以及一些检索系统等;
  2. 其次,Pravega实现了一个流数据抽象层,用于事件流和字节流的抽象;
  3. 再者,从整体架构上来讲Pravega符合软件定义存储的设计规则,其控制面与数据面分离,数据面的集合统称为段存储层,控制实例组成控制面,实现了检索流信息、监控集群、收集相关指标等功能,同时为了实现高可用,通常有多个(建议至少3个)控制实例同时对外提供服务。
  4. Pravega采用Zookeeper作为集群中的协调组件。
  5. Pravega的存储系统由两部分组成:第1层为短期存储层,主要关注性能,用于存储热点数据,由bookkeeper实现,保证了存储系统的低时延、高性能。第2层为长期存储层,主要关注成本,提供数据的持久性以及长期存储,由开源的或者商业的存储产品组成。第1层保留热点数据,随着第1层中数据的老化,数据将自动分层流入第2层。

数据架构

下图展示了Pravega的数据架构图以及数据流分层:

数据架构图

  1. Pravega客户端可以通过调用控制器接口管理流的创建、删除和缩放以及进行事务管理:启动事务、创建事务、跟踪事务状态;
  2. 所有的数据对读来说都是透明的,客户端的读写操作直接与段存储(数据面)进行交互,而不通过控制器;
  3. 段存储里有缓存组件保证了读写的高性能,热点数据放在bookkeeper里作为一层存储;
  4. 数据老化后会自动流转到长期存储(例如:对象存储系统,文件存储系统,HDFS等)里以便降低存储成本;

关键子功能 - 零接触缩放

零接触缩放:段的动态拆分与合并

段的拆分与合并

如上图所示,1)拆分:在t1时刻系统负载加大,段0被拆分成段1和段2,同时段0封装不再写入;t2时刻系统负载继续加大,段2被拆分成段3与段4,同时段2被封装不再写入;t3时刻系统负载又继续加大,段1被拆分成段5和段6,同时段1被封装不再写入;2)合并:t4时刻系统负载降低,段6与段3被合并成段7,同时段6与段3被封装不再写入。而且所有的这些行为都是Pravega里自动完成的无需人工干预。

零接触缩放:写并行 - 与Kafka比较

写并行

当并行写入的时候:

  1. 在Pravega里流段的数量会根据负载和服务质量目标而动态变化,并且段的拆分与合并都是自动进行的无需人工干预,同时拆分或合并流段是,写客户端的配置是静态不变的;

  2. 在Kafka里主题分区数(写并行性)是静态的,添加或删除分区时需要手动配置服务并且当分区数更改时,必须手动更新生产者配置。

零接触缩放:读并行 - 与Kafka比较

读并行

并行读取时:

  1. 在Pravega里,当拆分或者合并流段时,读客户端通过流协议获得通知从而使得读并行与流段缩放保持同步;
  2. 在Kafka里,当分区数更改时,必须手动更改使用者配置。

关键子功能 - 智能工作负载分配

智能工作负载分配 - 与Kafka比较

智能工作负载分配

在Pravega里,热点段会自动拆分,子段在整个集群中重新分配缓解热点,同时最大限度地利用集群的可用IOPS能力;而在Kafka里没有减轻“热点”分区的机制,其强制部署并且过度配置资源以获得处理其“峰值负载”的能力。

关键子功能 - I/O路径隔离

I/O路径隔离

流存储的基础数据结构为仅附加写入的日志结构。考虑到高吞吐量,Pravega支持追赶读,同时为了保证低时延,Pravega还支持尾部读取以及尾部写入,从而进行了IO路径的隔离。

关键子功能 - 事务

智能工作负载分配

Pravega提供了事务功能,事务是写客户端可以“批处理”一堆事件并将它们作为一个处理单元原子性地提交到流中。这一堆事件要么所有都处理成功,要么所有都处理失败。在提交事务之前,发布到事务中的事件永远不会被读客户端看到。如上图所示,第一步,先将一堆事件封装在一个事务里;第二步,提交这个事务。这个事务里所有的事件要么全部都处理成功要么全部都处理失败。

总结

本文分析了物联网场景下的数据存储商业现状以及技术现状,为平衡商业成本与技术成本推出了分布式流存储系统Pravega,同时本文还介绍了流存储的特殊需求点以及与Kafka做了简要对比,此外还介绍了一些Pravega的关键架构以及一些关键特性。有关Pravega的更多详细信息,请参阅官方网站。另作者能力有限,如有不足之处欢迎留言批评指正。

任务和算子链

对于分布式执行,Flink将算子子任务链接到任务中。每个任务由一个线程执行。将算子链接到任务中是一项有用的优化:它可以减少线程到线程切换和缓冲的开销,并在降低延迟的同时提高整体吞吐量。可以配置链接行为; 有关详细信息,请参阅链接文档。

下图中的示例数据流由五个子任务执行,因此具有五个并行线程。

算子链接到任务

作业管理器,任务管理器,客户端

Flink运行时包含两种类型的进程:

  • JobManagers(也称为主作业)协调分布式执行。他们调度任务,协调检查点,协调故障恢复等。

总是至少有一个Job Manager。高可用性配置将具有多个JobManagers,其中一个始终是领导者,其他人则是备用者。

  • TaskManagers(也叫工作者)执行数据流的任务(或者更具体地说,子任务),并且缓冲和交换数据流。

必须至少有一个TaskManager。

JobManagers和TaskManagers可以通过多种方式启动:直接作为独立集群、在容器中、或由YARN或Mesos等资源框架管理。TaskManagers连接到JobManagers,宣布它们自己是可用,并被分配工作。

客户端不是运行时和程序执行的一部分,而是被用来准备和发送的数据流的JobManager。之后,客户端可以断开连接或保持连接以接收进度报告。客户端既可以作为触发执行的Java / Scala程序的一部分运行,也可以在命令行进程中运行./bin/flink run …。

执行Flink数据流所涉及的过程

任务槽和资源

每个worker(TaskManager)都是一个JVM进程,可以在不同的线程中执行一个或多个子任务。为了控制worker接受的任务数量,worker有所谓的任务槽(至少一个)。

每个任务槽代表TaskManager的固定资源子集。例如,具有三个插槽的TaskManager将其托管内存的1/3专用于每个插槽。对资源进行分隔意味着子任务不会与来自其他作业的子任务竞争托管内存,而是具有一定数量的保留托管内存。请注意,此处不会发生CPU隔离; 当前插槽只分离任务的托管内存。

通过调整任务槽的数量,用户可以定义子任务如何相互隔离。每个TaskManager有一个插槽意味着每个任务组在一个单独的JVM中运行(例如,可以在一个单独的容器中启动)。拥有多个插槽意味着更多子任务共享同一个JVM。同一JVM中的任务共享TCP连接(通过多路复用)和心跳消息。它们还可以共享数据集和数据结构,从而减少每任务开销。

具有任务槽和任务的TaskManager

默认情况下,Flink允许子任务共享插槽,即使它们是不同任务的子任务,只要它们来自同一个作业。结果是一个槽可以容纳整个作业的管道。允许此插槽共享有两个主要好处:

Flink集群需要与作业中使用的最高并行度一样多的任务槽。无需计算程序总共包含多少任务(具有不同的并行性)。

更容易获得更好的资源利用率。如果没有插槽共享,非密集型源/ map()子任务将阻止与资源密集型窗口子任务一样多的资源。通过插槽共享,将示例中的基本并行性从2增加到6可以充分利用插槽资源,同时确保繁重的子任务在TaskManagers之间公平分配。

具有共享任务槽的TaskManagers

API还包括可用于防止不期望的插槽共享的资源组机制。

根据经验,一个好的默认任务槽数就是CPU核心数。使用超线程,每个插槽然后需要2个或更多硬件线程上下文。

状态后端

存储键/值索引的确切数据结构取决于所选的状态后端。一个状态后端将数据存储在内存中的哈希映射中,另一个状态后端使用RocksDB作为键/值存储。除了定义保存状态的数据结构之外,状态后端还实现逻辑以获取键/值状态的时间点快照,并将该快照存储为检查点的一部分逻辑。

检查点和快照

保存点

用Data Stream API编写的程序可以从保存点恢复执行。保存点允许更新程序和Flink群集,而不会丢失任何状态。

保存点是手动触发的检查点,它将程序的快照写入状态后端。他们依赖于常规的检查点机制。在执行期间,程序会周期性地在工作节点上创建快照并生成检查点。对于恢复,仅需要最后完成的检查点,并且一旦新的检查点完成,就可以安全地丢弃旧的检查点。

保存点与这些定期检查点类似,不同之处在于它们由用户触发,并且在完成较新的检查点时不会自动过期。可以从命令行或通过REST API取消作业时创建保存点。

抽象层次

Flink提供不同级别的抽象来开发流/批处理应用程序。

抽象层次

  • 最低级抽象只提供有状态流。它通过Process Function嵌入到DataStream API中。它允许用户自由处理来自一个或多个流的事件,并使用一致的容错状态。此外,用户可以注册事件时间和处理时间回调,允许程序实现复杂的计算。

  • 实际上,大多数应用不需要上述低级抽象,而是针对Core API编程, 如DataStream API(有界/无界流)和DataSet API (有界数据集)。这些流动的API提供了用于数据处理的通用构建块,例如各种形式的用户指定的转换,连接,聚合,窗口,状态等。在这些API中处理的数据类型在相应的编程语言中表示为类。

低级Process Function与DataStream API集成,因此只能对某些操作进行低级抽象。DataSet API提供的有限数据集的其他原语,如循环/迭代。

  • Table API是以表为中心的声明性DSL,其可以是动态地改变的表(表示流时)。Table API遵循(扩展)关系模型:表附加了一个模式(类似于在关系数据库中的表),API提供了类似的操作,如选择,项目,连接,分组依据,聚合等。Table API程序以声明方式定义应该执行的逻辑操作,而不是准确指定 操作代码的外观。虽然Table API可以通过各种类型的用户定义函数进行扩展,但它的表现力不如Core API,但使用更简洁(编写的代码更少)。此外,Table API程序还会通过优化程序,在执行之前应用优化规则。

可以在表和DataStream / DataSet之间无缝转换,允许程序混合Table API以及DataStream 和DataSet API。

  • Flink提供的最高级抽象是SQL。这种抽象在语义和表达方面类似于Table API,但是将程序表示为SQL查询表达式。在SQL抽象与 Table API紧密地相互作用,和SQL查询可以在Table API中定义的表上执行。

程序和数据流

Flink程序的基本构建块是流和转换。(请注意,Flink的DataSet API中使用的DataSet也是内部流 - 稍后会详细介绍。)从概念上讲,流是(可能永无止境的)数据记录流,而转换是将一个或多个流作为输入,并产生一个或多个流输出的结果。

执行时,Flink程序映射到流数据流,由流和转换运算符组成。每个数据流都以一个或多个源开头,并以一个或多个接收器结束。数据流类似于任意有向无环图 (DAG)。尽管通过迭代结构允许特殊形式的循环 ,但为了简单起见,我们将在大多数情况下对此进行掩饰。

DataStream程序及其数据流

通常,程序中的转换与数据流中的运算符之间存在一对一的对应关系。但是,有时一个转换可能包含多个转换运算符。

源流和接收器记录在流连接器和批处理连接器文档中。DataStream运算符和DataSet转换中记录了转换。

并行数据流

Flink中的程序本质上是并行和分布式的。在执行期间,流具有一个或多个流分区,并且每个运算符具有一个或多个运算符子任务。运算符子任务彼此独立,并且可以在不同的线程中执行,并且可能在不同的机器或容器上执行。

运算符子任务的数量是该特定运算符的并行度。流的并行性始终是其生成运算符的并行性。同一程序的不同运算符可能具有不同的并行级别。

并行数据流

流可以以一对一(或转发)模式或以重新分发模式在两个算子之间传输数据:

  • 一对一流(例如,在上图中的Source和map()算子之间)保留元素的分区和排序。这意味着map()算子的subtask [1] 将以与Source算子的subtask [1]生成的顺序相同的顺序看到相同的元素。

  • 重新分配流(在上面的map()和keyBy / window之间,以及 keyBy / window和Sink之间)重新分配流。每个算子子任务将数据发送到不同的目标子任务,具体取决于所选的转换。实例是 keyBy() (其通过散列密钥重新分区),广播() ,或重新平衡() (其重新分区随机地)。在重新分配交换中,元素之间的排序仅保留在每对发送和接收子任务中(例如,map()的子任务[1] 和子任务[2]keyBy / window)。因此,在此示例中,保留了每个密钥内的排序,但并行性确实引入了关于不同密钥的聚合结果到达接收器的顺序的非确定性。

有关配置和控制并行性的详细信息,请参阅并行执行的文档。

视窗

聚合事件(例如,计数,总和)在流上的工作方式与批处理方式不同。例如,不可能计算流中的所有元素,因为流通常是无限的(无界)。相反,流上的聚合(计数,总和等)由窗口限定,例如“在最后5分钟内计数”或“最后100个元素的总和”。

Windows可以是时间驱动的(例如:每30秒)或数据驱动(例如:每100个元素)。一个典型地区分不同类型的窗口,例如翻滚窗口(没有重叠), 滑动窗口(具有重叠)和会话窗口(由不活动的间隙打断)。

时间和计数Windows

更多窗口示例可以在此博客文章中找到。更多详细信息在窗口文档中。

时间

当在流程序中引用时间(例如定义窗口)时,可以参考不同的时间概念:

  • 事件时间是创建事件的时间。它通常由事件中的时间戳描述,例如由生产传感器或生产服务附加。Flink通过时间戳分配器访问事件时间戳。

  • 摄取时间是事件在源操作员处输入Flink数据流的时间。

  • 处理时间是执行基于时间的操作的每个算子的本地时间。

事件时间,摄取时间和处理时间

有关如何处理时间的更多详细信息,请参阅事件时间文档

有状态的操作

虽然数据流中的许多操作只是一次查看一个单独的事件(例如事件解析器),但某些操作会记住多个事件(例如窗口操作符)的信息。这些操作称为有状态。

状态操作的状态保持在可以被认为是嵌入式键/值存储的状态中。状态被分区并严格地与有状态算子读取的流一起分发。因此,只有在keyBy()函数之后才能在键控流上访问键/值状态,并且限制为与当前事件的键相关联的值。对齐流和状态的密钥可确保所有状态更新都是本地操作,从而保证一致性而无需事务开销。此对齐还允许Flink重新分配状态并透明地调整流分区。

状态和分区

有关更多信息,请参阅有关状态的文档。

容错检查点

Flink使用流重放和检查点的组合实现容错。检查点与每个输入流中的特定点以及每个算子的对应状态相关。通过恢复算子的状态并从检查点重放事件,可以从检查点恢复流数据流,同时保持一致性(恰好一次处理语义)。

检查点间隔是在执行期间用恢复时间(需要重放的事件的数量)来折衷容错开销的手段。

容错内部的描述提供了有关Flink如何管理检查点和相关主题的更多信息。有关启用和配置检查点的详细信息,请参阅检查点API文档。

批处理流

Flink执行批处理程序作为流程序的特殊情况,其中流是有界的(有限数量的元素)。数据集做为数据流在内部处理。因此,上述概念以适用于流程序相同的方式应用于批处理程序,只是少数例外:

  • 批处理程序的容错不使用检查点。而是通过完全重放流来恢复。这是可能的,因为输入是有界的。这会使成本更多高,但却使常规处理更便宜,因为它避免了检查点。

  • DataSet API中的有状态操作使用简化的内存/核外数据结构,而不是键/值索引。

  • DataSet API引入了特殊的同步(基于超前的)迭代,这在有界流上是可行的。有关详细信息,请查看迭代文档

下一步

Flink的Distributed Runtime。

Apache Flink文档

本文档适用于Apache Flink master版。

Apache Flink是一个用于分布式流和批处理数据处理的开源平台。Flink的核心是流数据流引擎,为数据流上的分布式计算提供数据分发,通信和容错。Flink在流引擎之上构建批处理,涵盖原生的迭代支持,受管理的内存和程序优化。

第一步

概念:从Flink的数据流编程模型分布式运行时环境的基本概念开始。这将有助于您了解文档的其他部分,包括配置和编程指南。我们建议您先阅读这部分内容。

教程:

编程指南:您可以阅读我们关于基本API概念DataStream APIDataSet API的指南,以了解如何编写您的第一个Flink程序。

部署

在将Flink作业投入生产之前,请阅读生产准备清单

发行说明

发行说明涵盖了Flink版本之间的重要更改。如果您计划将Flink升级到更高版本,请仔细阅读这些说明。

外部资源

关于Flink项目,一般会经常被问到以下问题。

常见问题

Apache Flink仅用于(近)实时处理用例吗?

Flink是一个非常通用的系统,用于数据处理和数据驱动的应用程序,数据流作为核心构建块。这些数据流可以是实时数据流,也可以是存储的历史数据流。例如,在Flink的视图中,文件是存储的字节流。因此,Flink支持实时数据处理和应用,以及批处理应用。

流可以是无界的(没有结束,事件不断发生)或受限制(流有开始和结束)。例如,来自消息队列的Twitter馈送或事件流通常是无界流,而来自文件的字节流是有界流。

如果一切都是流,为什么Flink中有DataStream和DataSet API?

有界流通常比无界流更有效。在(近)实时处理无限事件流需要系统能够立即对事件起作用并产生中间结果(通常具有低延迟)。处理有界流通常不需要产生低延迟结果,因为无论如何数据都是旧的(相对而言)。这允许Flink以简单且更有效的方式处理数据。

DataStream API通过支持低延时的结果和对事件和时间(包括事件时间)灵活反应的模型捕获无界流和有界流的连续处理,

DataSet API具有加快有界数据流的处理的技术。将来,社区计划将这些优化与DataStream API中的技术相结合。

Flink如何与Hadoop堆栈相关?

Flink独立于Apache Hadoop,并且在没有任何Hadoop依赖性的情况下运行。

但是,Flink与许多Hadoop组件集成得非常好,例如HDFS,YARN或HBase。与这些组件一起运行时,Flink可以使用HDFS读取数据,或写入结果和检查点/快照。Flink可以通过YARN轻松部署,并与YARN和HDFS Kerberos安全模块集成。

Flink运行的其他堆栈是什么?

Flink可以在Kubernetes,Mesos, Docker上运行 ,甚至作为独立服务运行。

使用Flink有哪些先决条件?

您需要Java 8来运行Flink作业/应用。
Scala API(可选)依赖于Scala 2.11。
Apache ZooKeeper需要高度可用且没有单点故障的设置。
对于可以从故障中恢复的高可用流处理设置,Flink需要某种形式的分布式存储用于检查点(HDFS / S3 / NFS / SAN / GFS / Kosmos / Ceph / …)。

Flink支持多大的规模?

用户在非常小的设置(少于5个节点)和1000个节点以及状态的TB上运行Flink作业。

Flink是否仅限于内存数据集?

对于DataStream API,Flink支持大于内存的状态来配置RocksDB状态后端。

对于DataSet API,所有操作(delta迭代除外)都可以扩展到主内存之外。

常见错误消息

“ 获得帮助”页面上列出了常见错误消息。

参考资料

[1].https://flink.apache.org/faq.html