漫谈实时流计算

什么是实时计算?这个概念与离线计算是相伴而生的。以前我在「数据工程师是个有前途的职业么?」一文中写过的数据处理链路就属于离线计算:通过 ETL 程序清洗上报的数据,然后把数据存入 HDFS,通过 Hadoop 的批处理 Job 把数据导入 Hive、做 ORC 压缩处理、进行数据分区,然后再通过 Presto jdbc 定时调度任务,将 Hive 数据做统计聚合至 MySQL,然后为业务人员提供数据查询、钻取和分析的功能。

为什么叫离线计算呢?因为 Hadoop 批处理 job 的执行时间往往需要几十分钟到几个小时(不同的数据量),所以一般数据的处理是按照时间区间处理的,比如在每天凌晨启动定时任务处理前一天的数据,等所有的 Job 执行完成,有效数据被聚合到 MySQL 中,数据产品经理就可以在控制台查看昨天及以前的所有数据了。这叫离线,但是离线计算不能满足所有的业务需求。

很多时候,即使是海量数据,我们也希望即时去查看一些数据指标。比如工程师们上午9点上线了一款产品,或推出了一个新功能,产品经理迫切希望知道这个新功能的活跃程度和转化率等数据,当他们过来找你的时候……咔,你掏出了一次性打火机点燃一只烟并吐出一个烟圈,说,等明天早晨就可以看了(室内禁止吸烟)。这时候你很容易被愤怒的产品经理、BD 和业务工程师联合起来打死。为了避免这样的惨剧发生,实时流计算应运而生。

多说一句,无论是离线计算还是实时计算,对数据的要求都是「大」,几百万甚至千万级别的数据记录都不需要这些技术。有一次和一位产品经理聊实时流计算,他两眼烁烁放光说,我们也需要啊。我说你们数据量多大,他说这么大(用手比划了一下),我说那放数据库不是很好么,直接就实时查询了。他说很慢啊。我说那可能该优化数据库了,比如加索引分库分表什么的。

他陷入了沉思……

实时流计算有两个特点,一个是实时,随时可以看数据;另一个是流,流水不争,绵延不绝,断无可断。这就像我给部门里的海大师分配任务,我说大师你把 A 做一下,他说,好的 A 已经做完了。我说,你小子行那接着做 B 吧,咔,B 也做完了,然后我就源源不断的把实时任务分配给海大师,海大师兵来将挡水来土屯,来一个处理一个完成的滴水不漏,这叫人肉实时流计算。再举个例子,《X战警:天启》中的万磁王在影片最后翻然悔悟,调动周围所有钢筋铁块组成一条钢铁 Stream 向天启发起了潮水般的攻击,但是天启幻化出能量墙把攻击消弭于无形,要不是凤凰女发飙灭掉天启,还不知道万磁王怎么收场呢,总不能把地球上所有的钢铁都扔完吧……这叫钢铁实时流计算。

有了这样的需求和产品定义,自然会有相关的技术实现。目前工业级别的老牌实时流计算框架是 Storm,后起之秀有 Spark Streaming,Samza、Flink 等等。我们目前采用的框架是 Storm。

从数据和事件角度,实时流计算需要一套完整的解决方案,比如 Flume + Kafka + Storm。Flume 是一个分布式、高可用的海量数据聚合的系统,用于收集数据,进行简单处理后把数据推送到数据队列处理系统,比如 Kafka。Kafka 是一种分布式的、基于发布/订阅模式的消息系统,与遵循 AMQP 标准的 RabbitMQ 不同,Kafka 是一个更通用的消息系统,「以时间复杂度为O(1)的方式提供消息持久化能力,对 TB 级别的数据也能保证常数时间复杂度的访问性能」,如果你也和我一样对这句话似懂非懂,只要知道 Kafka 在大数据消息处理领域处于暂时领先的地位就可以了。它负责接收消息,然后再把消息传给 Storm,对于 Storm 来说,Kafka 就是一个永不停歇的数据源。

Storm 在这里扮演的就是前文中提到的海大师和天启的角色,事件来一个处理一个,生生不息。它是怎么做到的呢?Storm 采用了组件化的处理方式,整个架构体系有多个组件构成,各司其职,协同工作。结构图如下:

《漫谈实时流计算》
《漫谈实时流计算》

