Storm知识点以及面试

PV(Page View)访问量,
    即页面浏览量或点击量,衡量网站用户访问的网页数量;在一定统计周期内用户每打开或刷新一个页面就记录1次,多次打开或刷新同一页面则浏览量累计。
UV(Unique Visitor)独立访客    
    统计1天内访问某站点的用户数(以cookie为依据);访问网站的一台电脑客户端为一个访客。可以理解成访问某网站的电脑的数量。网站判断来访电脑的身份是通过来访电脑的cookies实现的。
    如果更换了IP后但不清除cookies,再访问相同网站,该网站的统计中UV数是不变的。如果用户不保存cookies访问、清除了cookies或者更换设备访问,
    计数会加1。00:00-24:00内相同的客户端多次访问只计为1个访客。
IP(Internet Protocol)独立IP数
    是指1天内多少个独立的IP浏览了页面,即统计不同的IP浏览用户数量。同一IP不管访问了几个页面,独立IP数均为1;不同的IP浏览页面,计数会加1。 IP是基于用户广域网IP地址来区分不同的访问者的,所以,多个用户(多个局域网IP)在同一个路由器(同一个广域网IP)内上网,可能被记录为一个独立IP访问者。如果用户不断更换IP,则有可能被多次统计。
会话次数(网站访问量)Session
    会话是指在指定的时间段内在您的网站上发生的一系列互动,所以会话次数是一段时间内用户向您的网站发起的会话(Session)总数量。一次会话会浏览一个或多个页面
    
实际问题

    1)pv计算,典型的聚合场景

    Spout消费MQ中的数据并发送出去。

    第一个Bolt进行分词和提取,判断每条数据记录,如果是访问记录,则emit出去1。

    第二个Bolt进行局部的聚合,计算本地PV,并发送(thread_id,pv)标志线程级别的唯一性。

    第三个Bolt进行全局聚合,计算总PV,这个全局聚合只能有一个,内部维护一个Hash的数据结构(thread_id,pv),收到数据后实时更新,然后实时/每隔一段时间对pv进行求和。

     2)UV计算,典型的去重聚合场景

    常规思路:与之前PV计算类似

    Spout消费MQ中的数据并发送出去。

    第一个Bolt进行一些预处理,将(session_id,1)为单位发送出去。

    第二个Bolt订阅第一个Bolt的数据,内部维护一个HashMap的结构存储局部的(session_id,count)信息。然后把(thread_id,hashmap)发送出去。

    第三个Bolt进行全局聚合,计算总的UV,这个全局聚合只能有1个,内部维护一个HashMap>的结构存储(thread_id,hashmap(session_id,count)),

    收到数据后实时进行更新,然后实时/每隔一段时间对UV进行session粒度的聚合。

    特殊思路:我们可以想到在WordCount场景下不一定全局聚合只可以一个Bolt实例。完全可以通过hash(session_id)的方式,把相同的session_id控制在同一个的Bolt实例内。

     Spout消费MQ中的数据并发送出去。  

    第一个Bolt进行一些预处理,将(hash_session_id,session_id,1)发射出去。

    第二个Bolt注意要使用fieldsGrouping的方式,指定hash_session_id为grouping的依据字段,也就是说第一个Bolt发出的数据,只要hash_session_id相同,就会被发送到同一个Bolt实例。

    第二个Bolt直接对session_id进行全局聚合,因为同一个session_id只会被发送到同一个Bolt实例,因此数据是准确的。内部直接维护一个HashMap的格式(session_id,count)。

    这个Bolt实例可以有多个,将它们的数据分别持久化就可以了。


Nimbus:
    Storm集群的主控节点负责接收和验证提交的Topology,分配Topology,向ZK写入任务相关的元信息。另外,Nimbus还负责通过Zookeeper来监控supervisor节点和Topology的健康情况,
    当有Supervisor节点挂掉或者Worker进程出现问题时,及时进行任务重新分配。Nimbus分配任务的结果不是直接下发给Supervisor,而是通过Zookeeper维护分配数据进行过渡。
Supervisor:
    Storm集群的计算节点获取Zookeeper分配到该节点的任务信息,根据该分配信息启动/停止工作进程Worker。Supervisor不执行具体的数据处理工作,所有的数据处理工作都交给Worker完成。
    Supervisor需要定期向ZK写入活跃端口信息以便Nimbus及时监控。
Topology:
    运行于Storm集群的计算任务topology 中文翻译为拓扑,类似于 hdfs 上的一个 mapreduce 任务。一个 topology 定义了运行一个 Storm 任务的所有必要元件,主要包括 spout 和 bolt,以及 spout 和 bolt 之间的流向关系。
Worker:
    执行任务的进程实际的数据处理工作最后都在Worker内执行完成。Worker需要定期向Supervsior汇报心跳,该心跳希尔本地磁盘,Supervisor通过读本地磁盘状态信息完成心跳交互过程。
    同样,Worker也会通过Zookeeper向Nimbus汇报心跳。由此可见,Nimbus、Supervisor和Worker均为无状态(Stateless)的,支持快速失败(Fail-Fast),
    这为Storm的扩展性和容错能力提供了很好的保障。
