Parallel processing 有没有从Java8流中提取数据块的好方法?

Parallel processing 有没有从Java8流中提取数据块的好方法?,parallel-processing,java-8,java-stream,Parallel Processing,Java 8,Java Stream,我在一个ETL过程中从Spring数据存储库中检索了很多实体。然后我使用并行流将实体映射到不同的实体。 我可以使用使用者将这些新实体逐个存储在另一个存储库中,也可以将它们收集到列表中,并在单个批量操作中存储它们。 第一种方法的成本很高,而第二种方法可能会超出可用内存 有没有一种好方法可以收集流中一定数量的元素(像limit那样),使用该块,并继续并行进行,直到所有元素都被处理?您可以编写自己的收集器,它可以累积实体,然后执行批量更新 方法可以将实体添加到内部临时缓存中,直到缓存变大为止。当缓存足

我在一个ETL过程中从Spring数据存储库中检索了很多实体。然后我使用并行流将实体映射到不同的实体。 我可以使用使用者将这些新实体逐个存储在另一个存储库中,也可以将它们收集到列表中,并在单个批量操作中存储它们。 第一种方法的成本很高,而第二种方法可能会超出可用内存


有没有一种好方法可以收集流中一定数量的元素(像limit那样),使用该块,并继续并行进行,直到所有元素都被处理?

您可以编写自己的
收集器,它可以累积实体,然后执行批量更新

方法可以将实体添加到内部临时缓存中,直到缓存变大为止。当缓存足够大时,您可以将其批量存储到其他存储库中

Collector.merge()
需要将两个线程的收集器缓存合并到一个缓存中(可能还需要合并)

最后,流结束时调用
Collector.finisher()
方法,以便将缓存中剩余的任何内容也存储在这里

由于您已经在使用并行流,并且似乎可以同时执行多个加载,因此我假设您已经处理了线程安全

更新

我对线程安全和并行流的评论是指实际保存/存储到存储库中,而不是临时集合中的并发性

我认为每个收集器都应该在自己的线程中运行。并行流应该通过多次调用
supplier()
来创建多个收集器实例。因此,您可以将收集器实例视为单线程,它应该可以正常工作

例如,在Javadoc For
java.util.IntSummaryStatistics
中,它说:

此实现不是线程安全的。但是,在并行流上使用Collectors.toIntStatistics()是安全的,因为stream.collect()的并行实现为安全高效的并行执行提供了必要的分区、隔离和结果合并。
我使用分块进行批量操作的方法是使用分区拆分器包装器,以及另一个将默认拆分策略(批量大小的算术级数以1024为增量)覆盖为简单固定批量拆分的包装器。像这样使用它:

Stream<OriginalType> existingStream = ...;
Stream<List<OriginalType>> partitioned = partition(existingStream, 100, 1);
partitioned.forEach(chunk -> ... process the chunk ...);
streamexistingstream=。。。;
流分区=分区(现有流,100,1);
forEach(chunk->…处理chunk…);
以下是完整的代码:

import java.util.ArrayList;
import java.util.List;
import java.util.Spliterator;
import java.util.Spliterators.AbstractSpliterator;
import java.util.function.Consumer;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

public class PartitioningSpliterator<E> extends AbstractSpliterator<List<E>>
{
  private final Spliterator<E> spliterator;
  private final int partitionSize;

  public PartitioningSpliterator(Spliterator<E> toWrap, int partitionSize) {
    super(toWrap.estimateSize(), toWrap.characteristics() | Spliterator.NONNULL);
    if (partitionSize <= 0) throw new IllegalArgumentException(
        "Partition size must be positive, but was " + partitionSize);
    this.spliterator = toWrap;
    this.partitionSize = partitionSize;
  }

  public static <E> Stream<List<E>> partition(Stream<E> in, int size) {
    return StreamSupport.stream(new PartitioningSpliterator(in.spliterator(), size), false);
  }

  public static <E> Stream<List<E>> partition(Stream<E> in, int size, int batchSize) {
    return StreamSupport.stream(
        new FixedBatchSpliterator<>(new PartitioningSpliterator<>(in.spliterator(), size), batchSize), false);
  }

  @Override public boolean tryAdvance(Consumer<? super List<E>> action) {
    final ArrayList<E> partition = new ArrayList<>(partitionSize);
    while (spliterator.tryAdvance(partition::add) 
           && partition.size() < partitionSize);
    if (partition.isEmpty()) return false;
    action.accept(partition);
    return true;
  }