运行 Nimbus 服务的是 Storm 集群中主节点(Master),主要起到调度器的作用,为工作节点指派任务、监控状态,是个大管家。一般情况下 Nimbus 不用考虑其单点故障,一是负载小,而是即便 Nimbus 挂了,相关信息保存在 ZooKeeper 和硬盘上,不会影响 Worker 和 Topology 的正常运行。

运行 Supervisor 服务的是 Storm 集群中工作节点,用来接收任务,并唤起或终止工作进程。

Nimbus 和 Supervisor 都是无状态的,能够快速失败并恢复,元数据存储在 ZooKeeper 中,这种机制保证了系统具备很高的容错性。Nimbus 与 Supervisor 之间的协调工作是通过 ZooKeeper 完成。

Supervisor 对应多个 Worker,每个 Worker 里有很多 Executor 线程,每个 Executor 又包含很多 Task,Task 是 Storm 的最小处理单元。每个 Executor 都会启动一个消息循环线程,用来接收、处理和发送消息。当 Executor 收到某个 Task 的消息后,就会调用该 Task 对应的处理逻辑对数据进行处理。

那数据到底是怎么来的,又是怎么处理的呢?这里引出了一个新的概念叫做拓扑(Topology)。

Topology 的概念类似于 Hadoop 中的 MapReduce Job,用来编排、容纳一组逻辑计算单元,Worker、Executor、Task 运行的程序就是这个 Topology。

一个 Topology 由 Spout 和 Bolt 组成,Spout 是数据源,比如订阅 Kafka 上持续不断的消息,Bolt 是数据处理单元,封装了业务逻辑,比如接收数据然后存储到 MySQL 数据库里。根据不同的功能粒度划分,一个 Topology 可以由多个数据源和数据处理单元组成,数据单元切分的越清晰,这个 Topology 越容易复用。它的结构可以是这样的:

《漫谈实时流计算》
《漫谈实时流计算》

你可以把 Topology 看做一个应用程序,由 Worker 驱动,一旦运行就抽刀断水,永不停止,除非手动干预(显式执行 bin/storm kill )或意外故障(如停机、整个Storm集群挂掉)让它终止。

Storm 的结构设计的非常巧妙,看起来很复杂,实际上对于开发者来说,只需要设计 Topology,编写 Spout 和 Bolt 程序就可以了,并且 Storm 针对不同类型的 Spout 和 Bolt 设计了各种基类,开发者只需要继承,并实现自己的业务逻辑就好了。

Storm 在消息保障方面提供了 at least once 机制。消息传输保障一般有三种:at most once,at least once 和 exactly once。第一个就是最多一次,不保证消息不丢失。第二个就是消息至少会到达一次,但可能重复。第三个是只有一次,精确保障。如果你采用了 Storm 的 low level 实现,根据业务场景,可能需要去处理重复消息。

为什么说 Storm 的 low level 实现呢?因为 Storm 的基础框架像一个 core,虽然功能强大,但是用起来略显复杂。于是 Storm 在 core 的基础之上,又实现了个一个高级抽象 Trident,支持微批量处理,支持消息的 exactly once 等高级功能。微批量是什么鬼?Storm 的处理机制是来一条消息处理一条,Trident 可以做到来一批消息处理一批,还不影响实时性。

所以,如果你的业务定制化要求没那么高,直接使用 Storm Trident 就可以。

至于 Storm 和 Trident 是如何保证 at least once 和 exactly once 机制的,下次再写,这些内容放在一篇文章里还是有点长了。

这篇算是技术文章吧,其实写技术文并不是一件轻松的事情,费时费力不说,阅读多少不说,打赏必然是少的。很多人问过我,面向开发者创业是不是个好方向,我一惯是持否定态度的。开发者事实上是一个非常难伺候爱挑刺的群体,喜欢白拿白用,早年间我购买正版软件还被人嘲笑过,我就说,开发者不支持开发者,那还怎么指望别人买我们的软件呢?

现在情况好了很多,很多开发者开始购买自己需要的正版开发软件,但也没好到哪去,看看微信赞赏就知道了,那些基本上只写技术类文章的作者(比如陈天的「程序人生」,唐巧的「iOS开发」),收到的赞赏往往寥寥无几,那你们怎么还好意思人家天天生产干货呢?

喜欢干货的,自己抱一根劈柴过日子就好了,记得别放冰箱,会潮。

文章首发在公众号 MacTalk,喜欢的可以搜索 sagacity-mac 关注下。

    原文作者:池建强
    原文地址: https://zhuanlan.zhihu.com/p/21885268
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