一、使用组件的并行度代替线程池
Storm 自身是一个分布式、多线程的框架,对每个Spout 和Bolt,我们都可以设置其并发度;它也支持通过rebalance 命令来动态调整并发度,把负载分摊到多个Worker 上。
如果自己在组件内部采用线程池做一些计算密集型的任务,比如JSON 解析,有可能使得某些组件的资源消耗特别高,其他组件又很低,导致Worker 之间资源消耗不均衡,这种情况在组件并行度比较低的时候更明显。
如果自己在组件内部采用线程池做一些计算密集型的任务,比如JSON 解析,有可能使得某些组件的资源消耗特别高,其他组件又很低,导致Worker 之间资源消耗不均衡,这种情况在组件并行度比较低的时候更明显。
比如某个Bolt 设置了1 个并行度,但在Bolt 中又启动了线程池,这样导致的一种后果就是,集群中分配了这个Bolt 的Worker 进程可能会把机器的资源都给消耗光了,影响到其他Topology 在这台机器上的任务的运行。如果真有计算密集型的任务,我们可以把组件的并发度设大,Worker 的数量也相应提高,让计算分配到多个节点上。
为了避免某个Topology 的某些组件把整个机器的资源都消耗光的情况,除了不在组件内部启动线程池来做计算以外,也可以通过CGroup 控制每个Worker 的资源使用量。
为了避免某个Topology 的某些组件把整个机器的资源都消耗光的情况,除了不在组件内部启动线程池来做计算以外,也可以通过CGroup 控制每个Worker 的资源使用量。
二、不要用DRPC 批量处理大数据
RPC 提供了应用程序和Storm Topology 之间交互的接口,可供其他应用直接调用,使用Storm 的并发性来处理数据,然后将结果返回给调用的客户端。这种方式在数据量不大的情况下,通常不会有问题,而当需要处理批量大数据的时候,问题就比较明显了。
(1)处理数据的Topology 在超时之前可能无法返回计算的结果。
(2)批量处理数据,可能使得集群的负载短暂偏高,处理完毕后,又降低回来,负载均衡性差。
批量处理大数据不是Storm 设计的初衷,Storm 考虑的是时效性和批量之间的均衡,更多地看中前者。需要准实时地处理大数据量,可以考虑Spark Stream 等批量框架。
(1)处理数据的Topology 在超时之前可能无法返回计算的结果。
(2)批量处理数据,可能使得集群的负载短暂偏高,处理完毕后,又降低回来,负载均衡性差。
批量处理大数据不是Storm 设计的初衷,Storm 考虑的是时效性和批量之间的均衡,更多地看中前者。需要准实时地处理大数据量,可以考虑Spark Stream 等批量框架。
三、不要在Spout 中处理耗时的操作
Spout 中nextTuple 方法会发射数据流,在启用Ack 的情况下,fail 方法和ack 方法会被触发。
需要明确一点,在Storm 中Spout 是单线程(JStorm 的Spout 分了3 个线程,分别执行nextTuple 方法、fail 方法和ack 方法)。如果nextTuple 方法非常耗时,某个消息被成功执行完毕后,Acker 会给Spout 发送消息,Spout 若无法及时消费,可能造成ACK 消息超时后被丢弃,然后Spout 反而认为这个消息执行失败了,造成逻辑错误。反之若fail 方法或者ack方法的操作耗时较多,则会影响Spout 发射数据的量,造成Topology 吞吐量降低。
需要明确一点,在Storm 中Spout 是单线程(JStorm 的Spout 分了3 个线程,分别执行nextTuple 方法、fail 方法和ack 方法)。如果nextTuple 方法非常耗时,某个消息被成功执行完毕后,Acker 会给Spout 发送消息,Spout 若无法及时消费,可能造成ACK 消息超时后被丢弃,然后Spout 反而认为这个消息执行失败了,造成逻辑错误。反之若fail 方法或者ack方法的操作耗时较多,则会影响Spout 发射数据的量,造成Topology 吞吐量降低。
四、注意fieldsGrouping 的数据均衡性
fieldsGrouping 是根据一个或者多个Field 对数据进行分组,不同的目标Task 收到不同
的数据,而同一个Task 收到的数据会相同。
假设某个Bolt 根据用户ID 对数据进行fieldsGrouping,如果某一些用户的数据特别多,而另外一些用户的数据又比较少,那么就可能使得下一级处理Bolt 收到的数据不均衡,整个处理的性能就会受制于某些数据量大的节点。可以加入更多的分组条件或者更换分组策略,使得数据具有均衡性。
的数据,而同一个Task 收到的数据会相同。
假设某个Bolt 根据用户ID 对数据进行fieldsGrouping,如果某一些用户的数据特别多,而另外一些用户的数据又比较少,那么就可能使得下一级处理Bolt 收到的数据不均衡,整个处理的性能就会受制于某些数据量大的节点。可以加入更多的分组条件或者更换分组策略,使得数据具有均衡性。
五、优先使用localOrShuffleGrouping
localOrShuffleGrouping 是指如果目标Bolt 中的一个或者多个Task 和当前产生数据的Task 在同一个Worker 进程里面,那么就走内部的线程间通信,将Tuple 直接发给在当前Worker 进程的目的Task。否则,同shuffleGrouping。
localOrShuffleGrouping 的数据传输性能优于shuffleGrouping,因为在Worker 内部传输,只需要通过Disruptor 队列就可以完成,没有网络开销和序列化开销。因此在数据处理的复杂度不高, 而网络开销和序列化开销占主要地位的情况下, 可以优先使用localOrShuffleGrouping 来代替shuffleGrouping。
localOrShuffleGrouping 的数据传输性能优于shuffleGrouping,因为在Worker 内部传输,只需要通过Disruptor 队列就可以完成,没有网络开销和序列化开销。因此在数据处理的复杂度不高, 而网络开销和序列化开销占主要地位的情况下, 可以优先使用localOrShuffleGrouping 来代替shuffleGrouping。
六、设置合理的MaxSpoutPending 值
在启用Ack 的情况下,Spout 中有个RotatingMap 用来保存Spout 已经发送出去,但还没有等到Ack 结果的消息。RotatingMap 的最大个数是有限制的,为p*num-tasks。其中p 是topology.max.spout.pending 值,也就是MaxSpoutPending(也可以由TopologyBuilder 在setSpout 通过setMaxSpoutPending 方法来设定),num-tasks 是Spout 的Task 数。如果不设置MaxSpoutPending 的大小或者设置得太大,可能消耗掉过多的内存导致内存溢出,设置太小则会影响Spout 发射Tuple 的速度。
七、设置合理的Worker 数
Worker 数越多,性能越好?先看一张Worker 数量和吞吐量对比的曲线(来源于JStorm文档:https://github.com/alibaba/jstorm/tree/master/docs/ 0.9.4.1jstorm https://github.com/alibaba/jstorm/tree/master/docs/ 0.9.4.1jstorm 性能测试.docx)。
从图可以看出,在12 个Worker 的情况下,吞吐量最大,整体性能最优。这是由于一方面,每新增加一个Worker 进程,都会将一些原本线程间的内存通信变为进程间的网络通信,这些进程间的网络通信还需要进行序列化与反序列化操作,这些降低了吞吐率。
另一方面,每新增加一个Worker 进程,都会额外地增加多个线程(Netty 发送和接收线程、心跳线程、SystemBolt 线程以及其他系统组件对应的线程等),这些线程切换消耗了不少CPU,sys 系统CPU 消耗占比增加,在CPU 总使用率受限的情况下,降低了业务线程的使用效率。
另一方面,每新增加一个Worker 进程,都会额外地增加多个线程(Netty 发送和接收线程、心跳线程、SystemBolt 线程以及其他系统组件对应的线程等),这些线程切换消耗了不少CPU,sys 系统CPU 消耗占比增加,在CPU 总使用率受限的情况下,降低了业务线程的使用效率。
八、平衡吞吐量和时效性
Storm 的数据传输默认使用Netty。在数据传输性能方面,有如下的参数可以调整:
(1)storm.messaging.netty.server_worker_threads:为接收消息线程;
(2)storm.messaging.netty.client_worker_threads:发送消息线程的数量;
(3)netty.transfer.batch.size:是指每次Netty Client 向Netty Server 发送的数据的大小,
如果需要发送的Tuple 消息大于netty.transfer.batch.size , 则Tuple 消息会按照netty.transfer.batch.size 进行切分,然后多次发送。
(4)storm.messaging.netty.buffer_size:为每次批量发送的Tuple 序列化之后的Task
Message 消息的大小。
(5)storm.messaging.netty.flush.check.interval.ms:表示当有TaskMessage 需要发送的时候, Netty Client 检查可以发送数据的频率。
降低storm.messaging.netty.flush.check.interval.ms 的值, 可以提高时效性。增加netty.transfer.batch.size 和storm.messaging.netty.buffer_size 的值,可以提升网络传输的吐吞量,使得网络的有效载荷提升(减少TCP 包的数量,并且TCP 包中的有效数据量增加),通常时效性就会降低一些。因此需要根据自身的业务情况,合理在吞吐量和时效性直接的平衡。
(1)storm.messaging.netty.server_worker_threads:为接收消息线程;
(2)storm.messaging.netty.client_worker_threads:发送消息线程的数量;
(3)netty.transfer.batch.size:是指每次Netty Client 向Netty Server 发送的数据的大小,
如果需要发送的Tuple 消息大于netty.transfer.batch.size , 则Tuple 消息会按照netty.transfer.batch.size 进行切分,然后多次发送。
(4)storm.messaging.netty.buffer_size:为每次批量发送的Tuple 序列化之后的Task
Message 消息的大小。
(5)storm.messaging.netty.flush.check.interval.ms:表示当有TaskMessage 需要发送的时候, Netty Client 检查可以发送数据的频率。
降低storm.messaging.netty.flush.check.interval.ms 的值, 可以提高时效性。增加netty.transfer.batch.size 和storm.messaging.netty.buffer_size 的值,可以提升网络传输的吐吞量,使得网络的有效载荷提升(减少TCP 包的数量,并且TCP 包中的有效数据量增加),通常时效性就会降低一些。因此需要根据自身的业务情况,合理在吞吐量和时效性直接的平衡。