概念
1、主节点:Nimbus,负责在集群上进行任务(Topology)的分发与资源的调度以及监控
2、工作节点:Supervisor,接收到任务请求后,启动一个或多个Worker进程来处理任务;默认情况下,一个Supervisor最多启动4个Worker
3、工作进程:Worker,在Supervisor中的子进程,存在着若干个Spout和Bolt线程,来负责Spout和Bolt组件处理任务(实际是开启的executor线程)
4、作业:Topologies(死循环,不会结束)
5、Spout:获取数据的组件
6、Bolt:处理数据的组件
7、Stream:Spout和Bolt之间数据流动的通道
8、Tuple:
1)、Stream的最小组成单位,Spout向Bolt发送一次数据叫一个Tuple
2)、同一个Stream中Tuple的类型相同,不同的Stream中可能相同/不同
3)、一个key-value形式的Map
常用分发策略
解决Spout和Bolt之间数据传输(发送Tuple元组)的问题
1)、shuffleGrouping:随机派发Stream中的Tuple到Bolt中
2)、fieldsGrouping:根据字段的哈希值与Bolt个数进行取模操作然后进行分组发送。
容错机制
异或方式<相同为0,不同为1>
tupleId-产生新数据,会产生一个tupleId;
整个过程中的tupleId按顺序两两异或到最后,若结果为0,则数据正确,否则错误。
messageId-代表整条信息,API中指定提供给程序员,long型;
rootId-代表某条信息,提供给storm框架
出现数据运算失败的两种情况:
1)、异常(数据异常)
2)、任务运行超时--认为处理失败
因为数据发送时导致的数据重复发送问题,如何解决?
1)、比如对订单信息做处理,处理成功后,把订单信息ID存储到Redis(set)
2)、信息发送时,判断是否处理过此信息
消息的可靠性保障和acker机制:
open/nextTuple/ack/fail/close
1)、Spout类:
在发送tuple时,Spout会提供一个msgId,用于在后续识别tuple;
Storm会根据msgId跟踪创建的tuple树,直到某个tuple被完整处理,根据msgId调用最初发送tuple的Spout中ack()方法,检测到超时就调用fail()方法,这两个方法的调用必须由最初创建这个tuple的Spout执行;
当Spout从消息队列(Kafka/RocketMQ)中取出一条数据时,实际上没有被取出,而是保持一个挂起状态,等待消息完成的信号,挂起状态的信息不会被发送到其它的消费者;
当该消息被"取出"时,队列会将消息体数据和一个唯一的msgId提供给客户端,当Spout的ack()/fail()方法被调用时,Spout根据发送的id向队列请求将消息从队列中移除/重新放入队列。
2)、acker任务:
高效的实现可靠性--必须显式的在Bolt中调用定义在Spout中的ack()和fail()方法。
Storm拓扑有一些特殊的称为"acker"的任务,负责跟踪Spout发送的tuple的DAG,当一个acker发现DAG结束后,它就会给创建Spout tuple的Spout任务发送一条消息,让这个任务来应答这个消息。
acker并不会直接的跟踪tuple树,在acker树中存储了一个表,用于将Spout tuple的id与一对值相映射,id为创建这个tuple的任务id,第二个值为一个64bit的数字(ackval),这个值是这棵树中所有被创建的或者被应答的tuple的tupleid进行异或运算的结果值。
3)、移除可靠性:
a.将Config.TOPOLOGY_ACKERS设置为0,在这种情况下,Storm 会在 Spout 发送 tuple 之后立即调用 ack 方法,tuple 树叶就不会被跟踪了。
b.在SpoutOutputCollector.emit方法中省略消息id来关闭spouttuple的跟踪功能
c.在发送tuple的时候选择发送“非锚定”的(unanchored)tuple
Storm并发度设置
一个节点是一个Worker,一个Bolt是一个task,全部节点的Spout或Bolt的个数叫并发度。
1)、Worker并发度:
首先按照集群规模和集群的物理位置来设定,一般会把Worker均分到每一个节点里。一个supervisor默认设置一个Worker
2)、Spout数量设定:
Spout总数默认等于Kafka(消息中间件)对应Topic的分区数,提高吞吐速度。一般一个Worker设置一个Spout
3)、Bolt1数量设定:
首先根据数据量和处理数据的时间来设定。一般情况下,Bolt1的数量是Spout数量的2倍(根据项目进行修改)
4)、Bolt2数量设定:
首先根据数据量和处理数据的时间来设定,因为Bolt1传过来的中间结果数据已经减少很多,Bolt2的数量可以酌情减少。
并行度配置
工作进程:Worker Process,也称为Worker
Config config = new Config();
config.setNumWorkers(3); //注意此参数不能大于supervisor.slots.ports数量。
执行器:Executor,即线程Thread
TopologyBuilder builder = new TopologyBuilder();
builder.setSpout(id, spout, parallelism_hint); //设置Spout的Executor数量参数parallelism_hint
builder.setBolt(id, bolt, parallelism_hint); //设置Bolt的Executor数量参数parallelism_hint
任务:Task
setNumTask() 函数指定每个 executor 的 task 数量
TopologyBuilder builder = new TopologyBuilder();
builder.setSpout(id, spout, parallelism_hint).setNumTasks(val); //设置Spout的Executor数量参数parallelism_hint,Task数量参数val
builder.setBolt(id, bolt, parallelism_hint).setNumTasks(val); //设置Bolt的Executor数量参数parallelism_hint,Task数量参数val
参考:https://www.cnblogs.com/chushiyaoyue/p/6268348.html
拓扑的并行度调整
- 使用Storm的WebUI
- 使用Storm的命令行工具,如下
调整worker并行数:storm rebalance topologyName -n XX
调整topology中某个spout或bolt的并行数:如 storm rebalance topologyName -e boltName=XX
# “myTopology” 拓扑使用5个Worker进程
# “blue-spout” Spout使用3个Executor
# “yellow-blot” Bolt使用10个Executor
storm rebalance myTopology -n 5 -e blue-spout=3 -e yellow-blot=10
注:spout和bolt的并行数,最多可以调整到它的taskNum,默认情况下,taskNum是和你设置的 paralismNum相同的。
设置Bolt或Spout的taskNum
builder.setBolt("cpp", new CppBolt(), 3).setNumTasks(5).noneGrouping(pre_name);
这时提交topology后,默认cpp Bolt的excutor数是3,我们可以通过rebalance -e cpp=5 将其最大调整到5。
参考:storm rebalance 命令调整topology并行数及问题分析-CSDN博客
Storm UI 解析
首页
- Supervisors 集群中配置的 supervisor 数量
- Used slots 集群中已用掉的 workers 数量
- Free slots 集群中空闲的 workers 数量
- Total slots 集群中总的的 workers 数量
- Executors 当前集群中总的 Executor 线程数量,该值等于集群中所有 topology 的所有 spout/bolt 配置的 executor 数量之和,其中默认情况下每个 worker 进程还会派生一个 acker executor 线程,也一并计算在内了
- Tasks 当前集群中总的 task 数量,也是所有 executor 派生的 task 数量之和
- Assigned Mem (MB) 分配给该 topolgoy 下所有 worker 工作内存之和,单个 worker 的内存配置可由 Config.WORKER_HEAP_MEMORY_MB 和 Config.TOPOLOGY_WORKER_MAX_HEAP_SIZE_MB 指定,默认为 768M,另外再加上默认 64M 的 logwritter 进程内存空间,则有 832M。
- slot 当前节点总的可用 worker 数
- used slot 已用掉的 worker 数
topology 页面
- Num executors 等于当前 topology 下所有 spout/bolt 的并行度总和,再加上所有 worker 下的 acker executor 线程总数(默认情况下一个 worker 派生一个 acker executor)
- Activate 激活此 topology
- Deactivate 暂停此 topology 运行
- Rebalance 调整并行度并重新平衡资源
- Kill 关闭并删除此 topology
- Debug 调试此 topology 运行,需要设置 topology.eventlogger.executors 数量 > 0
- Stop Debug 停止调试
- Change Log Level 调整日志级别
- Window 时间窗口,比如"10m 0s"表示在topology启动后10m 0s之内
- Emitted 此时间窗口内发射的总tuple数
- Transferred 此时间窗口内成功转移到下一个bolt的tuple数
- Complete latency (ms) 此时间窗口内每个tuple在tuple tree中完全处理所花费的平均时间
- Acked 此时间窗口内成功处理的tuple数
- Failed 此时间窗口内处理失败或超时的tuple数
- Id topologoy 中 spout 的名称,一般是在代码里设置的
- Executors 当前分配给此 spout 的 executor 线程总数
- Tasks 当前分配给此 spout 的 task 线程总数
- Emitted 截止当前发射的总tuple数
- Transferred 截止当前成功转移到下一个bolt的tuple数
- Complete latency (ms) 截止当前每个tuple在tuple tree中完全处理所花费的平均时间
- Acked 截止当前成功处理的tuple数
- Failed 截止当前处理失败或超时的tuple数
正常情况下 Failed 值为0,如果不为0,考虑增加该 spout 的并行度。这是最重要的一个判断依据;
正常情况下,Emitted、Transferred和Acked这三个值应该是相等或大致相等的,如果相差太远,要么该 spout 负载太重,要么下游负载过重,需要调节该 spout 的并行度,或下游 bolt 的并行度;
Complete latency (ms) 时间,如果很长,十秒以上就已经算很长的了。当然具体时间取决于代码逻辑,bolt 的结构,机器的性能等。
- Id topologoy 中 bolt 的名称,一般是在代码里设置的
- Executors 当前分配给此 bolt 的 executor 线程总数
- Tasks 当前分配给此 bolt 的 task 线程总数
- Emitted 截止当前发射的总tuple数
- Transferred 截止当前成功转移到下一个bolt的tuple数
- Complete latency (ms) 截止当前每个tuple在tuple tree中完全处理所花费的平均时间
- Capacity (last 10m) 性能指标,取值越小越好,当接近1的时候,说明负载很严重,需要增加并行度,正常是在 0.0x 到 0.1 0.2 左右。该值计算方式为 (number executed * average execute latency) / measurement time
- Execute latency (ms) 截止当前成功处理的tuple数
- Executed 截止当前处理过的tuple数
- Process latency (ms) 截止当前单个 tuple 的平均处理时间,越小越好,正常也是 0.0x 级别;如果很大,可以考虑增加并行度,但主要以 Capacity 为准
- Acked 截止当前成功处理的tuple数
- Failed 截止当前处理失败或超时的tuple数
Capacity (last 10m) 取值越小越好,当接近1的时候,说明负载很严重,需要增加并行度,正常是在 0.0x 到 0.1 0.2 左右
Process latency (ms) 单个 tuple 的平均处理时间,越小越好,正常也是 0.0x 级别;如果很大,可以考虑增加并行度,但主要以 Capacity 为准
如果自己在组件内部采用线程池做一些计算密集型的任务,比如JSON 解析,有可能使得某些组件的资源消耗特别高,其他组件又很低,导致Worker 之间资源消耗不均衡,这种情况在组件并行度比较低的时候更明显。
比如某个Bolt 设置了1 个并行度,但在Bolt 中又启动了线程池,这样导致的一种后果就是,集群中分配了这个Bolt 的Worker 进程可能会把机器的资源都给消耗光了,影响到其他Topology 在这台机器上的任务的运行。如果真有计算密集型的任务,我们可以把组件的并发度设大,Worker 的数量也相应提高,让计算分配到多个节点上。
spout 页面
注意看,Emitted,Transferred 和 Acked 这几个参数,看看是否所有的 executor 都均匀地分担了 tuple 的处理工作。
如果 spout 读取的是 kafka 的数据,那么正常情况下,设置为 topic 的分区数量即可。
kafka 只能保证同一分区下消息的顺序性,当 spout 配置了多个 executor 的时候,不同分区的消息会均匀的分发到不同的 executor 上消费,那么消息的整体顺序性就难以保证了,除非将 spout 并行度设为 1
bolt 页面
参数同之前页面,不赘述。
storm调优
1、硬件配置的优化
机器的CPU、带宽、磁盘性能等也会对 Storm 性能有影响。
2、代码层面的优化
3、topology 并行度的优化
见Storm UI解析红字
在设置线程的并行度时,最好能够根服务器集群的及其数量和其每台的cpu的核心数有所关联。我一般这样设置,比如,在我的使用的集群中,假如共有5台用于工作的服务器,则我在进行资源消耗较多的bolt的并行度设置时,一般设置其并行度是5的倍数,如10,15,20等。这样可以更容易的促进系统自动进行负载的均衡。
设置好worker的数量。worker是指工作进程的数量,其上可以运行多个工作线程。在设置worker的数量时,首先查看系统中的所有的executors线程数共有多少,比如,有45个。那么,在设置worker的数量时,最好使得每个worker进程所占有的线程数不要太大,也不要太小。我一般设置为3,也就是说每个worker进程大约有3个左右的线程。在上面的45个executors线程的例子中,若共有5台机器,则设置共有25个worker,则每台机器上有5个worker;对于45个线程,每台机器上有15个;这样一平均,则每个worker进程中有3个executors线程。
4、Storm 集群配置参数和 topology 运行参数的优化
/** 此参数比较重要,可适当调大一点 */
/** 通常情况下 spout 的发射速度会快于下游的 bolt 的消费速度,当下游的 bolt 还有 TOPOLOGY_MAX_SPOUT_PENDING 个 tuple 没有消费完时,spout 会停下来等待,该配置作用于 spout 的每个 task。 */
conf.put(Config.TOPOLOGY_MAX_SPOUT_PENDING, 10000);
/** 设置消息过期时间 秒 */
config.setMessageTimeoutSecs(3600);
/** 调整分配给每个 worker 的内存,关于内存的调节,上文已有描述 */
conf.put(Config.WORKER_HEAP_MEMORY_MB, 768);
conf.put(Config.TOPOLOGY_WORKER_MAX_HEAP_SIZE_MB, 768);
setMaxSpoutPending只对可靠任务起作用,如果不设置MaxSpoutPending 的大小或者设置得太大,可能消耗掉过多的内存导致内存溢出,设置太小则会影响Spout 发射Tuple 的速度。
setMessageTimeoutSecs这个配置设定了一个Tuple数需要应答的最大时间秒数限制,也就是超过这个时间就已经被认为失败,可能导致stormUI页面spout failed 。这个值设置的太小可能会导致tuple反复重新发送。 默认值为30s