17.大数据学习之旅——Strom集群中各角色说明&JStorm介绍&Storm的应用场景&Trident框架& Storm可靠性保证&Storm的容错机制

本文详细介绍了Storm集群的构成,包括Nimbus、Supervisor、ZooKeeper的角色和工作原理。探讨了Storm的任务分配调度流程、常见参数配置以及容错机制,特别强调了acker机制和worker故障的处理。同时,文章深入讲解了Trident框架,包括Trident的batch和partition概念,以及如何通过Filter和Function实现数据处理。此外,还讨论了Storm在实时计算场景中的应用,并介绍了阿里巴巴的JStorm,指出其在性能和功能上的改进。
摘要由CSDN通过智能技术生成

Strom集群中各角色说明


概述
每一个工作节点上运行的Supervisor监听分配给它那台机器的工作,根据需要启动/关闭工作进程,每一个工作进程执行一个Topology的一个子集;一个运行的Topology由运行在很多机器上的很多工作进程Worker组成。那么Storm的核心就是主节点(Nimbus)、工作节点(Supervisor)、协调器(ZooKeeper)、工作进程(Worker)、任务线程(Task)。

主节点Nimbus
主节点通常运行一个后台程序——Nimbus。
Nimbus守护进程的主要职责是管理,协调和监控在集群上运行的topology。包括topology的发布,任务指派,事件处理失败时重新指派任务。

将topology发布到Storm集群,将预先打包成jar文件的topology和配置信息提交(submitting)到nimbus服务器上。一旦nimbus接收到了topology的压缩包,会将jar包分发到足够数量的supervisor节点上。当supervisor节点接收到了topology压缩文件,nimbus就会指派task(bolt和spout实例)到每个supervisor并且发送信号指示supervisoer生成足够的worker来执行指派的task。

nimbus记录所有supervisor节点的状态和分配给它们的task。如果nimbus发现某个supervisor没有上报心跳或者已经不可达了,它会将故障supervisor分配的task重新分配到集群中的其他supervisor节点。

nimbus不会引起单点故障。这个特性是因为nimubs并不参与topology的数据处理过程,它仅仅是管理topology的初始化,任务分发和进行监控。实际上,如果nimbus守护进程在topology运行时停止了,只要分配的supervisor和worker健康运行,topology一直继续数据处理。此时你只需要重启nimbus进程即可,无任何影响。

但要注意的是:在nimbus已经停止的情况下supervisor异常终止,因为没有nimbus守护进程来重新指派失败这个终止的supervisor的任务,数据处理就会失败。不过这种概率是很小的,因为nimbus进程一般不会宕掉。
目前storm官方出于nimbus宕机对集群影响不大的考虑,并没有实现nimbus的高可用方案。

如果你想宕掉nimbus进程,使用kill -9即可。

工作节点Supervisor
工作节点同样会运行一个后台程序——Supervisor,用于收听工作指派并基于要求运行工作进程。而Nimbus和Supervisor之间的协调则通过ZooKeeper系统。

同样,你可以用kill-9来杀死Supervisor进程,然后重启就可以继续工作。
协调服务组件ZooKeeper
ZooKeeper是完成Nimbus和Supervisor之间协调的服务。Storm使用ZooKeeper协调集群,由于ZooKeeper并不用于消息传递,所以Storm给ZooKeeper带来的压力相当低。在大多数情况下,单个节点的ZooKeeper集群足够胜任,不过为了确保故障恢复或者部署大规模Storm集群,可能需要更大规模的ZooKeeper集群。Nimbus、Supervisor与ZooKeeper的关系如图所示。
在这里插入图片描述
在这里插入图片描述
Worker
具体处理事务进程Worker:运行具体处理组件逻辑的进程。
Task
具体处理线程Task:Worker中的每一个Spout/Bolt线程称为一个Task。同一个Spout/Bolt的Task可能会共享一个物理线程,该线程称为Executor。

Storm任务分配调度流程及调优


Storm的任务分配流程及算法如下:
1、先由nimbus来计算拓扑的工作量,及计算多少个task,task的数目是指spout和bolt的并发度的分别的和,例如一个拓扑中有一个spout和一个bolt,并且spout的task并发度为2,bolt的task并发度为3,则task数为5;

2、nimbus会把计算好的工作分配给supervisor去做,工作分配的单位是task,即把计算好的一堆task分配给supervisor去做,即将task-id映射到supervisor-id+port上去,具体流程如下:
①从zk上获得已有的assignment
②查找所有可用的slot,所谓slot就是可用的worker,在所有supervisor上配置的多个worker的端口。
③将任务均匀地分配给可用的worker

