Storm计算模型——java(模拟实现经典案例)

一、storm计算模型

1、Topology – DAG有向无环图的实现

  • 对于Storm实时计算逻辑的封装,即,由一系列通过数据流相互关联的Spout、Bolt所组成的拓扑结构
    在这里插入图片描述
  • 生命周期:此拓扑只要启动就会一直在集群中运行,直到手动将其kill,否则不会终止(区别于MapReduce当中的Job,MR当中的Job在计算执行完成就会终止)

2、Tuple – 元组

  • Stream中最小数据组成单元

3、Stream – 数据流

  • 从Spout中源源不断传递数据给Bolt、以及上一个Bolt传递数据给下一个Bolt,所形成的这些数据通道即叫做Stream,
  • Stream声明时需给其指定一个Id(默认为Default)
    实际开发场景中,多使用单一数据流,此时不需要单独指定StreamId

4、Spout – 数据源

  • 拓扑中数据流的来源。一般会从指定外部的数据源读取元组(Tuple)发送到拓扑(Topology)中
  • 一个Spout可以发送多个数据流(Stream)
            可先通过OutputFieldsDeclarer中的declare方法声明定义的不同数据流,发送数据时通过SpoutOutputCollector中的emit方法指定数据流Id(streamId)参数将数据发送出去;
            Spout中最核心的方法是nextTuple,该方法会被Storm线程不断调用、主动从数据源拉取数据,再通过emit方法将数据生成元组(Tuple)发送给之后的Bolt计算。
5、Bolt – 数据流处理组件
  • 拓扑中数据处理均有Bolt完成。对于简单的任务或者数据流转换,单个Bolt可以简单实现;更加复杂场景往往需要多个Bolt分多个步骤完成
  • 一个Bolt可以发送多个数据流(Stream)
            可先通过OutputFieldsDeclarer中的declare方法声明定义的不同数据流,发送数据时通过SpoutOutputCollector中的emit方法指定数据流Id(streamId)参数将数据发送出去;
            Bolt中最核心的方法是execute方法,该方法负责接收到一个元组(Tuple)数据、真正实现核心的业务逻辑。

二、java代码实现wordcount案例

1、主程序MainClass类

package storm.wordcount;

import backtype.storm.Config;
import backtype.storm.LocalCluster;
import backtype.storm.topology.TopologyBuilder;
import backtype.storm.tuple.Fields;

public class MainClass {
	
	public static void main(String[] args) throws InterruptedException {
		
		//拓扑构造器
		TopologyBuilder builder = new TopologyBuilder();
		
		//给拓扑设置水龙头
		builder.setSpout("sentence_spout", new SentenceSpout());
		//给拓扑设置闪电
		//以随机分组的形式接收sentence_spout发送过来的元组
		builder.setBolt("split_bolt", new SplitBolt(), 10)
			.shuffleGrouping("sentence_spout").setNumTasks(20);
		// 相同word值的元组分发给同一个bolt来处理,将word的值hash取模搞定
		builder.setBolt("count_bolt", new CountBolt())
			.fieldsGrouping("split_bolt", new Fields("word"));
		builder.setBolt("report_bolt", new ReportBolt())
			.globalGrouping("count_bolt");
		
		
		// 本地集群,模拟storm集群
		LocalCluster cluster = new LocalCluster();
		// 用于配置信息
		Config config = new Config();
		//启用debug模式
		config.setDebug(true);
		
		//向本地集群提交拓扑
		cluster.submitTopology("wordcount", config, builder.createTopology());
		
		Thread.sleep(5000);
		//关闭本地集群
		cluster.shutdown();
		
	}
	
}

2、SentenceSpout类(给拓扑设置水龙头)

package storm.wordcount;

import java.util.Map;

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;

public class SentenceSpout extends BaseRichSpout {
	
	private SpoutOutputCollector collector;
	
