亿级流量场景下,大型架构设计实现【2】---storm篇

时间:2021-10-20 19:40:20

承接之前的博:亿级流量场景下,大型缓存架构设计实现

续写本博客:

****************** start:

  接下来,我们是要讲解商品详情页缓存架构,缓存预热和解决方案,缓存预热可能导致整个系统崩溃的问题以及解决方案;

  缓存--->热: 预热;热数据

  解决方案中和架构设计中,会引入大数据的实时计算技术---> storm;

  为什么引入这storm,必须是storm吗,我们后面面去讲解那个解决方案的时候再说;

  为什么引入storm:

  因为一些热点数据相关的一些实时处理方案,比如快速预热,热点数据的实时感知以及快速降级,都会用到storm,

因为我们可能需要实时的去计算出热点缓存数据,实时计算,亿级流量,高并发,大量的请求处理。这个时候你要做一些实时计算,

那么必须涉及到分布式一些技术,分布式技术才能处理高并发,大量的请求,目前在计算的领域,最成熟的大数据技术就是storm;storm分布式的大数据实时计算技术/系统;

  java工程师跟storm之间的关系是什么呢?

  大公司的java工程师都会用到一些大数据的技术:比如:storm,hbase,zookeeper,或者hive,spark等。

  Storm: 实时缓存热点数据统计--->缓存预热,缓存热点数据自动降级;

  Hive:Hadoop生态栈中做数据仓库的系统,高并发访问下,海量请求日志的批量统计分析,日报,月报,周报,接口调用情况等;比如有一些公司将海量的请求日志达到hive里边

做离线分析,然后反过来优化自己的系统。

  Spark:离线批量数据处理,比如从DB中一次性批量处理几亿的数据,清洗和处理后写入Redis中提供后续系统使用;大型互联网公司的用户相关数据等。

  zookeeper:分布式协调,分布式锁。分布式选举-->高可用HA架构,轻量级元数据存储。

  HBase:海量数据的在线存储和简单查询,替代mysql的分库分表,提供更好的伸缩性。

  亿级流量场景下,大型架构设计实现【2】---storm篇

       亿级流量场景下,大型架构设计实现【2】---storm篇

         亿级流量场景下,大型架构设计实现【2】---storm篇

  

  storm,说句实话,在做热数据这块,如果要做复杂的热数据的统计和分析,亿流量,高并发的场景下,我还真觉得,最合适的技术就是storm,没有其他

缓存架构,热数据先关的架构设计,热数据相关的架构中最重要的唯一的可选技术。

************************入门介绍*********************************

  亿级流量场景下,大型架构设计实现【2】---storm篇

 

 

storm的特点是什么?

(1)支撑各种实时类的项目场景:实时处理消息以及更新数据库,基于最基础的实时计算语义和API(实时数据处理领域);对实时的数据流持续的进行查询或计算,同时将最新的计算结果持续的推送给客户端展示,同样基于最基础的实时计算语义和API(实时数据分析领域);对耗时的查询进行并行化,基于DRPC,即分布式RPC调用,单表30天数据,并行化,每个进程查询一天数据,最后组装结果

storm做各种实时类的项目都ok

(2)高度的可伸缩性:如果要扩容,直接加机器,调整storm计算作业的并行度就可以了,storm会自动部署更多的进程和线程到其他的机器上去,无缝快速扩容

扩容起来,超方便

(3)数据不丢失的保证:storm的消息可靠机制开启后,可以保证一条数据都不丢

数据不丢失,也不重复计算

(4)超强的健壮性:从历史经验来看,storm比hadoop、spark等大数据类系统,健壮的多的多,因为元数据全部放zookeeper,不在内存中,随便挂都不要紧

特别的健壮,稳定性和可用性很高

(5)使用的便捷性:核心语义非常的简单,开发起来效率很高

用起来很简单,开发API还是很简单的

二、Storm的集群架构以及核心概念

1、Storm的集群架构

Nimbus,Supervisor,ZooKeeper,Worker,Executor,Task

2、Storm的核心概念

Topology,Spout,Bolt,Tuple,Stream

