• Storm一致性事物


      Storm是一个分布式的流处理系统,利用anchor和ack机制保证所有的tuple都被处理成功。如果tuple出错,则可以被重传,但是如何保证出错的tuple只被处理一次呢?换句话说Storm如何保证事物性呢?本节从简单的事物实现入手,最后引出事物型Topology的原理。

    一.一致性事物设计

      1.简单设计一,强顺序流

      保证tuple只被处理一次,最简单的方式是将tuple刘变成强顺序的,并且每次只处理一个tuple。从1开始,给给个tuple都加上一个顺序id。在处理tuple的时候,把处理成功的tupleID和计算结果都存储到数据库中。下一个tuple到来的时候,将他和数据库中的id做比较。如果相同说明tuple已经被成功处理,可以忽略,如果不同,根据强顺序性,说明这个tuple没有被处理过,将它的id和计算结果存储到数据库。这种当时的缺点就是每次智能处理一个tuple,没办法实现分布式。

      2.简单设计二,强顺序batch流  

      为了实现分布式,我们可以一次处理一批tuple,称为一个batch。一个batch中的每个tuple可以被并行处理。

      为了保证每个batch只被处理一次,实现方式和上面的一样,只不过数据库中存储的是batch id。batch的中间计算结果先存在局部变量中,当一个batch中所有的tuple都被处理完之后,判断batch id,如果和数据库中的结果不同,则将中间结果更新到数据库。

      如何保证bath中的每个tuple都被处理完呢?可以利用Storm提供的CoordinatedBolt,如下图:
      

      但是强顺序batch流也有局限,每次只能处理一个batch,batch之间无法并行。要想实现真正的分布式事务处理,可以使用storm提供的Transactional Topology。在此之前,我们先详细介绍一下CoordinateBolt的原理。

      3.CoordinateBolt原理

      CoordinateBolt的具体原理如下:

      a.真正执行计算的Bolt外面封装了一个CoordinateBolt。我们将真正执行任务的Bolt称为Real Bolt。

      b.每个CoordinateBolt记录两个值,有哪些Task给我们发送了tuple,以及我们要给那些Task发送信息

      c.Real Bolt发出一个tuple后,其外层的CoordinateBolt会记录下这些tuple发送给了那些Task

      d.等所有的tuple都发送完之后,CoordinateBolt通过另外一个特殊的Stream以emitDirect的方式告诉所有它发送过tuple的Task,它发送了多少tuple给这些task。下游这些task会将这个数字和自己已经接收到的tuple数量对比。如果相等,则说明处理完了所有的tuple。

      e.下游的task会重复上面的步骤,通知其下游

      整个过程如下图所示:

      

      CoordinateBolt主要用于一下场景。1:DRPC,2:Transactional Topology。

      CoordinateBolt对于业务是有入侵的,要使用CoordinateBolt提供的功能,你必须包保证你的每个Bolt发送的每个tuple的第一个field是request-id,所谓我已经处理完我的上游的意思是说当前这个Bolt对于当前这个request-id所需要做的工作做完了。这个request-id在DRPC里面代表一个请求,在Transactional Topology里面代表一个batch。

      4.Transactional Topology

      Storm的Transactional Topology将batch的计算分为两个阶段:process阶段和commit阶段。process阶段可以同时处理多个batch,不用保证顺序性,commit阶段保证batch的强顺序性,并且在commit阶段每次只能处理一个batch。

      下面以storm自带的例子TransactionalGlobalCount,来说明。

       MemoryTransactionalSpout spout = new MemoryTransactionalSpout(DATA, new Fields("word"), PARTITION_TAKE_PER_BATCH);
        TransactionalTopologyBuilder builder = new TransactionalTopologyBuilder("global-count", "spout", spout, 3);
        builder.setBolt("partial-count", new BatchCount(), 5).noneGrouping("spout");
        builder.setBolt("sum", new UpdateGlobalCount()).globalGrouping("partial-count");
    
        LocalCluster cluster = new LocalCluster();
    
        Config config = new Config();
        config.setDebug(true);
        config.setMaxSpoutPending(3);
    
        cluster.submitTopology("global-count-topology", config, builder.buildTopology());
    
        Thread.sleep(3000);
        cluster.shutdown();
      TransactionalTopologyBuilder共接收了如下的四个参数:

      1.这个Transactional Topology的id,用来在zookeeper中保存当前topology的进度,如果这个topology重启,可以接着之前的进度执行

      2.Spout在这个Topology中的id

      3.一个Transactional Spout。一个Transactional Topology只能有一个Transactional Spout,本例中使用的是MemoryTransactionalSpout,从内存变量DATA中读取数据。

      4.Transactional Spout的并行度(可选)

      下面是BatchCount的定义:

    public static class BatchCount extends BaseBatchBolt {
        Object _id;
        BatchOutputCollector _collector;
    
        int _count = 0;
    
        @Override
        public void prepare(Map conf, TopologyContext context, BatchOutputCollector collector, Object id) {
          _collector = collector;
          _id = id;
        }
    
        @Override
        public void execute(Tuple tuple) {
          _count++;
        }
    
        @Override
        public void finishBatch() {
          _collector.emit(new Values(_id, _count));
        }
    
        @Override
        public void declareOutputFields(OutputFieldsDeclarer declarer) {
          declarer.declare(new Fields("id", "count"));
        }
      }

      BatchCount的prepare方法最后一个参数是batchid,在Transactional Topology里面这个id是一个TransactionAttempt对象,Transactional Topology里面发送的tuple,读必须以TransactionAttempt作为第一个field,Storm根据这个field爱判断tuple属于哪个batch。

      TransactionAttempt里面包含两个值,一个是txid,两外一个是attemptid,txid就是上面我们介绍的对于每一个batch里的tuple是唯一的。attemptid是每个batch唯一的一个ID,但是对每一个batch,它reply之后的attemptid和reply之前的attemptid是不一样的,我们可以吧attemptid理解为reply-times,Storm利用这个ID来区别一个batch发射的tuple的版本。

      execute方法会为batch中的每一个tuple执行一次,你应该把这个batch里面的计算状态保持在一个本地变量里面。对于这个例子来说,它在execute方法里面递增tuple的个数。

      最后,当这个Bolt接收到每个batch的所有的tuple之后,finishBatch方法会被执行。

      UpdateGlobalCount的定义如下:

    public static class UpdateGlobalCount extends BaseTransactionalBolt implements ICommitter {
        TransactionAttempt _attempt;
        BatchOutputCollector _collector;
    
        int _sum = 0;
    
        @Override
        public void prepare(Map conf, TopologyContext context, BatchOutputCollector collector, TransactionAttempt attempt) {
          _collector = collector;
          _attempt = attempt;
        }
    
        @Override
        public void execute(Tuple tuple) {
          _sum += tuple.getInteger(1);
        }
    
        @Override
        public void finishBatch() {
          Value val = DATABASE.get(GLOBAL_COUNT_KEY);
          Value newval;
          if (val == null || !val.txid.equals(_attempt.getTransactionId())) {
            newval = new Value();
            newval.txid = _attempt.getTransactionId();
            if (val == null) {
              newval.count = _sum;
            }
            else {
              newval.count = _sum + val.count;
            }
            DATABASE.put(GLOBAL_COUNT_KEY, newval);
          }
          else {
            newval = val;
          }
          _collector.emit(new Values(_attempt, newval.count));
        }
    
        @Override
        public void declareOutputFields(OutputFieldsDeclarer declarer) {
          declarer.declare(new Fields("id", "sum"));
        }
      }

       UpdateGlobalCount实现了ICommitter接口,所以Storm会在commit阶段调用finishBatch方法,儿execute方法可以在任何阶段完成。

       在UpdateGlobalCount的finishBatch方法中将当前的txid与数据库中的id作比较。如果相同,则忽略这个batch,如果不同则吧这个batch的计算结果合并到总的结果中,并更新数据库。

      Transactional Topology的运行示意图如下:
      

      
      Transactional Topology的一些特性:
      1.Transactional Topology将事物机制封装好,其内部使用CoordinateBOlt保证一个batch中的tuple被处理完

      2.TransactionalSpout只有一个,它将所有的tuple分组为一个一个的batch,而且保证同一个batch的txid始终一样

      3.BatchBolt处理一个batch中的每一个tuple,对每一个tuple调用execute方法,并在所有的tuple都处理完后调用finishBatch方法

      4.如果batchBolt实现了ICommitter接口,则只能在commit阶段调用finishBatch

  • 相关阅读:
    c#遍历文件夹获得所有文件
    var的一些理解
    Pycharm使用秘籍
    RESTful规范
    Django文档阅读-Day4
    Django文档阅读-Day3
    Django文档阅读-Day2
    Django文档阅读-Day1
    你的博客园奇丑无比怎么办?
    基于scrapy框架输入关键字爬取有关贴吧帖子
  • 原文地址:https://www.cnblogs.com/senlinyang/p/8081701.html
Copyright © 2020-2023  润新知