	private String[] sentences = {
			"The logic for a realtime application is packaged into a Storm topology",
			"The stream is the core abstraction in Storm",
			"A spout is a source of streams in a topology",
			"All processing in topologies is done in bolts",
			"for each bolt which streams it should receive as input",
			"every spout tuple will be fully processed by the topology",
			"Each spout or bolt executes as many tasks across the cluster",
			"Topologies execute across one or more worker processes"
	};
	
	private int index = 0;
	
	@Override
	public void open(Map conf, TopologyContext context, SpoutOutputCollector collector) {
		// TODO Auto-generated method stub
		//当集群初始化该水龙头之后,调用该方法
		this.collector = collector;
	}

	@Override
	public void nextTuple() {
		//每次调用该方法,就从数据源读取一个句子,然后发射出去
		
		//发送当前元组
//		this.collector.emit(tuple);
		//发射指定的元组,同时给该元组设置一个消息ID,当该元组在下个bolt中处理失败,则该spout重发
//		this.collector.emit(tuple, messageId);
		// 可以指定使用哪个流来发射该元组,streamId就是在declareOutputFields声明的流名称
//		this.collector.emit(streamId, tuple);
//		this.collector.emit(streamId, tuple, messageId);
		//将当前元祖发送给哪个任务,可以通过taskId来指定,直接分组
//		this.collector.emitDirect(taskId, tuple);
		//还可以保证消息处理的完整性 messageId
//		this.collector.emitDirect(taskId, tuple, messageId);
		//指定流来发送当前元组,直接分组策略
//		this.collector.emitDirect(taskId, streamId, tuple);
		//可以保证消息完整性 messageId
//		this.collector.emitDirect(taskId, streamId, tuple, messageId);
		
		this.collector.emit(new Values(sentences[index % sentences.length]));
		index++;
		
		try {
			// 模拟流的均匀流进来
			Thread.sleep(10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void declareOutputFields(OutputFieldsDeclarer declarer) {
		//声明一个流,使用的是默认流Default,声明元组的结构
//		declarer.declare(fields);
		// 声明一个流,可以声明为直接流,同时声明元组的结构,使用的是默认的流default
//		declarer.declare(direct, fields);
		//声明一个流,可以设置流的名称,声明元组的结构
//		declarer.declareStream(streamId, fields);
		// 声明一个流,可以声明为直接流,同时声明元组的结构,还可以设置流的名称
//		declarer.declareStream(streamId, direct, fields);
		// 声明元组的结构,字段名称为sentence,下个闪电bolt可以通过该名称获取元组的内容
		declarer.declare(new Fields("sentence"));
		
	}

}

3、随机分组SplitBolt类(以随机分组的形式接收sentence_spout发送过来的元组)

package storm.wordcount;

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.Fields;
import backtype.storm.tuple.Tuple;
import backtype.storm.tuple.Values;

public class SplitBolt extends BaseRichBolt {
	
	private OutputCollector collector;
	
	@Override
	public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
		// 该方法当work初始化好该闪电之后调用
		// 获取collector用于发射元组
		this.collector = collector;
	}

	@Override
	public void execute(Tuple input) {
		//获取上游发送过来的元组,从该元组中根据字段名称获取值
		String sentence = input.getStringByField("sentence");
		//主要的业务逻辑,拆词
		String[] words = sentence.split(" ");
		
		for (String word : words) {
			//发送二元组
			this.collector.emit(new Values(word, 1));
		}
		
	}

	@Override
	public void declareOutputFields(OutputFieldsDeclarer declarer) {
		//声明二元组,第一个是单词,第二个是单词的计数,此处都是1
		declarer.declare(new Fields("word", "count"));
	}

}

4、CountBolt类(相同word值的元组分发给同一个bolt来处理)

package storm.wordcount;

import java.util.HashMap;
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.Fields;
import backtype.storm.tuple.Tuple;
import backtype.storm.tuple.Values;

public class CountBolt extends BaseRichBolt {
	
	private OutputCollector collector;
	private Map<String, Long> counts;
	
	@Override
	public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
		// TODO Auto-generated method stub
		this.collector = collector;
		counts = new HashMap<>();
	}

	@Override
	public void execute(Tuple input) {
		
		String word = input.getStringByField("word");
		Integer count = input.getIntegerByField("count");
		
		if (counts.get(word) == null) {
			counts.put(word, 0L);
		}
		
		Long sum = counts.get(word) + count;
		
		counts.put(word, sum);
		
		this.collector.emit(new Values(word, sum));
	}

	@Override
	public void declareOutputFields(OutputFieldsDeclarer declarer) {
		declarer.declare(new Fields("word", "sum"));
	}
}

5、ReportBolt(类)获取输出结果

package storm.wordcount;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

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 ReportBolt extends BaseRichBolt {
	
	private Map<String, Long> counts;
	
	@Override
	public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
		// TODO Auto-generated method stub
		counts = new HashMap<>();
	}