拓扑:务虚的一个概念

Spout:数据源的一个代码组件,就是我们可以实现一个spout接口,写一个java类,在这个spout代码中,我们可以自己尝试去数据源获取数据,比如说从kafka中消费数据

bolt:一个业务处理的代码组件,spout会将数据传送给bolt,各种bolt还可以串联成一个计算链条,java类实现了一个bolt接口,一堆spout+bolt,就会组成一个topology,就是一个拓扑,实时计算作业,spout+bolt,一个拓扑涵盖数据源获取/生产+数据处理的所有的代码逻辑,topology

tuple:就是一条数据,每条数据都会被封装在tuple中,在多个spout和bolt之间传递

stream:就是一个流,务虚的一个概念,抽象的概念,源源不断过来的tuple,就组成了一条数据流

亿级流量场景下,大型架构设计实现【2】---storm篇

 

亿级流量场景下,大型架构设计实现【2】---storm篇

 什么是并行度?什么是流分组?

亿级流量场景下,大型架构设计实现【2】---storm篇

  好多年前,我第一次接触storm的时候,真的,我觉得都没几个人能彻底讲清楚,用一句话讲清楚什么是并行度,什么是流分组

很多时候,你以外你明白了,其实你不明白

比如我经常面试一些做过storm的人过来,我就问一个问题,就知道它的水深水浅,流分组的时候,数据在storm集群中的流向,你画一下

比如你自己随便设想一个拓扑结果出来,几个spout,几个bolt,各种流分组情况下,数据是怎么流向的,要求具体画出集群架构中的流向

worker,executor,task,supervisor,流的

几乎没几个人能画对,为什么呢,很多人就没搞明白这个并行度和流分组到底是什么


并行度:Worker->Executor->Task,没错,是Task

流分组:Task与Task之间的数据流向关系

Shuffle Grouping:随机发射,【负载均衡】
Fields Grouping:根据某一个,或者某些个,fields,进行分组,那一个或者多个fields如果值完全相同的话,那么这些tuple,就会发送给下游bolt的其中固定的一个task

  你发射的每条数据是一个tuple,每个tuple中有多个field作为字段

比如tuple,3个字段,name,age,salary

{"name": "tom", "age": 25, "salary": 10000} -> tuple -> 3个field,name,age,salary

  其他方式:

All Grouping
Global Grouping
None Grouping
Direct Grouping
Local or Shuffle Grouping

 

************************代码实例*********************************

