全量数据处理使用的大多是鼎鼎大名的hadoop或者hive,作为一个批处理系统,hadoop以其吞吐量大、自动容错等优点,在海量数据处理上 得到了广泛的使用。但是,hadoop不擅长实时计算。这时需要strom实时计算系统
Storm集群由一个主节点和多个工作节点组成。主节点运行了一个名为“Nimbus”的守护进程,用于分配代码、布置任务及故障检测。每个工作节点都运行了一个名为“Supervisor”的守护进程,用于监听工作,开始并终止工作进程。Nimbus和Supervisor都能快速失败,而且是无状态的,这样一来它们就变得十分健壮,两者的协调工作是由Zookeeper来完成的。ZooKeeper用于管理集群中的不同组件,ZeroMQ是内部消息系统,JZMQ是ZeroMQMQ的Java Binding。有个名为storm-deploy的子项目,可以在AWS上一键部署Storm集群.
Nimbus:负责资源分配和任务调度。
Supervisor:负责接受nimbus分配的任务,启动和停止属于自己管理的worker进程。
Worker:运行具体处理组件逻辑的进程。
Task:worker中每一个spout/bolt的线程称为一个task. 在storm0.8之后,task不再与物理线程对应,spout/bolt的线程称为executor。 一个线程可以承载两个task(即为对象)
Topology:storm中运行的一个实时应用程序,因为各个组件间的消息流动形成逻辑上的一个拓扑结构。
Spout:在一个topology中产生源数据流的组件。通常情况下spout会从外部数据源中读取数据,然后转换为topology内部的源 数据。Spout是一个主动的角色,其接
口中有个nextTuple()函数,storm框架会不停地调用此函数,用户只要在其中生成源数据即可。Strom数据的入口与
Bolt:在一个topology中接受数据然后执行处理的组件。Bolt可以执行过滤、函数操作、合并、写数据库等任何操作。Bolt是一个被 动的角色,其接口中有个execute(Tupleinput)函数,在接受到消息后会调用此函数,用户可以在其中执行自己想要的操作。Strom具体的业务逻辑处理
Tuple:一次消息传递的基本单元。本来应该是一个keyvalue的map,但是由于各个组件间传递的tuple的字段名称已经事先定义好,所以tuple中只要按序填入各个value就行了,所以就是一个value list. spout与各个Bolt之间数据的传输格式
Stream:源源不断传递的tuple就组成了stream。
常用类
BaseRichSpout (消息生产者) 可从外部文件飞关系数据读取数据
BaseBasicBolt (消息处理者) 处理数据
TopologyBuilder (拓扑的构建器)
Values (将数据存放到values ,发送到下个组件)
Tuple(发送的数据被封装到Tuple,可以通tuple接收上个组件发送的消息)
Config (配置)
StormSubmitter / LocalCluster (拓扑提交器)
在storm里面worker是具体执行工作的进程,Supervisor会监听分配给它那台机器的工作,根据需要启动/关闭工作进程,这个工作进程就是worker。即为具体干活的。
Task为一个bolt或者spout,任务。
整个过程:
nimbus分配任务后,会把任务写到zk里面,Supervisor(工作节点)到zk中领任务,然后会启动一定数量的worker,在work中会有一定数量的task(bolt或者是spout对象,在work中可以设置多个task即多个spout或者多个bolt来做同样的工作,提高工作效率),来处理相应的业务逻辑。
示例代码:
TopologyBuilder builder = new TopologyBuilder(); //定义spout并且制定出业务处理方法RandomWordSpout,和该spout的id //2表示该support的并发数,不加.setNumTask线程数和task的数量是一样的即为多少 线程就有多少个对象 builder.setSpout("random", new RandomWordSpout(), 2); //定义一个bolt,对spout接受到的数据做第一步处理,制定其id和业务实现以及并发数,并制定该bolt数据输入口的消息分发策略是按照随机分发。 //4表示启用4个线程来执行这个bolt,每个线程执行的东西一样,即开4个线程来承载bolt对象从spout里面读取数据 //8表示实例化出8个bolt对象来读取spout传送过来的数据,这样就是一个线程处理2个bolt对象,即一个线程执行一个对象一会,然后在执行另一个一会 builder.setBolt("transfer", new TransferBolt(),4).shuffleGrouping("random").setNumTasks(8); //4表示开启4个线程来读取上一个bolt的数据,没有配置setNumTask默认一个线程承载一个对象,new Fields("word")表示读取上一个bolt的value字段 builder.setBolt("writer", new WriterBolt(), 4).fieldsGrouping("transfer", new Fields("word")).setNumTasks(8); Config conf = new Config(); //设置处理这个任务的worker数量。 conf.setNumWorkers(4); //acker消息跟踪,也为一个task,0表示不启用,1表示启用 conf.setNumAckers(0); conf.setDebug(false); StormSubmitter.submitTopology("comp-test-1", conf, builder.createTopology());
在storm中存在多个对象的原理是有new出来一个对象序列化到硬盘中,假设后面是setnumTask(8)的话反序列化出8个对象。
有7中消息分发,即为各个组件传递消息的规则策略,常用以下3中
Shuffle Grouping:随机分组,随机派发stream里面的tuple,保证每个bolt接收到的tuple数目相同。
Fields Grouping:按字段分组,比如按userid来分组,具有同样userid的tuple会被分到相同的Bolts,而不同的userid则会被分配到不同的Bolts。一个bolts可能会有多种userid
All Grouping:广播发送,对于每一个tuple,所有的Bolts都会收到。
builder.setBolt("transfer", new TransferBolt(),4).shuffleGrouping("random");
builder.setBolt("writer", new WriterBolt(),4).fieldsGrouping("transfer", new Fields("word"));
shuffleGrouping 表示是spout发送给TransferBolt这一组件时是以随机的方式分发
fieldsGrouping 表示TransferBolt是按照word字段名称分发给WriterBolt这一组件。相同字段的数据会跑到相同bolt里面
1、Spout组件:
declareOutputFields()(调用一次)提交集群之前
发送数据前声明发送几个字段,发送字段的名称是什么,其中declareOutputFields()方法可以接受可变参数的数据。
open() (调用一次)
提交集群之后第一个被执行,做一些配置,在主方法中Config传入 map类型的配置
activate() (调用一次)接着执行该方法,激活
把该soupt激活
nextTuple() (循环调用 )
获取消息并 发送给下一组组件的方法
deactivate() (手动调用)
不激活
2、Bolt方法
declareOutputFields() (调用一次)
发送数据前生命发送几个字段,发送字段的名称是什么,其中declareOutputFields()方法可以接受可变参数的数据。提交集群之前
prepare() (调用一次)
做一些配置
execute()
具体的运行
SpoutOutputCollector 作为消息发送的工具类。Collectot.emit(new values(Obj))发送出去.
Values是集成一个list结构的数据结构,
如:在declareOutputFields方法中定义列名d
declarer.declare(new Fields("id",”name”,”str”));
在消息发送时可以按照顺序匹配上面定义的列名
Collectot.emit(new values(1,“tom”,”1”));
可靠传输实现IRichSpout这个接口,这个接口比basicSpout多了两个方法
public void ack(Object msgId) {} //msgId消息id @Override public void fail(Object msgId) {} 其中ack发送成功会被调用,fail发送失败会被调用,这两个方法可以手动的处理 但有时候会重复可以使用事务的方式来处理。 public void execute(Tuple input) { String word = input.getString(0); if (count == 5) { collector.fail(input); } else { try { writer.write(word); writer.write("\r\n"); writer.flush(); } catch (IOException e) { e.printStackTrace(); } //称之为锚定 collector.emit(input, new Values(word)); collector.ack(input); } count++; }
Strom在实际应用中时spout不是直接从数据源中读取数据,而是从消息队列中拿到数据(kafka或者是MetaQ中),拿到数据后就开始进行处理,在对数据库操作或者做持久化存储时由于避免多个线程同时对数据库进行操作出现问题采用zk的分布式锁来处理(zk分布式锁第三方操作框架curator)。