3、Supervisor会根据nimbus分配给他的任务信息来让自己的worker做具体的工作

4、Worker会到zookeeper上去查找给他分配了哪些task,并且根据这些task-id来找到相应的spout/bolt,它还需要计算出这些spout/bolt会给哪些task发送消息,然后建立与这些task的连接,然后在需要发消息的时候就可以给相应的task发消息。

Nimbus的任务分配算法特点如下:
1、在slot(槽位)充沛的情况下,能够保证所有topology的task被均匀的分配到整个集群的所有机器上
2、在slot不足的情况下,它会把topology的所有的task分配到仅有的slot上去,这时候其实不是理想状态,所以在nimbus发现有多余slot的时候,它会重新分配topology的task分配到空余的slot上去以达到理想状态。
3、 在没有slot的时候,它什么也不做

Storm与任务分配相关的配置选项

Storm自身的分配机制会尽量保证一个Topology会被平均分配到当前集群上,但是它没有考虑整个集群的负载均衡;例如现在集群有三台机器(三台Supervisor),每个上面的可用Slot数目均为四个,那么现在提交Topology,并且Topology占用1个worker,提交多个Topology后,它会先将整个集群中的一个机器占满,然后再去给别的机器分配。这种分配方式对有些场景是不太适用的,因此Storm自身的分配机制增加了额外的一个配置;

配置项如下:
default.schedule.mode: “average”

如果default.schedule.mode配置为average,则在使用默认的分配机制时会优先将任务分配给空闲Slot数目最多的机器。

Storm常见参数配置


示例:

#Storm集群对应的ZooKeeper集群的主机列表
storm.zookeeper.servers:
     - "ip01"
     - "ip02"
     - "ip03"
 
#Storm集群对应的ZooKeeper集群的服务端口,ZooKeeper默认端口为2181
storm.zookeeper.port: 2181
#Storm的元数据在ZooKeeper中存储的根目录
storm.zookeeper.root: /storm
#整个Storm集群的Nimbus节点
nimbus.host: ip01

#Storm的Slot(槽位)。Slot和Worker是一一对应的。即有一个slot就可以启动一个worker进程。因为进程和进程之间需要通信,比如传输数据,所以需要配置端口号。
配置几个端口,每台服务器就会启动几个slot。(注意端口不要冲突)
配置建议1:因为Storm是基于内存的实时计算,所以在配置Slot个数时,最好是服务器core的整数倍。比如一台服务器8核,slot的个数:8、16、24……
配置建议2:slot个数不要超过: (物理内存 - 虚拟内存)/每个java进程最大的内存使用量
比如:物理内存:64  虚拟内存:4  每个java进程:1
一般配置32个  。
supervisor.slots.ports:
        - 6700
        - 6701
        - 6702
        - 6703
        - 6704
        - 6705
        - 6706
        - 6707
        - 6708
        - 6709
        - 6710
 
#Storm集群的UI地址端口号,默认是8080
ui.port :8080

Storm的容错机制


任务级容错
Bolt任务crash引起的消息未被应答。此时,acker中所有与此Bolt任务关联的消息都会因为超时而失败,对应的Spout的fail方法将被调用。
acker任务失败。如果acker任务本身失败了,它在失败之前持有的所有消息都将超时而失败。Spout的fail方法将被调用。
Spout任务失败。在这种情况下,与Spout任务对接的外部设备(如MQ)负责消息的完整性。例如,当客户端异常时,kestrel队列会将处于pending状态的所有消息重新放回队列中。

任务槽(slot)故障
Worker失败。每个Worker中包含数个Bolt(或Spout)任务。Supervisor负责监控这些任务,当worker失败后会尝试在本机重启它,如果它在启动时连续失败了一定的次数,无法发送心跳信息到Nimbus,Nimbus将在另一台主机上重新分配worker。

Supervisor失败。Supervisor是无状态(所有的状态都保存在Zookeeper或者磁盘上)和快速失败(每当遇到任何意外的情况,进程自动毁灭)的,因此Supervisor的失败不会影响当前正在运行的任务,只要及时将他们重新启动即可。

Nimbus失败。Nimbus也是无状态和快速失败的,因此Nimbus的失败不会影响当前正在运行的任务,但是当Nimbus失败时,无法提交新的任务,只要及时将它重新启动即可。

