贫农的大数据之四(Storm简介及开发)

Storm是什么

Storm是一个分布式实时计算系统

Storm特点

  • 速度快 - 每个节点每秒能处理百万级的数据
  • 扩展性好 - 集群本身扩展简单;Topology的每个部分的并行数都能轻易的进行调节;线上环境能够随时进行rebalance,重新分配系统资源的占用
  • 容错能力强 - 当Woker死掉,Storm会自动重启它;当节点死掉,运行在它上面的Worker会被重新分配到新的节点上运行
  • 可靠性 - 通常,Storm保证每个消息至少被处理一次,消息处理失败时Storm会重放消息;如果想保证消息被精确地处理一次,可以使用Trident
  • 多语言支持 - Java、Ruby、Python、Javascript、Perl、PHP
  • 易于部署和维护 - 部署简单,维护成本低

Storm应用场景

     实时分析、在线机器学习、持续计算、分布式RPC、ETL

Storm集群中的节点类型

  • Nimbus - 在集群中分发代码;指定任务给集群中的工作节点;监控任何失败。类似于Hadoop中的JobTracker
  • Supervisor - 集群中的每个工作节点都运行一个Supervisor守护进程,它接收来自Nimbus的任务,开始/停止本节点上的Worker。类似于Hadoop中的TaskTracker
  • Worker - 由Supervisor管理,每个Worker是一个JVM,其中可以运行多个线程
  • Zookeeper - Nimbus和Supervisor间的协作是通过zookeeper完成的。另外,Nimbus和Supervisor是快速失败且无状态的,所有的状态都保存在Zookeeper中。这样即使Nimbus和Supervisor被Kill -9,它们也能不受任何影响的重新启动。


Storm编程相关概念

  • Stream - Storm中的一个核心抽象,指无限的Tuple序列。在Storm中,一个流可以被转换成另一个流,这都是通过Spout和Bolt来完成的
  • Tuple - Storm中的数据模型,可以看做一个事件或消息,在Storm的API中可以直接构造和访问。Tuple中的字段是有序且有名字的,顺序、名字及类型完全取决于前一个Spout/Bolt产生该Tuple的逻辑,Tuple本身支持很多常用的数据类型。
  • Spout - 读取数据源,产生数据流,供之后的Bolt进行处理。数据源可以是数据库、文件、消息队列等
  • Bolt - 接收数据流,对其进行处理。例如:过滤数据、转换流、对流进行聚合和连接、写入数据库等。
  • Topology - 是一个完整计算任务的抽象,类似MapReduce中的Job。Topology包含Spout和Bolt,它清晰的定义数据流在Spout和Bolt中如何流转,也定义自身的一些配置,例如Topology所占用的Worker数量、每个Spout/Bolt的并发数等。Topology的生命周期是从成功提交开始,结束于被kill掉。期间任何一个Spout或者Bolt线程死掉,Storm都尽量保证它们能被重新启动。
  • Task - 是一个线程,每一个Task都属于某个具体的Spout/Bolt。一个Spout/Bolt的并发数决定了他所拥有的Task数量,而这些Task由Storm均匀的分布到该Topology所有的Worker中。例如:一个Topology的所有Spout/Bolt的并发数总共为300,它拥有50个Worker可用,那么Storm会尽量让每个Woker执行6个Task,这些Task不一定属于同一个Spout/Bolt

Stream groupings

定义了一个Bolt应该接收哪个数据流,这些数据流又该如何在该Bolt的Task之间分配。Storm默认提供7种分配方式
  • Shuffle grouping:随机分组,每个Task处理的Tuple数量尽量保证相同
  • Fields grouping:按字段分组,在分组字段上具有相同值的Tuple会始终被同一个Task处理。利用这一点可以做进程内的缓存。
  • All grouping:同一个Tuple会被所有的Task处理
  • Global grouping:所有的Tuple都被同一个Task处理,Storm会选择具有最小id的Task
  • None grouping:当前与Shuffle grouping作用一样。原意是不希望使用任何分组策略,之后的版本可能会实现不同的策略
  • Direct grouping:直接指定要目标Bolt中的哪个Task负责接收并处理Tuple
  • Local or shuffle grouping:如果目标Bolt中的一个或多个Task与当前的生产Task运行在同一个Worker中,Tuple将被随机分配到本Worker中的这些Task中,保证Tuple传输是在Worker内进行的;否则使用Shuffle grouping策略。

