Linux Storm原理及部署

Storm

原理:

Storm是一个分布式的、高容错的实时计算系统。

Storm对于实时计算的的意义相当于Hadoop对于批处理的意义。Hadoop为我们提供了Map和Reduce原语,使我们对数据进行批处理变的非常的简单和优美。同样,Storm也对数据的实时计算提供了简单Spout和Bolt原语。

Storm适用的场景:

1、流数据处理:Storm可以用来用来处理源源不断的消息,并将处理之后的结果保存到持久化介质中。

2、分布式RPC:由于Storm的处理组件都是分布式的,而且处理延迟都极低,所以可以Storm可以做为一个通用的分布式RPC框架来使用。

基本概念:

1 元组(tuple)

元组是Storm中消息传输的基本单元,是一个命名的值列表(List)。

元组支持所有基本类型、字符串、字节数组作为字段的值,只要实现类型的序列化接口就可以使用该类型的对象。

元组本来应该是一个Key-value的Map,但是由于组件之间传递的元组的字段名称已经事先定义好,所以只需要按照顺序,将值填入List即可。

2 流(Stream)

流是一个无序的元组序列。每个流被声明后都会赋予一个id,用于唯一识别这个流。

由于单个流的Spout和Bolt很多(Spout和Bolt都可以声明输出多个流,但是情况很少),因此,使用OutputFieldsDeclarer可以声明一个不指定id的流,这个时候,流被赋予了一个默认的id值(64位)。

3 喷口(Spout)

Spout是Storm的数据来源,一般Spout会从消息队列中读取数据。

Spout可以是可靠的,也可以是不可靠的。可靠的Spout会重发处理失败的元组,而不可靠的Spout选择遗忘。

nextTuple()是Spout的核心函数,Storm框架会不断调用这个函数,从外部读取tuple。

当一个元组被完全处理后,Storm调用Spout的ack()函数,否则调用fail()函数。

IRichSpout是Spout必须要实验的一个接口。

4 螺栓(Bolt)

Topology中的所有业务处理,都在Bolt中完成,比如数据的过滤、业务处理、存储等。

Bolt的关键函数是execute(),这个函数的主要功能是处理接收到的数据,发射0个或者多个基于当前元组的元组,然后应答输入元组(ack)。

5 拓扑(Topology)

拓扑就是Storm中真正在运行的程序,节点表示Spout或Bolt,完成数据处理;边表示数据的流动方式。

6 主控节点和工作节点

Storm集群中节点有两类:主控节点和工作节点。其中主控节点1个,工作节点多个。

主控节点上运行了一个称为Numbus的守护进程,负责在集群中分发代码,对节点分配任务,并监视主机故障。

工作节点上运行了一个称为Supervisor的守护进程,负责监听其主机上分配的作业,启动和停止已经分配的工作进程。

7 流分组(Stream grouping)

是拓扑定义的一部分,负责确定每个Bolt应该从哪个拓扑节点获取数据。

Storm内置7中流分组方式,此外,也可以通过实现CustomStreamGrouping接口,设计自定义的流分组方式。

8 工作进程(Worker)

worker在物理上,是一个JVM和拓扑中任务子集。

每一台服务器可以部署一个或者多个worker进程,每个 worker进程执行一个Topology中的部分任务。

9 任务(task)

worker中的每一个Spout或者Bolt线程(相当于Topology中的一个节点),称为一个任务。

Storm集群中,每个Spout或者Bolt执行很多任务,可以通过设置拓扑中每个Spout或Bolt的任务数(setNumTasks()),调整他们执行的任务数。

10 执行器(Executor)

Storm0.8之后,task不再和线程对应,多个task可以共享一个线程。

所以一台服务器可以部署多个worker进程;每个worker进程中可以有多个Executor(对应一个线程);每个Executor中执行一个或者多个task。

Executor的个数,可以通过setSpout或者SetBolt中的并行度参数进行调整。

11 可靠性

Storm可以保证每一个Spout元组被完全处理,它跟踪每个Spout元组的元组树,当元组树中创建元组节点或者完成元组节点时,对应的组件(Spout或Bolt)会通知Storm。

当一个Spout元组没有在“消息超时时间”内完成时,Spout认为元组处理失败,并重发元组。

一个Storm集群的基本组件

storm的集群表面上看和hadoop的集群非常像。但是在Hadoop上面你运行的是MapReduce的Job, 而在Storm上面你运行的是Topology。它们是非常不一样的—一个关键的区别是:一个MapReduce Job最终会结束,而一个Topology运永远运行(除非你显式的杀掉他)。

在Storm的集群里面有两种节点:控制节点(master node)和工作节点(worker node)。控制节点上面运行一个后台程序:Nimbus,它的作用类似Hadoop里面的JobTracker。Nimbus负责在集群里面分布代码,分配工作给机器,并且监控状态。

每一个工作节点上面运行一个叫做Supervisor的节点(类似 TaskTracker)。Supervisor会监听分配给它那台机器的工作,根据需要启动/关闭工作进程。每一个工作进程执行一个Topology(类似 Job)的一个子集;一个运行的Topology由运行在很多机器上的很多工作进程 Worker(类似 Child)组成。

Storm VS MapReduce

Nimbus和Supervisor之间的所有协调工作都是通过一个Zookeeper集群来完成。并且,nimbus进程和supervisor都是快速失败(fail-fast)和无状态的。所有的状态要么在Zookeeper里面,要么在本地磁盘上。这也就意味着你可以用kill -9来杀死nimbus和supervisor进程,然后再重启它们,它们可以继续工作,就好像什么都没有发生过似的。这个设计使得storm不可思议的稳定。

序列化(Serialization)

1 元组中可以包含任意的对象,所以,元组在任务之间传递时,需要有序列化和反序列化的过程。

2 Storm使用Kryo序列化,Kryo是一个灵活快速的序列化库。

3 除了使用Kryo,Storm中还可以使用自定义序列化(需要注册)和Java序列化(消耗大量资源)

4 动态类型

Storm将对象放置到字段时,并没有声明字段的类型。Strom会动态的找出字段类型并将其序列化。

使用动态类型的原因:

(1)简化API,动态类型简答且易用。

(2)Bolt方法中的元组可能来自任意流,所以有不同的类型组合。

(3)Storm可以被动态语音更直接的使用(Clojure和Jruby)

容错机制

1 Worker进程死亡

Worker死亡时,对应节点的Supervisor会尝试重启Worker。

如果连续重启失败一定次数,无法发送心跳信息到Nimbus,Nimbus会在另一台主机上重新分配Worker.

2 节点死亡

Nimbus会将当前节点的任务分配给其他节点的主机。

3 Nimbus或Supervisor死亡

Nimbus和Supervisor被设计成快速失败(出现故障时,进程自动毁灭)和无状态的(所有状态保存在Zookeeper或者磁盘上)。

Nimbus和Supervisor应该配合Daemontool或者monit工具监控运行,当他们死亡了,可以马上重启。

由于他们是无状态的,所以重启后会向什么都没发生一样继续工作,并且不影响Worker进程的工作。

4 Nimbus的“单点故障”

虽然失去Nimbus节点,worker节点可以正常工作,但是当worker或者节点死亡后,他们的任务无法被安排到其他节点上。

可靠性机制(保证消息完整处理)

1 消息被“完全处理”的含义

对于任意一个Spout发出的tuple,经过Topology中Bolt的一系列运算,都会形成一个“元组树”。

当这个树创建完成(Anchor锚定),并且树中的每一个元素都已经被处理(ack),那么Storm认为这个Spout的tuple被“完全处理”。

如果在超时时间内(默认30s),元组树中的元组没有被处理完,认为这个Spout的tuple处理失败。

2 Spout的处理过程

(1)Storm调用Spout的nextTuple方法从Spout请求一个元组。

(2)Spout调用open方法,发射一个元组到输出流,并给这个元组提供一个唯一的id。

(3)元组被发送到Bolt,Storm负责跟踪元组的链接已经处理状态。

(4)如果处理成功,Storm调用ack方法,并将Spout的tuple的id传递给ack函数;否则调用fail方法,同样传递id。

3 Storm如何保证可靠性

想要保证可靠性,需要保证两点:一是当在元组树上创建一个新链接时,需要告诉Storm;二是,当一个元组处理完成时,告诉Storm。

(1)保证第一条

建立链接的过程其实就是Bolt发射元组的过程。Bolt通过“锚定”来通知Storm,这里建立的新的数据链接。

具体的方法是,Bolt调用emit方法时,将输入tuple作为emit方法的第一个参数,这样,后续发送出去的tuple就都锚定在了输入tuple上,形成了一个元组树。只有后续的元组都被ack,这个输入元组才被认为是ack的。

一个输出元组也可以锚定多个输入元组,称为“复合锚定”,输出参数时多个输入元组组成的List。这样当下游元组未被成功处理时,将会触发Spout的多个元组的重发。

其实emit也可以不把输入tuple作为第一个参数(即不使用锚定),这个时候,如果下游的节点没有被处理,Storm的机制将无法将对应的Spout元组重发。这取决于用户需要的可靠性级别。因为不使用锚定可以提高一些效率。

(2)保证第二条

当完成元组树中的单个元组操作时,需要调用OutputCollector类的ack或者fail方法,通知Storm。

fail方法会使对应的Spout元组立即失败,这样Spout就不需要等到超时时间重发,速度会更快。

ack方法通知Storm,当前组件的输入tuple处理成功。

需要注意的是,每一个元组必须执行ack或者fail方法,Storm使用内存追踪每个元组,付过不返回ack/fail,那么最终会导致内存耗尽。

4 Storm如何实现可靠性

Storm的可靠性,主要依赖一组Acker任务。对于每一个Spout元组,Acker任务跟踪元组的有向图。

Storm使用哈希取模的方式,将Spout元组的id(64位id)映射到一个Acker任务,对应的Acker任务就负责跟踪这个Spout元组。

当Spout的元组发送到Bolt中时,Spout的id会复制到Bolt产生的新的tuple中,同理,Bolt向下游Bolt转发tuple时,同样会把这个Spout元组的id复制到新的Tuple中。这样,一系列的tuple都可以根据Spout的id,找到他们对应的Acker任务。

在进行计算时,当一个组件ack时,它发送给Acker一个消息,告诉Acker,当前的tuple已经处理完成,后面又锚定了新的tuple。这样,Acker任务就可以实时的追踪Spout元组的元组树以及元组树中元祖的完成情况。

Acker任务默认只有一个,当拓扑规模比较大时,可以通过配置,让storm产生多个Acker任务。

5 Acker的跟踪算法

Storm中有大量的节点,如果Acker显示的跟踪Spout元组的所有后续节点,那将导致内存溢出。

事实上,Acker只存储来自一个Spout元组的一对key-value的map值,key是创建Spout元组的任务的id,value是一个64位的值,称为ack val,它是所以在元组树上创建的元组以及ack的元组的id的异或值(XOR),如果这个值为0,表示这个Spout元组已经被处理成功。

上面的算法有一个问题,由于Spout元组的id是随机生成的,如果元组生成的id就是0,那么将无法判断是否真的完成。但是这种情况的概率非常小,可以忽略不计。

6 Storm面对失败案例如何保证数据不丢失

(1)任务挂了,导致元组没有ack

对应的Spout元组将会超时重发。

(2)acker任务挂了

同样,Spout元组会超时重发。

(3)Spout任务挂了

Spout的ack方法不会被执行,重启后,可以重新从队列中读取未处理的值。

7 调节可靠性

如果用户不要求每条数据都要被处理,而是对效率比较看重,那么可以选择取消可靠性保护机制。有以下三种方法:

(1)设置Config.TOPOLOGY_ACKERS为0,这种情况下,Storm不启动Acker任务,Spout发射一个元组后,它的ack方法会立即被调用。

(2)在Spout的SpoutOutputCollector.emit方法中,忽略消息的spout id,这样,这个Spout发射的元组将不会被追踪,且Spout不会受到任何关于ack或fail方法的回调。

(3)如果不介意下游元组的特定子集无法被处理,可以作为非锚定元组发射,这样,下游的元组不会被追踪。

消息传输机制

1 默认的ZeroMQ

(1)本地化的消息库,过度依赖操作系统。

(2)安装麻烦。

(3)在不同的Storm版本之间,ZeroMQ的稳定性差异大。

2 新引入的Netty

(1)纯Java的消息通信解决方案,不依赖平台。

(2)传输性能是ZeroMQ的两倍。

Storm的并行度

与并行度相关的有三个实体:工作进程(Worker),执行器(Executor,即线程),任务(Task)

1 三者之间的关系

Nimbus将Worker平均分配到服务器集群中的节点上。

将所有Executor平均分配到所有的Worker上。

所有任务平均分配到“其对应的”Executor上。(这里需要对应,原因是创建Topology时,同一个组件的Executor和Tast是对应设置的)

2 设置方式

(1)worker

配置选项:TOPOLOGY_WORKERS

代码:Config.setNumWorkers

(2)Executor

配置选项:无

代码:TopologyBuilder.setSpout/setBolt

(3)Task

配置选项:TOPOLOGY_TASK

代码:ComponentConfigurationDeclarer.setNumTasks

 

版本:2.2.0

部署:

  1. 解压

tar xvf apache-storm-2.2.0 -C /data/middleware/storm

  1. 修改配置文件

vim /data/middleware/storm/conf/storm.yaml

配置文件:

#集群中zookeeper的地址

storm.zookeeper.servers:

- "10.0.3.2"

- "10.0.3.3"

- "10.0.3.4"

#集群中zookeeper的端口

storm.zookeeper.port: 2186

storm.zookeeper.connection.timeout: 60000

storm.zookeeper.retry.interval: 2000

storm.zookeeper.session.timeout: 100000

storm.local.dir: "/data/middleware/storm/apache-storm-2.2.0/StormLocalDir"

storm.log.dir: "/data/middleware/storm/apache-storm-2.2.0/logs"

#集群中nimbus的主机名

nimbus.seeds: ["nn-pp-mw000002","nn-pp-mw000003","nn-pp-mw000004"]

#supervisor节点:

supervisor.slots.ports:

- 17601

- 17602

- 17603

- 17604

- 17605

- 17606

- 17607

- 17608

- 17609

- 17610

nimbus.childopts: "-Xmx2048m"

worker.childopts: "-Xmx4096m"

supervisor.childopts: "-Xmx512m"

topology.receiver.buffer.size: 512

topology.acker.executors: 60

topology.acker.tasks: 60

java.library.path: "/usr/local/lib:/opt/local/lib:/usr/lib:/data/middleware/apache-storm-2.2.0/lib"

storm.messaging.netty.server_worker_threads: 80

storm.messaging.netty.buffer_size: 50971520

storm.messaging.netty.max_retries: 30

storm.messaging.netty.max_wait_ms: 30000

storm.messaging.netty.min_wait_ms: 2000

#storm web的端口

ui.port: 17888

supervisor.worker.timeout.secs: 60

topology.worker.receiver.thread.count: 5

topology.enable.message.timeouts: true

topology.message.timeout.secs: 180

  1. 启动 nimbus+ui supervisor

#nimbus节点启动nimbus ui 其他启动supervisor

nohup bin/storm nimbus &

nohup bin/storm ui &

nohup bin/storm supervisor &

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值