一、Storm简介
Storm时Twitter开源的分布式实时大数据处理框架,被业界称为实时版的hadoop。
1.1 storm的优点
Storm使用了netty来传送消息,消除了中间消息排队的过程,在消息的背后,storm使用了一种序列化反序列化的原语类型的自动化且高效的机制。
storm的一个最有趣的地方时它注重容错和管理,Storm 实现了有保障的消息处理,所以每个元组Turple都会通过该拓扑topology结构进行全面管理。
如果一个元组还未处理会自动从spout处重发,storm还实现了任务级的故障检测,在一个任务发生故障时,消息会自动分配以快速重新开始处理。
1.2 Storm特性
使用场景广泛
Storm可以实时处理消息和更新DB,对一个数据进行持续查询并返回客户端
对一个耗资源的查询做一个实时并行化的处理
可伸缩性高
Storm使用ZooKeeper来协调集群内的各种配置使得Storm的集群可以很容易的扩展。
保证数据无丢失
实时系统必须保证所有的数据被成功的处理。storm保证每一条消息都会被处理。
异常健壮
Storm集群非常容易管理,轮流重启节点不影响应用。
容错性高
在处理消息的时候会出现异常,Storm会进行重试
语言无关性
Storm的topology和消息处理组件(Bolt)可以用任何语言来定义, 这一点使得任何人都可以使用Storm
二、Storm的物理架构
2.1 nimbus
Storm的master,负责资源的分配和调度,一个Storm集群只有一个nimbus
集群的主节点,对整个集群资源使用情况进行管理
但是nimbus是一个无状态的节点,所有的一切都存储在Zookeeper中
2.2 supervisor
Storm的salve,负责接收Nimbus分配的任务,管理所有worker
一个supervisor节点中包含多个worker进程,默认时4个
一般情况下,一个topology对应一个worker
2.3 worker
工作进程,每个工作进程中有多个task
2.4 task
在Storm 集群中每个 Spout 和 Bolt 都由若干个任务(tasks)来执行。
worker中每一个spout/bolt的线程称为一个task
同一个spout/bolt的task可能会共享一个物理线程(Thread),该线程称为executor
2.5 Storm的并行机制
Topology由一个或多个Spout/Bolt组件构成。运行中的Topology由一个或多个Supervisor节点中的worker组成
默认情况下一个Supervisor节点运行4个Worker
worker为特定拓扑的一个或多个组件Spout/Bolt产生一个或多个Executor。默认情况下一个 Worker运行一个Executor。
Executor为特定拓扑的一个或多个组件Spout/Bolt实例运行一个或多个Task。默认情况下一个Executor运行一个Task。
三、Storm的计算框架
流式计算框架
客户端发送消息给MQ
Storm从MQ中取消息进行计算,将结果存储到数据库中,客户端充当
成产者,Storm充当消费者,客户端不必要求服务器返回结果,可以一直向Storm发送数据
3.1 Topology
计算拓扑
Storm的拓扑是对实时计算应用逻辑的封装,它的作用与 MapReduce 的任务(Job)很相似
区别在于 MapReduce 的一个 Job 在得到结果之后总会结束,而拓扑会一直在集群中运行,直到你手动取终止它
拓扑还可以理解成一系列数据流和关联的相互关联的 Spout 和 Bolt 组成的拓扑结构
3.2 Stream
数据流,Storm中最核心的抽象概念
一个数据流指的是在分布式环境中并行创建、处理的一组元组(tuple)的无界序列。
数据流可以由可以由一种能够表述数据流中元组的域(fields)的模式来定义。
3.3 Tuple
Stram中的最小数据组成单元
每个Tuple可以包含多列,字段类型可以是: integer, long, short, byte, string, double, float, boolean和byte array
3.4 spout
数据源是拓扑中数据的来源
一般Spout 会从一个外部的数据源读取元组然后将他们发送到拓扑中。
根据需求的不同,Spout 既可以定义为可靠的数据源,也可以定义为不可靠的数据源。
一个可靠的数据源能够在它发送的元组处理失败时重新发送该元组,以确保所有的元组都能得到正确的处理
不可靠的Spout 就不会在元组发送之后对元组进行任何其他的处理。
一个spout可以发送多个数据流。
3.5 Blot
拓扑中的所有数据处理都是由Blot完成的
通过数据过滤(filtering)、函数处理(functions)、聚合(aggregations)、联结(joins)、数据交互等
一个Bolt 可以实现简单的数据流转换,而更复杂的数据流变换通常需要使用多个 Bolt 并通过多个步骤完成
第一级Bolt的输出可以作为下一级Bolt的输入。而Spout不能有上一级。
Bolt几乎能够完成任何一种数据处理需求。
Blot的主要方法是execute(死循环)连续处理传入的tuple,
3.6 StreamGroup
为拓扑中的每个 Bolt 的确定输入数据流是定义一个拓扑的重要环节。
数据流分组定义了在 Bolt 的不同任务(tasks)中划分数据流的方式。在 Storm 中有八种内置的数据流分组方式。
3.7 Reliablity
可靠性
Storm可以通过拓扑来确保每个发送的元组都能得到正确处理。
通过跟踪由 Spout 发出的每个元组构成的元组树可以确定元组是否已经完成处理。
每个拓扑都有一个“消息延时”参数,如果 Storm 在延时时间内没有检测到元组是否处理完成,就会将该元组标记为处理失败,并会在稍后重新发送该元组。
四、Storm的数据分发策略
4.1 ShuffleGrouping
随机分组,随机派发stream里面的tuple,保证每个bolttask接收到的tuple数目大致相同。轮询,平均分配
优点:
为tuple选择task的代价小;
bolt的tasks之间的负载比较均衡;
缺点:
上下游components之间的逻辑组织关系不明显;
4.2 FieldsGrouping
按字段分组
比如,按"user-id"这个字段来分组,那么具有同样"user-id"的tuple会被分到相同的Bolt里的一个 task,而不同的"user-id"则可能会被分配到不同的task。
优点:
上下游的conponents之间的逻辑关系显著
缺点:
付出为tuple选择task的代价;
bolt的task之间的负载可能不均衡,根据field字段而定
4.3 AllGrouping
广播发送,对于每一个tuple,所有的bolts都会收到
优点:
上游事件可以通知下游bolt中所有task;
缺点:
tuple消息冗余,对性能有损耗,请谨慎使用;
4.4 GlobalGrouping
全局分组,把tuple分配给taskid最低的task。
优点:
所有上游消息全部汇总,便于合并,统计等。
缺点:
bolt的tasks之间的负载可能不均衡,id最小的task负载过重
4.5 DirectGrouping
指向型分组,这是一种比较特别的分组方法,用这种分组意味着消息(tuple)的发送者指定由消息接收者的哪个task处理这个消息。
只有被声明为DirectStream的消息流可以声明这种分组方法。
而且这种消息tuple必须使用emitDirect方法来发射。
消息处理者可以通过TopologyContext来获取处理它的消息的task的id(OutputCollector.emit方法也会返回task的id)
优点:
Topology的可控性强,且组件的各task的负载可控;
缺点:
当实际负载与预估不符时性能削弱;
4.6 LocalorshufflerGrouping
本地或随机分组。如果目标bolt有一个或者多个task与源bolt的task在同一个工作进程中,tuple将会被随机发送给这些同进程中的tasks。否则,和普通的ShuffleGrouping行为一致
优点:
相对于ShuffleGrouping,因优先选择同进程task间传输而降低tuple网络传输代价,但因寻找同进程的task而消耗CPU和内存资源,因此应视情况来确定选择 ShuffleGrouping或LocalOrShuffleGrouping;
缺点:
上下游components之间的逻辑组织关系不明显;
4.7 NoneGrouping
不分组,这个分组的意思是说stream不关心到底怎样分组。目前这种分组和Shufflegrouping是一 样的效果。有一点不同的是storm会把使用nonegrouping的这个bolt放到这个bolt的订阅者同一个线程里面去执行(未来Storm如果可能的话会这样设计)。
4.8 CustomGrouping
自定义,相当于mapreduce那里自己去实现一个partition一样。
五、Storm的通信机制
5.1 Worker进程间的通信原理
worker进程间消息传递机制,消息的接收和处理的流程如下图
worker进程
为了管理流入和传出的消息,每个worker进程都有一个独立的接收线程和发送线程接收线程来负责将外部发送过来的消息移动到对应的executor线程的incoming-queue中法送线程负责从worker的transfer-queue中读取消息,并通过网络发送给其他worker
executor线程
每个executor有独立的incoming-queue 和outgoing-queue,Worker接收线程将收到的消息通过task编号传递给对应的executor的incoming-queues,executor有单独的线程分别来处理spout/bolt的业务逻辑,业务逻辑输出的中间数据会存放在outgoing-queue,当executor的outgoing-queue中的tuple达到一定的阀值,executor的发送线程将批量获取 outgoing-queue中的tuple,并发送到transfer-queue中
每个worker进程控制一个或多个executor线程,用户可在代码中进行配置。其实就是我们在代码中设置的并发度个数。
5.2 Worker进程内通信原理
Disruptor是一个queue
Disruptor是实现了“队列”的功能,而且是一个有界队列(长度有限)。而队列的应用场景自然就是“生产者-消费者”模型
Disruptor是一种线程之间无锁的交换模式
Disruptor主要特点
没有竞争=没有锁=非常快
所有的访问者都记录自己的序号的实现方式,允许多个生产者与多个消费者共享相同的数据结构
Disruptor的核心技术点
Disruptor可以看成一个事件监听或消息机制,在队列中一边生产者放入消息,另外一边消费者并行取出处理.
底层是单个数据结构:一个ring buffer(环形数据缓冲区)
六、Storm的容错机制
6.1 集群节点宕机
Nimbus宕机
单点故障
1.0.0版本之后Nimbus是高可用的
非Nimbus节点
故障时,该节点上所有Task任务都会超时,Nimbus会将这些Task任务重新分配到其他服务器上运行
6.2 进程故障
Worker
每个Worker中包含数个Bolt(或Spout)任务。
Supervisor负责监控这些任务,当worker失败后会尝试在本机重启它,如果启动过程中仍然一直失败,并且无法向Nimbus发送心跳,Nimbus会将该Worker重新分配到其他服务器上。
Supervisor
无状态(所有的状态信息都存放在Zookeeper中来管理)
快速失败(每当遇到任何异常情况,都会自动毁灭)
快速失败
在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改 (增加、删除、修改) 则会抛出Concurrent Modification Exception,java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改
安全失败
采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历,java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。
Nimbus
无状态(所有的状态信息都存放在Zookeeper中来管理)
快速失败(每当遇到任何异常情况,都会自动毁灭)
6.3 任务级容错
Bolt任务crash引起的消息未被应答。
此时,acker中所有与此Bolt任务关联的消息都会因为超时而失败,对应的Spout的fail方法将被调用。
acker任务失败
如果acker任务本身失败了,它在失败之前持有的所有消息都将超时而失败。Spout的fail方法将被调用。
Spout任务失败
在这种情况下,与Spout任务对接的外部设备(如MQ)负责消息的完整性。
6.4 消息的完整性
消息的完整性定义
每个从Spout(Storm中数据源点)发出的Tuple(Storm中最小的消息单元)可能会生成成千上万个新的Tuple
形成一颗Tuple树,当整颗Tuple树的节点都被成功处理了,我们就说从Spout发出的Tuple被 完全处理了。
消息完整性机制--Acker
acker的任务就是追踪从spout中流出来的每一个message id绑定的若干tuple的处理路径,如果在用户设置的最大超时时间内这些tuple没有被完全处理,那么acker就会告知spout该消 息处理失败了,相反则会告知spout该消息处理成功了。
XOR异或(同为0,异为1)
验证方式:
spout或者bolt在处理完tuple后,都会告诉acker我已经处理完了该源tuple(如 tupleId=1),如果emit一个tuple的话,同时会告诉acker我发射了一个tuple(如tupleId=2),如果在大量的高并发的消息的情况下,传统的在内存中跟踪执行情况的方式,内存的开销会非常大,甚至内存溢出
acker巧妙的利用了xor的机制,只需要维护一个msgId的标记位即可,处理方法是acker 在初始的时候,对每个msgId初始化一个校验值ack-val(为0),在处理完tuple和emit tuple的时候,会先对这两个个值做xor操作,生成的中间值再和acker中的当前校验值ack-val做xor生成新的ack-val值,当所有的tuple都处理完成都得到确认,那么最后的ack-val自然就为0了
七、记录级容错Storm的DRPC
DRPC(Distributed RPC) 分布式远程过程调用
DRPC是通过一个 DRPC 服务端(DRPC server)来实现分布式 RPC 功能的。
DRPCServer 负责接收 RPC 请求,并将该请求发送到 Storm中运行的 Topology,等待接收 Topology 发送的处理结果,并将该结果返回给发送请求的客户端。
DRPC设计目的
为了充分利用Storm的计算能力实现高密度的并行实时计算。 Storm接收若干个数据流输入,数据在Topology当中运行完成,然后通过DRPC将结果进行输出。
客户端通过向 DRPC 服务器发送待执行函数的名称以及该函数的参数来获取处理结果。
实现该函数的拓扑使用一个DRPCSpout 从 DRPC 服务器中接收一个函数调用流。 DRPC 服务器会为每个函数调用都标记了一个唯一的 id。随后拓扑会执行函数来计算结果,并在拓扑的最后使用一个名为 ReturnResults 的 bolt 连接 到 DRPC 服务器 根据函数调用的 id 来将函数调用的结果返回。