目录
Strom的参考项目
一 Spout特性
Spout的tail特性
tail适合文本源
如果是mq(消息队列)源的话,该特性没必要
如果是消息队列,spout只需要执行消息队列的consumer的代码就可以了,相当于就是一个consumer的角色
小结
测试发现正常readLine读到Null后,就结束了。文本再怎么变也不会读取到新数据了,这里应该是Storm做了什么处理
分组策略
stream grouping用来定义一个stream
应该如何分配给Bolts
上面的多个Executors
(多线程、多并发)。
Storm里面有7种类型的stream grouping
Shuffle Grouping
: 随机分组,轮询
,平均分配。随机派发stream里面的tuple,保证每个bolt接收到的tuple数目大致相同
。Fields Grouping
:按字段分组
,比如按userid来分组,具有同样userid的tuple会被分到相同的Bolts里的一个task,而不同的userid则会被分配到不同的bolts里的task。- All Grouping:
广播发送
,对于每一个tuple,所有的bolts都会收到。 - Global Grouping:全局分组,这个tuple被分配到storm中的一个bolt的其中一个task。再具体一点就是分配给
id值最低
的那个task。 - Non Grouping:不分组,这stream grouping个分组的意思是说stream不关心到底谁会收到它的tuple。目前这种分组
和Shuffle grouping是一样的效果
。在多线程情况下不平均分配。 - Direct Grouping:直接分组,这是一种
比较特别
的分组方法,用这种分组意味着消息的发送者指定由消息接收者的哪个task
处理这个消息。只有被声明为Direct Stream的消息流可以声明这种分组方法。而且这种消息tuple必须使用emitDirect方法
来发射。消息处理者可以通过TopologyContext来获取处理它的消息的task的id
(OutputCollector.emit方法也会返回task的id)。 - Local or shuffle grouping:如果
目标bolt
有一个或者多个task在同一个工作进程
中,tuple将会被随机发送给这些tasks。否则,和普通的Shuffle Grouping行为一致。
读文件案例的问题思考
读文件的方式不可取,只有学习阶段才会用
读文件的缺陷
并发度
如下代码表示会有5个spout的实例存在,需要注意会不会重复
发数据
builder.setSpout("spout", new RandomSentenceSpout(), 5);
并发度
和实际并发数
的区别
work进程只能服务
于一个topology
并发度官方例子
可以看出默认是平均分配
的
动态设置并发度
线程安全问题
参考:Storm的进程、线程、任务数和线程安全问题研究(一篇明白)
小结
- component 中的
成员变量
在 prepare/execute(open/nextTuple) 方法中被操作,是线程安全
的(实例不会被共享) - component 中的
静态变量
在 prepare/execute(open/nextTuple) 方法中被操作,是非线程安全
的
二 案例实战-PVUV
方案一
因为shuffleGrouping轮询分配
//驱动函数中配置如下:
builder.setSpout("PVSpout", new PVSpout(), 1);
builder.setBolt("PVBolt1", new PVBolt1(), 4).shuffleGrouping("PVSpout");
在PVBolt1中输出时
System.err.println("threadid:" + Thread.currentThread().getId() + " pv:" + pv*4);
- 优点:简单、计算量小
- 缺点:稍有误差,但绝大多数场景能接受
方案二
PVSpout
package com.atguigu.storm.pv;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Map;
import org.apache.storm.spout.SpoutOutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.IRichSpout;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Values;
public class PVSpout implements IRichSpout{
private static final long serialVersionUID = 1L;
private SpoutOutputCollector collector ;
private BufferedReader reader;
@SuppressWarnings("rawtypes")
@Override
public void open(Map conf, TopologyContext context, SpoutOutputCollector collector) {
this.collector = collector;
try {
reader = new BufferedReader(new InputStreamReader(new FileInputStream("e:/website.log"),"UTF-8"));
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void close() {
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void activate() {
}
@Override
public void deactivate() {
}
private String str;
@Override
public void nextTuple() {
try {
while((str = reader.readLine()) != null){
collector.emit(new Values(str));
Thread.sleep(500);
}
} catch (Exception e) {
}
}
@Override
public void ack(Object msgId) {
}
@Override
public void fail(Object msgId) {
}
@Override
public void declareOutputFields(OutputFieldsDeclarer declarer) {
declarer.declare(new Fields("log"));
}
@Override
public Map<String, Object> getComponentConfiguration() {
return null;
}
}
创建数据处理
pvbolt1
package com.atguigu.storm.pv;
import java.util.Map;
import org.apache.storm.task.OutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.IRichBolt;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Tuple;
import org.apache.storm.tuple.Values;
public class PVBolt1 implements IRichBolt {
private static final long serialVersionUID = 1L;
private OutputCollector collector;
private long pv = 0;
@SuppressWarnings("rawtypes")
@Override
public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
this.collector = collector;
}
@Override
public void execute(Tuple input) {
// 获取传递过来的数据
String logline = input.getString(0);
// 截取出sessionid
String session_id = logline.split("\t")[1];
// 根据会话id不同统计pv次数
if (session_id != null) {
pv++;
}
// 提交
collector.emit(new Values(Thread.currentThread().getId(), pv));
System.err.println("threadid:" + Thread.currentThread().getId() + " pv:" + pv);
}
@Override
public void declareOutputFields(OutputFieldsDeclarer declarer) {
declarer.declare(new Fields("thireadID", "pv"));
}
@Override
public void cleanup() {
}
@Override
public Map<String, Object> getComponentConfiguration() {
return null;
}
}
创建PVSumBolt(只有task只有一个),所以没有线程安全问题
。怎么最后还是单线程了??
package com.atguigu.storm.pv;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.apache.storm.task.OutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.IRichBolt;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.tuple.Tuple;
public class PVSumBolt implements IRichBolt {
private static final long serialVersionUID = 1L;
private Map<Long, Long> counts = new HashMap<>();
@SuppressWarnings("rawtypes")
@Override
public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
}
@Override
public void execute(Tuple input) {
Long threadID = input.getLong(0);
Long pv = input.getLong(1);
counts.put(threadID, pv);
long word_sum = 0;
Iterator<Long> iterator = counts.values().iterator();
while (iterator.hasNext()) {
word_sum += iterator.next();
}
System.err.println("pv_all:" + word_sum);//那岂不是该线程处理的并发量还是很大
}
@Override
public void cleanup() {
}
@Override
public void declareOutputFields(OutputFieldsDeclarer declarer) {
}
@Override
public Map<String, Object> getComponentConfiguration() {
return null;
}
}
驱动类
package com.atguigu.storm.pv;
import org.apache.storm.Config;
import org.apache.storm.LocalCluster;
import org.apache.storm.StormSubmitter;
import org.apache.storm.topology.TopologyBuilder;
public class PVMain {
public static void main(String[] args) {
TopologyBuilder builder = new TopologyBuilder();
builder.setSpout("PVSpout", new PVSpout(), 1);
builder.setBolt("PVBolt1", new PVBolt1(), 4).shuffleGrouping("PVSpout");
builder.setBolt("PVSumBolt", new PVSumBolt(), 1).shuffleGrouping("PVBolt1");
Config conf = new Config();
conf.setNumWorkers(2);
if (args.length > 0) {
try {
StormSubmitter.submitTopology(args[0], conf, builder.createTopology());
} catch (Exception e) {
e.printStackTrace();
}
}else {
LocalCluster cluster = new LocalCluster();
cluster.submitTopology("pvtopology", conf, builder.createTopology());
}
}
}
三 集群启动和停止
日志查看
ssh无密码访问
因为这个功能本质是写一个远程脚本
参考:实现scp拷贝时无需输入密码