package com.roncoo.eshop.storm;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import org.apache.storm.Config;
import org.apache.storm.LocalCluster;
import org.apache.storm.StormSubmitter;
import org.apache.storm.spout.SpoutOutputCollector;
import org.apache.storm.task.OutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.TopologyBuilder;
import org.apache.storm.topology.base.BaseRichBolt;
import org.apache.storm.topology.base.BaseRichSpout;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Tuple;
import org.apache.storm.tuple.Values;
import org.apache.storm.utils.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* 单词计数拓扑
*
* 我认识很多java工程师,都是会一些大数据的技术的,不会太精通,没有那么多的时间去研究
* storm的课程,我就只是讲到,最基本的开发,就够了,java开发广告计费系统,大量的流量的引入和接入,就是用storm做得
* 用storm,主要是用它的成熟的稳定的易于扩容的分布式系统的特性
* java工程师,来说,做一些简单的storm开发,掌握到这个程度差不多就够了
*
* @author Administrator
*
*/
public class WordCountTopology {

/**
* spout
*
* spout,继承一个基类,实现接口,这个里面主要是负责从数据源获取数据
*
* 我们这里作为一个简化,就不从外部的数据源去获取数据了,只是自己内部不断发射一些句子
*
* @author Administrator
*
*/
public static class RandomSentenceSpout extends BaseRichSpout {

private static final long serialVersionUID = 3699352201538354417L;

private static final Logger LOGGER = LoggerFactory.getLogger(RandomSentenceSpout.class);

private SpoutOutputCollector collector;
private Random random;

/**
* open方法
*
* open方法,是对spout进行初始化的
*
* 比如说,创建一个线程池,或者创建一个数据库连接池,或者构造一个httpclient
*
*/
@SuppressWarnings("rawtypes")
public void open(Map conf, TopologyContext context,
SpoutOutputCollector collector) {
// 在open方法初始化的时候,会传入进来一个东西,叫做SpoutOutputCollector
// 这个SpoutOutputCollector就是用来发射数据出去的
this.collector = collector;
// 构造一个随机数生产对象
this.random = new Random();
}

/**
* nextTuple方法
*
* 这个spout类,之前说过,最终会运行在task中,某个worker进程的某个executor线程内部的某个task中
* 那个task会负责去不断的无限循环调用nextTuple()方法
* 只要的话呢,无限循环调用,可以不断发射最新的数据出去,形成一个数据流
*
*/
public void nextTuple() {
Utils.sleep(100);
String[] sentences = new String[]{"the cow jumped over the moon", "an apple a day keeps the doctor away",
"four score and seven years ago", "snow white and the seven dwarfs", "i am at two with nature"};
String sentence = sentences[random.nextInt(sentences.length)];
LOGGER.info("【发射句子】sentence=" + sentence);
// 这个values,你可以认为就是构建一个tuple
// tuple是最小的数据单位,无限个tuple组成的流就是一个stream
collector.emit(new Values(sentence));
}

/**
* declareOutputFielfs这个方法
*
* 很重要,这个方法是定义一个你发射出去的每个tuple中的每个field的名称是什么
*
*/
public void declareOutputFields(OutputFieldsDeclarer declarer) {
declarer.declare(new Fields("sentence"));
}

}

/**
* 写一个bolt,直接继承一个BaseRichBolt基类
*
* 实现里面的所有的方法即可,每个bolt代码,同样是发送到worker某个executor的task里面去运行
*
* @author Administrator
*
*/
public static class SplitSentence extends BaseRichBolt {

private static final long serialVersionUID = 6604009953652729483L;

private OutputCollector collector;

/**
* 对于bolt来说,第一个方法,就是prepare方法
*
* OutputCollector,这个也是Bolt的这个tuple的发射器
*
*/
@SuppressWarnings("rawtypes")
public void prepare(Map conf, TopologyContext context, OutputCollector collector) {
this.collector = collector;
}

/**
* execute方法
*
* 就是说,每次接收到一条数据后,就会交给这个executor方法来执行
*
*/
public void execute(Tuple tuple) {
String sentence = tuple.getStringByField("sentence");
String[] words = sentence.split(" ");
for(String word : words) {
collector.emit(new Values(word));
}
}

/**
* 定义发射出去的tuple,每个field的名称
*/
public void declareOutputFields(OutputFieldsDeclarer declarer) {
declarer.declare(new Fields("word"));
}

}

public static class WordCount extends BaseRichBolt {

private static final long serialVersionUID = 7208077706057284643L;

private static final Logger LOGGER = LoggerFactory.getLogger(WordCount.class);

private OutputCollector collector;
private Map<String, Long> wordCounts = new HashMap<String, Long>();

@SuppressWarnings("rawtypes")
public void prepare(Map conf, TopologyContext context, OutputCollector collector) {
this.collector = collector;
}

public void execute(Tuple tuple) {
String word = tuple.getStringByField("word");

Long count = wordCounts.get(word);
if(count == null) {
count = 0L;
}
count++;

wordCounts.put(word, count);

LOGGER.info("【单词计数】" + word + "出现的次数是" + count);

collector.emit(new Values(word, count));
}

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

}

public static void main(String[] args) {
// 在main方法中,会去将spout和bolts组合起来,构建成一个拓扑
TopologyBuilder builder = new TopologyBuilder();

// 这里的第一个参数的意思,就是给这个spout设置一个名字
// 第二个参数的意思,就是创建一个spout的对象
// 第三个参数的意思,就是设置spout的executor有几个
builder.setSpout("RandomSentence", new RandomSentenceSpout(), 2);
builder.setBolt("SplitSentence", new SplitSentence(), 5)
.setNumTasks(10)
.shuffleGrouping("RandomSentence");
// 这个很重要,就是说,相同的单词,从SplitSentence发射出来时,一定会进入到下游的指定的同一个task中
// 只有这样子,才能准确的统计出每个单词的数量
// 比如你有个单词,hello,下游task1接收到3个hello,task2接收到2个hello
// 5个hello,全都进入一个task
builder.setBolt("WordCount", new WordCount(), 10)
.setNumTasks(20)
.fieldsGrouping("SplitSentence", new Fields("word"));

Config config = new Config();

// 说明是在命令行执行,打算提交到storm集群上去
if(args != null && args.length > 0) {
config.setNumWorkers(3);
try {
StormSubmitter.submitTopology(args[0], config, builder.createTopology());
} catch (Exception e) {
e.printStackTrace();
}
} else {
// 说明是在eclipse里面本地运行
config.setMaxTaskParallelism(20);

LocalCluster cluster = new LocalCluster();
cluster.submitTopology("WordCountTopology", config, builder.createTopology());

Utils.sleep(60000);

cluster.shutdown();
}
}

}

 ************************部署一个storm集群*********************************