Storm的Nimbus目前不具备HA。因为官方给出的解释:Nimbus是无状态和快速失败,不会对已经运行的task有影响。
1.如果想让Nimbus具有HA机制,建议学习阿里的JStorm(性能也要比原生的Strom要高)
2.可以查看Storm最新的1.0版本

集群节点(机器)
Storm集群中的节点故障。此时Nimbus会将此机器上所有正在运行的任务转移到其他可用的机器上运行。
Zookeeper集群中的节点故障。Zookeeper保证少于半数的机器宕机系统仍可正常运行,及时修复故障机器即可。

Storm—anchor(锚定)与ack机制


概述
Storm提供了一种API能够保证spout发送出来的每个tuple都能够执行完整的处理过程。在我们之前做的例子中,并没有实现这种消息的可靠性保证。

spout的可靠性
在Storm中,可靠的消息处理机制是从spout开始的。一个提供了可靠的处理机制的spout需要记录它发射出去的tuple,当下游bolt处理tuple或者子tuple失败时spout能够重新发射。子tuple可以理解为bolt处理spout发射的原始tuple后,作为结果发射出去的tuple。另外一个视角来看,可以将spout发射的数据流看作一个tuple树的主干。
在这里插入图片描述

在图中,实线部分表示从spout发射的原始主干tuple,虚线部分表示的子tuple都是源自于原始tuple。这样产生的图形叫做tuple树。

在有保障数据的处理过程中,bolt每收到一个tuple,都需要向上游确认应答(ack)者报错。对主干tuple中的一个tuple,如果tuple树上的每个bolt进行了确认应答,spout会调用ack方法来标明这条消息已经完全处理了。如果树中任何一个bolt处理tuple报错,或者处理超时,spout会调用fail方法。

Spout的nextTuple()发送一个tuple。为实现可靠的消息处理,首先要给每个发出的tuple带上唯一的ID,并且将ID作为参数传递给SpoutOutputCollector的emit()方法

bolt的可靠性
bolt要实现可靠的消息处理机制包含两个步骤:
1.当发射衍生的tuple时,需要锚定读入的tuple
2.当处理消息成功或者失败时分别确认应答或者报错
锚定一个tuple的意思是,建立读入tuple和衍生出的tuple之间的对应关系,这样下游的bolt就可以通过应答确认、报错或超时来加入到tuple树结构中。

注:非锚定的tuple不会对数据流的可靠性起作用。如果一个非锚定的tuple在下游处理失败,原始的根tuple不会重新发送。

案例——可靠的单词计数
为了进一步说明可控性,让我们增强SentenceSpout类,支持可靠的tuple发射方式。需要记录所有发送的tuple,并且分配一个唯一的ID。我们使用HashMap<UUID,Values>来存储已发送待确认的tuple。每当发送一个新的tuple,分配一个唯一的标识符并且存储在我们的hashmap中。当收到一个确认消息,从待确认列表中删除该tuple。如果收到报错,从新发送tuple:

Spout代码示例:

public class WordCountSpout extends BaseRichSpout{
   
 
 
private ConcurrentHashMap<UUID,Values> pending;
 
private String[] data=new String[]{
   
"hello storm",
"hello world",
"hello hadoop",
"hello world"        
};
private SpoutOutputCollector collector;
private static int i=0;
 
@Override
public void nextTuple() {
   
 
Values line=new Values(data[i]);
 
//为当前的tuple生成一个标识id
UUID msgId=UUID.randomUUID();
 
this.pending.put(msgId,line);
 
collector.emit(line,msgId);
 
if(i==data.length-1){
   
i=0;
}else{
   
i++;
}                
}
 
@Override
public void open(Map conf, TopologyContext context, SpoutOutputCollector collector) {
   
this.collector=collector;
this.pending=new ConcurrentHashMap<>();
 
}
 
 
@Override
public void declareOutputFields(OutputFieldsDeclarer declarer) {
   
declarer.declare(new Fields("line"));
 
}
 
@Override
public void ack(Object msgId) {
   
this.pending.remove(msgId);
}
 
@Override
public void fail(Object msgId) {
   
collector.emit(this.pending.get(msgId),msgId);
}
 
 
 
}
 

为支持有保障的处理,需要修改bolt,将输出的tuple和输入的tuple锚定,并且应答确认输入的tuple:

Bolt代码示例:

