Chris Lu
4 years ago
6 changed files with 101 additions and 361 deletions
-
41other/java/client/src/main/java/seaweedfs/client/SeaweedOutputStream.java
-
2other/java/hdfs2/src/main/java/seaweed/hdfs/SeaweedFileSystemStore.java
-
16other/java/hdfs2/src/main/java/seaweed/hdfs/SeaweedHadoopOutputStream.java
-
2other/java/hdfs3/src/main/java/seaweed/hdfs/SeaweedFileSystemStore.java
-
64other/java/hdfs3/src/main/java/seaweed/hdfs/SeaweedHadoopOutputStream.java
-
337other/java/hdfs3/src/main/java/seaweed/hdfs/SeaweedOutputStream.java
@ -0,0 +1,16 @@ |
|||
package seaweed.hdfs; |
|||
|
|||
// adapted from org.apache.hadoop.fs.azurebfs.services.AbfsOutputStream |
|||
|
|||
import seaweedfs.client.FilerGrpcClient; |
|||
import seaweedfs.client.FilerProto; |
|||
import seaweedfs.client.SeaweedOutputStream; |
|||
|
|||
public class SeaweedHadoopOutputStream extends SeaweedOutputStream { |
|||
|
|||
public SeaweedHadoopOutputStream(FilerGrpcClient filerGrpcClient, final String path, FilerProto.Entry.Builder entry, |
|||
final long position, final int bufferSize, final String replication) { |
|||
super(filerGrpcClient, path.toString(), entry, position, bufferSize, replication); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,64 @@ |
|||
package seaweed.hdfs; |
|||
|
|||
// adapted from org.apache.hadoop.fs.azurebfs.services.AbfsOutputStream |
|||
|
|||
import org.apache.hadoop.fs.StreamCapabilities; |
|||
import org.apache.hadoop.fs.Syncable; |
|||
import seaweedfs.client.FilerGrpcClient; |
|||
import seaweedfs.client.FilerProto; |
|||
import seaweedfs.client.SeaweedOutputStream; |
|||
|
|||
import java.io.IOException; |
|||
import java.util.Locale; |
|||
|
|||
public class SeaweedHadoopOutputStream extends SeaweedOutputStream implements Syncable, StreamCapabilities { |
|||
|
|||
public SeaweedHadoopOutputStream(FilerGrpcClient filerGrpcClient, final String path, FilerProto.Entry.Builder entry, |
|||
final long position, final int bufferSize, final String replication) { |
|||
super(filerGrpcClient, path, entry, position, bufferSize, replication); |
|||
} |
|||
|
|||
/** |
|||
* Similar to posix fsync, flush out the data in client's user buffer |
|||
* all the way to the disk device (but the disk may have it in its cache). |
|||
* |
|||
* @throws IOException if error occurs |
|||
*/ |
|||
@Override |
|||
public void hsync() throws IOException { |
|||
if (supportFlush) { |
|||
flushInternal(); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Flush out the data in client's user buffer. After the return of |
|||
* this call, new readers will see the data. |
|||
* |
|||
* @throws IOException if any error occurs |
|||
*/ |
|||
@Override |
|||
public void hflush() throws IOException { |
|||
if (supportFlush) { |
|||
flushInternal(); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Query the stream for a specific capability. |
|||
* |
|||
* @param capability string to query the stream support for. |
|||
* @return true for hsync and hflush. |
|||
*/ |
|||
@Override |
|||
public boolean hasCapability(String capability) { |
|||
switch (capability.toLowerCase(Locale.ENGLISH)) { |
|||
case StreamCapabilities.HSYNC: |
|||
case StreamCapabilities.HFLUSH: |
|||
return supportFlush; |
|||
default: |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
} |
@ -1,337 +0,0 @@ |
|||
package seaweed.hdfs; |
|||
|
|||
// adapted from org.apache.hadoop.fs.azurebfs.services.AbfsOutputStream |
|||
|
|||
import com.google.common.base.Preconditions; |
|||
import org.apache.hadoop.fs.FSExceptionMessages; |
|||
import org.apache.hadoop.fs.Path; |
|||
import org.apache.hadoop.fs.StreamCapabilities; |
|||
import org.apache.hadoop.fs.Syncable; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
import seaweedfs.client.ByteBufferPool; |
|||
import seaweedfs.client.FilerGrpcClient; |
|||
import seaweedfs.client.FilerProto; |
|||
import seaweedfs.client.SeaweedWrite; |
|||
|
|||
import java.io.IOException; |
|||
import java.io.InterruptedIOException; |
|||
import java.io.OutputStream; |
|||
import java.nio.ByteBuffer; |
|||
import java.util.Locale; |
|||
import java.util.concurrent.*; |
|||
|
|||
import static seaweed.hdfs.SeaweedFileSystemStore.getParentDirectory; |
|||
|
|||
public class SeaweedOutputStream extends OutputStream implements Syncable, StreamCapabilities { |
|||
|
|||
private static final Logger LOG = LoggerFactory.getLogger(SeaweedOutputStream.class); |
|||
|
|||
private final FilerGrpcClient filerGrpcClient; |
|||
private final Path path; |
|||
private final int bufferSize; |
|||
private final int maxConcurrentRequestCount; |
|||
private final ThreadPoolExecutor threadExecutor; |
|||
private final ExecutorCompletionService<Void> completionService; |
|||
private final FilerProto.Entry.Builder entry; |
|||
private final boolean supportFlush = false; // true; |
|||
private final ConcurrentLinkedDeque<WriteOperation> writeOperations; |
|||
private long position; |
|||
private boolean closed; |
|||
private volatile IOException lastError; |
|||
private long lastFlushOffset; |
|||
private long lastTotalAppendOffset = 0; |
|||
private ByteBuffer buffer; |
|||
private long outputIndex; |
|||
private String replication = "000"; |
|||
|
|||
public SeaweedOutputStream(FilerGrpcClient filerGrpcClient, final Path path, FilerProto.Entry.Builder entry, |
|||
final long position, final int bufferSize, final String replication) { |
|||
this.filerGrpcClient = filerGrpcClient; |
|||
this.replication = replication; |
|||
this.path = path; |
|||
this.position = position; |
|||
this.closed = false; |
|||
this.lastError = null; |
|||
this.lastFlushOffset = 0; |
|||
this.bufferSize = bufferSize; |
|||
this.buffer = ByteBufferPool.request(bufferSize); |
|||
this.outputIndex = 0; |
|||
this.writeOperations = new ConcurrentLinkedDeque<>(); |
|||
|
|||
this.maxConcurrentRequestCount = Runtime.getRuntime().availableProcessors(); |
|||
|
|||
this.threadExecutor |
|||
= new ThreadPoolExecutor(maxConcurrentRequestCount, |
|||
maxConcurrentRequestCount, |
|||
120L, |
|||
TimeUnit.SECONDS, |
|||
new LinkedBlockingQueue<Runnable>()); |
|||
this.completionService = new ExecutorCompletionService<>(this.threadExecutor); |
|||
|
|||
this.entry = entry; |
|||
|
|||
} |
|||
|
|||
private synchronized void flushWrittenBytesToServiceInternal(final long offset) throws IOException { |
|||
try { |
|||
SeaweedWrite.writeMeta(filerGrpcClient, getParentDirectory(path), entry); |
|||
} catch (Exception ex) { |
|||
throw new IOException(ex); |
|||
} |
|||
this.lastFlushOffset = offset; |
|||
} |
|||
|
|||
@Override |
|||
public void write(final int byteVal) throws IOException { |
|||
write(new byte[]{(byte) (byteVal & 0xFF)}); |
|||
} |
|||
|
|||
@Override |
|||
public synchronized void write(final byte[] data, final int off, final int length) |
|||
throws IOException { |
|||
maybeThrowLastError(); |
|||
|
|||
Preconditions.checkArgument(data != null, "null data"); |
|||
|
|||
if (off < 0 || length < 0 || length > data.length - off) { |
|||
throw new IndexOutOfBoundsException(); |
|||
} |
|||
|
|||
// System.out.println(path + " write [" + (outputIndex + off) + "," + ((outputIndex + off) + length) + ")"); |
|||
|
|||
int currentOffset = off; |
|||
int writableBytes = bufferSize - buffer.position(); |
|||
int numberOfBytesToWrite = length; |
|||
|
|||
while (numberOfBytesToWrite > 0) { |
|||
|
|||
if (numberOfBytesToWrite < writableBytes) { |
|||
buffer.put(data, currentOffset, numberOfBytesToWrite); |
|||
outputIndex += numberOfBytesToWrite; |
|||
break; |
|||
} |
|||
|
|||
// System.out.println(path + " [" + (outputIndex + currentOffset) + "," + ((outputIndex + currentOffset) + writableBytes) + ") " + buffer.capacity()); |
|||
buffer.put(data, currentOffset, writableBytes); |
|||
outputIndex += writableBytes; |
|||
currentOffset += writableBytes; |
|||
writeCurrentBufferToService(); |
|||
numberOfBytesToWrite = numberOfBytesToWrite - writableBytes; |
|||
writableBytes = bufferSize - buffer.position(); |
|||
} |
|||
|
|||
} |
|||
|
|||
/** |
|||
* Flushes this output stream and forces any buffered output bytes to be |
|||
* written out. If any data remains in the payload it is committed to the |
|||
* service. Data is queued for writing and forced out to the service |
|||
* before the call returns. |
|||
*/ |
|||
@Override |
|||
public void flush() throws IOException { |
|||
if (supportFlush) { |
|||
flushInternalAsync(); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Similar to posix fsync, flush out the data in client's user buffer |
|||
* all the way to the disk device (but the disk may have it in its cache). |
|||
* |
|||
* @throws IOException if error occurs |
|||
*/ |
|||
@Override |
|||
public void hsync() throws IOException { |
|||
if (supportFlush) { |
|||
flushInternal(); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Flush out the data in client's user buffer. After the return of |
|||
* this call, new readers will see the data. |
|||
* |
|||
* @throws IOException if any error occurs |
|||
*/ |
|||
@Override |
|||
public void hflush() throws IOException { |
|||
if (supportFlush) { |
|||
flushInternal(); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Query the stream for a specific capability. |
|||
* |
|||
* @param capability string to query the stream support for. |
|||
* @return true for hsync and hflush. |
|||
*/ |
|||
@Override |
|||
public boolean hasCapability(String capability) { |
|||
switch (capability.toLowerCase(Locale.ENGLISH)) { |
|||
case StreamCapabilities.HSYNC: |
|||
case StreamCapabilities.HFLUSH: |
|||
return supportFlush; |
|||
default: |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Force all data in the output stream to be written to Azure storage. |
|||
* Wait to return until this is complete. Close the access to the stream and |
|||
* shutdown the upload thread pool. |
|||
* If the blob was created, its lease will be released. |
|||
* Any error encountered caught in threads and stored will be rethrown here |
|||
* after cleanup. |
|||
*/ |
|||
@Override |
|||
public synchronized void close() throws IOException { |
|||
if (closed) { |
|||
return; |
|||
} |
|||
|
|||
LOG.debug("close path: {}", path); |
|||
try { |
|||
flushInternal(); |
|||
threadExecutor.shutdown(); |
|||
} finally { |
|||
lastError = new IOException(FSExceptionMessages.STREAM_IS_CLOSED); |
|||
ByteBufferPool.release(buffer); |
|||
buffer = null; |
|||
outputIndex = 0; |
|||
closed = true; |
|||
writeOperations.clear(); |
|||
if (!threadExecutor.isShutdown()) { |
|||
threadExecutor.shutdownNow(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private synchronized void writeCurrentBufferToService() throws IOException { |
|||
if (buffer.position() == 0) { |
|||
return; |
|||
} |
|||
|
|||
position += submitWriteBufferToService(buffer, position); |
|||
|
|||
buffer = ByteBufferPool.request(bufferSize); |
|||
|
|||
} |
|||
|
|||
private synchronized int submitWriteBufferToService(final ByteBuffer bufferToWrite, final long writePosition) throws IOException { |
|||
|
|||
bufferToWrite.flip(); |
|||
int bytesLength = bufferToWrite.limit() - bufferToWrite.position(); |
|||
|
|||
if (threadExecutor.getQueue().size() >= maxConcurrentRequestCount) { |
|||
waitForTaskToComplete(); |
|||
} |
|||
final Future<Void> job = completionService.submit(() -> { |
|||
// System.out.println(path + " is going to save [" + (writePosition) + "," + ((writePosition) + bytesLength) + ")"); |
|||
SeaweedWrite.writeData(entry, replication, filerGrpcClient, writePosition, bufferToWrite.array(), bufferToWrite.position(), bufferToWrite.limit(), path.toUri().getPath()); |
|||
// System.out.println(path + " saved [" + (writePosition) + "," + ((writePosition) + bytesLength) + ")"); |
|||
ByteBufferPool.release(bufferToWrite); |
|||
return null; |
|||
}); |
|||
|
|||
writeOperations.add(new WriteOperation(job, writePosition, bytesLength)); |
|||
|
|||
// Try to shrink the queue |
|||
shrinkWriteOperationQueue(); |
|||
|
|||
return bytesLength; |
|||
|
|||
} |
|||
|
|||
private void waitForTaskToComplete() throws IOException { |
|||
boolean completed; |
|||
for (completed = false; completionService.poll() != null; completed = true) { |
|||
// keep polling until there is no data |
|||
} |
|||
|
|||
if (!completed) { |
|||
try { |
|||
completionService.take(); |
|||
} catch (InterruptedException e) { |
|||
lastError = (IOException) new InterruptedIOException(e.toString()).initCause(e); |
|||
throw lastError; |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void maybeThrowLastError() throws IOException { |
|||
if (lastError != null) { |
|||
throw lastError; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Try to remove the completed write operations from the beginning of write |
|||
* operation FIFO queue. |
|||
*/ |
|||
private synchronized void shrinkWriteOperationQueue() throws IOException { |
|||
try { |
|||
while (writeOperations.peek() != null && writeOperations.peek().task.isDone()) { |
|||
writeOperations.peek().task.get(); |
|||
lastTotalAppendOffset += writeOperations.peek().length; |
|||
writeOperations.remove(); |
|||
} |
|||
} catch (Exception e) { |
|||
lastError = new IOException(e); |
|||
throw lastError; |
|||
} |
|||
} |
|||
|
|||
private synchronized void flushInternal() throws IOException { |
|||
maybeThrowLastError(); |
|||
writeCurrentBufferToService(); |
|||
flushWrittenBytesToService(); |
|||
} |
|||
|
|||
private synchronized void flushInternalAsync() throws IOException { |
|||
maybeThrowLastError(); |
|||
writeCurrentBufferToService(); |
|||
flushWrittenBytesToServiceAsync(); |
|||
} |
|||
|
|||
private synchronized void flushWrittenBytesToService() throws IOException { |
|||
for (WriteOperation writeOperation : writeOperations) { |
|||
try { |
|||
writeOperation.task.get(); |
|||
} catch (Exception ex) { |
|||
lastError = new IOException(ex); |
|||
throw lastError; |
|||
} |
|||
} |
|||
LOG.debug("flushWrittenBytesToService: {} position:{}", path, position); |
|||
flushWrittenBytesToServiceInternal(position); |
|||
} |
|||
|
|||
private synchronized void flushWrittenBytesToServiceAsync() throws IOException { |
|||
shrinkWriteOperationQueue(); |
|||
|
|||
if (this.lastTotalAppendOffset > this.lastFlushOffset) { |
|||
this.flushWrittenBytesToServiceInternal(this.lastTotalAppendOffset); |
|||
} |
|||
} |
|||
|
|||
private static class WriteOperation { |
|||
private final Future<Void> task; |
|||
private final long startOffset; |
|||
private final long length; |
|||
|
|||
WriteOperation(final Future<Void> task, final long startOffset, final long length) { |
|||
Preconditions.checkNotNull(task, "task"); |
|||
Preconditions.checkArgument(startOffset >= 0, "startOffset"); |
|||
Preconditions.checkArgument(length >= 0, "length"); |
|||
|
|||
this.task = task; |
|||
this.startOffset = startOffset; |
|||
this.length = length; |
|||
} |
|||
} |
|||
|
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue