有向无环图Directed Acyclic Graph(DAG)
DAG是一个没有 有向循环的、有限的有向图 。
条件
每个顶点出现且只出现一次
若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面。
计算一个DAG的拓扑关系
http://storm.apache.org/
Storm是Twitter开源的分布式实时大数据处理框架,被业界称为实时版Hadoop
。随着越来越多的场景对Hadoop的MapReduce高延迟无法容忍,比如网站统计、推荐系统、预警系统、金融系统(高频交易、股票)等等,大数据实时处理解决方案(流计算)的应用日趋广泛,目前已是分布式技术领域最新爆发点,而Storm更是流计算技术中的佼佼者和主流。
Storm对于实时计算的意义类似于Hadoop对于批处理的意义。Hadoop提供了map、reduce原语,使我们的批处理程序变得简单和高效。同样,Storm也为实时计算提供了一些简单高效的原语,而且Storm的Trident是基于Storm原语更高级的抽象框架,类似于基于Hadoop的Pig框架,让开发更加便利和高效。
Storm 实现的一些特征决定了它的性能和可靠性的,Storm 使用 Netty 传送消息,这就消除了中间的排队过程,使得消息能够直接在任务自身之间流动,在消息的背后,是一种用于序列化和反序列化
Storm 的原语类型的自动化且高效的机制。
Storm 的一个最有趣的地方是它注重容错和管理,Storm 实现了有保障的消息处理,所以每个元组(Turple)都会通过该拓扑(Topology)结构进行全面处理;
如果一个元组还未处理会自动从Spout处重发,Storm 还实现了任务级的故障检测,在一个任务发生故障时,消息会自动重新分配以快速重新开始处理。
适用场景广泛:storm可以实时处理消息和更新DB,对一个数据量进行持续的查询并返回客户端(持续计算)
对一个耗资源的查询作实时并行化的处理(分布式方法调用,即DRPC),storm的这些基础API可以满足大量的场景。
可伸缩性高: Storm的可伸缩性可以让storm每秒可以处理的消息量达到很高。
Storm使用ZooKeeper来协调集群内的各种配置使得Storm的集群可以很容易的扩展。
保证无数据丢失:实时系统必须保证所有的数据被成功的处理。storm保证每一条消息都会被处理。
异常健壮:storm集群非常容易管理,轮流重启节点不影响应用。
容错性好:在消息处理过程中出现异常, storm会进行重试
语言无关性:Storm的topology和消息处理组件(Bolt)可以用任何语言来定义。
Hadoop | Storm | |
---|---|---|
系统角色 | JobTracker | Nimbus |
系统角色 | TaskTracker | Supervisor |
系统角色 | Child | Worker |
应用名称 | Job | Topology |
组件接口 | Mapper/Reducer | Spout/Bolt |
Hadoop | Storm | |
---|---|---|
数据来源 | Hadoop处理的是HDFS上TB级别的数据(历史数据) | Storm处理的是实时新增的某一笔数据(实时数据) |
处理过程 | Hadoop是分Map阶段和Reduce阶段 | Storm是由用户定义处理流程,流程中可以包含多个步骤,每个步骤可以是数据源(Spout)或处理逻辑(Bolt) |
是否结束 | Hadoop最后是要结束的 | Storm没有结束状态,到最后一步时,就停在那,直到有新数据进入时再从头开始 |
处理速度 | Hadoop是以处理HDFS上大量数据为目的的,处理速度慢 | Storm是只要处理新增的某一笔数据即可,可以做到很快 |
适用场景 | Hadoop是在要处理批量数据时用的,不讲究时效性 | Storm是要处理某一新增数据时使用的,讲究时效性 |
nimbus
supervisor
Storm的Slave,负责接收Nimbus分配的任务,管理所有Worker
一个Supervisor节点中包含多个Worker进程。默认是4个
一般情况下一个topology对应一个worker
woker
工作进程(Process),每个工作进程中都有多个Task。
Task
在 Storm 集群中每个 Spout 和 Bolt 都由若干个任务(tasks)来执行。
worker中每一个spout/bolt的线程称为一个task
同一个spout/bolt的task可能会共享一个物理线程(Thread),该线程称为executor
配置worker的数量
//注意此参数不能大于supervisor.slots.ports数量。
Config config = new Config();
config.setNumWorkers(3);
配置Executor的数量
TopologyBuilder builder = new TopologyBuilder();
//设置Spout的Executor数量参数parallelism_hint
builder.setSpout(id, spout, parallelism_hint);
//设置Bolt的Executor数量参数parallelism_hint
builder.setBolt(id, bolt, parallelism_hint);
配置任务Task数量
TopologyBuilder builder = new TopologyBuilder();
//设置Spout的Executor数量参数parallelism_hint,Task数量参数val
builder.setSpout(id, spout, parallelism_hint).setNumTasks(val);
//设置Bolt的Executor数量参数parallelism_hint,Task数量参数val
builder.setBolt(id, bolt, parallelism_hint).setNumTasks(val);
示例代码:
//启动Topology Config
conf = new Config();
//设置项目需要的进程数
conf.setNumWorkers(2);
topologyBuilder.setBolt("Blue", new WordcountBoltLine(), 2);
topologyBuilder.setBolt("Green", new WordcountBoltLine(), 2).setNumTasks(4)
topologyBuilder.setBolt("Yellow", new WordcountBoltLine(), 6)
设置workers为2,说明该项目有两个进程,默认情况下Executor数量和Task数量是一比一的,且Exector在Worker中也是均匀分配的。
如上如例子,Blue设置两个Executor,那么默认两个Executor分在两个Worker中,并且每个Executor分配一个Task。
Green设置两个Executor,4个Task,那么两个Executor分在两个Worker中,且每一个Executor中包含两个Task
Yellow设置六个Executor,那么6个Executor分在两个Worker中,并且默认每个Executor包含一个Task
计算拓扑
Storm 的拓扑是对实时计算应用逻辑的封装,它的作用与 MapReduce 的任务(Job)很相似,区别在于 MapReduce 的一个 Job 在得到结果之后总会结束,而拓扑会一直在集群中运行,直到你手动去终止它。
拓扑还可以理解成由一系列通过数据流(Stream Grouping)相互关联的 Spout 和 Bolt 组成的拓扑结构。
数据流(Streams)是 Storm 中最核心的抽象概念。
一个数据流指的是在分布式环境中并行创建、处理的一组元组(tuple)的无界序列。数据流可以由一种能够表述数据流中元组的域(fields)的模式来定义。
Stream中最小数据组成单元每个tuple可以包含多列,字段类型可以是: integer, long, short, byte, string, double, float,boolean和byte array。其结构就类似于python语法中的元组
数据源(Spout)是拓扑中数据流的来源。
一般 Spout 会从一个外部的数据源读取元组然后将他们发送到拓扑中。
根据需求的不同,Spout 既可以定义为可靠的数据源,也可以定义为不可靠的数据源。
一个可靠的 Spout能够在它发送的元组处理失败时重新发送该元组,以确保所有的元组都能得到正确的处理;storm在检测到一个tuple被整个topology成功处理的时候调用ack, 否则调用fail。
不可靠的 Spout 就不会在元组发送之后对元组进行任何其他的处理。一个 Spout可以发送多个数据流。
拓扑中所有的数据处理均是由 Bolt 完成的。
通过数据过滤(filtering)、函数处理(functions)、聚合(aggregations)、联结(joins)、数据库交互等功能。
一个 Bolt 可以实现简单的数据流转换,而更复杂的数据流变换通常需要使用多个 Bolt 并通过多个步骤完成。
第一级Bolt的输出可以作为下一级Bolt的输入。而Spout不能有上一级。
Bolt 几乎能够完成任何一种数据处理需求。
Bolts的主要方法是execute(死循环)连续处理传入的tuple,成功处理完每一个tuple调用OutputCollector的ack方法,以通知storm这个tuple被处理完成了。
处理失败时,可以调fail方法通知Spout端可以重新发送该tuple。
为拓扑中的每个 Bolt 的确定输入数据流是定义一个拓扑的重要环节。
数据流分组定义了在 Bolt 的不同任务(tasks)中划分数据流的方式。在 Storm 中有八种内置的数据流分组方式。
可靠性
Storm 可以通过拓扑来确保每个发送的元组都能得到正确处理。通过跟踪由 Spout 发出的每个元组构成的元组树可以确定元组是否已经完成处理。
每个拓扑都有一个“消息延时”参数,如果 Storm 在延时时间内没有检测到元组是否处理完成,就会将该元组标记为处理失败,并会在稍后重新发送该元组。
随机分组,随机派发stream里面的tuple,保证每个bolttask接收到的tuple数目大致相同。
轮询,平均分配
//设置拓扑关系(Bolt)
//如果不设置Task的数量,默认executor:Task为1:1
topologyBuilder.setBolt("numberBolt",new NumberBolt()).shuffleGrouping("numberSpout");
优点:
为tuple选择task的代价小;
bolt的tasks之间的负载比较均衡;
缺点:
上下游components之间的逻辑组织关系不明显;
按字段分组
topologyBuilder.setBolt("FieldBolt",new FieldBolt(),3).fieldsGrouping("groupSpout",new Fields("word"));
比如,按"user-id"这个字段来分组,那么具有同样"user-id"的tuple会被分到相同的Bolt里的一个
task,而不同的"user-id"则可能会被分配到不同的task。
一般来说,例如需要统计单词个数的时候,如果Executor的个数不唯一,如果使用随机分组,那么有可能相同的分组会在不同的Task中,导致无法计算出该单词的数量。因此一般会使用一个Bolt将句子切分为单词,之后使用FieldsGrouping的分发方式,按照单词字段分发,相同的单词就会分到相同的Bolt中。
优点:
上下游components之间的逻辑组织关系显著;
缺点:
付出为tuple选择task的代价;
bolt的tasks之间的负载可能不均衡,根据field字段而定;
广播发送,对于每一个tuple,所有的bolts都会收到
topologyBuilder.setBolt("AllBolt",new AllBolt(),2).allGrouping("groupSpout")
The stream is replicated across all the bolt’s tasks. Use this grouping with care.
优点:
上游事件可以通知下游bolt中所有task;
缺点:
tuple消息冗余,对性能有损耗,请谨慎使用;
全局分组,把tuple分配给taskid最低的task。
The entire stream goes to a single one of the bolt’s tasks. Specifically, it goes to the task
with the lowest id.
优点:
所有上游消息全部汇总,便于合并、统计等;
缺点:
bolt的tasks之间的负载可能不均衡,id最小的task负载过重;
指向型分组,这是一种比较特别的分组方法,用这种分组意味着消息(tuple)的发送者指定由消息接收者的哪个task处理这个消息。
只有被声明为DirectStream的消息流可以声明这种分组方法。
而且这种消息tuple必须使用emitDirect方法来发射。
消息处理者可以通过TopologyContext来获取处理它的消息的task的id(OutputCollector.emit方法也会返回task的id)
优点:
Topology的可控性强,且组件的各task的负载可控;
缺点:
当实际负载与预估不符时性能削弱;
本地或随机分组。如果目标bolt有一个或者多个task与源bolt的task在同一个工作进程中,tuple将
会被随机发送给这些同进程中的tasks。否则,和普通的ShuffleGrouping行为一致
If the target bolt has one or more tasks in the same worker process, tuples will be
shuffled to just those in-process tasks. Otherwise, this acts like a normal shuffle
grouping.
优点:
相对于ShuffleGrouping,因优先选择同进程task间传输而降低tuple网络传输代价,但因寻找同进程的task而消耗CPU和内存资源,因此应视情况来确定选择ShuffleGrouping或LocalOrShuffleGrouping;
缺点:
上下游components之间的逻辑组织关系不明显;
不分组,这个分组的意思是说stream不关心到底怎样分组。目前这种分组和Shufflegrouping是一
样的效果。有一点不同的是storm会把使用nonegrouping的这个bolt放到这个bolt的订阅者同一个
线程里面去执行(未来Storm如果可能的话会这样设计)。
自定义,相当于mapreduce那里自己去实现一个partition一样
worker进程间消息传递机制,消息的接收和处理的流程如下图
worker进程
为了管理流入和传出的消息,每个worker进程都有一个独立的接收线程和发送线程接收线程来负责将外部发送过来的消息移动到对应的executor线程的incoming-queue中
发送线程负责从worker的transfer-queue中读取消息,并通过网络发送给其他worker
executor线程
每个executor有独立的incoming-queue 和outgoing-queue
Worker接收线程将收到的消息通过task编号传递给对应的executor的incoming-queues,executor有单独的线程分别来处理spout/bolt的业务逻辑,业务逻辑输出的中间数据会存放在outgoing-queue。
当executor的outgoing-queue中的tuple达到一定的阀值,executor的发送线程将批量获取outgoing-queue中的tuple,并发送到transfer-queue中
每个worker进程控制一个或多个executor线程,用户可在代码中进行配置。其实就是我们在代码中设置的并发度个数。
通信技术
netty:Netty是一个NIO client-server(客户端服务器)框架
https://blog.csdn.net/qq_28959087/article/details/86501141
Disruptor是一个Queue。
Disruptor是实现了“队列”的功能,而且是一个有界队列(长度有限)。而队列的应用场景自然就
是“生产者-消费者”模型
Disruptor一种线程之间信息无锁的交换方式
(使用CAS(Compare And Swap/Set)操作)
Disruptor主要特点
Disruptor 核心技术点
Disruptor可以看成一个事件监听或消息机制,在队列中一边生产者放入消息,另外一边消费
者并行取出处理.
底层是单个数据结构:一个ring buffer(环形数据缓冲区)
每个生产者和消费者都有一个次序计算器,以显示当前缓冲工作方式。每个生产者消费者能够操作自己的次序计数器的能够读取对方的计数器,生产者能够读取消费者的计算器确保其在没有锁的情况下是可写的。
核心组件
Ring Buffer 环形的缓冲区,负责对通过 Disruptor 进行交换的数据(事件)进行存储和更新。Sequence 通过顺序递增的序号来编号管理通过其进行交换的数据(事件),对数据(事件)的处理过程总是沿着序号逐个递增处理。
RingBuffer底层是个数组,次序计算器是一个64bit long 整数型,平滑增长。
Nimbus宕机
非Nimbus节点
故障时,该节点上所有Task任务都会超时,Nimbus会将这些Task任务重新分配到其他服务器上运行
Worker
每个Worker中包含数个Bolt(或Spout)任务。
Supervisor负责监控这些任务,当worker失败后会尝试在本机重启它
如果启动过程中仍然一直失败,并无法向Nimbus发送心跳,Nimbus会将该Worker重新分配到其他服务器上
Supervisor
无状态(所有的状态信息都存放在Zookeeper中来管理)
快速失败(每当遇到任何异常情况,都会自动毁灭)
快速失败(fail-fast)
安全失败(fail-safe)
Nimbus
无状态(所有的状态信息都存放在Zookeeper中来管理)
快速失败(每当遇到任何异常情况,都会自动毁灭)
Bolt任务crash引起的消息未被应答。
acker任务失败。
Spout任务失败。
消息的完整性定义
消息完整性机制–Acker
XOR异或
异或的运算法则为:0异或0=0,1异或0=1,0异或1=1,1异或1=0(同为0,异为1)
A xor B…xor B xor A = 0,其中每一个操作数出现且仅出现两次
验证方式:
spout或者bolt在处理完tuple后,都会告诉acker我已经处理完了该源tuple(如tupleId=1),如果emit一个tuple的话,同时会告诉acker我发射了一个tuple(如tupleId=2),如果在大量的高并发的消息的情况下,传统的在内存中跟踪执行情况的方式,内存的开销会非常大,甚至内存溢
acker巧妙的利用了xor的机制,只需要维护一个msgId的标记位即可,处理方法是acker
在初始的时候,对每个msgId初始化一个校验值ack-val(为0),在处理完tuple和emit tuple的时候,会先对这两个个值做xor操作,生成的中间值再和acker中的当前校验值ack-val做xor生成新的ack-val值,当所有的tuple都处理完成都得到确认,那么最后的ack-val自然就为0了
DRPC (Distributed RPC) 分布式远程过程调用
DRPC 是通过一个 DRPC 服务端(DRPC server)来实现分布式 RPC 功能的。
DRPC Server 负责接收 RPC 请求
并将该请求发送到 Storm中运行的 Topology
等待接收 Topology 发送的处理结果,并将该结果返回给发送请求的客户端。
DRPC设计目的:
为了充分利用Storm的计算能力实现高密度的并行实时计算。
Storm接收若干个数据流输入,数据在Topology当中运行完成,然后通过DRPC将结果进行输出。
客户端通过向 DRPC 服务器发送待执行函数的名称以及该函数的参数来获取处理结果。
实现该函数的拓扑使用一个DRPCSpout 从 DRPC 服务器中接收一个函数调用流。
DRPC 服务器会为每个函数调用都标记了一个唯一的 id。
随后拓扑会执行函数来计算结果,并在拓扑的最后使用一个名为 ReturnResults 的 bolt 连接
到 DRPC 服务器
根据函数调用的 id 来将函数调用的结果返回。
客户端给DRPC服务器发送要执行的方法的名字,以及这个方法的参数。
实现了这个函数的topology使用DRPCSpout从DRPC服务器接收函数调用流。每个函数调用被DRPC服务器标记了一个唯一的id。 随后topology计算结果,在topology的最后一个叫做ReturnResults的bolt会连接到DRPC服务器,并且把这个调用的结果发送给DRPC服务器(通过那个唯一的id标识)。DRPC服务器用那个唯一id来跟等待的客户端匹配上,唤醒这个客户端并且把结果发送给它。