Spring boot 如何设计一个Springbatch作业,它可以批量从数据库中获取记录,并并行运行多个处理器和写入程序

Spring boot 如何设计一个Springbatch作业,它可以批量从数据库中获取记录,并并行运行多个处理器和写入程序,spring-boot,architecture,spring-batch,jobs,Spring Boot,Architecture,Spring Batch,Jobs,场景:从数据库读取记录并从中创建4个不同的输出文件 技术堆栈: Springboot 2.x springBatch 4.2.x ArangoDB 3.6.x 当前方法:SpringBatch作业,按顺序包含以下步骤: jobBuilderFactory.get("alljobs") .start(step("readAllData")) //reads all records from db, stores it in Obj1 (R1) .next(step("p

场景:从数据库读取记录并从中创建4个不同的输出文件

技术堆栈:

  • Springboot 2.x
  • springBatch 4.2.x
  • ArangoDB 3.6.x
当前方法:SpringBatch作业,按顺序包含以下步骤:

jobBuilderFactory.get("alljobs")
      .start(step("readAllData")) //reads all records from db, stores it in Obj1 (R1)
      .next(step("processData1")) //(P1)
      .next(step("writer1"))      // writes it to file1(W1)
      .next(step("reader2"))      // reads the same obj1(R2)
      .next(step("processor2"))   // processes it (P2)
      .next(step("writer2"))      // writes it to file1(W2)
      .next(step("reader3"))      // reads the same obj1 (R3)
      .next(step("processor3"))   // processes it (P3)
      .next(step("writer3"))      // writes it to file1(W3)
      .next(step("reader4"))      // reads the same obj1(R4)
      .next(step("processor4"))   // processes it (P4)
      .next(step("writer4"))      // writes it to file1 (W4)
      .build()
问题:由于来自DB的数据量巨大,>200000条记录,因此现在我们通过游标在10000条记录中获取记录