	@Override
	public void execute(Tuple input) {
		// TODO Auto-generated method stub
		String word = input.getStringByField("word");
		Long sum = input.getLongByField("sum");
		
		counts.put(word, sum);
	}

	@Override
	public void declareOutputFields(OutputFieldsDeclarer declarer) {
		// TODO Auto-generated method stub
		
	}
	
	@Override
	public void cleanup() {
		System.err.println("============================================");
		for (Entry<String, Long> entry : counts.entrySet()) {
			System.err.println(entry.getKey() + "  ---  " + entry.getValue());
		}
		System.err.println("============================================");
	}
}

6、jar包

一、基于nginx+lua完成商品详情页访问流量实时上报kafka的开发 ==================================== 在nginx这一层,接收到访问请求的时候,就把请求的流量上报发送给kafka 这样的话,storm才能去消费kafka中的实时的访问日志,然后去进行缓存热数据的统计 用得技术方案非常简单,从lua脚本直接创建一个kafka producer,发送数据到kafka ``` wget https://github.com/doujiang24/lua-resty-kafka/archive/master.zip yum install -y unzip unzip lua-resty-kafka-master.zip cp -rf /usr/local/lua-resty-kafka-master/lib/resty /usr/hello/lualib nginx -s reload local cjson = require("cjson") local producer = require("resty.kafka.producer") local broker_list = { { host = "192.168.31.187", port = 9092 }, { host = "192.168.31.19", port = 9092 }, { host = "192.168.31.227", port = 9092 } } local log_json = {} log_json["headers"] = ngx.req.get_headers() log_json["uri_args"] = ngx.req.get_uri_args() log_json["body"] = ngx.req.read_body() log_json["http_version"] = ngx.req.http_version() log_json["method"] =ngx.req.get_method() log_json["raw_reader"] = ngx.req.raw_header() log_json["body_data"] = ngx.req.get_body_data() local message = cjson.encode(log_json); local productId = ngx.req.get_uri_args()["productId"] local async_producer = producer:new(broker_list, { producer_type = "async" }) local ok, err = async_producer:send("access-log", productId, message) if not ok then ngx.log(ngx.ERR, "kafka send err:", err) return end ``` 两台机器上都这样做,才能统一上报流量到kafka ``` bin/kafka-topics.sh --zookeeper 192.168.31.187:2181,192.168.31.19:2181,192.168.31.227:2181 --topic access-log --replication-factor 1 --partitions 1 --create bin/kafka-console-consumer.sh --zookeeper 192.168.31.187:2181,192.168.31.19:2181,192.168.31.227:2181 --topic access-log --from-beginning ``` (1)kafka在187上的节点死掉了,可能是虚拟机的问题,杀掉进程,重新启动一下 nohup bin/kafka-server-start.sh config/server.properties & (2)需要在nginx.conf中,http部分,加入resolver 8.8.8.8; (3)需要在kafka中加入advertised.host.name = 192.168.31.187,重启三个kafka进程 (4)需要启动eshop-cache缓存服务,因为nginx中的本地缓存可能不在了 二、基于storm+kafka完成商品访问次数实时统计拓扑的开发 ==============
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值