ฉันต้องการใช้การStream
ประมวลผลแบบขนานของชุดไฟล์ JSON ที่จัดเก็บจากระยะไกลซึ่งเป็นหมายเลขที่ไม่รู้จัก (จำนวนไฟล์ไม่ทราบล่วงหน้า) ไฟล์สามารถมีขนาดแตกต่างกันอย่างมากตั้งแต่ 1 ระเบียน JSON ต่อไฟล์สูงสุด 100,000 ระเบียนในไฟล์อื่น ๆ บันทึก JSONในกรณีนี้หมายถึงการที่ตนเองมีวัตถุ JSON แสดงเป็นหนึ่งบรรทัดในไฟล์
ฉันต้องการใช้ Streams สำหรับสิ่งนี้และดังนั้นฉันจึงใช้สิ่งนี้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;
}
}
}
}
ปัญหาที่ฉันมีคือในขณะที่สตรีมขนานกันอย่างสวยงามในตอนแรกในที่สุดไฟล์ที่ใหญ่ที่สุดจะถูกประมวลผลในเธรดเดียว ฉันเชื่อว่าสาเหตุใกล้เคียงมีการบันทึกไว้อย่างดี: ตัวแยกสัญญาณคือ "ไม่สมดุล"
เป็นรูปธรรมมากขึ้นปรากฏว่าtrySplit
วิธีการที่ไม่ได้เรียกหลังจากจุดหนึ่งในStream.forEach
วงจรชีวิตของดังนั้นจึงมีตรรกะพิเศษในการกระจายชุดเล็ก ๆ ในตอนท้ายของtrySplit
จะถูกดำเนินการไม่ค่อย
ขอให้สังเกตว่าตัวแบ่งข้อความทั้งหมดที่ส่งคืนจาก trySplit แบ่งปันตัวpaths
วนซ้ำเดียวกัน ฉันคิดว่านี่เป็นวิธีที่ชาญฉลาดจริงๆในการสร้างสมดุลระหว่างงานของผู้เรียงข้อความทั้งหมด แต่มันก็ยังไม่เพียงพอที่จะบรรลุความเท่าเทียมแบบเต็ม
ฉันต้องการให้การประมวลผลแบบขนานดำเนินการข้ามไฟล์เป็นครั้งแรกจากนั้นเมื่อไฟล์ขนาดใหญ่บางไฟล์ยังคงมีการแยกกันฉันต้องการที่จะขนานขนานกับไฟล์ที่เหลือ นั่นคือความตั้งใจของบล็อกในตอนท้ายของelse
trySplit
มีวิธีที่ง่าย / ง่าย / บัญญัติวิธีแก้ปัญหานี้หรือไม่?
Long.MAX_VALUE
ทำให้เกิดการแยกมากเกินไปและไม่จำเป็นในขณะที่การประมาณการอื่น ๆ นอกเหนือจากLong.MAX_VALUE
สาเหตุอื่น ๆ ที่ทำให้การแยกหยุดการฆ่าขนานกัน การคืนค่าประมาณที่แม่นยำนั้นดูเหมือนจะไม่นำไปสู่การปรับให้เหมาะสมอย่างชาญฉลาด
AbstractSpliterator
แต่เอาชนะtrySplit()
ซึ่งเป็นคำสั่งผสมที่ไม่ดีสำหรับสิ่งอื่นนอกเหนือจากที่คุณจะไม่ได้รับการปรับประมาณการขนาดLong.MAX_VALUE
trySplit()
หลังจากนั้นtrySplit()
การประมาณขนาดควรลดลงตามจำนวนองค์ประกอบที่ถูกแยกออก