作业的目标状态:一个读卡器作业,它通过一个游标从数据库中批量获取1000条记录:

  • 对于每一批1000条记录,我必须为同一批运行processor和writer
  • 另外,因为对于所有rest 3处理器和写入程序,数据集都是相同的(
    Obj1
    ,将从光标中获取),从而并行触发它们
  • 以下是我脑海中闪现的方法:

    • 在游标本身内调用作业,并在游标内逐个迭代执行所有步骤
      P1…W4
    • 调用第一步为
      Reader1
      的作业,然后在游标内调用另一个
      subJob
      ,该作业同时具有所有这些
      P1…W4
      ,因为我们不能离开游标
    请建议实施的最佳方式

    提前谢谢

    更新:

    我试图将Reader1步骤中的步骤(P1…W4)做成一个循环,但由于这里的所有内容都是作为一个步骤编写的,我无法确定如何在R1步骤中调用多个步骤。我尝试使用决策器,将P1…W4放入一个流(
    Flow
    ):

    但是我不能返回到下一个游标迭代,因为游标迭代只在R1步骤中。 我还尝试将所有步骤R1…W4(包括Reader1)放在同一个流中,但该流最终抛出循环流错误


    请提出更好的实施方法?如何在R1步骤迭代的游标内并行调用所有其他步骤。

    我相信使用4个并行步骤对您来说是一个不错的选择。即使有4个线程从相同的数据中读取数据,也应该从处理/写入阶段的并行步骤中获益。这肯定比按顺序执行4个步骤要好。顺便说一句,200k记录并没有那么多(当然这取决于记录的大小和映射方式,但我认为这应该是可以的,读取数据从来不是瓶颈)

    总是要权衡取舍。。在这里,由于并行的步骤,我用一点读取复制来换取更好的总体吞吐量。我不会自杀,以确保项目只读一次,并使事情复杂化


    在数据库世界中,这种权衡的一个很好的类比是接受一些数据复制以支持更快的查询(想想NoSQL设计,有时建议复制一些数据以避免昂贵的连接)。

    这就是我最终设计解决方案的方式:

    因此,我重新构建了整个流程,从基于Tasklet的方法到基于编排块的方法

    作业将有一个名为-fetchProcessAndWriteData的步骤

    jobBuilderFactory.get("allChunkJob")
          .start(step("fetchProcessAndWriteData"))
          .next(step("updatePostJobRunDetails")) 
          .build()
    
    fetchProcessAndWriteData:将具有块大小为10000的读卡器、主处理器和主写入器

    steps
            .get("fetchProcessAndWriteData")
            .chunk(BATCHSIZE)
            .reader(chunkReader)
            .processecor(masterProcessor)
            .writer(masterWriter)
            .listener(listener())
            .build()
    
    chunkReader-从数据库光标读取数据块,并将其传递给主处理器

    public Object process(Object item){
         .. 
         bean.setRecordType1(P1.process(item));
         bean.setRecordType2(P2.process(item));
         bean.setRecordType3(P3.process(item));
         bean.setRecordType4(P4.process(item));
         return bean;
        }
    
    主处理器逐个接受数据,并将记录传递给所有其他处理器C-P1、P2、P3、P4 并将处理后的数据存储在compositeResultBean中

    CompositeResultBean包含所有4种类型记录的数据持有者

    List<Record> recordType1.
        List<Record> recordType2.
        List<Record> recordType3.
        List<Record> recordType4.
    
    masterWriter-此步骤接受记录列表,即此处的compositeResultBean列表。迭代bean列表并调用相应的 writers W1、W2、W3、W4 writer()方法,其中每个compositeResultBean属性中都保存有数据

    公共无效写入(列表){

    这些整个步骤在一批10k记录中执行,并将数据写入文件

    在编写文件的过程中,我面临的另一个挑战是,在第一次写入记录时,我必须替换已经存在的文件,但在同一个文件中必须附加后续文件。 我解决了这个问题,在masterWriter中对chunkListener进行了超调,在那里我拉入了批#,并将静态标志isInitialBatch默认设置为TRUE。 此变量设置在

    beforeChunk() 
    if chunkContext.getStepContext().getStepExecution().getCommitCount()==0 as TRUE , else FALSE .
    
    在FileWriter中传递相同的布尔值,FileWriter以append-TRUE或FALSE模式打开文件

    W1.write(isInitialBatch,list.getRecordType1());
    

    感谢@Mahmoud的输入。但是,即使所有4个处理器使用相同的obj。在所有4种情况下,要写入的对象都不同。例如,P1读取相同的obj1。但创建objP1,然后由W1写入。P2读取相同的obj1,但创建objP2,然后由W2写入。因此,在这种情况下,我的4个处理器在nat中都不同好吧,这不是我从你的例子中理解的:
    b)W1(Obj1);e)W2(Obj1);| e)W3(Obj1);| e)W4(Obj1);
    。对我来说,这意味着要将同一个对象写入不同的目的地4次,因此我建议使用复合编写器。我将相应地编辑答案。感谢@Muhamoud提供更新的答案,这很有帮助。我试图实现类似的逻辑,但面临相同的问题。我一直在研究如何实现决策器和流这里有问题,所以需要一些帮助。我已经用我正在尝试的当前逻辑更新了我的问题。在springboot中有没有其他方法/概念可以让我使用?在这种情况下,请接受答案:。对于第二个问题,请问一个单独的问题
    list.forEach(record -> {
        W1.write(isInitialBatch,list.getRecordType1());
        W2.write(isInitialBatch,list.getRecordType2());
        W3.write(isInitialBatch,list.getRecordType3());
        W4.write(isInitialBatch,list.getRecordType4());
        }); 
      }
    
    beforeChunk() 
    if chunkContext.getStepContext().getStepExecution().getCommitCount()==0 as TRUE , else FALSE .
    
    W1.write(isInitialBatch,list.getRecordType1());