public class SplitBolt extends BaseRichBolt{
   
private OutputCollector collector;
 
@Override
public void execute(Tuple tuple) {
   
try {
   
 
String line=tuple.getStringByField("line");
String[] words=line.split(" ");
for(String word:words){
   
 
//将输入tuple和输出tuple进行锚定
collector.emit(tuple,new Values(word));
}
//ack方法的作用是向上游发送确认机制,表明此tuple以接收并成功处理
collector.ack(tuple);
} catch (Exception e) {
   
//fail方法的作用是向上游发送失败确认,上游收到后,会重新发送tuple
collector.fail(tuple);
}
 
 
 
}
 
@Override
public void prepare(Map arg0, TopologyContext arg1, OutputCollector arg2) {
   
collector=arg2;
 
}
 
@Override
public void declareOutputFields(OutputFieldsDeclarer declarer) {
   
declarer.declare(new Fields("word"));
 
}
 
}
 

补充:如果是用anchor和ack机制,对于Bolt组件,可以实现:BaseBasicBolt
可以不用省略锚定 ack和fail的代码
代码示例:

public class OtherBolt extends BaseBasicBolt{
   
 
@Override
public void execute(Tuple input, BasicOutputCollector collector) {
   
 
 
}
 
@Override
public void declareOutputFields(OutputFieldsDeclarer declarer) {
   
// TODO Auto-generated method stub
 
}
 
}

Storm可靠性保证


概述
Storm提供了数据流处理时的可靠性,所谓的可靠性是指spout发送的每个tuple都能够执行完整的处理过程。这种消息传输的可靠性保证其实有三个级别,分别是:

1)至多处理一次,但可能会丢失数据 at most once
2)至少处理一次,数据处理不会丢失,但可能会重复处理 at least once
3)精确处理一次,一定能被处理,且仅处理一次 exactly once

第一种级别实际上是一种最弱的保证,我们做的最开始的数字案例和单词统计案例就是这个级别。

第二种级别是要比第一种可靠很多,实际上我们用的acker机制就是实现了这种级别。但可能会带来的问题是,一条数据会被重复处理多次。
比如:当一个tuple在传输出去之后,下游节点需要在指定时间内反馈ack,如果超时,则认为处理失败,上游会重新发送。所以,如果某一时刻由于网络波动造成了较大的传输延迟,就可能会造成一个Tuple被上游重复发送,最后导致重复处理。
对于这种情况,也不是没有办法解决。我们可以调节topology.message.timeout.secs(default:30)
适当调大是更为稳妥的方式。

第三种级别是最理想的,虽然利用anchor和ack机制保证所有Tuple都被成功处理,如果Tuple出错,则可以重传,但是如何保证出错的Tuple只被处理一次(不被重复处理)?之前的Storm版本提供了一套事务性组件Transactional Topology,用来解决该问题。现在版本中已经弃用Transactional Topology原语,在Storm0.8之后版本取而代之的是Trident框架,提供了更加方便和直观的接口。

Trident框架


概述
Storm是一个分布式的实时计算系统,利用ack机制保证所有Tuple都被成功处理。如果Tuple出错,则可以重传,但是如何保证出错的Tuple只被处理一次。之前的Storm版本提供了一套事务性组件Transactional Topology,用来解决该问题。现在版本中已经弃用Transactional Topology原语,取而代之的是Trident框架。

Trident是在Storm基础上的以实时计算为目标的高度抽象。它在提供处理大吞吐量数据能力(每秒百万次消息)的同时,也提供了低时延分布式查询和有状态流式处理的能力。

Trident的batch和partition
Trident将多个Tuple元组变为能批量处理的集合(batch)来处理

此外,Trident在并发度的控制上,舍弃了worker,executor,task等繁琐的概念,取而代之的是用分区(partition)来刻画并发度。

可以这样理解:我们在Trident框架时,如何提高并发度?很简单,只需要设置并提高分区数量即可,而不需要再繁琐的设置有几个worker,有几个executor,每个executor运行几个task。所以这也是Trident框架的魅力所在,因为它大大简化了对Storm集群的使用门槛。可能有人会有疑惑,分区到底是什么?实际上,Trident的分区本质上就是对worker,executor,task的进一步封装。而一个分区到底有多少worker,executor,task,程序员不需要了解,因为Trident框架自身会根据集群环境做出调整和优化,经过大量的实践表明,使用Trident分区来控制并发度要远远比程序员手动设置原生的storm并发度要好。

batch(批次)和partition(分区)是什么关系?
batch是Trident框架处理流数据的最小逻辑单元,如果你设置一个batch里只运行一个tuple,那就相当于和原生的storm没任何差别,但很少有人这么多。所以一个batch就是一组tuple的集合。

partition(分区)默认是1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值