executor:
    执行任务的线程executor 是一个线程,由 worker 进程派生(spawned)。executor 线程负责根据配置派生 task 线程,默认一个 executor 创建一个 task,可通过 setNumTask() 函数指定每个 executor 的 task 数量。executor 将实例化后的 spout/bolt 传递给 task。同一个Worker里所有的Executor只能属于某一个Topology里的执行单元
Task:
    执行具体数据处理实体task 可以说是 topology 最终的实际的任务执行者,每个 task 承载一个 spout 或 bolt 的实例,并调用其中的 spout.nexTuple(),bolt.execute() 等方法,而 spout.nexTuple() 是数据的发射器,bolt.execute() 则是数据的接收方,业务逻辑的代码基本上都在这两个函数里面处理。一个Executor可以对应多个Task,定义Topology时指定,默认Executor和Task一一对应。
 
Spout:
    源数据的获取者用于从外部数据源(如kafka)不间断读取数据并向下游发送数据。
Bolt:
    数据的处理者可执行查询、过滤、聚合及各种复杂运算操作,Bolt的数据处理结果可以作为下游Bolt的输入不断迭代。
Tuple:
    组件(Spout/Bolt)之间数据交换的数据模型。

并行度
    Storm 集群可以运行一个或多个 topology,而每个 topology 包含一个或多个 worker 进程,每个 worer 进程可派生一个或多个 executor 线程,而每个 executor 线程则派生一个或多个 task,task 是实际的数据处理单元,也是 Storm 概念里最小的工作单元, spout 或 bolt 的实例便是由 task 承载。并行度大体分为3个方面:
    1.    一个 topology 指定多少个 worker 进程并行运行;
    2.    一个 worker 进程指定多少个 executor 线程并行运行;
    3.    一个 executor 线程指定多少个 task 并行运行。
 
Supervisor/Worker/Bolt/Spout
    假设有一个Storm集群,里面有2个Supervisor节点。每个Supervisor节点上至多可以运行3个Worker。没有运行Worker的时候,我们称之为slot(槽位)。
    计划在该集群上部署一个topology,该topology的拓扑结构如下:
    此topology占用4个Worker ,其各个spout/bolt的并发度分别为3,4,2,1。
注意事项:
集群中最好保留一定的slot。我们建议是1+1。也就是1个Supervisor节点所拥有的的slot+1个slot。现在我们生产环境配置为4个slot,所以一般建议集群在5个slot。如果集群偏大,比如50+以上的Supervisor节点,这个值相对大一些。


性能调优

11.1    合理地配置硬件资源
11.2    优化代码的执行性能

在业务逻辑层面进行优化
    在技术层面进行优化,手法就非常多了,比如连接数据库时,运用连接池,常用的连接池有 alibaba 的 druid,还有 redis 的连接池;比如合理地使用多线程,合理地优化JVM参数等等。这里举一个工作中可能会遇到的例子来介绍一下:
    在配置了多个并行度的 bolt 中,存取 redis 数据时,如果不使用 redis 线程池,那么很可能会遇到 topology 运行缓慢,spout 不断重发,甚至直接挂掉的情况。首先 redis 的单个实例并不是线程安全的,其次在不使用 redis-pool 的情况下,每次读取 redis 都创建一个 redis 连接,同创建一个 mysql 连接一样,在连接 redis 时所耗费的时间相较于 get/set 本身是非常巨大的。当一个配置了多个并行度的 topology 运行在集群上时,如果 redis 操作不当,很可能会造成运行该 redis 的 bolt 长时间阻塞,从而造成 tuple 传递超时,默认情况下 spout 在 fail 后会重发该 tuple,然而 redis 阻塞的问题没有解决,重发不仅不能解决问题,反而会加重集群的运行负担,那么 spout 重发越来越多,fail 的次数也越来越多, 最终导致数据重复消费越来越严重。使用 RedisCli 工具类,可以在多线程的环境下安全的使用 redis,从而解决了阻塞的问题。
在技术层面进行优化
    特定于 Storm,合理地规划 topology,即安排多少个 bolt,每个 bolt 做什么,链接关系如何
    
11.3    合理的配置并行度
    有几个手段可以配置 topology 的并行度:
    •    conf.setNumWorkers() 配置 worker 的数量
    •    builder.setBolt("NAME", new Bolt(), 并行度) 设置 executor 数量
    •    spout/bolt.setNumTask() 设置 spout/bolt 的 task 数量

