随着技术迭代的不断加速,大数据极大改变了行业领域对信息流动的限制。本期我们聚焦2017年领域内热门技术与应用实践,带领大家深度解析大数据技术难点和发展趋势。厉兵秣马今点将,群雄逐鹿正当时。
文 /吕能,吴惠君,符茂松
本文介绍了流计算的背景和重要概念,并详细分析了 Twitter 目前的流计算引擎—— Heron的结构及重要组件,希望能借此为大家提供一些在设计和构建流计算系统时的经验。
流计算又称实时计算,是继以 Map-Reduce 为代表的批处理之后的又一重要计算模型。随着互联网业务的发展以及数据规模的持续扩大,传统的批处理计算难以有效地对数据进行快速低延迟处理并返回结果。由于数据几乎处于不断增长的状态中,及时处理计算大批量数据成为了批处理计算的一大难题。在此背景之下,流计算应运而生。相比于传统的批处理计算,流计算具有低延迟、高响应、持续处理的特点。在数据产生的同时,就可以进行计算并获得结果。更可以通过 Lambda 架构将即时的流计算处理结果与延后的批处理计算结果结合,从而较好地满足低延迟、高正确性的业务需求。
Twitter 由于本身的业务特性,对实时性有着强烈的需求。因此在流计算上投入了大量的资源进行开发。第一代流处理系统 Storm 发布以后得到了广泛的关注和应用。根据 Storm 在实践中遇到的性能、规模、可用性等方面的问题,Twitter 又开发了第二代流处理系统——Heron,并在2016年将它开源。
在开始了解 Heron 的具体架构和设计之前,我们首先定义一些流计算以及在 Heron 设计中用到的基本概念:
Tuple:流计算任务中处理的最小单元数据的抽象。
Stream:由无限个 Tuple 组成的连续序列。
Spout:从外界数据源获得数据并生成 Tuple 的计算任务。
Bolt:处理上游 Spout 或者 Bolt 生成的 Tuple 的计算任务。
Topology:一个通过 Stream 将 Spout 和 Bolt 相连的处理 Tuple 的逻辑计算任务。
Grouping:流计算中的 Tuple 分发策略。在 Tuple 通过 Stream 传递到下游Bolt 的过程中,Grouping 策略决定了如何将一个 Tuple 路由给一个具体的Bolt 实例。典型的 Grouping 策略有:随机分配、基于 Tuple 内容的分配等。
Physical Plan:基于 Topology 定义的逻辑计算任务以及所拥有的计算资源,生成的实际运行时信息的集合。
在以上流处理基本概念的基础上,我们可以构建出流处理的三种不同处理语义:
至多一次(At-Most-Once): 尽可能处理数据,但不保证数据一定会被处理。吞吐量大,计算快但是计算结果存在一定的误差。
至少一次(At-Least-Once):在外部数据源允许 Replay(重演)的情况下,保证数据至少被处理一次。在出现错误的情况下会重新处理该数据,可能会出现重复处理多次同一数据的情况。保证数据的处理但是延迟升高。
仅有一次(Exactly-Once):每一个数据确保被处理且仅被处理一次。结果精确但是所需要的计算资源增多并且还会导致计算效率降低。
从上可知,三种不同的处理模式有各自的优缺点,因此在选择处理模式的时候需要综合考量一个 Topology 对于吞吐量、延迟、结果误差、计算资源的要求,从而做出最优的选择。目前的 Heron 已经实现支持至多一次和至少一次语义,并且正在开发对于仅有一次语义的支持。
保持与 Storm 接口(API)兼容是Heron的设计目标之一。因此,Heron 的数据模型与 Storm 的数据模型基本保持一致。每个提交给 Heron 的 Topology 都是一个由 Spout 和 Bolt 这两类结点(Vertex)组成的,以 Stream 为边(Edge)的有向无环图(Directed acyclic graph)。其中 Spout 结点是 Topology 的数据源,它从外部读取 Topology 所需要处理的数据,常见的如 kafka-spout,然后发送给后续的 Bolt 结点进行处理。Bolt 节点进行实际的数据计算,常见的运算如 Filter、Map 以及 FlatMap 等。
我们可以把 Heron 的 Topology 类比为数据库的逻辑查询计划。这种逻辑上的计划最后都要变成实质上的处理计划才能执行。用户在编写 Topology 时指定每个 Spout 和 Bolt 任务的并行度和 Tuple 在 Topology 中结点间的分发策略(Grouping)。所有用户提供的信息经过打包算法(Pakcing)的计算,这些 Spout 和 Bolt 任务(task)被分配到一批抽象容器中。最后再把这些抽象容器映射到真实的容器中,就可以生成一个物理上可执行的计划(Physical plan),它是所有逻辑信息(拓扑图、并行度、计算任务)和运行时信息(计算任务和容器的对应关系、实际运行地址)的集合。
总体上,Heron 的整体架构如图1所示。用户通过命令行工具(Heron-CLI)将 Topology 提交给 Heron Scheduler。再由 Scheduler 对提交的 Topology 进行资源分配以及运行调度。在同一时间,同一个资源平台上可以运行多个相互独立 Topology。
与 Storm 的 Service 架构不同,Heron 是 Library 架构。Storm 在架构设计上是基于服务的,因此需要设立专有的 Storm 集群来运行用户提交的 Topology。在开发、运维以及成本上,都有诸多的不足。而 Heron 则是基于库的,可以运行在任意的共享资源调度平台上。最大化地降低了运维负担以及成本开销。
目前的 Heron 支持 Aurora、YARN、Mesos 以及 EC2,而 Kubernetes 和 Docker 等目前正在开发中。通过可扩展插件 Heron Scheduler,用户可以根据不同的需求及实际情况选择相应的运行平台,从而达到多平台资源管理器的支持。
而被提交运行 Topology 的内部结构如图2所示,不同的计算任务被封装在多个容器中运行。这些由调度器调度的容器可以在同一个物理主机上,也可分布在多个主机上。其中每一个 Topology 的第一个容器(容器0)负责整个 Topology 的管理工作,主要运行一个 Topology Master 进程;其余各个容器负责用户提交的计算逻辑的实现,每个容器中主要运行一个 Stream Manager 进程,一个 Metrics Manager 进程,以及多个 Instance 进程。每个 Instance 都负责运行一个 Spout 或者 Bolt 任务(task)。对于 Topology Master、Stream Manager 以及 Instance 进程的结构及重要功能,我们会在本文的后面章节进行详细的分析。
Heron 的 State Manager 是一个抽象的模块,它在具体实现中可以是 ZooKeeper 或者是文件系统。它的主要作用是保存各个 Topology 的各种元信息:Topology 的提交者、提交时间、运行时生成的 Physical Plan 以及 Topology Master 的地址等,从而为 Topology 的自我恢复提供帮助。
每个容器中的 Metrics Manager 负责收集所在容器的运行时状态指标(Metrics),并上传给监控系统。当前 Heron 版本中,简化的监控系统集成在 Topology Master 中。将来这一监控模块将会成为容器0中的一个独立进程。Heron 还提供 Heron-Tracker 和 Heron-UI 这两个工具来查看和监测一个数据中心中运行的所有 Topology。
在一个 Topology 中,Topology Master 是整个 Topology 的元信息管理者,它维护着完整的 Topology 元信息。而 Stream Manager 是每个容器的网关,它负责各个 Instance 之间的数据通信,以及和 Topology Master 之间的控制信令。
当用户提交 Topology 之后,Scheduler 便会开始分配资源并运行容器。每个容器中启动一个 Heron Executor 的进程,它区分容器0和其他容器,分别启动 Topology Master 或者 Stream Manager 等进程。在一个普通容器中,Instance 进程启动后会主动向本地容器的 Stream Manager 进行注册。当 Stream Manager 收到所有 Instance 的注册请求后,会向 Topology Master 发送包含了自己的所负责的 Instance 的注册信息。当 Topology Master 收到所有 Stream Manager 的注册信息以后,会生成一个各个 Instance,Stream Manager 的实际运行地址的 Physical Plan 并进行广播分发。收到了 Physical Plan 的各个 Stream Manager 之间就可以根据这一 Physical Plan 互相建立连接形成一个完全图,然后开始处理数据。
Instance 进行具体的 Tuple 数据计算处理。Stream Manager 则不执行具体的计算处理任务,只负责中继转发 Tuple。从数据流网络的角度,可以把 Stream Manager 理解为每个容器的路由器。所有 Instance 之间的 Tuple 传递都是通过 Stream Manager 中继。因此容器内的 Instance 之间通信是一跳(hop)的星形网络。所有的 Stream Manager 都互相连接,形成 Mesh 网络。容器之间的通信也是通过 Stream Manager 中继的,是通过两跳的中继完成的。
TMaster 是 Topology Master 的简写。与很多 Master-Slave 模式分布式系统中的 Master 单点处理控制逻辑的作用相同,TMaster 作为 Master 角色提供了一个全局的接口来了解 Topology 的运行状态。同时,通过将重要的状态信息(Physical Plan)等记录到 ZooKeeper 中,保证了 TMaster 在崩溃恢复之后能继续运行。
实际产品中的 TMaster 在启动的时候,会在 ZooKeeper 的某一约定目录中创建一个 Ephemeral Node 来存储自己的 IP 地址以及端口,让 Stream Manager 能发现自己。Heron 使用 Ephemeral Node 的原因包括:
TMaster 主要有以下三个功能:
由于 Topology 的 Physical Plan 只有在运行时才能确定,因此 TMaster 就成为了构建、分发以及维护 Physical Plan 的最佳选择。在 TMaster 完成启动和向 ZooKeeper 注册之后,会等待所有的 Stream Manager 与自己建立连接。在 Stream Manager 与 TMaster 建立连接之后,Stream Manager 会报告自己的实际 IP 地址、端口以及自己所负责的 Instance 地址与端口。TMaster 在收到所有 Stream Manager 报告的地址信息之后就能构建出 Physical Plan 并进行广播分发。所有的 Stream Manager 都会收到由 TMaster 构建的 Physical Plan,并且根据其中的信息与其余的 Stream Manager 建立两两连接。只有当所有的连接都建立完成之后,Topology 才会真正开始进行数据的运算和处理。当某一个 Stream Manager 丢失并重连之后,TMaster 会检测其运行地址及端口是否发生了改变;若改变,则会及时地更新 Physical Plan 并广播分发,使 Stream Manager 能够建立正确的连接,从而保证整个 Topology 的正确运行。
TMaster 会接受 Stream Manager 定时发送的心跳信息并且维护各个 Stream Manager 的最近一次心跳时间戳。心跳首先能够帮助 TMaster 确认 Stream Manager 的存活,其次可以帮助其决定是否更新一个 Stream Manager 的连接并且更新 Physical Plan。
TMaster 还会接受由 Metrics Manager 发送的一部分重要 Metrics 并且向 Heron-Tracker 提供这些 Metrics。Heron-Tracker 可以通过这些 Metrics 来确定 Topology 的运行情况并使得 Heron-UI 能够基于这些重要的 Metrics 来进行监控检测。典型的 Metrics 有:分发 Tuple 的次数,计算 Tuple 的次数以及处于 backpressure 状态的时间等。
非常值得注意的一点是,TMaster 本身并不参与任何实际的数据处理。因此它也不会接受和分发任何的 Tuple。这一设计使得 TMaster 本身逻辑清晰,也非常轻量,同时也为以后功能的拓展留下了巨大的空间。
Stmgr 是 Stream Manager 的简写。Stmgr 管理着 Tuple 的路由,并负责中继 Tuple。当 Stmgr 拿到 Physical Plan 以后就能根据其中的信息知道与其余的 Stmgr 建立连接形成 Mesh 网络,从而进行数据中继以及 Backpressure 控制。Tuple 传递路径可以通过图3来说明,图3中容器1的 Instance D(1D)要发送一个 Tuple 给容器4中的 Instance C(4C),这个 Tuple 经过的路径为:容器1的1D,容器1的 Stmgr,容器4的 Stmgr,容器4的4C。又比如从3A到3B的 Tuple 经过的路径为:3A,容器3的 Stmgr,3B。与 Internet 的路由机制对比,Heron 的路由非常简单,这得益于 Stmgr 之间两两相连,使得所有的 Instance 之间的距离不超过2跳。
Stmgr 除了路由中继 Tuple 的功能以外,它还负责确认(Acking)Tuple 已经被处理。Acking 的概念在 Heron 的前身 Storm 中已经存在。Acking 机制的目的是为了实现 At-Least-Once 的语义。原理上,当一个 Bolt 实例处理完一个 Tuple 以后,这个 Bolt 实例发送一个特殊的 Acking Tuple 给这个 bolt 的上游 Bolt 实例或者 Spout 实例,向上游结点确认 Tuple 已经处理完成。这个过程层层向上游结点推进,直到 Spout 结点。实现上,当 Acking Tuple 经过 Stmgr 时候由异或(xor)操作标记 Tuple,由异或操作的特性得知是否处理完成。当一个 Spout 实例在一定时间内还没有收集到 Acking Tuple,那么它将重发对应的数据 Tuple。Heron 的 Acking 机制的实现与它的前任 Storm 一致。
Heron 引入了反压(Back Pressure)机制,来动态调整 Tuple 的处理速度以避免系统过载。一般来说,解决系统过载问题有三种策略:1. 放任不管;2. 丢弃过载数据;3. 请求减少负载。Heron 采用了第三种策略,通过 Backpressure 机制来进行过载恢复,保证系统不会在过载的情况下崩溃。
Backpressure 机制触发过程如下:当某一个 Bolt Instance 处理速度跟不上 Tuple 的输入速度时,会造成负责向该 Instance 转发 Tuple 的 Stmgr 缓存不断堆积。当缓存大小超过一个上限值(Hight Water Mark)时,该 Stmgr 会停止从本地的 Spout 中读取 Tuple 并向 Topology 中的其他所有 Stmgr 发送一个“开始 Backpressure”的信息。而其余的 Stmgr 在接收到这一消息时也会停止从他们所负责的 Spout Instance 处读取并转发 Tuple。至此,整个 Topology 就不再从外界读入 Tuple 而只处理堆积在内部的未处理 Tuple。而处理的速度则由最慢的 Instance 来决定。在经过一定时间的处理以后,当缓存的大小减低到一个下限值(Low Water Mark)时,最开始发送“开始 Backpressure”的 Stmgr 会再次发送“停止 Backpressure”的信息,从而使得所有的 Stmgr 重新开始从 Spout Instance 读取分发数据。而由于 Spout 通常是从具有允许重演(Replay)的消息队列中读取数据,因此即使冻结了也不会导致数据的丢失。
注意在 Backpressure 的过程中两个重要的数值:上限值(High Water Mark)和下限值(Low Water Mark)。只有当缓存区的大小超过上限值时才会触发 Backpressure,然后一直持续到缓存区的大小减低到下限值时。这一设计有效地避免了一个 Topology 不停地在 Backpressure 状态和正常状态之间震荡变化的情况发展,一定程度上保证了 Topology 的稳定。
Instance 是整个 Heron 处理引擎的核心部分之一。Topology 中不论是 Spout 类型结点还是 Bolt 类型结点,都是由 Instance 来实现的。不同于 Storm 的 Worker 设计,在当前的 Heron 中每一个 Instance 都是一个独立的 JVM 进程,通过 Stmgr 进行数据的分发接受,完成用户定义的计算任务。独立进程的设计带来了一系列的优点:便于调试、调优、资源隔离以及容错恢复等。同时,由于数据的分发传送任务已经交由 Stmgr 来处理,Instance 可以用任何编程语言来进行实现,从而支持各种语言平台。
Instance 采用双线程的设计,如图4所示。一个 Instance 的进程包含 Gateway 以及 Task Execution 这两个线程。Gateway 线程主要控制着 Instance 与本地 Stmgr 和 Metrics Manager 之间的数据交换。通过 TCP 连接,Gateway 线程:1. 接受由 Stmgr 分发的待处理 Tuple;2. 发送经 Task Execution 处理的 Tuple 给 Stmgr;3. 转发由 Task Execution 线程产生的 Metrics 给 Metrics Manager。不论是 Spout 还是 Bolt,Gateway 线程完成的任务都相同。
Task Execution 线程的职责是执行用户定义的计算任务。对于 Spout 和 Bolt,Task Execution 线程会相应地去执行 open()和 prepare()方法来初始化其状态。如果运行的 Instance 是一个 Bolt 实例,那么 Task Execution 线程会执行 execute()方法来处理接收到的 Tuple;如果是 Spout,则会重复执行 nextTuple()方法来从外部数据源不停地获取数据,生成 Tuple,并发送给下游的 Instance 进行处理。经过处理的 Tuple 会被发送至 Gateway 线程进行下一步的分发。同时在执行的过程中,Task Execution 线程会生成各种 Metrics(tuple 处理数量,tuple 处理延迟等)并发送给 Metrics Manager 进行状态监控。
Gateway 线程和 Task Execution 线程之间通过三个单向的队列来进行通信,分别是数据进入队列、数据发送队列以及 Metrics 发送队列。Gateway 线程通过数据进入队列向 Task Execution 线程传入 Tuple;Task Execution 通过数据发送队列将处理完的 Tuple 发送给 Gateway 线程;Task Execution 线程通过 Metrics 发送队列将收集的 Metric 发送给 Gateway 线程。
在本文中,我们介绍了流计算的背景和重要概念,并且详细分析了 Twitter 目前的流计算引擎—— Heron 的结构及重要组件。希望能借此为大家提供一些在设计和构建流计算系统时的经验,也欢迎大家向我们提供建议和帮助。如果大家对 Heron 的开发和改进感兴趣,可以在 Github 上进行查看。
文/吴惠君,吕能,符茂松
本文对比了 Heron 和常见的流处理项目,包括 Storm、Flink、Spark Streaming 和 Kafka Streams,归纳了系统选型的要点。此外实践了 Heron 的一个案例,以及讨论了 Heron 在这一年开发的新特性。
在今年6月期的“基础篇”中,我们通过学习 Heron 的基本概念、整体架构和核心组件等内容,对 Heron 的设计、运行等方面有了基本的了解。在这一期的“应用篇”中,我们将 Heron 与其他流行的实时流处理系统(Apache Storm 、Apache Flink、Apache Spark Streaming 和Apache Kafka Streams)进行比较。在此基础上,我们再介绍如何在实际应用中进行系统选型。然后我们将分享一个简单的案例应用。最后我们会介绍在即将完结的2017年里 Heron 有哪些新的进展。
当前流行的实时流处理系统主要包括 Apache 基金会旗下的 Apache Storm、Apache Flink、Apache Spark Streaming 和 Apache Kafka Streams 等项目。虽然它们和 Heron 同属于实时流处理范畴,但是它们也有各自的特点。
在 Twitter 内部,Heron 替换了 Storm,是流处理的标准。
Heron 兼容 Storm 的数据模型,或者说 Heron 兼容 Storm 的 API,但是背后的实现完全不同。所以它们的应用场景是一样的,能用 Storm 的地方也能用 Heron。但是 Heron 比 Storm 提供更好的效率,更多的功能,更稳定,更易于维护。
Storm Trident 是 Storm 基础上的项目,提供高级别的 API,如同 Heron 的函数式 API。Trident 以 checkpoint 加 rollback 的方式实现了 exactly once;Heron 以 Chandy 和 Lamport 发明的分布式快照算法实现了 effectively once。
Storm 的 worker 在每个 JVM 进程中运行多个线程,每个线程中执行多个任务。这些任务的 log 混在一起,很难调试不同任务的性能。Storm 的 nimbus 无法对 worker 进行资源隔离,所以多个 topology 的资源之间互相影响。另外 ZooKeeper 被用来管理 heartbeat,这使得 ZooKeeper 很容易变成瓶颈。
Heron 的每个任务都是单独的 JVM 进程,方便调试和资源隔离管理,同时节省了整个 topology 的资源。ZooKeeper 在 Heron 中只存放很少量的数据,heartbeat 由 tmaster 进程管理,对 ZooKeeper 没有压力。
Flink 框架包含批处理和流处理两方面的功能。Flink 的核心采用流处理的模式,它的批处理模式通过模拟块数据的的流处理形式得到。
Flink 在 API 方面采用 declarative 的 API 模式。Heron 既提供 declarative 模式 API 或者叫做 functional API 也提供底层 compositional 模式的 API,此外 Heron 还提供 Python 和 C++ 的 API。
在运行方面,Flink 可以有多种配置,一般情况采用的是多任务多线程在同一个 JVM 中的混杂模式,不利于调试。Heron 采用的是单任务单 JVM 的模式,利于调试与资源分配。
在资源池方面,Flink 和 Heron 都可以与多种资源池合作,包括 Mesos/Aurora、YARN、Kubernetes 等。
Spark Streaming 处理 tuple 的粒度是 micro-batch,通常使用半秒到几秒的时间窗口,将这个窗口内的 tuple 作为一个 micro-batch 提交给 Spark 处理。而 Heron 使用的处理粒度是 tuple。由于时间窗口的限制,Spark Streaming 的平均响应周期可以认为是半个时间窗口的长度,而 Heron 就没有这个限制。所以 Heron 是低延迟,而 Spark Streaming 是高延迟。
Spark Streaming 近期公布了一项提案,计划在下一个版本2.3中加入一个新的模式,新的模式不使用 micro-batch 来进行计算。
语义层面上,Spark Streaming 和 Heron 都实现了 exactly once/effectively once。状态层面上,Spark Streaming 和 Heron 都实现了 stateful processing。API 接口方面,Spark Streaming 支持 SQL,Heron 暂不支持。Spark Streaming 和 Heron 都支持 Java、Python 接口。需要指出的是,Heron 的 API 是 pluggable 模式的,除了 Java 和 Python 以外,Heron 可以支持许多编程语言,比如 C++。
任务分配方面,Spark Streaming 对每个任务使用单个线程。一个 JVM 进程中可能有多个任务的线程在同时运行。Heron 对每个任务都是一个单独的 heron-instance 进程,这样的设计是为了方便调试,因为当一个 task 失败的时候,只用把这个任务进程拿出来检查就好了,避免了进程中各个任务线程相互影响。
资源池方面,Spark Streaming 和 Heron 都可以运行在 YARN 和 Mesos 上。需要指出的是 Heron 的资源池设计是 pluggable interface 的模式,可以连接许多资源管理器,比如 Aurora 等。读者可以查看参考资料[11]了解 Heron 支持的资源池。
Kafka Streams 是一个客户端的程序库。通过这个调用库,应用程序可以读取 Kafka 中的消息流进行处理。
Kafka Streams 与 Kafka 绑定,需要订阅 topic 来获取消息流,这与 Heron 的 DAG 模型完全不同。对于 DAG 模式的流计算,DAG 的结点都是由流计算框架控制,用户计算逻辑需要按照 DAG 的模式提交给这些框架。Kafka Streams 没有这些预设,用户的计算逻辑完全用户控制,不必按照 DAG 的模式。此外,Kafka Streams 也支持反压(back pressure)和 stateful processing。
Kafka Streams 定义了2种抽象:KStream 和 KTable。在 KStream 中,每一对 key-value 是独立的。在 KTable 中,key-value 以序列的形式解析。
Kafka Streams 是完全基于 Kafka 来建设的,与 Heron 等流处理系统差别很大。Kafka Streams 的计算逻辑完全由用户程序控制,也就是说流计算的逻辑并不在 Kafka 集群中运行。Kafka Streams 可以理解为一个连接器,从 Kafka 集群中读取和写入键值序列,计算所需资源和任务生命周期等等都要用户程序管理。而 Heron 可以理解为一个平台,用户提交 topology 以后,剩下的由 Heron 完成。
归纳以上对各个系统的比较,我们可以得到如上的表基于以上表格的比较,我们可以得到如下的选型要点:
表1 各系统比较
总结上面,Spark Streaming、Kafka Streams、Flink 都有特定的应用场景,其他一般流处理情况下可以使用 Heron。
让我们在 Ubuntu 单机上来实践运行一个示例 topology,这包括如下几个步骤:
首先找到 Heron 的发布网页Heron找到最新的版本0.16.5。可以看到 Heron 提供了多个版本的安装文件,这些安装文件又分为几个类别:客户端 client、工具包 tools 和开发包 API 等。
下载客户端安装文件 heron-client-install-0.16.5-ubuntu.sh:
wget https://github.com/twitter/heron/releases/download/0.16.5/heron-client-install-0.16.5-darwin.sh
然后执行这个文件:
chmod +x heron-*.sh./heron-client-install-0.16.5--PLATFORM.sh --user
其中--user 参数让 heron 客户端安装到当前用户目录 ~/.hedon,同时在 ~/bin 下创建一个链接指向 ~/.heorn/bin 下的可执行文件。
Heron 客户端是一个名字叫 heron 的命令行程序。可以通过 export PATH=~/bin:$PATH 让 heron 命令能被直接访问。运行如下命令来检测 heron 命令是否安装成功:
heron version
首先添加 localhost 到 /etc/hosts,Heron 在单机模式时会用 /etc/hosts 来解析本地域名。
Heron 客户端安装时已经包含了一个示例 topology 的 jar 包,在 ~/.heron/example 目录下。我们可以运行其中一个示例 topology 作为例子:
heron submit local ~/.heron/examples/heron-examples.jar \com.twitter.heron.examples.ExclamationTopology ExclamationTopology \--deploy-deactivated
heron submit 命令提交一个 topology 给 heron 运行。关于 heron submit 的命令的格式,可以用过 heron help submit 来查看。
当 Heron 运行在单机本地模式时,它会将运行状态和日志等信息存放在 ~/.herondata 目录下。我们可以可以查看刚才运行的示例 topology 目录,具体位置是:
ls -al ~/.herondata/topologies/local/${USER_NAME}/ExclamationTopology
一个 topology 的生命周期包括如下几个阶段:
这些阶段都是通过 heron 命令行客户端来管理的。具体的命令格式可以通过 heron help 查看。
Heron 项目提供了一些工具,可以方便查看数据中心中运行的 topology 状态。在单机本地模式下,我们也可以来试试这些工具。这些工具主要包括:
一个数据中心内可以部署一套工具包来涵盖整个数据中心的所有 topology。
用安装 Heron 客户端类似的方法,找到安装文件,然后安装它:
wget https://github.com/twitter/heron/releases/download/0.16.5/heron-tools-install-0.16.5-darwin.shchmod +x heron-*.sh./heron-tools-install-0.16.5-PLATFORM.sh --user
启动 Tracker 服务器:heron-tracker
验证服务器 restful api:在浏览器中打开 http://localhost:8888
图1 启动 Tracker 服务器
启动 UI 网站:heron-ui
验证 UI 网站:在浏览器中打开http://localhost:8889
图2 启动 UI 网站
自从2016年夏 Twitter 开源 Heron 以来,Heron 社区开发了许多新的功能,特别是2017年 Heron 增加了“在线动态扩容/缩容”、“effectively once 传输语义”、“函数式 API”、“多种编程语言支持”、“自我调节(self-regulating)”等。
根据 Storm 的数据模型,topology 的并行度是 topology 的作者在编程 topology 的时候指定的。很多情况下,topology 需要应付的数据流量在不停的变化。topology 的编程者很难预估适合的资源配置,所以动态的调整 topology 的资源配置就是运行时的必要功能需求。
直观地,改变 topology 中结点的并行度就能快速改变 topology 的资源使用量来应付数据流量的变换。Heron 通过 update 命令来实现这种动态调整。Heron 命令行工具使用 packing 算法按照用户指定的新的并行度计算 topology 的新的 packing plan,然后通过资源池调度器增加或者减少容器数量,并再将这个 packing plan 发送给 tmaster 合并成新的 physical plan,使得整个 topology 所有容器状态一致。Heron 实现的并行度动态调整对运行时的 topology 影响小,调整快速。
Heron 在原有 tuple 传输模式 at most once和at least once 以外,新加入了 effectively once。原有的 at most once和at least once 都有些不足之处,比如 at most once 会漏掉某些 tuple;而 at least once 会重复某些 tuple。所以 effectively once 的目标是,当计算是确定性(deterministic)的时候,结果精确可信。
Effectively once 的实现可以概括为两点:
tmaster 定期向 spout 发送 marker tuple。当 topology 中的一个结点收集齐上游的 marker tuple时,会将当时自己的状态写入一个 state storage,这个过程就是 checkpoint。当整个 topology 的所有结点都完成 checkpoint 的时候,state storage 就存储了一份整个 topology 快照。如果 topology 遇到异常,可以从 state storage读取快照进行恢复并重新开始处理数据。
函数式编程是近年来的热点,Heron 适应时代潮流在原有 API 的基础上添加了函数式 API。Heron 的函数式 API 让 topology 编程者更专注于 topology 的应用逻辑,而不必关心 topology/spout/bolt 的具体细节。Heron 的函数式 API 相比于原有的底层 API 是一种更高层级上的 API,它背后的实现仍然是转化为底层 API 来构建 topology。
Heron 函数式 API 建立在 streamlet 的概念上。一个 streamlet 是一个无限的、顺序的 tuple 序列。Heron 函数式 API 的数据模型中,数据处理就是指从一个 streamlet 转变为另一个 streamlet。转变的操作包括:map、flatmap、join、filter 和 window 等常见的函数式操作。
以往 topology 编写者通常使用兼容 Storm 的 Java API 来编写 topology,现在 Heron 提供 Python 和 C++ 的 API,让熟悉 Python 和 C++ 的程序员也可以编写 topology。Python 和 C++ 的 API 设计与 Java API 类似,它们包含底层 API 用来构造 DAG,将来也会提供函数式 API 让 topology 开发者更专注业务逻辑。
在实现上,Python 和 C++ 的 API 都有 Python 和 C++ 的 heron-instance 实现。它们不与 heron-instance 的 Java 实现重叠,所以减少了语言间转化的开销,提高了效率。
Heron 结合 Dhalion 框架开发了新的 health manager 模块。Dhalion 框架是一个读取 metric 然后对 topology 进行相应调整或者修复的框架。Health manager 由2个部分组成:detector/diagnoser 和 resolver。Detector/diagnoser 读取 metric 探测 topology 状态并发现异常,resolver 根据发现的异常执行相应的措施让 topology 恢复正常。Health manager 模块的引入,让 Heron 形成了完整的反馈闭环。
现在常用的两个场景是:1. detector 监测 back pressure 和 stmgr 中队列的长度,发现是否有些容器是非常慢的;然后 resolver 告知 heron-scheduler 来重新调度这个结点到其他 host 上去;2. detector 监测所有结点的状态来计算 topology 在全局层面上是不是资源紧张,如果发现 topology 资源使用量很大,resolver 计算需要添加的资源并告知scheduler来进行调度。
在本文中,我们对比了 Heron 和常见的流处理项目,包括 Storm、Flink、Spark Streaming 和 Kafka Streams,归纳了系统选型的要点,此外我们实践了 Heron 的一个案例,最后我们讨论了Heron在这一年开发的新特性。
最后,作者希望这篇文章能为大家提供一些 Heron 应用的相关经验,也欢迎大家向我们提供建议和帮助。如果大家对 Heron 的开发和改进感兴趣,可以查看 Heron 官网(http://heronstreaming.io/)和 代码(https://github.com/twitter/heron)。
参考文献
[1] Maosong Fu, Ashvin Agrawal, Avrilia Floratou, Bill Graham, Andrew Jorgensen, Mark Li, Neng Lu, Karthik Ramasamy, Sriram Rao, and Cong Wang. "Twitter Heron: Towards Extensible Streaming Engines." In 2017 IEEE 33rd International Conference on Data Engineering (ICDE), pp. 1165-1172. IEEE, 2017.
[2] Sanjeev Kulkarni, Nikunj Bhagat, Maosong Fu, Vikas Kedigehalli, Christopher Kellogg, Sailesh Mittal, Jignesh M. Patel, Karthik Ramasamy, and Siddarth Taneja. "Twitter heron: Stream processing at scale." In Proceedings of the 2015 ACM SIGMOD International Conference on Management of Data, pp. 239-250. ACM, 2015.
[3] Maosong Fu, Sailesh Mittal, Vikas Kedigehalli, Karthik Ramasamy, Michael Barry, Andrew Jorgensen, Christopher Kellogg, Neng Lu, Bill Graham, and Jingwei Wu. "Streaming@ Twitter." IEEE Data Eng. Bull. 38, no. 4 (2015): 15-27.
[4] http://storm.apache.org/
[5] http://storm.apache.org/releases/current/Trident-tutorial.html
[6] https://flink.apache.org/
[7] https://spark.apache.org/streaming/
[8] https://kafka.apache.org/documentation/streams/
[9] https://twitter.github.io/heron/api/python/
[10] https://github.com/twitter/heron/tree/master/heron/instance/src/cpp
[11] https://github.com/twitter/heron/tree/master/heron/schedulers/src/java/com/twitter/heron/scheduler
文/董小珊,姚臻
如果把传统关系型数据库比做火车的话,那么到现在大数据时代,图数据库可比做高铁。它已成为 NoSQL 中关注度最高,发展趋势最明显的数据库。
在众多不同的数据模型里,关系数据模型自20世纪80年代就处于统治地位,而且出现了不少巨头,如 Oracle、MySQL 和 MSSQL,它们也被称为关系数据库管理系统(RDBMS)。然而,随着关系数据库使用范围的不断扩大,也暴露出一些它始终无法解决问题,其中最主要的是数据建模中的一些缺陷和问题,以及在大数据量和多服务器之上进行水平伸缩的限制。同时,互联网发展也产生了一些新的趋势变化:
而在应对这些趋势时,关系数据库产生了更多的不适应性,从而导致大量解决这些问题中某些特定方面的不同技术出现,它们可以与现有 RDBMS 相互配合或代替它们——亦被称为混合持久化(Polyglot Persistence)。数据库替代品并不是新鲜事物,它们已经以对象数据库(OODBMS)、层次数据库(如 LDAP)等形式存在很长时间了。但是,过去几年间,出现了大量新项目,它们被统称为 NoSQL 数据库(NoSQL-databases)。
NoSQL(Not Only SQL,不限于 SQL)是一类范围非常广泛的持久化解决方案,它们不遵循关系数据库模型,也不使用 SQL 作为查询语言。其数据存储可以不需要固定的表格模式,也经常会避免使用 SQL 的 JOIN 操作,一般有水平可扩展的特征。
简言之,NoSQL 数据库可以按照它们的数据模型分成4类:
在 NoSQL 四种分类中,图数据库从最近十年的表现来看已经成为关注度最高,也是发展趋势最明显的数据库类型。图1就是 db-engines.com 对最近三年来所有数据库种类发展趋势的分析结果。
图1 db-engines.com 对最近三年来所有数据库种类发展趋势的分析
图数据库源起欧拉和图理论,也可称为面向/基于图的数据库,对应的英文是 Graph Database。图数据库的基本含义是以“图”这种数据结构存储和查询数据,而不是存储图片的数据库。它的数据模型主要是以节点和关系(边)来体现,也可处理键值对。它的优点是快速解决复杂的关系问题。
图具有如下特征:
说得正式一些,图可以说是顶点和边的集合,或者说更简单一点儿,图就是一些节点和关联这些节点的联系(relationship)的集合。图将实体表现为节点,实体与其他实体连接的方式表现为联系。我们可以用这个通用的、富有表现力的结构来建模各种场景,从宇宙火箭的建造到道路系统,从食物的供应链及原产地追踪到人们的病历,甚至更多其他的场景。
通常,在图计算中,基本的数据结构表达就是:
G=(V, E)
V=vertex(节点)
E=edge(边)
如图2所示。
图2 简单的图数据库模型
当然,图模型也可以更复杂,例如图模型可以是一个被标记和标向的属性多重图(multigraph)。被标记的图每条边都有一个标签,它被用来作为那条边的类型。有向图允许边有一个固定的方向,从末或源节点到首或目标节点。
属性图允许每个节点和边有一组可变的属性列表,其中的属性是关联某个名字的值,简化了图形结构。多重图允许两个节点之间存在多条边,这意味着两个节点可以由不同边连接多次,即使两条边有相同的尾、头和标记。如图3所示。
图3 较为复杂的图模型
图数据库存储一些顶点和边与表中的数据。他们用最有效的方法来寻找数据项之间、模式之间的关系,或多个数据项之间的相互作用。
一张图里数据记录在节点,或包括的属性里面,最简单的图是单节点的,一个记录,记录了一些属性。一个节点可以从单属性开始,成长为成千上亿,虽然会有一点麻烦。从某种意义上讲,将数据用关系连接起来分布到不同节点上才是有意义的。
图计算是在实际应用中比较常见的计算类别,当数据规模大到一定程度时,如何对其进行高效计算即成为迫切需要解决的问题。大规模图数据,例如支付宝的关联图,仅好友关系已经形成超过1600亿节点、4000亿边的巨型图,要处理如此规模的图数据,传统的单机处理方式显然已经无能为力,必须采用由大规模机器集群构成的并行图数据库。
在处理图数据时,其内部存储结构往往采用邻接矩阵或邻接表的方式,图4就是这两种存储方式的简单例子。在大规模并行图数据库场景下,邻接表的方式更加常用,大部分图数据库和处理框架都采用了这一存储结构。
图4 大规模并行图数据库场景下的图数据库存储结构
在研究图数据库技术时,有两个特性需要多加考虑。
一些图数据库使用原生图存储,这类存储是优化过的,并且是专门为了存储和管理图而设计的。不过并不是所有图数据库使用的都是原生图存储,也有一些会将图数据序列化,然后保存到关系型数据库或面向对象数据库,或是其他通用数据存储中。
原生图存储的好处是,它是专门为性能和扩展性设计建造的。但相对的,非原生图存储通常建立在非常成熟的非图后端(如 MySQL)之上,运维团队对它们的特性烂熟于心。原生图处理虽然在遍历查询时性能优势很大,但代价是一些非遍历类查询会比较困难,而且还要占用巨大的内存。
图计算引擎技术使我们可以在大数据集上使用全局图算法。图计算引擎主要用于识别数据中的集群,或是回答类似于“在一个社交网络中,平均每个人有多少联系?”这样的问题。
图5展示了一个通用的图计算引擎部署架构。该架构包括一个带有 OLTP 属性的记录系统(SOR)数据库(如 MySQL、Oracle 或 Neo4j),它给应用程序提供服务,请求并响应应用程序在运行中发送过来的查询。每隔一段时间,一个抽取、转换和加载(ETL)作业就会将记录系统数据库的数据转入图计算引擎,供离线查询和分析。
图5 一个典型的图计算引擎部署架构
图计算引擎多种多样。最出名的是有内存的、单机的图计算引擎 Cassovary 和分布式的图计算引擎 Pegasus 和 Giraph。大部分分布式图计算引擎基于 Google 发布的 Pregel 白皮书,其中讲述了 Google 如何使用图计算引擎来计算网页排名。
一个成熟的图数据库架构应该至少具备图的存储引擎和图的处理引擎,同时应该有查询语言和运维模块,商业化产品还应该有高可用HA模块甚至容灾备份机制。一个典型的图数据库架构如图6所示。
图6 一个成熟的图数据库设计架构
各模块功能说明如下:
在图数据库和对外的接口上,图数据库应该也具有完备的对外数据接口和完善的可视化输出界面,如图7所示。
图7 一个完整的图数据库对外接口及部署模式
图数据库不仅可以导入传统关系型数据库中的结构化数据,也可以是文本数据、社交数据、机器日志数据、实时流数据等。
同时,计算结果可以通过标准的可视化界面展现出来,商业化的图数据库产品还应该能将图数据库中的数据进一步导出至第三方数据分析平台做进一步的数据分析。
我们可以将图领域划分成以下两部分:
这类技术被称为图数据库,它们和“通常的”关系型数据库世界中的联机事务处理(Online Transactional Processing,OLTP)数据库是一样的。
这类技术被称为图计算引擎。它们可以和其他大数据分析技术看做一类,如数据挖掘和联机分析处理(Online Analytical Processing,OLAP)。
图数据库一般用于事务(OLTP)系统中。图数据库支持对图数据模型的增、删、改、查(CRUD)方法。相应地,它们也对事务性能进行了优化,在设计时通常需要考虑事务完整性和操作可用性。
目前图数据库的巨大用途得到了认可,它跟不同领域的很多问题都有关联。最常用的图论算法包括各种类型的最短路径计算、测地线(Geodesic Path)、集中度测量(如 PageRank、特征向量集中度、亲密度、关系度、HITS 等)。那么,什么样的应用场景可以很好地利用图数据库?
目前,业内已经有了相对比较成熟的基于图数据库的解决方案,大致可以分为以下几类。
反欺诈多维关联分析场景
通过图分析可以清楚地知道洗钱网络及相关嫌疑,例如对用户所使用的帐号、发生交易时的 IP 地址、MAC 地址、手机 IMEI 号等进行关联分析。
图8 在图数据库中一个典型的反洗钱模型
反欺诈多维关联分析场景
反欺诈已经是金融行业一个核心应用,通过图数据库可以对不同的个体、团体做关联分析,从人物在指定时间内的行为,例如去过地方的 IP 地址、曾经使用过的 MAC 地址(包括手机端、PC 端、WIFI 等)、社交网络的关联度分析,同一时间点是否曾经在同一地理位置附近出现过,银行账号之间是否有历史交易信息等。
图9 在图数据库中一个典型的金融反欺诈关联分析模型
在社交网络中,公司、员工、技能的信息,这些都是节点,它们之间的关系和朋友之间的关系都是边,在这里面图数据库可以做一些非常复杂的公司之间关系的查询。比如说公司到员工、员工到其他公司,从中找类似的公司、相似的公司,都可以在这个系统内完成。
图10 在图数据库中典型的社交关系网络模型
图数据库可以对各种企业进行信息图谱的建立,包括最基本的工商信息,包括何时注册、谁注册、注册资本、在何处办公、经营范围、高管架构。围绕企业的经营范围,继续细化去查询企业究竟有哪些产品或服务,例如通过企业名称查询到企业的自媒体,从而给予其更多关注和了解。另外也包括对企业的产品和服务的数据关联,查看该企业有没有令人信服的自主知识产权和相关资质来支撑业务的开展。
企业在日常经营中,与客户、合作伙伴、渠道方、投资者都会打交道,这也决定了企业对社会各个领域都广有涉猎,呈现面错综复杂,因此可以通过企业数据图谱来查询,层层挖掘信息。基于图数据的企业信息查询可以真正了解企业的方方面面,而不再是传统单一的工商信息查询。
图11 在图数据库中一个典型的企业知识图谱模型
数十年来,开发者试图使用关系型数据库处理关联的、半结构化的数据集。关系型数据库设计之初是为了处理纸质表格以及表格化结构,它们试图对这种实际中的特殊联系进行建模。然而讽刺的是,关系型数据库在处理联系上做得却并不好。
关系数据库是强大的主流数据库,经过40年的发展和改进,已经非常可靠、强大并且很实用,可以保存大量的数据。如果你想查询关系型数据库里的单一结构或对应数据信息的话,在任何时间内都可以查询关于项目的信息,或者你想查询许多项目在相同类型中的总额或平均值,也将会很快得到答案。关系型数据库不擅长什么呢?当你寻找数据项、关系模式或多个数据项之间的关系时,它们常会以失败告终。
关系确实存在于关系型数据库自身的术语中,但只是作为连接表的手段。我们经常需要对连接实体的联系进行语义区分,同时限制它们的使用,但是关联关系什么也做不了。更糟糕的是,随着数据成倍地增加,数据集的宏观结构将愈发复杂和不规整,关系模型将造成大量表连接、稀疏行和非空检查逻辑。关系世界中连通性的增强都将转化为 JOIN 操作的增加,这会阻碍性能,并使已有的数据库难以响应变化的业务需求。
而图数据库天生的特点决定了其在关联关系上具有完全的优势,特别是在我们这个社交网络得到极大发展的互联网时代。例如我们希望知道谁 LOVES(爱着)谁(无论爱是否是单相思的),也想知道谁是谁的 COLLEAGUE_OF
(同事),谁是所有人的 BOSS_OF
(老板)。我们想知道谁没有市场了,因为他们和别人是 MARRIED_TO
(结婚)联系。我们甚至可以通过数据库在其他社交网络中发现不善交际的元素,用 DISLIKES(不喜欢)联系来表示即可。通过我们所掌握的这个图,就可以看看图数据库在处理关联数据时的性能优势了。
例如在下面这个例子中,我们希望在一个社交网络里找到最大深度为5的朋友的朋友。假设随机选择两个人,是否存在一条路径,使得关联他们的关系长度最多为5?对于一个包含100万人,每人约有50个朋友的社交网络,我们就以典型的开源图数据库 Neo4j 参与测试,结果明显表明,图数据库是用于关联数据的最佳选择,如表1所示。
表1 图数据库与关系型数据库执行时间对比
在深度为2时(即朋友的朋友),假设在一个在线系统中使用,无论关系型数据库还是图数据库,在执行时间方面都表现得足够好。虽然 Neo4j 的查询时间为关系数据库的2/3,但终端用户很难注意到两者间毫秒级的时间差异。当深度为3时(即朋友的朋友的朋友),很明显关系型数据库无法在合理的时间内实现查询:一个在线系统无法接受30s的查询时间。相比之下,Neo4j 的响应时间则保持相对平坦:执行查询仅需要不到 1s,这对在线系统来说足够快了。
在深度为4时,关系型数据库表现出很严重的延迟,使其无法应用于在线系统。Neo4j 所花时间也有所增加,但其时延在在线系统的可接受范围内。最后,在深度为5时,关系型数据库所花时间过长以至于没有完成查询。相比之下,Neo4j 则在 2s 左右的时间就返回了结果。在深度为5时,事实证明几乎整个网络都是我们的朋友,因此在很多实际用例中,我们可能需要修剪结果,并进行时间控制。
将社交网络替换为任何其他领域时,你会发现图数据库在性能、建模和维护方面都能获得类似的好处。无论是音乐还是数据中心管理,无论是生物信息还是足球统计,无论是网络传感器还是时序交易,图都能对这些数据提供强有力而深入的理解。
而关系型数据库对于超出合理规模的集合操作普遍表现得不太好。当我们试图从图中挖掘路径信息时,操作慢了下来。我们并非想要贬低关系型数据库,它在所擅长的方面有很好的技术能力,但在管理关联数据时却无能为力。任何超出寻找直接朋友或是寻找朋友的朋友这样的浅遍历查询,都将因为涉及的索引数量而使查找变得缓慢。而图数据库由于使用的是图遍历技术,所需要计算的数据量远小于关系型数据库,所以非常迅速。
不过,图数据库也并非完美,它虽然弥补了很多关系型数据库的缺陷,但是也有一些不适用的地方,例如以下领域:
虽然图数据库也能够处理“大数据”,但它毕竟不是 Hadoop、HBase 或 Cassandra,通常不会在图数据库中直接处理海量数据(以 PB 为单位)的分析。但如果你乐于提供关于某个实体及其相邻数据的关系(比如可以提供一个 Web 页面或某个 API 返回其结果),那么它是一种良好的选择。无论是简单的 CRUD 访问,或是复杂的、深度嵌套的资源视图都能够胜任。
综上所述,虽然关系型数据库对于保存结构化数据来说依然是最佳的选择,但图数据库更适合于管理半结构化数据、非结构化数据以及图形数据。如果数据模型中包含大量的关联数据,并且希望使用一种直观、有趣并且快速的数据库进行开发,那么可以考虑尝试图数据库。
在实际的生产环境下,一个真正成熟、有效的分析环境是应该包括关系型数据库和图数据库的,根据不同的应用场景相互结合起来进行有效分析。
整体而言,图数据库还有很多问题未解决,许多技术还需发展,比如超级节点问题和分布式大图的存储。可以预见的是,随着互联网数据的膨胀,图数据库将迎来发展契机,基于图的各种计算和数据挖掘岗位也会越来越热。
致谢
本文的部分内容参考了 Ian Robinson 所著的《图数据库》(第一版),在此表示感谢。
阅读全文: http://gitbook.cn/gitchat/geekbook/5a5eb029dff55721bc1dcada