  @Override public long estimateSize() {
    final long est = spliterator.estimateSize();
    return est == Long.MAX_VALUE? est
         : est / partitionSize + (est % partitionSize > 0? 1 : 0);
  }
}
import java.util.ArrayList;
导入java.util.List;
导入java.util.Spliterator;
导入java.util.Spliterators.AbstractSpliterator;
导入java.util.function.Consumer;
导入java.util.stream.stream;
导入java.util.stream.StreamSupport;
公共类PartitioningSpliterator扩展了AbstractSpliterator
{
专用最终拆分器拆分器;
私有最终整数分区大小;
公共分区拆分器(拆分器拖缆,int partitionSize){
super(toWrap.estimateSize(),toWrap.characteristics()| Spliterator.NONNULL);

如果(partitionSize,您可以使用自定义收集器优雅地执行此操作

请看我对类似问题的回答:

然后,您可以简单地使用上述收集器并行批处理流,将记录存储回存储库中,例如用法:

List<Integer> input = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

int batchSize = 3;
Consumer<List<Integer>> batchProcessor = xs -> repository.save(xs);

input.parallelStream()
     .map(i -> i + 1)
     .collect(StreamUtils.batchCollector(batchSize, batchProcessor));
List input=Arrays.asList(1,2,3,4,5,6,7,8,9,10);
int batchSize=3;
消费者batchProcessor=xs->repository.save(xs);
input.parallelStream()
.map(i->i+1)
.collect(StreamUtils.batchCollector(batchSize,batchProcessor));

以下是我的库提供的解决方案:


使用peek()或Collector.acculator()填充并发存储集合时我无法安全地确定缓存何时达到1000个条目。我必须锁定集合,计数,检索所有条目(如果已填充到所需的级别),然后再次释放集合。这将扼杀并行性。我曾希望在流API中隐藏的某个地方有一种好方法来完成此操作…@ChristophGrimmer Dietrich我不确定您是否需要担心。每个收集器都应该(我认为)在其自己的线程中运行。并行流应该通过多次调用
supplier()
来创建多个收集器实例。我将更新我的答案不应该
getComparator()
可以在
FixedBatchSpliterator
和委托中定义,即:
返回spliterator.getComparator();
?可能是
getComparator()
API对我来说仍然是个谜。
FixedBatchSpliterator
PartitioningSpliterator
之间有什么区别?我没有得到
分区(existingStream,100,1)
@Titmael
FixedBatchSpliterator
是关于对发送到ecah工作线程的数据进行非语义批处理。这种批处理的效果对于用户提供的lambda是不可见的。
PartitioningSpliterator
另一方面,生成显式传递给lambda的数据块。在我的情况下,我需要用c来分割流500个项目,我只能使用
PartitioningSpliterator#partition(Stream-in,int-size)
?可能重复的
import static java.util.stream.StreamSupport.stream;

import java.util.Spliterator;
import java.util.function.Consumer;
import java.util.stream.Stream;

public class FixedBatchSpliterator<T> extends FixedBatchSpliteratorBase<T> {
  private final Spliterator<T> spliterator;

  public FixedBatchSpliterator(Spliterator<T> toWrap, int batchSize, long est) {
    super(toWrap.characteristics(), batchSize, est);
    this.spliterator = toWrap;
  }
  public FixedBatchSpliterator(Spliterator<T> toWrap, int batchSize) {
    this(toWrap, batchSize, toWrap.estimateSize());
  }
  public FixedBatchSpliterator(Spliterator<T> toWrap) {
    this(toWrap, 64, toWrap.estimateSize());
  }

  public static <T> Stream<T> withBatchSize(Stream<T> in, int batchSize) {
    return stream(new FixedBatchSpliterator<>(in.spliterator(), batchSize), true);
  }

  public static <T> FixedBatchSpliterator<T> batchedSpliterator(Spliterator<T> toWrap, int batchSize) {
    return new FixedBatchSpliterator<>(toWrap, batchSize);
  }

  @Override public boolean tryAdvance(Consumer<? super T> action) {
    return spliterator.tryAdvance(action);
  }
  @Override public void forEachRemaining(Consumer<? super T> action) {
    spliterator.forEachRemaining(action);
  }
}
  @Test
public void streamTest(){

    Stream<Integer> data = Stream.generate(() -> {
        //Block on IO
        return blockOnIO();
    });


    AtomicInteger countDown = new AtomicInteger(1000);
    final ArrayList[] buffer = new ArrayList[]{new ArrayList<Integer>()};
    Object syncO = new Object();
    data.parallel().unordered().map(i -> i * 1000).forEach(i->{
        System.out.println(String.format("FE %s %d",Thread.currentThread().getName(), buffer[0].size()));
        int c;
        ArrayList<Integer> export=null;
        synchronized (syncO) {
            c = countDown.addAndGet(-1);
            buffer[0].add(i);
            if (c == 0) {
                export=buffer[0];
                buffer[0] = new ArrayList<Integer>();
                countDown.set(1000);
            }
        }
        if(export !=null){
            sendBatch(export);
        }

    });
    //export any remaining
    sendBatch(buffer[0]);
}

Integer blockOnIO(){
    try {
        Thread.sleep(50);
        return Integer.valueOf((int)Math.random()*1000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
}

void sendBatch(ArrayList al){
    assert al.size() == 1000;
    System.out.println(String.format("LOAD %s %d",Thread.currentThread().getName(), al.size()));
}
FE ForkJoinPool.commonPool-worker-2 996
FE ForkJoinPool.commonPool-worker-5 996
FE ForkJoinPool.commonPool-worker-4 998
FE ForkJoinPool.commonPool-worker-3 999
LOAD ForkJoinPool.commonPool-worker-3 1000
FE ForkJoinPool.commonPool-worker-6 0
FE ForkJoinPool.commonPool-worker-1 2
FE ForkJoinPool.commonPool-worker-7 2
FE ForkJoinPool.commonPool-worker-2 4
List<Integer> input = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

int batchSize = 3;
Consumer<List<Integer>> batchProcessor = xs -> repository.save(xs);

input.parallelStream()
     .map(i -> i + 1)
     .collect(StreamUtils.batchCollector(batchSize, batchProcessor));
stream.split(batchSize).parallel(threadNum).map(yourBatchProcessFunction);