setNumWorkers 应该取多少?取决于哪些因素?
    关于 worker 的并行度:worker 可以分配到不同的 supervisor 节点,这也是 Storm 实现多节点并行计算的主要配置手段。据此, workers 的数量,可以说是越多越好,但也不能造成浪费,而且也要看硬件资源是否足够。所以主要考虑集群各节点的内存情况:默认情况下,一个 worker 分配 768M 的内存,外加 64M 给 logwriter 进程;因此一个 worker 会耗费 832M 内存;题设的集群有3个节点,每个节点4G内存,除去 linux 系统、kafka、zookeeper 等的消耗,保守估计仅有2G内存可用来运行 topology,由此可知,当集群只有一个 topology 在运行的情况下,最多可以配置6个 worker。
    另外,我们还可以调节 worker 的内存空间。这取决于流过 topology 的数据量的大小以及各 bolt 单元的业务代码的执行时间。如果数据量特别大,代码执行时间较长,那么可以考虑增加单个 worker 的工作内存。有一点需要注意的是,一个 worker 下的所有 executor 和 task 都是共享这个 worker 的内存的,也就是假如一个 worker 分配了 768M 内存,3个 executor,6个 task,那么这个 3 executor 和 6 task 其实是共用这 768M 内存的,但是好处是可以充分利用多核 CPU 的运算性能。
总结起来,worker 的数量,取值因素有:
•    节点数量,及其内存容量
•    数据量的大小和代码执行时间
机器的CPU、带宽、磁盘性能等也会对 Storm 性能有影响,但是这些外在因素一般不影响 worker 数量的决策。
需要注意的是,Storm 在默认情况下,每个 supervisor 节点只允许最多4个 worker(slot)进程运行;如果所配置的 worker 数量超过这个限制,则需要在 storm 配置文件中修改。

11.4    优化配置参数
/** tuple发送失败重试策略,一般情况下不需要调整 */
spoutConfig.retryInitialDelayMs  = 0;
spoutConfig.retryDelayMultiplier = 1.0;
spoutConfig.retryDelayMaxMs = 60 * 1000;

/** 此参数比较重要,可适当调大一点 */
/** 通常情况下 spout 的发射速度会快于下游的 bolt 的消费速度,当下游的 bolt 还有 TOPOLOGY_MAX_SPOUT_PENDING 个 tuple 没有消费完时,spout 会停下来等待,该配置作用于 spout 的每个 task。  */
conf.put(Config.TOPOLOGY_MAX_SPOUT_PENDING, 10000)

/** 调整分配给每个 worker 的内存,关于内存的调节,上文已有描述 */
conf.put(Config.WORKER_HEAP_MEMORY_MB,             768);
conf.put(Config.TOPOLOGY_WORKER_MAX_HEAP_SIZE_MB,  768);

/** 调整 worker 间通信相关的缓冲参数,以下是一种推荐的配置 */
conf.put(Config.TOPOLOGY_RECEIVER_BUFFER_SIZE,             8); // 1.0 以上已移除
conf.put(Config.TOPOLOGY_TRANSFER_BUFFER_SIZE,            32);
conf.put(Config.TOPOLOGY_EXECUTOR_RECEIVE_BUFFER_SIZE, 16384);
conf.put(Config.TOPOLOGY_EXECUTOR_SEND_BUFFER_SIZE,    16384);

storm的如何处理反压机制?
    pending和timeout用来做反压(backpressure):当下游bolt跟不上上游的spout发送的tuple时,会停止发送
    Config conf = new Config();
    conf.setDebug(true);
    conf.setNumWorkers(2);
    conf.setMaxSpoutPending(1000);
    conf.setMessageTimeoutSecs(180);
    最新版本是自动反压机制,监控bolt中的接收队列,当超过高水位时专门的线程把反压信息写入到zookeeper中。zookeeper上watch会通知所有worker进程反压,降低spout发送速度。
    Storm1.0.0之前的版本中,想要限制topology的输入,只能通过打开ack机制并且设置topology.max.spout.pending的值,但这个配置是在supervisor启动后就生效,中途修改就得重启supervisor和topology,并不能在topology运行过程中动态设置。
    Storm1.0.0中加入了背压机制的功能,通过高低水位来调节task的buffer size,如果高水位被触发,Storm将减慢Spout取数据的速度,如果低水触发,Spout取数据的速度将恢复正常。通过这个处理,Spout的速度得到控制,内存OOM出现的概率也大降低。
    3.    对Spout和bolt之间消息传递使用的DisruptorQueue 做了升级与优化,大大提高了Spout的吞吐量


storm分组策略方式?
    1.Shuffle Grouping:随机分组,轮询,平均分配。随机派发stream里面的tuple,保证每个bolt接收到的tuple数目大致相同
    2.Fields Grouping:按字段分组,比如按userid来分组,具有同样userid的tuple会被分到相同的Bolts里的一个task,而不同的userid则会被分配到不同的bolts里的task。
    3.All Grouping:广播发送,对于每一个tuple,所有的bolts都会收到–同一份数据可能被处理三次。
    
当Nimbus或Supervisor守护进程死亡时会发生什么?
    Nimbus和Supervisor守护进程设计为快速失败(遇到任何意外情况时进程自毁)和无状态(所有状态保存在Zookeeper或磁盘上)
    Nimbus或supervisor的死亡不会影响worker流程。这与Hadoop形成鲜明对比,如果JobTracker死亡,所有正在运行的作业都将丢失.

你可能感兴趣的:(storm)