Tôi muốn sử dụng Stream
để xử lý song song một tập hợp các tệp JSON được lưu trữ từ xa không rõ số lượng (số lượng tệp không được biết trước). Các tệp có thể có kích thước khác nhau, từ 1 bản ghi JSON cho mỗi tệp cho đến 100.000 bản ghi trong một số tệp khác. Một bản ghi JSON trong trường hợp này có nghĩa là một đối tượng JSON độc lập được biểu diễn dưới dạng một dòng trong tệp.
Tôi thực sự muốn sử dụng Luồng cho việc này và vì vậy tôi đã triển khai việc này Spliterator
:
public abstract class JsonStreamSpliterator<METADATA, RECORD> extends AbstractSpliterator<RECORD> {
abstract protected JsonStreamSupport<METADATA> openInputStream(String path);
abstract protected RECORD parse(METADATA metadata, Map<String, Object> json);
private static final int ADDITIONAL_CHARACTERISTICS = Spliterator.IMMUTABLE | Spliterator.DISTINCT | Spliterator.NONNULL;
private static final int MAX_BUFFER = 100;
private final Iterator<String> paths;
private JsonStreamSupport<METADATA> reader = null;
public JsonStreamSpliterator(Iterator<String> paths) {
this(Long.MAX_VALUE, ADDITIONAL_CHARACTERISTICS, paths);
}
private JsonStreamSpliterator(long est, int additionalCharacteristics, Iterator<String> paths) {
super(est, additionalCharacteristics);
this.paths = paths;
}
private JsonStreamSpliterator(long est, int additionalCharacteristics, Iterator<String> paths, String nextPath) {
this(est, additionalCharacteristics, paths);
open(nextPath);
}
@Override
public boolean tryAdvance(Consumer<? super RECORD> action) {
if(reader == null) {
String path = takeNextPath();
if(path != null) {
open(path);
}
else {
return false;
}
}
Map<String, Object> json = reader.readJsonLine();
if(json != null) {
RECORD item = parse(reader.getMetadata(), json);
action.accept(item);
return true;
}
else {
reader.close();
reader = null;
return tryAdvance(action);
}
}
private void open(String path) {
reader = openInputStream(path);
}
private String takeNextPath() {
synchronized(paths) {
if(paths.hasNext()) {
return paths.next();
}
}
return null;
}
@Override
public Spliterator<RECORD> trySplit() {
String nextPath = takeNextPath();
if(nextPath != null) {
return new JsonStreamSpliterator<METADATA,RECORD>(Long.MAX_VALUE, ADDITIONAL_CHARACTERISTICS, paths, nextPath) {
@Override
protected JsonStreamSupport<METADATA> openInputStream(String path) {
return JsonStreamSpliterator.this.openInputStream(path);
}
@Override
protected RECORD parse(METADATA metaData, Map<String,Object> json) {
return JsonStreamSpliterator.this.parse(metaData, json);
}
};
}
else {
List<RECORD> records = new ArrayList<RECORD>();
while(tryAdvance(records::add) && records.size() < MAX_BUFFER) {
// loop
}
if(records.size() != 0) {
return records.spliterator();
}
else {
return null;
}
}
}
}
Vấn đề tôi gặp phải là lúc đầu Stream song song đẹp mắt, cuối cùng tệp lớn nhất lại bị xử lý trong một luồng. Tôi tin rằng nguyên nhân gần nhất được ghi lại rõ ràng: bộ chia là "không cân bằng".
Cụ thể hơn, dường như trySplit
phương thức này không được gọi sau một điểm nhất định trong Stream.forEach
vòng đời của nó, do đó, logic bổ sung để phân phối các lô nhỏ ở cuối của trySplit
hiếm khi được thực thi.
Lưu ý cách tất cả các trình phân chia được trả về từ trySplit chia sẻ cùng một paths
trình vòng lặp. Tôi nghĩ rằng đây là một cách thực sự thông minh để cân bằng công việc trên tất cả các bộ chia, nhưng nó không đủ để đạt được sự song song hoàn toàn.
Tôi muốn xử lý song song để tiến hành đầu tiên trên các tệp và sau đó khi một vài tệp lớn vẫn còn bị chia tách, tôi muốn song song trên các đoạn của các tệp còn lại. Đó là ý định của else
khối ở cuối trySplit
.
Có một cách dễ dàng / đơn giản / kinh điển xung quanh vấn đề này?
Long.MAX_VALUE
gây ra sự chia tách quá mức và không cần thiết, trong khi bất kỳ ước tính nào khác ngoài việc Long.MAX_VALUE
gây ra sự chia tách tiếp tục bị đình trệ, giết chết sự song song. Trả lại một hỗn hợp các ước tính chính xác dường như không dẫn đến bất kỳ tối ưu hóa thông minh nào.
AbstractSpliterator
nhưng ghi đè trySplit()
đó là một kết hợp xấu cho bất cứ điều gì khác Long.MAX_VALUE
, vì bạn không điều chỉnh ước tính kích thước trong trySplit()
. Sau đó trySplit()
, ước tính kích thước nên được giảm theo số lượng phần tử đã được tách ra.