Storm编程模型

1.编写Spout。对于常见的数据源,会有相应的开源实现,不用自己动手写
2.编写Bolt。有多种实现方式,笔者介绍两种比较常用的方式

     实现一:实现接口backtype.storm.topology.IRichBolt,或扩展backtype.storm.topology.base.BaseRichBolt

public interface IRichBolt extends IBolt, IComponent {
}
public interface IBolt extends Serializable {


    public void prepare(Map map, TopologyContext tc, OutputCollector oc);


    public void execute(Tuple tuple);


    public void cleanup();
}
public interface IComponent extends Serializable {


    public void declareOutputFields(OutputFieldsDeclarer ofd);


    public Map<String, Object> getComponentConfiguration();
}

详细说明:         
  • declareOutputFields - 声明该Bolt将要产生的Tuple,如Tuple中字段的名字,顺序
  • getComponentConfiguration - 必要时可以通过这个方法来配置Bolt如何运行
  • prepare - 在Topology开始时,负责当前Bolt相关的初始化工作,如初始化相关变量等。
  • cleanup - 在Topology终结时,负责当前Bolt相关的清理工作,如释放数据库连接等。Stom不保证这个方法肯定被调用,一个经验就是在kill一个Topology时要给足延迟时间,让他能够做清理工作
  • execute - 处理消息。基于输入的Tuple进行业务处理,然后通过OutputCollector发送0至多个Tuple给下一个Bolt,发送完成后必须调用OutputCollector的ack方法来通知Storm该Tuple被当前Bolt处理完毕。

      实现二:实现接口backtype.storm.topology.IBasicBolt,或扩展backtype.storm.topology.base.BaseBasicBolt(常用)
          与IRichBolt相比,所有的ack都自动触发,不用再手动ack

3.编写Topology
    组装所有的Spout和Bolt,设置每个组件的并发数和数据流的流转策略等,例如
        
KafkaSpout kafkaSpout = new KafkaSpout(config);
        TopologyBuilder builder = new TopologyBuilder();
        builder.setSpout("kafka-spout", kafkaSpout, 3);
        builder.setBolt("avro-deserializer", new AvroDeserializeBolt(), 3).shuffleGrouping("kafka-spout");
        builder.setBolt("add-location", new IpToLocationBolt(), 3).fieldsGrouping("avro-deserializer", new Fields("ip"));
        builder.setBolt("add-mode-group", new MacToModeGroupBolt(), 3).fieldsGrouping("add-location", new Fields("mac"));
        builder.setBolt("track-letv-device", new TrackLetvDevice(), 3).fieldsGrouping("add-mode-group", new Fields("mac"));
        builder.setBolt("write-hbase", new SmartHbaseWriterBolt(), 6).shuffleGrouping("track-letv-device”);

Storm开发Tips

  • 虽然Storm强调并发,但是在开发Bolt时其实完全可以不用考虑并发编程相关的概念。每一个Task都是一个线程,一个Task拥有一个完整的Bolt实例,在prepare中初始化的变量不要使用static修饰符,在execute方法中处理业务时也很少需要考虑并发编程。像数据库连接池可以不需要了、作为缓存也可以用HashMap实现
  • 在不需要手动ack的Bolt实现中,产生Tuple的语句放到execute方法的最后一行是个好习惯
  • 如果产生的Tuple中含可变的对象(常见的是Map或Collection相关类),绝对不要让两个以上的Bolt/Task同时访问该Tuple,否则经典的ConcurrentModificationException就要让你挠头了
  • 在需要Bolt实现缓存功能的时候,要评估当前业务缓存需要的内存,通过增加Task数量来分散内存压力;同时通过Fields grouping能够极大提高缓存命中率
  • 由于Storm的理念是快速失败,任何一个异常都会导致Task死掉并重启,所以需要仔细处理各种可能的异常。另外使用OutputCollector的reportError方法可以将异常输出到nimbus的web界面,当然在节点的日志目录中也可以找到
  • 当需要保证Tuple必须被处理时,即使有异常发生(如写入数据库时数据库服务不可用),可以抛出backtype.storm.topology.FailedException来保证Storm会replay这个Tuple
  • 每个Bolt只干一件事,保证Bolt的实现简单,易于维护和复用
  • Storm不是万能的,某些场景下还是要依靠其他的组件完成任务,如数据库、Redis、Memcached等
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值