1.Apache Storm 流式计算框架
1.Storm 基础
1.Storm是什么
Hadoop在处理数据的时候,时效性不够,市场期望能够尽快得到处理后的数据。
Storm是一个流式计算框架,数据源源不断的产生,源源不断的收集,源源不断的计算。(一条数据一条数据的处理)
Storm只负责数据的计算,不负责数据的存储。
2013年前后,阿里巴巴基于storm框架,使用java语言开发了类似的流式计算框架佳作,Jstorm。
2016年年底 阿里巴巴将源码贡献给了Apache storm,两个项目开始合并,新的项目名字叫做storm2.x。阿里巴巴团队专注flink开发。
2.Storm架构
Nimbus:负责资源分配和任务调度。
Supervisor:负责接受nimbus分配的任务,启动和停止属于自己管理的worker进程。
Worker:运行具体处理组件逻辑的进程。
Task:worker中每一个spout/bolt的线程称为一个task。
在storm0.8之后,task不再与物理线程对应,同一个spout/bolt的task可能会共享一个物理线程,该线程称为executor。
2.Storm编程模型
1.Topology拓扑:Storm中运行的一个实时应用程序,因为各个组件间的消息流动形成逻辑上的一个拓扑结构。
2.Spout:
在一个topology中 产生源数据流 的组件。通常情况下spout会从外部数据源中读取数据,然后转换为topology内部的源数据。
Spout是一个主动的角色,其接口中有个nextTuple()函数,storm框架会不停地调用此函数,用户只要在其中生成源数据即可。
3.Bolt:
在一个topology中 接收数据,然后执行处理的组件。Bolt可以执行过滤、函数操作、合并、写数据库等任何操作。
Bolt是一个被动的角色,其接口中有个execute(Tuple input)函数,在接受到消息后会调用此函数,用户可以在其中执行自己想要的操作。
4.Tuple(元祖/数组):
一次消息传递的基本单元。本来应该是一个key-value的map,但是由于各个组件间传递的tuple的字段名称已经事先定义好,
所以tuple中只要按序填入各个value就行了,所以就是一个value list.
5.Stream流:源源不断传递的tuple就组成了stream。
3.分组策略
1.Stream Grouping(流分组):即消息的partition分区 方法。
2.Stream Grouping(流分组) 定义了一个流 在Bolt任务间该如何被切分。这里有Storm提供的 6个 Stream Grouping(流分组) 类型:
1.随机分组(Shuffle grouping):
随机分发 tuple(元祖/数组) 到Bolt的任务,保证每个任务获得相等数量的tuple(元祖/数组)。
跨服务器通信,浪费网络资源,尽量不适用。
2.字段分组(Fields grouping):
根据指定字段分割数据流,并分组。例如,根据“user-id”字段,相同“user-id”的元组总是分发到同一个任务,
不同“user-id”的元组可能分发到不同的任务。跨服务器,除非有必要,才使用这种方式。
3.全部分组(All grouping):tuple(元祖/数组) 被复制到bolt的所有任务。这种类型需要谨慎使用。人手一份,完全不必要。
4.全局分组(Global grouping):全部Stream流都分配到bolt的同一个任务。明确地说,是分配给ID最小的那个task。
5.无分组(None grouping):
你不需要关心Stream流是如何分组。目前,无分组等效于随机分组。
但最终,Storm将把无分组的Bolts放到Bolts或Spouts订阅它们的同一线程去执行(如果可能)。
6.直接分组(Direct grouping):这是一个特别的分组类型。元组生产者决定tuple(元祖/数组)由哪个元组处理者任务接收。
3.LocalOrShuffle 分组:优先将数据发送到本地的Task,节约网络通信的资源。
2.WordCount案例分析
1.功能说明
设计一个topology,来实现对一个句子里面的单词出现的频率进行统计。
整个topology分为三个部分:
RandomSentenceSpout:数据源,在已知的英文句子中,随机发送一条句子出去。
SplitSentenceBolt:负责将单行文本记录(句子)切分成单词
WordCountBolt:负责对单词的频率进行累加
2.导入最新的依赖包
<dependency>
<groupId>org.apache.storm</groupId>
<artifactId>storm-core</artifactId>
<version>1.1.1</version>
</dependency>
目前<scope>可以使用5个值:
compile,缺省值,适用于所有阶段,会随着项目一起发布。
provided,类似compile,期望JDK、容器或使用者会提供这个依赖。如servlet.jar。
runtime,只在运行时使用,如JDBC驱动,适用运行和测试阶段。
test,只在测试时使用,用于编译和运行测试代码。不会随项目发布。
system,类似provided,需要显式提供包含依赖的jar,Maven不会在Repository中查找它。 -->
例子:<scope>provided</scope>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>realtime.WordCountTopology</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
3.TopologyMain 驱动类
package cn.itcast.realtime;
import org.apache.storm.Config;
import org.apache.storm.LocalCluster;
import org.apache.storm.StormSubmitter;
import org.apache.storm.generated.AlreadyAliveException;
import org.apache.storm.generated.AuthorizationException;
import org.apache.storm.generated.InvalidTopologyException;
import org.apache.storm.topology.TopologyBuilder;
//组装应用程序--驱动类
public class WordCountTopology
{
public static void main(String[] args) throws InvalidTopologyException, AuthorizationException, AlreadyAliveException
{
//1.创建一个job(topology)
TopologyBuilder topologyBuilder = new TopologyBuilder();
//2.设置job的详细内容
topologyBuilder.setSpout("ReadFileSpout",new ReadFileSpout(),1);
//SentenceSplitBolt 负责将单行文本记录(句子)切分成单词
topologyBuilder.setBolt("SentenceSplitBolt",new SentenceSplitBolt(),1).shuffleGrouping("ReadFileSpout");
//WordCountBolt 负责对单词的频率进行累加
topologyBuilder.setBolt("WordCountBolt",new WordCountBolt(),1).shuffleGrouping("SentenceSplitBolt");
//准备配置项
Config config = new Config();
config.setDebug(false);
//3.提交job
//提交由两种方式:一种本地运行模式、一种集群运行模式。
if (args != null && args.length > 0)
{
//运行集群模式
config.setNumWorkers(1);
StormSubmitter.submitTopology(args[0],config,topologyBuilder.createTopology());
}
else
{
//本地运行模式
LocalCluster localCluster = new LocalCluster();
localCluster.submitTopology("wordcount", config, topologyBuilder.createTopology());
}
}
}
4.ReadFileSpout
package cn.itcast.realtime;
import org.apache.storm.spout.SpoutOutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.base.BaseRichSpout;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Values;
import java.util.Map;
//Spout 需要继承一个模板
public class ReadFileSpout extends BaseRichSpout
{
private SpoutOutputCollector collector;
/**
* Map conf:应用程序能够读取的配置文件
* TopologyContext context:应用程序的上下文
* SpoutOutputCollector collector:Spout输出的数据丢给SpoutOutputCollector。
*/
@Override
public void open(Map conf, TopologyContext context, SpoutOutputCollector collector)
{
//1、Kafka 连接 / MYSQL 连接 / Redis 连接
//2、将SpoutOutputCollector 复制给成员变量
this.collector = collector;
}
//storm框架有个while循环,一直在调用nextTuple()函数
//Spout是一个主动的角色,其接口中有个nextTuple()函数,storm框架会不停地调用此函数,用户只要在其中生成源数据即可。
@Override
public void nextTuple()
{
// 发送数据,使用collector.emit方法
// Values extends ArrayList<Object>
collector.emit(new Values("i love u"));
try
{
Thread.sleep(500);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
@Override
public void declareOutputFields(OutputFieldsDeclarer declarer)
{
declarer.declare(new Fields("biaobai"));
}
}
5.SentenceSplitBolt(SentenceSplitBolt 负责将单行文本记录(句子)切分成单词)
package cn.itcast.realtime;
import org.apache.storm.task.OutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.base.BaseRichBolt;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Tuple;
import org.apache.storm.tuple.Values;
import java.util.Map;
//切割单词
public class SentenceSplitBolt extends BaseRichBolt
{
private OutputCollector collector;
/**
* 初始化方法
* Map stormConf:应用能够得到的配置文件
* TopologyContext context:应用程序的上下文
* OutputCollector collector:数据收集器
*/
@Override
public void prepare(Map stormConf, TopologyContext context, OutputCollector collector)
{
this.collector = collector;// 连接数据 连接redis 连接hdfs
}
//有个while不停的调用execute方法,每次调用都会发一个数据进行来
@Override
public void execute(Tuple input)
{
// String sentence = input.getString(0);
// 底层先通过 biaobai 这个字段在map中找到对应的index角标值,然后再valus中获取对应数据。
String sentence = input.getStringByField("biaobai");
String[] strings = sentence.split(" ");// 切割
for (String word : strings)
{
collector.emit(new Values(word,1));// 输出数据
}
}
@Override
public void declareOutputFields(OutputFieldsDeclarer declarer)
{
declarer.declare(new Fields("word","num"));// 声明 输出的是什么字段
}
}
6.WordCountBolt(负责对单词的频率进行累加)
package cn.itcast.realtime;
import org.apache.storm.task.OutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.base.BaseRichBolt;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Tuple;
import java.util.HashMap;
import java.util.Map;
//计数
public class WordCountBolt extends BaseRichBolt
{
private OutputCollector collector;
private HashMap<String, Integer> wordCountMap;
/**
* 初始化方法
* Map stormConf:应用能够得到的配置文件
* TopologyContext context:应用程序的上下文
* OutputCollector collector:数据收集器
*/
@Override
public void prepare(Map stormConf, TopologyContext context, OutputCollector collector)
{
this.collector = collector; // 连接数据 连接redis 连接hdfs
wordCountMap = new HashMap<String, Integer>();
}
//有个while不停的调用execute方法,每次调用都会发一个数据进行来execute函数中进行处理
@Override
public void execute(Tuple input)
{
String word = input.getStringByField("word");
Integer num = input.getIntegerByField("num");
// 先判断这个单词是否出现过
if (wordCountMap.containsKey(word))
{
Integer oldNum = wordCountMap.get(word);
wordCountMap.put(word, oldNum + num);
}
else
{
wordCountMap.put(word, num);
}
System.out.println(wordCountMap);
}
@Override
public void declareOutputFields(OutputFieldsDeclarer declarer)
{
// 声明 输出的是什么字段
declarer.declare(new Fields("fenshou"));
}
}
7.Component生命周期
1.Spout生命周期
2.Bolt生命周期
3.Bolt的两个抽象类
BaseRichBolt 需要手动调ack方法
BaseBasicBolt 由 storm框架自动调ack方法
8.StreamGrouping
public Map<List<Integer>, List<MsgInfo>> grouperBatch(List<MsgInfo> batch)
{
Map<List<Integer>, List<MsgInfo>> ret = new HashMap<List<Integer>, List<MsgInfo>>();
//optimize fieldGrouping & customGrouping
if (GrouperType.local_or_shuffle.equals(grouptype))
{
ret.put(local_shuffer_grouper.grouper(null), batch);
}
else if (GrouperType.global.equals(grouptype))
{
// send to task which taskId is 0
ret.put(JStormUtils.mk_list(out_tasks.get(0)), batch);
}
else if (GrouperType.fields.equals(grouptype))
{
fields_grouper.batchGrouper(batch, ret);
}
else if (GrouperType.all.equals(grouptype))
{
// send to every task
ret.put(out_tasks, batch);
}
else if (GrouperType.shuffle.equals(grouptype))
{
// random, but the random is different from none
ret.put(shuffer.grouper(null), batch);
}
else if (GrouperType.none.equals(grouptype))
{
int rnd = Math.abs(random.nextInt() % out_tasks.size());
ret.put(JStormUtils.mk_list(out_tasks.get(rnd)), batch);
}
else if (GrouperType.custom_obj.equals(grouptype) || GrouperType.custom_serialized.equals(grouptype))
{
for (int i = 0; i < batch.size(); i++ )
{
MsgInfo msg = batch.get(i);
List<Integer> out = custom_grouper.grouper(msg.values);
List<MsgInfo> customBatch = ret.get(out);
if (customBatch == null)
{
customBatch = JStormUtils.mk_list();
ret.put(out, customBatch);
}
customBatch.add(msg);
}
}
else if (GrouperType.localFirst.equals(grouptype))
{
ret.put(localFirst.grouper(null), batch);
}
else
{
LOG.warn("Unsupportted group type");
}
return ret;
}
9.Tuple是什么
10.并行度是什么
3.案例:实时交易数据统计
1.架构设计及思路
支付系统 + kafka + storm/Jstorm集群 + redis集群
1.支付系统发送 mq 到 kafka集群中,编写 storm程序 消费 kafka的数据 并计算实时的订单数量、订单数量
2.将计算的实时结果保存在redis中
3.外部程序访问redis的数据实时展示结果
2.工程设计
数据产生:编写kafka数据生产者,模拟订单系统发送 mq
数据输入:使用 PaymentSpout 消费 kafka中的数据
数据计算:使用 CountBolt 对数据进行统计
数据存储:使用 Sava2RedisBolt 对数据进行存储,将结果数据存储到redis中
数据展示:编写java app客户端,访问redis,对数据进行展示,展示方式为打印在控制台。
1.获取外部数据源,MQSpout----Open(连接你的RMQ)---nextTuple()-----emit(json)
2.ParserPaymentInfoBolt()----execute(Tuple)------解析Json----JavaBean
productId,orderId,time,price(原价,订单价,优惠价,支付价),user,收货地址
total:原价、total:订单价、total:订单人数……
3.Save2ReidsBolt,保存相关业务指标
问题:在redis中存放整个网站销售的原价, b:t:p:20160410 ---> value
redis:String----> value1+value2 + value3 + value4 incrBy
b:t:p:20160410
b:t:p:20161111
b:t:p:20160412
3.项目依赖
<!-- storm core-->
<dependencies>
<dependency>
<groupId>org.apache.storm</groupId>
<artifactId>storm-core</artifactId>
<version>1.1.1</version>
<scope>provided</scope>
</dependency>
<!-- storm kafka KafkaSpout-->
<!-- use new kafka spout code -->
<dependency>
<groupId>org.apache.storm</groupId>
<artifactId>storm-kafka-client</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>0.10.0.0</version>
</dependency>
<!-- redis jedis 依赖-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.0</version>
</dependency>
<!-- json Gson/fastjson-->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.4</version>
</dependency>
</dependencies>
4.数据生产
package cn.itcast.realtime.kanban.producer;
import cn.itcast.realtime.kanban.domain.PaymentInfo;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import java.util.Properties;
public class PaymentInfoProducer
{
public static void main(String[] args)
{
//1、准备配置文件
Properties props = new Properties();
props.put("bootstrap.servers", "node01:9092");
/**
* 当生产者将ack设置为“全部”(或“-1”)时,min.insync.replicas指定必须确认写入被认为成功的最小副本数。
* 如果这个最小值不能满足,那么生产者将会引发一个异常(NotEnoughReplicas或NotEnoughReplicasAfterAppend)。
* 当一起使用时,min.insync.replicas和acks允许您执行更大的耐久性保证。
* 一个典型的情况是创建一个复制因子为3的主题,将min.insync.replicas设置为2,并使用“全部”选项来产生。
* 这将确保生产者如果大多数副本没有收到写入引发异常。
*/
props.put("acks", "all");
//设置一个大于零的值,将导致客户端重新发送任何失败的记录
props.put("retries", 0);
/**
* 只要有多个记录被发送到同一个分区,生产者就会尝试将记录一起分成更少的请求。
* 这有助于客户端和服务器的性能。该配置以字节为单位控制默认的批量大小。
*/
props.put("batch.size", 16384);
/**
*在某些情况下,即使在中等负载下,客户端也可能希望减少请求的数量。
* 这个设置通过添加少量的人工延迟来实现这一点,即不是立即发出一个记录,
* 而是等待达到给定延迟的记录,以允许发送其他记录,以便发送可以一起批量发送
*/
props.put("linger.ms", 1);
/**
* 生产者可用于缓冲等待发送到服务器的记录的总字节数。
* 如果记录的发送速度比发送给服务器的速度快,那么生产者将会阻塞,之后会抛出异常。
* 这个设置应该大致对应于生产者将使用的总内存,但不是硬性限制,
* 因为不是所有生产者使用的内存都用于缓冲。
* 一些额外的内存将被用于压缩(如果压缩被启用)以及用于维护正在进行的请求。
*/
props.put("buffer.memory", 33554432);
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
//2、创建 Kafka Producer
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<String, String>(props);
while (true)
{
//3、发送数据
kafkaProducer.send(new ProducerRecord<String, String>("itcast_shop_order",new PaymentInfo().random()));
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
5.驱动类
package cn.itcast.realtime.kanban.Storm;
import org.apache.storm.Config;
import org.apache.storm.LocalCluster;
import org.apache.storm.StormSubmitter;
import org.apache.storm.generated.AlreadyAliveException;
import org.apache.storm.generated.AuthorizationException;
import org.apache.storm.generated.InvalidTopologyException;
import org.apache.storm.kafka.spout.KafkaSpout;
import org.apache.storm.kafka.spout.KafkaSpoutConfig;
import org.apache.storm.topology.TopologyBuilder;
//组装应用程序--驱动类
public class KanBanTopology
{
public static void main(String[] args) throws InvalidTopologyException, AuthorizationException, AlreadyAliveException
{
//1、创建一个job(topology)
TopologyBuilder topologyBuilder = new TopologyBuilder();
//2、设置job的详细内容
KafkaSpoutConfig.Builder<String, String> builder = KafkaSpoutConfig.builder("node01:9092","itcast_shop_order");
builder.setGroupId("bigdata_kanban_order");
KafkaSpoutConfig<String, String> kafkaSpoutConfig = builder.build();
topologyBuilder.setSpout("KafkaSpout",new KafkaSpout<String,String>(kafkaSpoutConfig), 1);
topologyBuilder.setBolt("ETLBolt",new ETLBolt(),1).shuffleGrouping("KafkaSpout");
topologyBuilder.setBolt("ProcessBolt",new ProcessBolt(),1).shuffleGrouping("ETLBolt");
//准备配置项
Config config = new Config();
config.setDebug(false);
//3、提交job
//提交由两种方式:一种本地运行模式、一种集群运行模式。
if (args != null && args.length > 0)
{
//运行集群模式
config.setNumWorkers(1);
StormSubmitter.submitTopology(args[0],config,topologyBuilder.createTopology());
}
else
{
//运行本地运行模式
LocalCluster localCluster = new LocalCluster();
localCluster.submitTopology("KanBanTopology", config, topologyBuilder.createTopology());
}
}
}
6.ETLBolt
package cn.itcast.realtime.kanban.Storm;
import cn.itcast.realtime.kanban.domain.PaymentInfo;
import com.google.gson.Gson;
import org.apache.storm.task.OutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.base.BaseRichBolt;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Tuple;
import org.apache.storm.tuple.Values;
import java.util.Map;
public class ETLBolt extends BaseRichBolt
{
private OutputCollector collector;
/**
* 初始化方法
* Map stormConf:应用能够得到的配置文件
* TopologyContext context:应用程序的上下文
* OutputCollector collector:数据收集器
*/
@Override
public void prepare(Map stormConf, TopologyContext context, OutputCollector collector)
{
this.collector = collector;
}
//有个while不停的调用execute方法,每次调用都会发一个数据进行来。
@Override
public void execute(Tuple input)
{
String json = input.getString(4);
json = input.getStringByField("value");
// 将json串转成 Java对象
Gson gson = new Gson();
PaymentInfo paymentInfo = gson.fromJson(json, PaymentInfo.class);
// 其它的操作,比如说根据商品id查询商品的一级分类,二级分类,三级分类
if(paymentInfo!=null)
{
collector.emit(new Values(paymentInfo));
}
}
@Override
public void declareOutputFields(OutputFieldsDeclarer declarer)
{
// 声明 输出的是什么字段
declarer.declare(new Fields("paymentInfo"));
}
}
7.processBolt
package cn.itcast.realtime.kanban.Storm;
import cn.itcast.realtime.kanban.domain.PaymentInfo;
import org.apache.storm.task.OutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.base.BaseRichBolt;
import org.apache.storm.tuple.Tuple;
import redis.clients.jedis.Jedis;
import java.util.Map;
public class ProcessBolt extends BaseRichBolt
{
private Jedis jedis;
/**
* 初始化方法
* Map stormConf:应用能够得到的配置文件
* TopologyContext context:应用程序的上下文
* OutputCollector collector:数据收集器
*/
@Override
public void prepare(Map stormConf, TopologyContext context, OutputCollector collector)
{
jedis = new Jedis("redis", 6379);
}
//有个while不停的调用execute方法,每次调用都会发一个数据进行来
@Override
public void execute(Tuple input)
{
//获取上游发送的javabean
PaymentInfo value = (PaymentInfo) input.getValue(0);
//先计算总数据 来一条算一条
jedis.incrBy("kanban:total:ordernum",1);
jedis.incrBy("kanban:total:orderPrice",value.getPayPrice());
jedis.incrBy("kanban:total:orderuser",1);
//计算商家(店铺的销售情况)
String shopId = value.getShopId();
jedis.incrBy("kanban:shop:"+shopId+":ordernum",1);
jedis.incrBy("kanban:shop:"+shopId+":orderPrice",value.getPayPrice());
jedis.incrBy("kanban:shop:"+shopId+":orderuser",1);
//计算每个品类(品类id)一级品类
String Level1 = value.getLevel1();
jedis.incrBy("kanban:shop:"+Level1+":ordernum",1);
jedis.incrBy("kanban:shop:"+Level1+":orderPrice",value.getPayPrice());
jedis.incrBy("kanban:shop:"+Level1+":orderuser",1);
}
@Override
public void declareOutputFields(OutputFieldsDeclarer declarer) { }
}
8.看板
package cn.itcast.realtime.kanban.view;
import redis.clients.jedis.Jedis;
public class Kanban
{
public static void main(String[] args)
{
Jedis jedis = new Jedis("redis",6379);
while (true)
{
System.out.println("kanban:total:ordernum 指标是"+jedis.get("kanban:total:ordernum"));
System.out.println("kanban:total:orderPrice指标是"+jedis.get("kanban:total:orderPrice"));
System.out.println("kanban:total:orderuser指标是"+jedis.get("kanban:total:orderuser"));
System.out.println("---------------------------");
System.out.println();
try
{
Thread.sleep(2000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
Storm原理
1.Storm 任务提交的过程
1.读取数据的Spout类:
1.Spout类要读取topic主题中多个分区数中的数据的话,那么所设置的运行Spout类的线程数则是和topic主题中的分区数应为一致。
根据一个分区只能被一个消费者所读取数据的原则,那么有多少个分区就应设置多少个消费者,所以此处就应设置多少个线程运行Spout类
2.Spout类要读取本地文件中数据的话,应设置1个线程执行Spout类,即Spout类只会读取本地文件一次。
如果设置多个线程执行Spout类的话,那么便会有多个线程同时执行多个Spout类的task任务,那么便会读取本地文件多次,
便会造成读取多次没必要,一个线程默认有一个Spout类的task任务。
2.处理数据的Blot类:可以设置任意多个线程执行Blot类
3.数据分发的策略:(推荐使用localOrShuffleGrouping)
localOrShuffleGrouping(推荐):
优先把“处理完毕后的”tuple元祖数据分发给本worker进程内的任意一个线程中的task任务,因此节约网络通信资源。一个线程中默认只有一个task任务。
例子:TopologyBuilder对象.setBolt(自定义唯一id字符串, new 自定义Bolt处理类(),线程数).localOrShuffleGrouping(发送数据过来的类对应的自定义唯一id字符串)
4.setNumTasks(task任务数)
一个线程中默认只有一个task任务。但可以设置一个线程中跑多个task任务,相当于协程。
设置一个线程中跑2个task任务:
TopologyBuilder对象.setBolt(自定义唯一id字符串, new 自定义Bolt处理类(),1).shuffleGrouping(发送数据过来的类得自定义唯一id字符串).setNumTasks(2)
本地模式运行 该程序
LocalCluster本地模式运行:
LocalCluster本地模式指的是 可以本地window执行,也可以把 项目打包为jar包 或 同时把当前项目和所依赖的第三方jar包打包为一个jar包
放到Linux下的storm集群中执行,但是在Storm UI监控管理页面(192.168.25.100:8088)是无法查看到 本地模式运行的该程序的运行信息
1.给“读取数据的”Spout类 设置线程数:
1.Spout类要读取topic主题中多个分区数中的数据的话,那么所设置的运行Spout类的线程数则是和topic主题中的分区数应为一致。
根据一个分区只能被一个消费者所读取数据的原则,那么有多少个分区就应设置多少个消费者,所以此处就应设置多少个线程运行Spout类
2.Spout类要读取本地文件中数据的话,应设置1个线程执行Spout类,即Spout类只会读取本地文件一次。
如果设置多个线程执行Spout类的话,那么便会有多个线程同时执行多个Spout类的task任务,那么便会读取本地文件多次,
便会造成读取多次没必要,一个线程默认有一个Spout类的task任务。
2.给“处理数据的Blot类 设置线程数:可以设置任意多个线程执行Blot类
Storm 集群搭建
1.安装准备
1.安装jdk、安装zookeeper
storm是依赖于zookeeper的,搭建storm集群前,必须先把zookeeper集群搭建好
2.tar -zxvf apache-storm-1.1.1.tar.gz -C /root/storm
3.启动zookeeper 都必须执行 时间同步命令:ntpdate
4.每台机器都启动zookeeper(启动zookeeper 都必须执行 时间同步命令:ntpdate )
cd /root/zookeeper/bin/
start
查看zookeeper集群状态、主从信息:
/root/zookeeper/bin/
2../ status # 查看状态:一个leader,两个follower
3.“follower跟随者”的打印结果:
JMX enabled by default
Using config: /root/zookeeper/bin/../conf/zoo.cfg
Mode: follower
4.“leader领导者”的打印结果:
JMX enabled by default
Using config: /root/zookeeper/bin/../conf/zoo.cfg
Mode: leader
2.修改storm中的配置文件storm.yaml
1.vim /root/storm/conf/storm.yaml
2.配置文件storm.yaml:
storm.zookeeper.servers:# 表示 zookeeper集群的每个主机名/IP地址
- "NODE1"
- "NODE2"
- "NODE3"
# storm集群中作为nimbus的每台linux的主机名/IP地址。多台linux执行命令“storm nimbus”启动nimbus即可提供nimbus高可用,
# 只有先启动的第一台linux作为主,后启动的每台都作为备份。
nimbus.seeds: ["NODE1", "NODE2", "NODE3"]
storm.local.dir: "/root/stormData" # storm 数据目录
ui.port: 8088 # 监控管理storm 的webUI页面的访问端口,不指定的话默认是使用的8080端口进行访问
# 多台linux执行命令“storm supervisor”启动supervisor,那么当前linux便作为supervisor提供woker进程用于执行task任务
# 下面配置的是每个woker进程的可用端口号
# 在storm集群中node2和node3都作为supervisor启动使用的话,那么storm集群中便一共有8个worker进程的端口以供使用
supervisor.slots.ports:
- 6700
- 6701
- 6702
- 6703
3.每台linux都需要 mkdir -p /root/stormData
4.把 node1下配置好的 storm文件夹 推送到 node2、node3中:
scp -r /root/storm NODE2:/root
scp -r /root/storm NODE3:/root
3.启动storm集群
1.NODE1、NODE2、NODE3 分别都作为nimbus:
前台启动:cd /root/storm/bin
chmod 777 storm
./storm nimbus
后台启动:cd /root/storm
chmod 777 ./bin/storm
nohup bin/storm nimbus >/dev/null 2>&1 &
先启动的NODE1中的nimbus作为主,后启动的NODE2、NODE3中的nimbus作为副本
使用jps命令查看nimbus是否成功启动:一开始会显示config_value 表示正在加载配置,再继续jps查看后会出现nimbus,表示加载配置并启动成功nimbus
2.NODE2、NODE3 分别作为supervisor:(在storm集群中node2和node3都作为supervisor启动使用的话,那么storm集群中便一共有8个worker进程的端口以供使用)
前台启动:cd /root/storm/bin
chmod 777 storm
./storm supervisor
后台启动:cd /root/storm
chmod 777 ./bin/storm
nohup bin/storm supervisor >/dev/null 2>&1 &
多台linux执行命令“storm supervisor”启动supervisor,那么当前linux便作为supervisor提供woker进程用于执行task任务
使用jps命令查看nimbus是否成功启动:一开始会显示config_value 表示正在加载配置,再继续jps查看后会出现Supervisor,表示加载配置并启动成功Supervisor
3.node1中 启动webUI页面:监控管理storm
前台启动:cd /root/storm/bin
chmod 777 storm
./storm ui
后台启动:cd /root/storm
chmod 777 ./bin/storm
nohup bin/storm ui >/dev/null 2>&1 &
使用jps命令查看成功启动webUI页面,会出现core。
使用 192.168.25.100:8088 或 node1:8088 即可打开 webUI页面 来监控管理storm
4.日志查看工具
前台启动:cd /root/storm/bin
chmod 777 storm
./storm logviewer
后台启动:cd /root/storm
chmod 777 ./bin/storm
nohup bin/storm logviewer >/dev/null 2>&1 &
使用jps命令查看成功启动 logviewer的话,会出现logviewer
5.storm没有提供一键启动,需要自己手动编写一键启动脚本
1.创建slave文件:(如果脚本无法读取slave文件中的第三行信息的话,则给第四行来一个空行,那么脚本才能顺利读取到第三行信息)
NODE1
NODE2
NODE3
2.一键启动storm:
source /etc/profile
nohup storm nimbus >/dev/null 2>&1 &
nohup storm ui >/dev/null 2>&1 &
cat /export/servers/storm/bin/slave | while read line
do
{
echo $line
ssh $line "source /etc/profile;nohup storm supervisor >/dev/null 2>&1 &"
}&
wait
done
2.一键关闭storm:stopstorm.sh(cut -c 1-6:截取第1个字符到第6字符的进程号,但要注意的是如果进程是7个数字就无法完整获取)
source /etc/profile
jps | awk '{ if( $2 == "nimbus" || $2 == "supervisor" || $2 == "core" ) print $1 }' |xargs kill -s 9
cat /export/servers/storm/bin/slave | while read line
do
{
echo $line
ssh $line "source /etc/profile;jps |grep "supervisor" |cut -c 1-6 |xargs kill -s 9"
}&
wait
done
linux下的 storm集群中 以集群模式 运行该程序
StormSubmitter(storm集群模式运行):
以集群模式运行表示 只能把 项目打包为jar包 或 同时把当前项目和所依赖的第三方jar包打包为一个jar包 放到Linux下的storm集群中执行
此时可在Storm UI监控管理页面(192.168.25.100:8088)查看到storm集群模式下运行的该程序的运行信息
1.给“读取数据的”Spout类 设置线程数:
1.Spout类要读取topic主题中多个分区数中的数据的话,那么所设置的运行Spout类的线程数则是和topic主题中的分区数应为一致。
根据一个分区只能被一个消费者所读取数据的原则,那么有多少个分区就应设置多少个消费者,所以此处就应设置多少个线程运行Spout类
2.Spout类要读取本地文件中数据的话,应设置1个线程执行Spout类,即Spout类只会读取本地文件一次。
如果设置多个线程执行Spout类的话,那么便会有多个线程同时执行多个Spout类的task任务,那么便会读取本地文件多次,
便会造成读取多次没必要,一个线程默认有一个Spout类的task任务。
2.给“处理数据的Blot类 设置线程数:可以设置任意多个线程执行Blot类
linux下的storm集群中 以集群模式 运行该程序:
1.把 stormWordcount-1.0-SNAPSHOT.jar 拷贝到 /root目录下,执行 cd /root
2.执行 ./storm jar 项目名.jar Topology驱动类的全限定名称(包名.类名)
此处执行:
cd /root/storm/bin
./storm jar /root/stormWordcount-1.0-SNAPSHOT.jar cn.itcast.storm.wc.WordCountTopology
以集群模式运行该程序后,会把运行结果信息记录在worker.log文件中
1.具体路径:/root/storm/logs/workers-artifacts/wordcount1-1-时间/端口号/worker.log
端口号可在监控页面中查看该blot类所运行所在的哪个node节点和在该node节点上所使用的端口号。
2.例子:/root/storm/logs/workers-artifacts/wordcount1-1-1538035874/6700/worker.log
linux下的Redis安装
1.第一步:先安装C的编译环境:yum install gcc gcc-c++ libstdc++-devel tcl -y
因为需要使用gcc进行编译才能安装Redis,Redis是基于C语言开发的。
2.第二步:查看是否安装gcc成功,输入gcc或make是否出现以下提示。
3.第三步:可联网下载 redis-4.0.11.tar.gz或其他版本的源代码包,并进行安装
1.wget http://download.redis.io/releases/redis-4.0.10.tar.gz
2.tar xzf redis-4.0.11.tar.gz(必须把压缩包放到linux解压,不能在window解压好再拖到linxu中,因为这样很多文件都没有权限)
redis-4.0.11(进入解压后目录)
4.make(使用make命令会进行编译)
5.make install PREFIX=/usr/local/redis(安装到/usr/local/redis目录下)
4.第四步:进入 cd /usr/local/redis/bin 目录下,查看到多个redis的命令文件
Redis服务器端的启动和停止
1.第一步:进入 cd /usr/local/redis/bin目录下,有如下多个redis的命令文件
2.第二步:
1.前台启动模式:在当前/usr/local/redis/bin目录下启动Redis服务器的命令:./redis-server
2.后台启动模式:(每次修改完redis.conf请重新启动Redis服务器并再次指定加载该新修改后的redis.conf)
1.第一步:把 /redis-3.0.0的源码安装包目录下的 redis.conf 拷贝到 /usr/local/redis/bin 的安装目录下
cp /root/redis-4.0.11/redis.conf /usr/local/redis/bin
2.第二步:修改 vim /usr/local/redis/bin/redis.conf:修改为 daemonize yes,即把原来的 no 改为 yes,设置为后台运行。
每次修改完redis.conf请重新启动Redis服务器并再次指定加载该新修改后的redis.conf
修改redis-server默认绑定的127.0.0.1的IP为具体的IP。
目的:如果redis-server继续使用默认绑定的127.0.0.1的话,那么外部window就无法访问linux内部的redis
vim /usr/local/redis/bin/redis.conf
把 bind 127.0.0.1 修改为 bind 192.168.xx.xxx
4.第三步:在当前 /usr/local/redis/bin 目录下同时指定 redis.conf 启动Redis服务器的命令:
cd /usr/local/redis/bin
./redis-server redis.conf 或者 /usr/local/redis/bin/redis-server /usr/local/redis/bin/redis.conf
即Redis服务器加载并应用该redis.conf中的配置信息再进行启动。
3.第三步:查看是否启动成功
4.第四步:
1.停止 前台模式下的redis服务器:ctrl + c
2.停止 后台模式下的redis服务器:
1.第一种方式:查看到redis-server的进程号,然后执行kill -9 进程号:ps aux|grep redis
2.第二种方式:在/usr/local/redis/bin目录下,使用redis客户端进行停止redis服务器:./redis-cli shutdown 或 /usr/local/redis/bin/redis-cli shutdown
3.第三种方式:执行了./redis-cli进入了redis客户端的输入模式下,执行shutdown命令也可停止redis服务器
Redis客户端的启动和停止
1.第一种启动方式:在/usr/local/redis/bin目录下,执行:./redis-cli 或者 /usr/local/redis/bin/redis-cli
2.第二种启动方式:连接远程linux上的redis服务器,执行:./redis-cli -h 远程linux的IP -p 6379
默认连接localhost运行在6379端口的redis服务。
-h:连接的服务器的地址
-p:服务的端口号
3.第三种启动方式:
1.加上--raw的作用:
在客户端中显示查询出的中文数据时,如果以“/16进制”的形式显示中文数据的话,
那么使用“./redis-cli --raw”在进入客户端,那么查询出的中文数据便能正常以中文字符的形式显示。
2.例子:./redis-cli --raw 或 /usr/local/redis/bin/redis-cli --raw 或 /usr/local/redis/bin/redis-cli -h 192.168.25.100 --raw
./redis-cli -h 远程linux的IP -p 6379 --raw
4.redis客户端下使用ping命令查看redis服务器是否正常运行:
设置Redis密码
1.测试环境忽略Redis的访问密码,生产环境必须配置Redis的访问密码
2..开启Redis的访问密码:
在/usr/local/redis/bin目录下的redis.conf配置文件中有个参数:“requirepass 密码”。
redis.conf配置文件中默认配置的访问密码被注释掉了,所以首先要取消注释,然后可以修改设置redis访问密码。
修改redis访问密码:requirepass redis访问密码
每次修改完redis.conf请重新启动Redis服务器并再次指定加载该新修改后的redis.conf
3.不重启Redis设置密码:
注意:这种方式设置密码,当redis重启,密码失效。
进入redis客户端(./redis-cli)下,执行“config set requirepass 密码”可以进行设置访问密码;
如果提示需要先输入验证已设置的访问密码的话,那么执行“auth 密码”之后,再执行“config set requirepass 密码”设置新的密码。
4.验证密码的命令:“auth 密码”
在操作Redis数据库中的数据之前,会提示必须先进行密码验证才能继续操作Redis数据库中的数据,所以执行“auth 密码”进行密码验证。
“auth 密码”命令跟其他redis命令一样,是没有加密的;阻止不了攻击者在网络上窃取你的密码;
认证层的目标是提供多一层的保护。如果防火墙或者用来保护redis的系统防御外部攻击失败的话,外部用户如果没有通过密码认证还是无法访问redis的。
kafka + storm 的集成
kafka + storm 集成和程序编写的流程
kafka + storm 集成和程序编写的流程:
1.第一步:使用shell命令 创建kafka的topic主题,创建的时候并指定多个分片数/分区数
2.第二步:编写kafka的生产者的java程序 生产大量数据 往kafka的topic主题中存储
3.第三步(测试):使用shell命令 从kafka的topic主题中消费数据 以用于测试是否成功
4.第四步:编写kafka + storm集成的java程序
1.首先就需要在仅storm-core的storm环境的基础上,再加上依赖storm-kafka-client、kafka-clients 才能完成 kafka + storm集成
2.class KanBanTopology驱动类:通过KafkaSpoutConfig类连接kafka的IP:端口和topic主题名,以进行消费topic主题中存储的数据
3.class ParserPaymentBolt解析数据类:从topic主题中取出的数据都是JSON字符串,把JSON字符串转换为JavaBean对象,然后对JavaBean对象进行相应的数据计算
4.class PaymentIndexProcessBolt存储数据到redis类:把计算好的数据又存放进Redis数据库中
本地模式运行 该程序
LocalCluster本地模式运行:
LocalCluster本地模式指的是 可以本地window执行,也可以把 项目打包为jar包 或 同时把当前项目和所依赖的第三方jar包打包为一个jar包
放到Linux下的storm集群中执行,但是在Storm UI监控管理页面(192.168.25.100:8088)是无法查看到 本地模式运行的该程序的运行信息
1.第一步:
1.使用shell命令 创建kafka的topic主题,创建的时候并指定多个分片数/分区数
cd /root/kafka
格式:bin/kafka-topics.sh --create --zookeeper zookeeper的IP:2181 --replication-factor 副本数 --partitions 分片数/分区数 --topic 主题名
例子:bin/kafka-topics.sh --create --zookeeper NODE1:2181 --replication-factor 1 --partitions 3 --topic payment
partitions 3:此处设置分片数/分区数为3,根据一个partition分区只能被一个消费者进行消费,
此处KafkaSpoutConfig类作为消费者负责连接partition分区进行获取数据。
因为有 3个分片数/分区数,那么作为消费者的KafkaSpoutConfig类和后面的Blot处理类每个类便都需要设置为3个线程执行。
2.查看当前服务器中的所有 topic
cd /root/kafka
格式:bin/kafka-topics.sh --list --zookeeper zookeeper的IP:2181
例子:bin/kafka-topics.sh --list --zookeeper NODE1:2181
2.第二步:编写kafka的生产者的java程序 生产大量数据 往kafka的topic主题中存储
1.定义JavaBean类class Payment:定义了 订单支付的信息
2.编写kafka的生产者:根据Payment类生产大量订单相关信息存到topic主题“payment”中(直接本地运行该程序)
3.第三步(测试):使用shell命令 从kafka的topic主题payment中消费数据 以用于测试是否成功
cd /root/kafka
格式:bin/kafka-console-consumer.sh --zookeeper zookeeper的IP:2181 --from-beginning --topic 主题名
例子:bin/kafka-console-consumer.sh --zookeeper NODE1:2181 --from-beginning --topic payment
4.编写kafka + storm集成的java程序
1.首先就需要在仅storm-core的storm环境的基础上,再加上依赖storm-kafka-client、kafka-clients 才能完成 kafka + storm集成
1.如果storm集群上也提供了storm-core.jar,那么需要在项目编译的时候排除掉项目中所依赖的storm-core,只需要设置为provided,
目的为防止jar包冲突:storm集群中和编译打包后的项目jar包 两者中都同时存在storm-core.jar的话便会发生jar包冲突
2.provided的使用注意:
1.如果仅把项目单独打包为一个jar包放到linux中的storm集群中运行的话,就会直接使用storm集群中的storm-core.jar。
2.但是如果同时把项目和项目所依赖的storm-core.jar一起打包为一个jar包放到linux中的storm集群中运行的话,
就要注意是否会发生jar包冲突,因为项目所依赖的jar包和storm集群环境中的jar包会发生冲突。
此处storm-core.jar包冲突的话,报该错误信息 Caused by: java.io.IOException: Found multiple defaults.yaml resources
需要在仅storm-core的storm环境的基础上,再加上依赖storm-kafka-client、kafka-clients 才能完成 kafka + storm集成
需要同时把项目和项目所依赖的storm-core.jar一起打包为一个jar包的话,需要配置以下插件信息
1.给“读取数据的”Spout类 设置线程数:
1.Spout类要读取topic主题中多个分区数中的数据的话,那么所设置的运行Spout类的线程数则是和topic主题中的分区数应为一致。
根据一个分区只能被一个消费者所读取数据的原则,那么有多少个分区就应设置多少个消费者,所以此处就应设置多少个线程运行Spout类
2.Spout类要读取本地文件中数据的话,应设置1个线程执行Spout类,即Spout类只会读取本地文件一次。
如果设置多个线程执行Spout类的话,那么便会有多个线程同时执行多个Spout类的task任务,那么便会读取本地文件多次,
便会造成读取多次没必要,一个线程默认有一个Spout类的task任务。
2.给“处理数据的Blot类 设置线程数:可以设置任意多个线程执行Blot类
2.class KanBanTopology驱动类:
通过KafkaSpoutConfig类连接kafka的IP:端口和topic主题名,以进行消费topic主题中存储的数据
3.class ParserPaymentBolt解析数据类:
从topic主题中取出的数据都是JSON字符串,把JSON字符串转换为JavaBean对象,然后对JavaBean对象进行相应的数据计算
4.class PaymentIndexProcessBolt存储数据到redis类:
把计算好的数据又存放进Redis数据库中
报错问题:.SocketTimeoutException: Read timed out
分析:当使用一个redis连接,没有使用redis连接池的话,一旦遇到大量数据要存入到redis数据库的话,便会出现以上问题,无法快速地把大量数据存入到redis数据库
解决:使用redis连接池
5.以本地运行模式 运行该程序后,就能在Redis中查看到结果数据,下面是redis的相关操作
查看所有key:keys *
查看所有key的数量:DBSIZE
删除所有key:FLUSHALL
linux下的 storm集群中 以集群模式 运行该程序
StormSubmitter(storm集群模式运行):
以集群模式运行表示 只能把 项目打包为jar包 或 同时把当前项目和所依赖的第三方jar包打包为一个jar包 放到Linux下的storm集群中执行
此时可在Storm UI监控管理页面(192.168.25.100:8088)查看到storm集群模式下运行的该程序的运行信息
stormKafka-1.0-SNAPSHOT.jar:仅把当前项目打包为jar包,不包括所依赖的第三方jar包
stormKafka-1.0-SNAPSHOT-jar-with-dependencies.jar:同时把当前项目和所依赖的第三方jar包打包为一个jar包
linux下的storm集群中 以集群模式 运行该程序:
1.把 stormKafka-1.0-SNAPSHOT-jar-with-dependencies.jar 拷贝到 /root目录下,执行 cd /root
2.执行 ./storm jar 项目名.jar Topology驱动类的全限定名称(包名.类名)
此处执行:
cd /root/storm/bin
./storm jar /root/stormKafka-1.0-SNAPSHOT-jar-with-dependencies.jar cn.itcast.realtime.storm.kanban.KanBanTopology
3.以集群运行模式 运行该程序后,就能在Redis中查看到结果数据,下面是redis的相关操作
查看所有key:keys *
查看所有key的数量:DBSIZE
删除所有key:FLUSHALL
class KanBanTopology驱动类:
通过KafkaSpoutConfig类连接kafka的IP:端口和topic主题名,以进行消费topic主题中存储的数据