在谈Strom内部消息之前,先大致了解一下storm的结构。整体氛围3个组件,ui, supervisor, nimus. nimus是主控制组件,用来分发代码,监控作业等等,supervisor是具体工作的节点,ui就是一个前端展示,提供了相应JOB信息。
以上组件属于物理上的,能够看得到,摸得到,逻辑概念又包含,topology spout, bolt , worker, executor, task. spout ,bolt不说了,一个获取消息再发消息,一个处理消息,再存入结果。
我们更加关注的是topology, worker, executor, task , topology表示整个拓扑,可以设置一些参数,比如max_pending, 主要的工作全部在worker, worker是一个具体的JVM进程,我们假设整个topology只有1个worker, 那么再分配executor, 它是在worker里分配的一个线程,用来处理TASK的。 默认一个exector对应一个task,
conf.setNumWorkers(3); 设置worker数量
builder.setBolt("Handler2001Bolt", new HandlerBolt(fieldNameList), 3) 设置并发数量
setNumTasks(3) 设置TASK数量
关系清楚了吗? worker里分配executor,然后去执行TASK, 就是这么简单的关系。 如果你只有一个worker, 内存分配了1个G(配置:worker.childopts: "-Xmx1024m") ,
然后你有6个TASK,默认如果你不是设置exector,那么exector对应TASK也是6个,然后一个worker跑了6个exector去跑对应的TASK。
假设你的task和exector设置数量不同,很显然,storm会为你分配,假设我设置并发数量为3,但是task设置了6,那么1个exector就跑2个task.
在生产环境,到底1个worker跑几个executor ? 以及TASK又到底分几个合理呢?这个在storm性能方面做具体介绍。下面我们先说说storm内部的一些发送消息及接受消息的原理。
Storm从发送消息spout 到 接收消息bolt , 怎么样才认为消息已经发送成功了. Storm把spout, bolt 形成一个tree用来跟踪, spout发送消息后,会给每个message分配一个ID,这个ID是唯一的,用于跟踪消息,消息传送到bolt之后,然后处理,bolt通过ack,然后告诉spout,这条消息已经处理完成, spout然后把消息pop., 整个流程就结束了。
根据官网的介绍,storm获取一条消息是先open, 然后把消息置于pending状态,随后发送给bolt, 一旦spout收到bolt的ack,那么消息将会pop, 所谓pop就拿走,已经不需要了。
在这期间如果发生比如连接断开,或者出了网络问题等等,那么消息会从pending状态返回初始状态,等待再次送。
要保证上面的流程,有几个必要条件:
1. 必须手设置message id, 这个唯一ID是用来跟踪消息的
2. 必须设置TOPOLOGY_ACKERS大于0 ( Storm defaults TOPOLOGY_ACKERS to one task per worker. 默认是够了,如果你发现ack慢,可以考虑增加这个参数)
3. 必须anchored tuples
上面几个条件随便哪一个没有满足,那么storm就不会跟踪消息,也就是消息如果失败了,不会重发,这个是否和kafka的 at most once类似? 如果你对消息的要求不高,这个性能肯定是最好的,不需要那么多处理环节。
另外要解释一下什么是anchored tuples, 这个大家也可以去官网看,大致意思就是bolt在emit消息的同时必须把上一个tuple放入emit中,看下面代码:
public void execute(Tuple tuple) {
String sentence = tuple.getString(0);
for(String word: sentence.split(" ")) {
_collector.emit(tuple, new Values(word));
}
_collector.ack(tuple);
}
我们发送新的消息new values(word), 但是在emit还把原始的tuple也带上了,这个就是anchored tuple, 用于构建一个连接,来跟踪消息。
再来思考一下,如果我所有环节条件都满足,但是我就是不发ack给spout, 会怎么样? 最后的结果就是OOM, 因为所有的跟踪处理都会在内存里.
Storm有2种接口,basic和rich , richbolt表示我们所有的细节需要自己处理,比如分配message id , anchored tuples, 必须显示发送命令才可以。如果我们使用了basic接口,那么不需要去分配一个message id ,系统会自动帮我们分配。
整个过程其实也不是很复杂,但是实际上还是有些疑问,这些官网上并没有介绍很清楚,多级bolt 怎么保证消息重新发送处理? A->B->C->D, 假如是这样一个tree, B已经处理完成,并发送了新的消息给C,然后B 发送ACK消息给A,但是C出错了,按照理论,C需要重新处理这个消息,但是这个消息是从A发送出来的,但是A已经收到了B的ack, 消息已经pop了, 这个时候A是在怎么处理的? 我看了官网,好像没做做具体介绍。