jstorm 是阿里巴巴开源的基于storm采用Java重写的一套分布式实时流计算框架,使用简单,特点如下:
1,开发非常迅速: 接口简单,容易上手,只要遵守Topology,Spout,Bolt的编程规范即可开发出一个扩展性极好的应用,底层rpc,worker之间冗余,数据分流之类的动作完全不用考虑。
2,扩展性极好:当一级处理单元速度,直接配置一下并发数,即可线性扩展性能
3,健壮:当worker失效或机器出现故障时, 自动分配新的worker替换失效worker
4,数据准确性: 可以采用Acker机制,保证数据不丢失。 如果对精度有更多一步要求,采用事务机制,保证数据准确。
为什么要选择jstorm,而不采用twitter的storm呢?jstorm对比storm有如下优点:
1,Nimbus 实现HA
2,彻底解决Storm雪崩问题:底层RPC采用netty + disruptor保证发送速度和接受速度是匹配的
3,新增supervisor、Supervisor shutdown时、提交新任务,worker数不够时,均不自动触发任务rebalance
4,新topology不影响现有任务,新任务无需去抢占老任务的cpu,memory,disk和net
5,减少对ZK的访问量:去掉大量无用的watch;task的心跳时间延长一倍;Task心跳检测无需全ZK扫描减少对ZK的访问量:去掉大量无用的watch;task的心跳时间延长一倍;Task心跳检测无需全ZK扫描
6,Worker 内部全流水线模式:Spout nextTuple和ack/fail运行在不同线程
7,性能:采用ZeroMq, 比storm快30%;采用netty时, 和storm快10%,并且稳定非常多
下面写一个 简单的demo,完成jstorm的初体验(上面是目录结构)。
这个小demo 包含4个类:
具体的class 源码在这里贴出来:
com.muhao.demo.WordSpout 实现:
package com.muhao.demo;
import java.util.Map;
import org.apache.commons.lang.math.RandomUtils;
import backtype.storm.spout.SpoutOutputCollector;
import backtype.storm.task.TopologyContext;
import backtype.storm.topology.IRichSpout;
import backtype.storm.topology.OutputFieldsDeclarer;
import backtype.storm.tuple.Fields;
import backtype.storm.tuple.Values;
public class WordSpout implements IRichSpout {
// 一定要 生成 一个 serialVersionUID,因为这些class 都是要经过序列化的
private static final long serialVersionUID = -4515102038086645770L;
private String[] strs= {"one","two","three","four","five","six"};
SpoutOutputCollector collector;
public WordSpout() {
super();
System.out.println("WordSpout()****************************");
}
/**
* 定义发射的字段类型,是第一个要是执行的方法。
* @param declarer
*/
@Override
public void declareOutputFields(OutputFieldsDeclarer declarer) {
declarer.declare(new Fields("word"));
}
/**
* 打开 stream 流资源,只会执行一次
* @param conf
* @param context
* @param collector
*/
@Override
public void open(Map conf, TopologyContext context, SpoutOutputCollector collector) {
System.out.println("*****************open(Map conf, TopologyContext context, SpoutOutputCollector collector)");
this.collector=collector;
}
/**
* 循环执行,向外发送 Tuple
*/
@Override
public void nextTuple() {
int index=RandomUtils.nextInt(6);
collector.emit(new Values(strs[index]));
System.out.println("***************nextTuple() : "+strs[index]);
}
@Override
public void close() {
}
@Override
public void activate() {
}
@Override
public void deactivate() {
}
@Override
public void ack(Object msgId) {
}
@Override
public void fail(Object msgId) {
}
@Override
public Map getComponentConfiguration() {
return null;
}
}
com.muhao.demo.CountBolt 实现:
package com.muhao.demo;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.log4j.Logger;
import backtype.storm.task.OutputCollector;
import backtype.storm.task.TopologyContext;
import backtype.storm.topology.IRichBolt;
import backtype.storm.topology.OutputFieldsDeclarer;
import backtype.storm.tuple.Tuple;
public class CountBolt implements IRichBolt{
// 一定要 生成 一个 serialVersionUID,因为这些class 都是要经过序列化的
private static final long serialVersionUID = 8740926838799779884L;
Map map=new HashMap<>();
private FileWriter writer;
public CountBolt() {
System.out.println("CountBolt:**********************************");
}
/**
* prepare 在这里仅仅是启动了一个文件写的定时线程,每2秒将结果写到文件中,并清空map.
*/
@Override
public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
try {
//以文件追加的方式打开文件
writer = new FileWriter("e://my_log.txt",true);
} catch (IOException e) {
e.printStackTrace();
}
//开启定时线程
pool.scheduleAtFixedRate(()->{
try {
writer.write("\r\n");
writer.write("***************************************");
System.out.println("***************************************");
for(Entry entry:map.entrySet()) {
writer.write(entry.getKey()+" : "+entry.getValue());
writer.write("\r\n");
System.out.println(entry.getKey()+" : "+entry.getValue());
}
writer.flush();
map.clear();
} catch (IOException e) {
e.printStackTrace();
}
}, 2000L, 2000L, TimeUnit.MILLISECONDS);
}
/**
* 收到 spout 发送来的 Word 进行统计
*/
@Override
public void execute(Tuple input) {
System.out.println("**********execute(Tuple input)");
String word=input.getString(0);
if(map.get(word)==null) {
map.put(word, 1);
}else {
map.put(word, map.get(word)+1);
}
}
/**
* Topology 被 shutdown时会被触发的动作,我们可以用来做一些清理工作
*/
@Override
public void cleanup() {
System.out.println("*******************public void cleanup()");
for(Entry entry:map.entrySet()) {
System.out.println(entry.getKey()+" : "+entry.getValue());
}
map.clear();
}
@Override
public void declareOutputFields(OutputFieldsDeclarer declarer) {
// TODO Auto-generated method stub
}
@Override
public Map getComponentConfiguration() {
// TODO Auto-generated method stub
return null;
}
}
com.muhao.demo.LocalPology 实现:
package com.muhao.demo;
import backtype.storm.Config;
import backtype.storm.LocalCluster;
import backtype.storm.topology.TopologyBuilder;
public class LocalPology {
public static void main(String[] args) throws InterruptedException {
TopologyBuilder builder = new TopologyBuilder();
builder.setSpout("out-word", new WordSpout(),1);
builder.setBolt("word-count", new CountBolt(),1).shuffleGrouping("out-word");
//本地模式:本地提交
LocalCluster cluster = new LocalCluster();
Config conf = new Config();
conf.setNumWorkers(2);
conf.setDebug(true);
conf.put(Config.TOPOLOGY_MAX_TASK_PARALLELISM, 1);
cluster.submitTopology("firstTopo", conf, builder.createTopology());
//一定要等待足够的时间,否则程序没来得及运行就已经结束了,程序启动需要消耗时间
Thread.sleep(30000);
cluster.killTopology("firstTopo");
cluster.shutdown();
}
}
com.muhao.demo.RemoteTopology 实现:
package com.muhao.demo;
import java.util.HashMap;
import java.util.Map;
import backtype.storm.Config;
import backtype.storm.StormSubmitter;
import backtype.storm.generated.AlreadyAliveException;
import backtype.storm.generated.AuthorizationException;
import backtype.storm.generated.InvalidTopologyException;
import backtype.storm.topology.BoltDeclarer;
import backtype.storm.topology.SpoutDeclarer;
import backtype.storm.topology.TopologyBuilder;
/**
* 抄自 http://www.jstorm.io/QuickStart_cn/Example.html
*/
public class RemoteTopology {
public static void main(String[] args) throws AlreadyAliveException, InvalidTopologyException, AuthorizationException {
Map conf = new HashMap();
//topology所有自定义的配置均放入这个Map
TopologyBuilder builder = new TopologyBuilder();
//创建topology的生成器
int spoutParal = 1;
//获取spout的并发设置
SpoutDeclarer spout = builder.setSpout("out-word",new WordSpout(), spoutParal);
//创建Spout, 其中new SequenceSpout() 为真正spout对象,SequenceTopologyDef.SEQUENCE_SPOUT_NAME 为spout的名字,注意名字中不要含有空格
int boltParal = 1;
//获取bolt的并发设置
BoltDeclarer totalBolt = builder.setBolt("word-count", new CountBolt(),boltParal).shuffleGrouping("out-word");
//创建bolt, SequenceTopologyDef.TOTAL_BOLT_NAME 为bolt名字,TotalCount 为bolt对象,boltParal为bolt并发数,
//shuffleGrouping(SequenceTopologyDef.SEQUENCE_SPOUT_NAME),
//表示接收SequenceTopologyDef.SEQUENCE_SPOUT_NAME的数据,并且以shuffle方式,
//即每个spout随机轮询发送tuple到下一级bolt中
int ackerParal = 1;
Config.setNumAckers(conf, ackerParal);
//设置表示acker的并发数
int workerNum = 2;
conf.put(Config.TOPOLOGY_WORKERS, workerNum);
//表示整个topology将使用几个worker
conf.put(Config.STORM_CLUSTER_MODE, "distributed");
//设置topolog模式为分布式,这样topology就可以放到JStorm集群上运行
StormSubmitter.submitTopology("streamName", conf,builder.createTopology());
//提交topology
}
}
pom.xml 依赖:
4.0.0
com.muhao.test
z_jstorm_demo
0.0.1-SNAPSHOT
UTF-8
2.2.1
storm-0.9.2-incubating
com.alibaba.jstorm
jstorm-core
${jstorm.version}
provided
junit
junit
4.10
test
commons-collections
commons-collections
3.2.1
org.yaml
snakeyaml
1.11
commons-io
commons-io
2.4
com.google.guava
guava
16.0.1
commons-lang
commons-lang
2.5
io.dropwizard.metrics
metrics-core
3.1.2
org.apache.maven.plugins
maven-surefire-plugin
pertest
-Xms1024m -Xmx4096m
org.apache.maven.plugins
maven-compiler-plugin
1.8
${project.build.sourceEncoding}
org.apache.maven.plugins
maven-shade-plugin
1.7.1
package
shade
META-INF/spring.handlers
META-INF/spring.schemas
com.muhao.demo.RemoteTopology
写入的文件内容:
***************************************
***************************************
***************************************
***************************************
***************************************
***************************************
***************************************
***************************************
***************************************
***************************************
***************************************
***************************************
***************************************six : 84
four : 64
one : 91
three : 64
two : 84
five : 58
***************************************six : 11973
four : 12195
one : 12220
two : 11924
five : 11975
three : 11966
***************************************six : 11702
four : 11670
one : 11727
three : 11595
five : 11573
two : 11596
***************************************six : 11126
four : 11083
one : 11116
three : 10981
five : 11014
two : 10849
***************************************six : 10798
four : 10803
one : 10853
five : 10729
two : 10604
three : 10682
***************************************
***************************************
***************************************
***************************************
可见 在开始的 20 秒左右的时间程序没有启动,没有 运行 spout 和 bolt 程序,所以设置 Sleep 时间要长一些。
由于没有手动 shutdown,所以只有通过文件查看结果,和面结果一样,就不显示了。
jstorm 说的是原先在storm运行的代码不用动一行就可以直接运行,在原来的基础上在pom.xml 中将jstorm的依赖改成对storm的依赖,但是还需要在具体的类中更改引用的class:要将backtype.storm.XXX 改成 org.apache.storm.XXX 。 因为 jstorm : backtype.storm ,而 storm : org.apache.storm 。并且由于jstorm写的 类 和 storm 写的 类都是相同的,比如 :jstorm 的 backtype.storm.tuple.Tuple; 和 storm 的 org.apache.storm.tuple.Tuple 。
具体的代码在我的博客中也上传了。