讲了手写了storm wordcount程序

蕴含了很多的知识点

(1)Spout
(2)Bolt
(3)OutputCollector,Declarer
(4)Topology
(5)设置worker,executor,task,流分组

storm的核心基本原理,基本的开发,学会了

storm集群部署,怎么将storm的拓扑扔到storm集群上去跑

部署一个storm集群

(1)安装Java 7和Pythong 2.6.6 --  此处略过

(2)下载storm安装包,解压缩,重命名,配置环境变量

-------------------------------------------------

上传apache-storm-1.0.6.tar.gz到 /usr/local

cd /usr/local

tar -zxvf apache-storm-1.0.6.tar.gz

mv apache-storm-1.0.5 storm

cd ./storm/conf

编辑storm.yaml

-------------------------------------------------------

storm.zookeeper.servers:
- "eshop-cache01"
- "eshop-cache02"
- "eshop-cache03"

nimbus.seeds: ["eshop-cache01"]
storm.local.dir: "/var/storm"
supervisor.slots.ports:
- 6700
- 6701
- 6702
- 6703

-------------------------------------------------------

创建数据文件目录:mkdir /var/storm

 

将storm分发到其他主机上:

scp -r  /usr/local/storm/   root@eshop-cache02:/usr/local
scp -r /usr/localstorm/   root@eshop-cache03:/usr/local

 

在所有主机上添加storm的环境变量: vi /etc/profile

export STORM_HOME=/usr/local/storm
export PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/java/latest/bin:/root/bin:/usr/local/zookeeper/bin:$STORM_HOME/bin

 source  /etc/profile

(4)启动storm集群和ui界面

eshop-cache01:

storm nimbus >/dev/null 2>&1 &
storm supervisor >/dev/null 2>&1 &
storm ui >/dev/null 2>&1 &

eshop-cache02:

storm supervisor >/dev/null 2>&1 &
storm ui >/dev/null 2>&1 &

eshop-cache03:

storm supervisor >/dev/null 2>&1 &
storm ui >/dev/null 2>&1 &

 

访问一下ui界面,8080端口:亿级流量场景下,大型架构设计实现【2】---storm篇

--------------------------------界面简单介绍:

  • Used slots:使用的worker数。
  • Free slots:空闲的worker数。
  • Executors:每个worker的物理线程数。

下图为细节图:

 亿级流量场景下,大型架构设计实现【2】---storm篇

亿级流量场景下,大型架构设计实现【2】---storm篇

 

--------------------------------停止storm执行的拓扑任务:

 亿级流量场景下,大型架构设计实现【2】---storm篇

停止此任务命令写法为: 
kill 后面的名称为 上面截图中 红圈的名称
这样就关闭了累加的storm任务
 storm kill WordCountTopology
  亿级流量场景下,大型架构设计实现【2】---storm篇

 

线上日志截图:

亿级流量场景下,大型架构设计实现【2】---storm篇

 ---------------------------------------------- end