twitter在使用storm过程中发现了一些storm的弊端,开发Herons相对storm需要提升的几个特性:1)更好的扩展性, 2)更容易调试 3)更高的性能 4)更好管理–可以和其他系统复用资源
storm的短板:
1)storm的worker调度策略非常复杂,一台机器上的多个worker由操作系统调度,worker中的每个executor会映射两个线程,这些线程的调度就使用jvm预先设定的基于优先级的调度策略,而每个线程需要运行多个task,excutor有需要根据输入数据运行合适的task。这个多层次的调度规则非常复杂,根本搞不清目前到底是那个task在被调度执行。
2)每个worker可能运行完全不同的task,比如:一个kafka spout,一个做关联的bolt,和一个把输出数据写入KV系统的bolt可能运行在一个JVM中,这种情况下根本无法优化某个task的问题,如果有问题只能重启topology,而重启之后这几个task又不见得会分配到同一个worker中,导致问题很难定位。
3)同一个worker中的不同task的日志会打印在同一个文件中,导致某个task的错误日志非常难以查找。更有甚着,如果某个task有问题可能导致真个worker进程崩掉,导致其他task也不能正常工作了,也就是说局部的错误会被放大。
4)在资源分配方面,storm任务每个worker都是对等的,这个假设经常会造成资源浪费,例如3个spouts和1个bolt , 加入每个spout和bolt各自要5G和10G内存 , 这样的话 , topoogy必须为每个worker预留15G的内存来跑一个spout和一个bolt , 假如用户设置worker数为2 , 那么两个worker就要总共预留30G内存 , 但是实际上只要 3*5 + 1 *10 = 25G内存 , 这样就白费了5G。这个问题当worker中的组建变得越来越多,越来越复杂的时候回变得更加糟糕。在tiwtter使用高级别抽线的DSL
summingbird(杨晓青注:summingbird是tiwtter开源的类似pig的同一套代码可以同时在hadoop和storm平台运行的DSL)的时候这种问题经常出现。
5)随着worker JVM heap的变大,当使用jstack或者heap dump对worker进行问题定位的时候,经常会导致worker的hearbeat超时,导致supervisor将worker kill掉并重启。这个导致问题非常难以定位。
6)storm的worker使用一些线程和队列进行tuple的发送和接受,一个全局的接受线程负责接收上游数据,一个全局的发送线程负责数据发送,然后每个executor有一个logic thread执行用户代码,一个本地的send thread来做logic thread与全局的发送线程之间做数据通信,每个tuple进入一个worker到出来需要经过4个线程转发。
storm nimus的一些问题
nimbus负责很多功能,包括:任务调度,任务监控,分发jar,而且作为metric信息的server进行一些metric信息的统计。当topology变多的时候,nimbus会成为瓶颈。
1)nimbus的调度器不支持worker细粒度的资源隔离,不同topology的worker会运行到同一个屋里机器,可能会互相影响,如果每个机器上运行一个worker又太浪费资源了,及时使用storm on yarn的模式也解决不了这个问题。
2)storm使用zk作为存储所有的心跳信息,以及任务的分配信息,当topology变多的时候zk会成为瓶颈。(杨晓青注:在阿里巴巴的经验,机器到300台,excutor到达10w的时候会出现zk性能问题)
3)nimbus单点故障,不是HA
缺少背压机制:
storm没有背压机制,如果某个元组无法处理输入的data/tuple,发送者会简单的丢弃这些tuple,虽然这是个fail-fast的设计,而且很简单,不过会带来如下弊端:
1)当通知机制关闭的时候,会有很多的tuple被丢弃掉,而且很难看到这些丢弃的过程。
2)上游的计算随着中间tuple的丢弃而丢失
3)系统的行为非常难以预测
效率
在生产环境,很多实例的性能无法预测,他们可能发生tuple失败,重发,数据的到达速率超过元组处理速率等,这些影响性能问题基本都是由以下原因:
不合适的重发—-整个tuple 树的任何地方出错都会导致整个tuple树的失败,针对这个失败系统只是做了简答的从源头重发。
长时间的GC—-当worker占用很多内容时,JVM GC的时候时间会比较长,但只高延时和很多tuple失败重发。
队列竞争—-一些情况下在transfer queue上会有很多竞争,特别是一个worker中跑很多executor的时候。
为了避免这些问题,我们经常得多分配资源,带来更多的资源消耗。
例如,在一个简单的三阶段的storm topology大概需要600个占用率在20%到30%的cpu核,为了更好的理解cpu时间的消耗,我们写了一个简单的程序接收所有的tuple并且使用thrift进行反序列化,这个过程消耗了25个使用率在90%的cpu核,也就是说需要使用率在30%的核75个,只有600个核的1/8.
即使在最差的情况下,及时进行count计算和数据移动的消耗和读取数据并进行反序列化的消耗一样,那也只需要150个核。
(未完待续)