设计思想
在Storm中也有对流(Stream)的抽象,流是一个不间断的、无界的连续Tuple。Tuple是包含一个或多个键值对的集合。
Storm认为每个流都有一个Stream源,也就是原始元组的源头,所以它将这个源头抽象为Spout(水龙头),Spout可能连接Twitter API并不断发出推文(Tweet),也可能从某个队列中不断读取队列元素并装配为Tuple发射。
有了源头即Spout也就是有了流,同样的思想,Twitter将流的中间状态转换抽象为Bolt,Bolt可以消费任意数量的输入流,只要将流方向导向该Bolt,同时它也可以发送新的流给其他Bolt使用,这样一来,只要打开特定的Spout(管口),再将Spout中流出的Tuple导向特定的Bolt,由Bolt处理导入的流后再导向其他Bolt或者目的地。
假设Spout就是一个一个的水龙头,并且每个水龙头里流出的水是不同的,想获得哪种水就拧开哪个水龙头,然后使用管道将水龙头的水导向到一个水处理器(Bolt),水处理器处理后使用管道导向另一个处理器或者存入容器中。如下图所以Spout、Tuple和Bolt之间的关系和流程。
图解1
图解2
Java工程建立一个Storm
Spout--负责发送数据(数据源的生产者)
Bolt--建立节点处理源流出来的数据(运算)
Topology--提交拓扑到Storm集群执行(拓扑)
数据源--NumberSpout.java
import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.Map;
import java.util.Random;
import backtype.storm.spout.SpoutOutputCollector;
import backtype.storm.task.TopologyContext;
import backtype.storm.topology.OutputFieldsDeclarer;
import backtype.storm.topology.base.BaseRichSpout;
import backtype.storm.tuple.Fields;
import backtype.storm.tuple.Values;
//开发Spout组件的方式---extends
public class NumberSpout extends BaseRichSpout {
private SpoutOutputCollector collector;
@Override
public void nextTuple() {//此方法用于产生数据源,而且此方法会不断地被调用,直到整个拓扑被终止
int number=new Random().nextInt(100);//每调用一次就会产生一个随机数
SimpleDateFormat time = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss");
String date = time.format(new java.util.Date());
Values value=new Values(number,date);//把数据封装到Tuple的value对象中,value对象的参数是可变参数(个数不限,类型不限--object)
collector.emit(value);//通过发射器将tuple发送给下游
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
//主键的初始化方法,且只会被调用一次,一般做资源的初始化
public void open(Map arg0, TopologyContext arg1, SpoutOutputCollector collector) {
this.collector=collector;
}
@Override
public void declareOutputFields(OutputFieldsDeclarer declarer) {//声明key
//声明tuple的key字段--tuple和key一一对应,且数量保持一致
declarer.declare(new Fields("number","date"));
}
}
数据处--PrintBolt.java
import java.sql.Date;
import java.util.Map;
import backtype.storm.task.OutputCollector;
import backtype.storm.task.TopologyContext;
import backtype.storm.topology.OutputFieldsDeclarer;
import backtype.storm.topology.base.BaseRichBolt;
import backtype.storm.tuple.Tuple;
public class PrintBolt extends BaseRichBolt {
private OutputCollector collector;//创建bolt组建的tuple发射器,用于向下游发射tuple
@Override
public void execute(Tuple input) {
int number=input.getIntegerByField("number");//元祖是一种key-v结构,通过key来去value值
String date=input.getStringByField("date");
System.out.println(number+"...."+date);
System.out.println(System.currentTimeMillis());//添加一个时间戳
}
@Override
public void prepare(Map arg0, TopologyContext arg1, OutputCollector collector) {
//此方法是bolt组件的初始化方法--最常用的是初始化发生器
this.collector=collector;
}
@Override
public void declareOutputFields(OutputFieldsDeclarer arg0) {
// TODO Auto-generated method stub
}
}
提交拓扑--NumberTopology.java
import backtype.storm.Config;
import backtype.storm.LocalCluster;
import backtype.storm.generated.StormTopology;
import backtype.storm.topology.TopologyBuilder;
import numberberrouter.Number;
public class NumberTopology {
public static void main(String[] args) {
Config conf=new Config();
Number spout=new Number();
PrintBolt printBolt=new PrintBolt();
//创建拓扑构建者,用于绑定各组件的上下游关系
TopologyBuilder builder=new TopologyBuilder();
//绑定数据源,组件标识id 组件对象
builder.setSpout("number_spout", spout);
//绑定Bolt并制定上游组件是数据源
builder.setBolt("print_bolt", printBolt).shuffleGrouping("number_spout");
//生成拓扑对象--即一个job任务
StormTopology topology=builder.createTopology();
//创建本地测试对象
LocalCluster cluster=new LocalCluster();
//提交拓扑运行
cluster.submitTopology("number_topolopy", conf, topology);
}
}
运行结果:
82....2019-01-03 19:57:24
1546516644994
4....2019-01-03 19:57:27
1546516647994