1.5.2.storm介绍
Storm用来实时处理数据,特点:低延迟、高可用、分布式、可扩展、数据不丢失。提供简单容易理解的接口,便于开发。
http://apache.fayea.com/
应用场景
日志分析
从海量日志中分析出特定的数据,并将分析的结果存入外部存储器用来辅佐决策。
管道系统
将一个数据从一个系统传输到另外一个系统,比如将数据库同步到Hadoop
消息转化器
将接受到的消息按照某种格式进行转化,存储到另外一个系统如消息中间件
核心组件
架构
Nimbus:负责资源分配和任务调度。
Supervisor:接受任务,并启动worker。worker的数量根据端口号来的。
Worker:执行任务的具体组件(其实就是一个JVM),可以执行两种类型的任务,Spout任务或者bolt任务。
Task:Task=线程=executor。 一个Task属于一个Spout或者Bolt并发任务。
Zookeeper:保存任务分配的信息、心跳信息、元数据信息。
编程模型
DataSource:外部数据源
Spout:接受外部数据源的组件,将外部数据源转化成Storm内部的数据,以Tuple为基本的传输单元下发给Bolt
Bolt: 接收Spout发送的数据,或上游的bolt的发送的数据。根据业务逻辑进行处理。发送给下一个Bolt或者是存储到某种介质上。介质可以是Redis可以是mysql,或者其他。
Tuple:Storm内部中数据传输的基本单元,里面封装了一个List对象,用来保存数据。
StreamGrouping:数据分组策略
7种:shuffleGrouping(Random函数),Non Grouping(Random函数),FieldGrouping(Hash取模)、Local or ShuffleGrouping 本地或随机,优先本地。
并发度
用户指定的一个任务,可以被多个线程执行,并发度的数量等于线程的数量。一个任务的多个线程,会被运行在多个Worker(JVM)上,有一种类似于平均算法的负载均衡策略。尽可能减少网络IO,和Hadoop中的MapReduce中的本地计算的道理一样。
Worker与topology
一个worker只属于一个topology,每个worker中运行的task只能属于这个topology。 反之,一个topology包含多个worker,其实就是这个topology运行在多个worker上。
一个topology要求的worker数量如果不被满足,集群在任务分配时,根据现有的worker先运行topology。如果当前集群中worker数量为0,那么最新提交的topology将只会被标识active,不会运行,只有当集群有了空闲资源之后,才会被运行。
Storm 任务提交的过程
1、客户端运行storm
2、nimbus启动之后 ,接受客户端提交任务
3、接受到任务之后,会将任务进行分配,分配会产生一个assigment对象,该对象会保存到zk中(目录是/storm/assigments,该目录只保存正在运行的topology任务)
4、supervisor通过watch机制,感知到nimbus在zk上的任务分配信息,从zk上拉取任务信息,分辨出属于自己任务
5、根据自己的任务信息,启动自己的worker,并分配一个端口
6、worker启动之后,连接zk,拉取任务
7、worker根据任务类型,分别执行spout任务或者bolt任务
spout的生命周期:open、 newxtTuple、outPutField
bolt的生命周期:prepare、exectue(tuple)、outPutField
1.5.3.Storm通信机制
Worker间的通信经常需要通过网络跨节点进行,Storm使用ZeroMQ或Netty(0.9以后默认使用)作为进程间通信的消息框架。
Worker进程内部通信:不同worker的thread通信使用LMAX Disruptor来完成。
不同topologey之间的通信,Storm不负责,需要自己想办法实现,例如使用kafka等
Worker进程间通信分析
1、对于worker进程来说,为了管理流入和传出的消息,每个worker进程有一个独立的接收线程。(对配置的TCP端口supervisor.slots.ports进行监听)
对应Worker接收线程,每个worker存在一个独立的发送线程,它负责从worker的transfer-queue中读取消息,并通过网络发送给其他worker
2、每个executor有自己的incoming-queue和outgoing-queue
Worker接收线程将收到的消息通过task编号传递给对应的executor(一个或多个)的incoming-queues;
3、每个executor有单独的线程分别来处理spout/bolt的业务逻辑,业务逻辑输出的中间数据会存放在outgoing-queue中,当executor的outgoing-queue中的tuple达到一定的阀值,executor的发送线程将批量获取outgoing-queue中的tuple,并发送到transfer-queue中。
每个worker进程控制一个或多个executor线程,用户可在代码中进行配置。其实就是我们在代码中设置的并发度个数。
Worker 内部通信技术(Disruptor)
Disruptor是一个Queue。Disruptor是实现了“队列”的功能,而且是一个有界队列。而队列的应用场景自然就是“生产者-消费者”模型。
(在JDK中Queue有很多实现类,包括不限于ArrayBlockingQueue、LinkBlockingQueue,这两个底层的数据结构分别是数组和链表。数组查询快,链表增删快,能够适应大多数应用场景。
但是ArrayBlockingQueue、LinkBlockingQueue都是线程安全的。涉及到线程安全,就会有synchronized、lock等关键字,这就意味着CPU会打架。)
Disruptor一种线程之间信息无锁的交换方式(使用CAS(Compare And Swap/Set)操作)。
Disruptor可以看成一个事件监听或消息机制,在队列中一边生产者放入消息,另外一边消费者并行取出处理.
底层是单个数据结构:一个ring buffer。
每个生产者和消费者都有一个次序计算器,以显示当前缓冲工作方式。
核心组件
Ring Buffer 环形的缓冲区,负责对通过 Disruptor 进行交换的数据(事件)进行存储和更新。
Sequence 通过顺序递增的序号来编号管理通过其进行交换的数据(事件),对数据(事件)的处理过程总是沿着序号逐个递增处理。
RingBuffer底层是个数组,次序计算器是一个64bit long 整数型,平滑增长。
Storm 消息容错机制
总体介绍
在storm中,可靠的信息处理机制是从spout开始的。
一个提供了可靠的处理机制的spout需要记录他发射出去的tuple
当下游bolt处理tuple或者子tuple失败时spout能够重新发射。
Storm通过调用Spout的nextTuple()发送一个tuple。为实现可靠的消息处理,首先要给每个发出的tuple带上唯一的ID,并且将ID作为参数传递给SoputOutputCollector的emit()方法:collector.emit(new Values(“value1”,“value2”), msgId); messageid就是用来标示唯一的tuple的,而rootid是随机生成的
给每个tuple指定ID告诉Storm系统,无论处理成功还是失败,spout都要接收tuple树上所有节点返回的通知。如果处理成功,spout的ack()方法将会对编号是msgId的消息应答确认;如果处理失败或者超时,会调用fail()方法。
(ack val表示了整棵树的的状态,无论这棵树多大,只需要这个固定大小的数字就可以跟踪整棵树。当消息被创建和被应答的时候都会有相同的消息id发送过来做异或。 每当acker发现一棵树的ack val值为0的时候,它就知道这棵树已经被完全处理)
Storm 系统中有一组叫做"acker"的特殊的任务,它们负责跟踪DAG(有向无环图)中的每个消息。
acker任务保存了spout id到一对值的映射。第一个值就是spout的任务id,通过这个id,acker就知道消息处理完成时该通知哪个spout任务。第二个值是一个64bit的数字,我们称之为"ack val", 它是树中所有消息的随机id的异或计算结果
ack-fail机制
1.需要acjfail时,请为每个tuple生成一个messageID,这个messageID是用来表示你关心的tuple,当这个tuple完全处理时,storm框架会调用spout的ack方法,否则调用fail。至于你的消息是否重发完全由自己处理
2.在spout有并发度的情况下,storm会根据tuple最开始的所属的spout taskId,通知相应的spoutTask
3.在流式计算中topology的bolt组件是可以配置多个的,在每个环节中,都需要bolt组件显示告诉storm框架,自己对当前接受的这个tuple处理完成
4.ack机制里面,发送两种类型的tuple。一种是原始消息(数据Tuple),另外一种是ackTuple<RootId,tupleId>,数据Tuple中会包含一个MessageId对象
并行度配置
对于并发度的配置, 在storm里面可以在多个地方进行配置, 优先级为:
defaults.yaml < storm.yaml < topology-specific configuration
< internal component-specific configuration < external component-specific configuration
worker processes的数目, 可以通过配置文件和代码中配置, worker就是执行进程, 所以考虑并发的效果, 数目至少应该大亍machines的数目
executor的数目, component的并发线程数,只能在代码中配置(通过setBolt和setSpout的参数), 例如, setBolt(“green-bolt”, new GreenBolt(), 2)
tasks的数目, 可以不配置, 默认和executor1:1, 也可以通过setNumTasks()配置
Topology的worker数通过config设置,即执行该topology的worker(java)进程数。它可以通过 storm rebalance 命令任意调整。
Config conf = newConfig();
conf.setNumWorkers(2); //用2个worker
topologyBuilder.setSpout(“blue-spout”, newBlueSpout(), 2); //设置2个并发度
topologyBuilder.setBolt(“green-bolt”, newGreenBolt(), 2).setNumTasks(4).shuffleGrouping(“blue-spout”); //设置2个并发度,4个任务
topologyBuilder.setBolt(“yellow-bolt”, newYellowBolt(), 6).shuffleGrouping(“green-bolt”); //设置6个并发度
StormSubmitter.submitTopology(“mytopology”, conf, topologyBuilder.createTopology());